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

Posted

技术标签:

【中文标题】WPF View 在关闭时将 ViewModel 属性设置为 null【英文标题】:WPF View sets ViewModel properties to null on closing 【发布时间】:2010-11-03 09:05:08 【问题描述】:

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

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

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

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

我的资源中的数据模板

<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");
    

关于如何阻止我的视图将我的视图模型中的值归零的任何想法?我在想,也许我需要在 View 关闭之前将其 DataContext 设置为 null,但我不确定如何使用当前绑定的方式来解决这个问题。

【问题讨论】:

【参考方案1】:

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

事实证明,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 销毁之前不会破坏视图 - 这将完全回避问题。

【讨论】:

我也遇到过同样的问题。将可视子项的 DataContext 设置为 null 部分解决了它。隐藏视图而不是破坏它没有任何区别。我仍在寻找完整的解决方案。【参考方案2】:

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

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

我不是 WPF 或 MVVM 方面的专家,但是这听起来不太对劲。我很难相信视图的 WPF 处理会导致您的问题。至少,在我有限的经验中,我从来没有发生过这样的事情。我怀疑罪魁祸首要么是视图模型中的代码,要么是交换出用于数据上下文的视图模型的代码。

【讨论】:

【参考方案3】:

在尝试通过各种方式停止 null 设置后,我放弃了,而是让它按如下方式工作。在关闭视图之前,我将 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;
    

它似乎是这样工作的,尽管我仍然很想知道一个更好的解决方案。

【讨论】:

【参考方案4】:

我遇到了同样的问题。对我有用的是从我的 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>

容器视图模型:

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

【讨论】:

【参考方案5】:

UpdateSourceTrigger 显式设置为 LostFocus

如果视图正在关闭并将其数据设置为 null,则它不会影响视图模型中的数据。

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

【讨论】:

以上是关于WPF View 在关闭时将 ViewModel 属性设置为 null的主要内容,如果未能解决你的问题,请参考以下文章

wpf mvvm模式下 在ViewModel关闭view

WPF:MVVM模式下ViewModel关闭View

WPF ViewModel与多个View绑定后如何解决的问题

WPF 解决View属性不能注销的问题

wpf mvvm下viewmodel中对view进行操作

WPF MVVM 如何在ViewModel中操作View中的控件事件