WPF View在关闭时将ViewModel属性设置为null

时间:2009-06-16 15:06:34

标签: c# .net wpf mvvm

我有一个应用程序,我在GroupBox中显示UserControls。为了显示控件,我绑定到主窗体的ViewModel中的属性,该属性返回要显示的ViewModel。我已经设置了DataTemplates,以便表单自动知道用于显示每个ViewModel的UserControl / View。

当我显示不同的UserControl时,我保持上一个控件的ViewModel处于活动状态,但WPF会自动丢弃视图。

我遇到的问题是,当视图关闭时,ViewModel中属性的任何双向绑定都会立即设置为null,因此当我再次显示ViewModel时,所有值都只是设置为在UI中为null。

我认为这是因为作为View关闭的一部分,它会处理并清除它包含的控件中的任何值,并且由于绑定到位,它们也会向下传播到ViewModel。

我资源中的DataTemplates

<DataTemplate DataType="{x:Type vm:HomeViewModel}">
    <vw:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
    <vw:SettingsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:JobListViewModel}">
    <vw:JobListView />
</DataTemplate>

用于显示用户控件的代码

<GroupBox>
    <ContentControl  Content="{Binding Path=RightPanel}" />
</GroupBox>

我在其中一个视图中绑定的控件示例:

    <ComboBox Name="SupervisorDropDown" ItemsSource="{Binding Path=Supervisors}" DisplayMemberPath="sgSupervisor" 
           SelectedValuePath="idSupervisor" SelectedValue="{Binding Path=SelectedSupervisorID}" />

以及相关的ViewModel属性:

public ObservableCollection<SupervisorsEntity> Supervisors
    {
        get
        {
            return supervisors;
        }
    }

public int? SelectedSupervisorID
{
    get
    {
        return selectedSupervisorID;
    }
    set
    {
        selectedSupervisorID = value;
        this.OnPropertyChanged("SelectedSupervisorID");
    }
}

关于如何停止我的视图使我的ViewModel中的值无效的任何想法?我想也许我需要在关闭之前将View的DataContext设置为null,但我不确定如何通过当前绑定的方式来解决这个问题。

5 个答案:

答案 0 :(得分:0)

我找到了一种可能的解决方案,但我真的不喜欢它。

事实证明DataContext IS已经设置为null,但这没有帮助。它在属性设置为null之前发生。似乎正在发生的事情是在UserControl / View处理自身之前没有删除数据绑定,因此当删除控件时,null值会向下传播。

因此,当DataContext更改时,如果新上下文为null,则删除ComboBox上的相关绑定,如下所示:

private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue == null)
    {
        SupervisorDropDown.ClearValue(ComboBox.SelectedValueProperty);
    }
}

我不是这种方法的忠实粉丝,因为这意味着我必须记住为我使用的每个数据绑定控件执行此操作。如果有一种方法我可以让每个UserControl只是在它们关闭时自动删除它们的绑定就可以了,但我想不出任何方法可以做到这一点。

另一种选择可能只是重构我的应用程序,以便在ViewModel执行之前不会销毁视图 - 这将完全回避问题。

答案 1 :(得分:0)

  

当我显示不同的时候   UserControl,我保留了ViewModel   以前的控件是活动的,但是   视图会自动丢弃   WPF。

     

我遇到的问题是   当视图关闭时,任何两种方式   绑定到的属性   ViewModel立即设置为null,   所以当我显示ViewModel时   再次设置所有值   在UI中为null。

我不是WPF或MVVM的专家,但有些事情听起来不对。我很难相信WPF处理视图会导致您的问题。至少,在我有限的经历中,我从未有过这样的事情。我怀疑罪魁祸首是视图模型中的代码或代码交换出哪个视图模型用于datacontext。

答案 2 :(得分:0)

尝试通过各种方式停止空设置后,我放弃了,而是按照以下方式使其工作。在关闭视图之前,我将ViewModel设置为只读。我在我的ViewModelBase类中实现了这一点,我在其中添加了一个IsReadOnly布尔属性。然后在ViewModelBase.SetProperty()(见下文)中,当IsReadOnly为真时,我忽略任何属性更改。

    protected bool SetProperty<T>( ref T backingField, T value, string propertyName )
    {
        var change = !IsReadOnly && !EqualityComparer<T>.Default.Equals( backingField, value );

        if ( change ) {
            backingField = value;
            OnPropertyChanged( propertyName );
        }
        return change;
    }

似乎这样工作,虽然我仍然想知道一个更好的解决方案。

答案 3 :(得分:0)

我遇到了同样的问题。对我有用的是从我的SelectedValueBindings中删除 UpdateSourceTrigger = PropertyChanged 。使用该模式时,PropertyChanged UpdateSourceTriggers似乎触发关闭视图的绑定属性:

