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的主要内容,如果未能解决你的问题,请参考以下文章