【问题标题】:WPF View sets ViewModel properties to null on closingWPF View 在关闭时将 ViewModel 属性设置为 null
【发布时间】: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,但我不确定如何使用当前绑定的方式来解决这个问题。

【问题讨论】:

    标签: c# .net wpf mvvm


    【解决方案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}" />
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-02-01
            • 1970-01-01
            • 2018-09-02
            • 1970-01-01
            相关资源
            最近更新 更多