<!--Users DataGrid-->
<DataGrid Grid.Row="0" ItemsSource="{Binding DealsUsersViewSource.View}"
    AutoGenerateColumns="False" CanUserAddRows="True" CanUserDeleteRows="False"
    HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <DataGrid.Resources>
        <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#FFC5D6FB"/>
    </DataGrid.Resources>
    <DataGrid.Columns>

          <!--Username Column-->
          <DataGridComboBoxColumn 
            SelectedValueBinding="{Binding Username}" Header="Username" Width="*">
              <DataGridComboBoxColumn.ElementStyle>
                  <Style TargetType="{x:Type ComboBox}">
                      <Setter Property="ItemsSource" Value="{Binding DataContext.DealsUsersCollection.ViewModels,
                          RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
                      <Setter Property="SelectedValuePath" Value="Username"/>
                      <Setter Property="DisplayMemberPath" Value="Username"/>
                  </Style>
              </DataGridComboBoxColumn.ElementStyle>
              <DataGridComboBoxColumn.EditingElementStyle>
                  <Style TargetType="{x:Type ComboBox}">
                      <Setter Property="ItemsSource" Value="{Binding DataContext.BpcsUsers,
                          RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
                      <Setter Property="SelectedValuePath" Value="Description"/>
                      <Setter Property="DisplayMemberPath" Value="Description"/>
                      <Setter Property="IsEditable" Value="True"/>
                  </Style>
              </DataGridComboBoxColumn.EditingElementStyle>
          </DataGridComboBoxColumn>

          <!--Supervisor Column-->
          <DataGridComboBoxColumn 
            SelectedValueBinding="{Binding Supervisor}" Header="Supervisor" Width="*">
              <DataGridComboBoxColumn.ElementStyle>
                  <Style TargetType="{x:Type ComboBox}">
                      <Setter Property="ItemsSource" Value="{Binding DataContext.DealsUsersCollection.ViewModels,
                          RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
                      <Setter Property="SelectedValuePath" Value="Username"/>
                      <Setter Property="DisplayMemberPath" Value="Username"/>
                  </Style>
              </DataGridComboBoxColumn.ElementStyle>
              <DataGridComboBoxColumn.EditingElementStyle>
                  <Style TargetType="{x:Type ComboBox}">
                      <Setter Property="ItemsSource" Value="{Binding DataContext.BpcsUsers,
                          RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
                      <Setter Property="SelectedValuePath" Value="Description"/>
                      <Setter Property="DisplayMemberPath" Value="Description"/>
                      <Setter Property="IsEditable" Value="True"/>
                  </Style>
              </DataGridComboBoxColumn.EditingElementStyle>
          </DataGridComboBoxColumn>

          <!--Plan Moderator Column-->
          <DataGridCheckBoxColumn Binding="{Binding IsPlanModerator}" Header="Plan Moderator?" Width="*"/>

          <!--Planner Column-->
          <DataGridCheckBoxColumn Binding="{Binding IsPlanner}" Header="Planner?" Width="*"/>

    </DataGrid.Columns>
</DataGrid>

容器视图:

<!--Pre-defined custom styles-->
<a:BaseView.Resources>

    <DataTemplate DataType="{x:Type vm:WelcomeTabViewModel}">
        <uc:WelcomeTabView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:UserSecurityViewModel}">
        <uc:UserSecurityView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:PackItemRegisterViewModel}">
        <uc:PackItemsRegisterView/>
    </DataTemplate>

</a:BaseView.Resources>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="30"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="30"/>
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>

    <TabPanel Grid.Column="1" Grid.Row="1">
        <TabControl TabStripPlacement="Top" ItemsSource="{Binding TabCollection}" SelectedIndex="{Binding SelectedTabIndex}"
                    DisplayMemberPath="DisplayName" MinWidth="640" MinHeight="480"/>
    </TabPanel>

</Grid>

容器ViewModel:

TabCollection.Add(new WelcomeTabViewModel());
TabCollection.Add(new UserSecurityViewModel(_userService, _bpcsUsersLookup));
TabCollection.Add(new PackItemRegisterViewModel(_packItemService, _itemClassLookup));
SelectedTabIndex = 0;

答案 4 :(得分:0)

UpdateSourceTrigger 明确设置为 LostFocus

如果视图正在关闭并将其数据设置为null,则它对viewmodel中的数据没有影响。

<ComboBox Name="SupervisorDropDown" ItemsSource="{Binding Path=Supervisors}" DisplayMemberPath="sgSupervisor" 
SelectedValuePath="idSupervisor" 
SelectedValue="{Binding Path=SelectedSupervisorID, UpdateSourceTrigger=LostFocus}" />