WPF视图在关闭时将ViewModel属性设置为null。

10
我有一个应用程序,在GroupBox中显示UserControl。为了显示控件,我绑定到主窗体的ViewModel中的属性,该属性返回要显示的ViewModel。我设置了DataTemplates,以便表单自动知道要使用哪个UserControl / View来显示每个ViewModel。
当我显示不同的UserControl时,我保持先前控件的ViewModel处于活动状态,但是Views会被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");
    }
}

您有没有想过如何防止视图将ViewModels中的值设置为null?我认为,也许需要在视图关闭之前将其DataContext设置为null,但是目前的绑定方式让我不确定该如何操作。

5个回答

1

UpdateSourceTrigger显式设置为LostFocus

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

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

0
在尝试通过各种方式停止空设置后,我放弃了,并改为按以下方式工作。在关闭其视图之前,我将ViewModel设置为只读。我在我的ViewModelBase类中完成此操作,其中我添加了一个IsReadOnly布尔属性。然后,在ViewModelBase.SetProperty()(见下文)中,当IsReadOnly为true时,我忽略任何属性更改。
    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;
    }

看起来是这样工作的,虽然我仍然很想知道一个更好的解决方案。


0

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

事实证明,DataContext 已经被设置为 null,但这并没有帮助。它发生在属性被设置为 null 之前。看起来正在发生的是,在 UserControl/View 销毁自身之前,数据绑定没有被移除,因此当控件被移除时,null 值会向下传播。

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

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

我对这种方法并不太喜欢,因为这意味着我必须记住为我使用的每个数据绑定控件都要这样做。如果有一种方式可以让每个用户控件在关闭时自动删除它们的绑定,那就没问题了,但我想不出任何办法。

另一个选择可能是重新构建我的应用程序,以便在视图模型结束之前不销毁视图 - 这将完全避开这个问题。


我也遇到了同样的问题。将可视子元素的DataContext设置为null部分解决了它。隐藏视图而不是销毁它没有任何区别。我仍在寻找完整的解决方案。 - HappyNomad

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>

容器视图模型:

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

0
当我显示一个不同的UserControl时,我保持先前控件的ViewModel处于活动状态,但是Views会被WPF自动丢弃。 我遇到的问题是,当视图关闭时,ViewModel中与属性相关的任何双向绑定都会立即设置为null,因此当我再次显示ViewModel时,所有值都仅在UI中设置为null。
我对WPF或MVVM都不是专家,但这种情况听起来有些不对。 我很难相信WPF丢弃视图会导致您的问题。 最起码,在我的有限经验中,我从未遇到过类似的事情。 我怀疑罪魁祸首是视图模型中的代码或交换使用哪个视图模型作为数据上下文的代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接