【问题标题】:Setting ItemsSource of a DataGrid in User Control在用户控件中设置 DataGrid 的 ItemsSource
【发布时间】:2024-04-22 20:50:02
【问题描述】:

此示例是我的要求的简化版本。如果我能解决这个问题,那么剩下的就很简单了(我希望如此)。假设我有一个

的类层次结构

A 类:

Name
List <Class B>


B 类:

Name
List <Class C>


C 类:

Name

每个类都有一个单独的视图、视图模型和模型。我的主视图将每个视图放在 DockPanel 中。每个类的视图都是一个 DataGrid,它简单地放置每个列表的名称。当我单击 A 类的名称时,它会在下面显示 B 类的名称。当我单击 B 类的名称时,它会显示 C 类的名称。所以基本上 3 个用户控件都是在我的 MainView 的停靠面板上显示的所有 DataGrid 控件。

我仍然在 MVVM 中找到自己的立足点,但我不确定这是否是最好的方法。我的做法是,每个视图模型都有一个名为 SelectedItem 的属性,该属性绑定到其视图上 DataGrid 中的 SelectedItem。设置该属性时,将设置另一个名为 ChildViewModel 的属性。但我不确定如何在我的 MainView 以及每个控件中进行设置。如何将控件绑定到它的父数据上下文以及如何将项源绑定到父项中的选定项?

【问题讨论】:

    标签: wpf mvvm binding wpf-controls datacontext


    【解决方案1】:

    我将有一个 ViewModel,它是 Window/UserControl/etc 的 DataContext,这些控件都分组在其中。然后设置 ClassA 的集合加上 3 个 SelectedItem 属性。在 SelectedItem 属性中,您需要将下一个属性设置为 null 或默认值。

    private ObservableCollection<ClassA> _CollectionOfA;
    public ObservableCollection<ClassA> CollectionOfA 
    {
       get { return _CollectionOfA; }
       set
       {
          if (value == _CollectionOfA)
             return;
          _CollectionOfA = value;
          RaisePropertyChanged(() => CollectionOfA);
       }
    }
    
    private ClassA _SelectedClassA;
    public ClassA SelectedClassA 
    {
       get { return _SelectedClassA; }
       set
       {
          if (value == _SelectedClassA)
             return;
          _SelectedClassA = value;
          SelectedClassB = null; //Or default this to first item in the list, etc.
          RaisePropertyChanged(() => SelectedClassA);
       }
    }
    
    //Repeat SelectedClassA pattern for SelectedClassB/SelectedClassC
    public ClassB SelectedClassB { get; set; }
    public ClassC SelectedClassC { get; set; }
    

    然后在您的绑定中,您将使用 SelectedClassA.CollectionOfClassB 属性作为第二个 DataGrid 的 ItemsSource 等。

    <DataGrid ItemsSource="{Binding CollectionOfClassA}" SelectedItem="{Binding SelectedClassA}">
        //columns defined here
    </DataGrid>
    <DataGrid ItemsSource="{Binding SelectedClassA.CollectionOfClassB}" SelectedItem="{Binding SelectedClassB}">
        //columns defined here
    </DataGrid>
    <DataGrid ItemsSource="{Binding SelectedClassB.CollectionOfClassC}" SelectedItem="{Binding SelectedClassC}">
        //columns defined here
    </DataGrid>
    

    编辑: 第一个选项是我的KISS 方法。另一种选择是使用MVVM Light 之类的东西,它通过messenger 类实现weak event pattern。在第二个 ViewModel 的构造函数中,您注册以侦听第一个 ViewModel 中 SelectedItem 的更改。在第一个 ViewModel 的设置器中,您发送一条消息,说明 SelectedItem 已更改。这是 ClassBViewModel 的示例(ClassAViewModel 只需要其 SelectedClassA 设置器来发送消息,构造函数中没有侦听器。而 ClassCViewModel 会侦听但不需要发送)。

    public class ClassBViewModel : ViewModelBase
    {
       private ClassA _SelectedClassA;
       public ClassA SelectedClassA 
       {
          get { return _SelectedClassA; }
          set
          {
             if (value == _SelectedClassA)
                return;
             _SelectedClassA = value;
             RaisePropertyChanged(() => SelectedClassA);
          }
       }
    
       private ClassB _SelectedClassB;
       public ClassB SelectedClassB
       {
          get { return _SelectedClassB; }
          set
          {
              if (value == _SelectedClassB)
                 return;
              var oldValue = _SelectedClassB;
              _SelectedClassB = value;
              Messenger.Default.Send(new PropertyChangedMessage<ClassB>(oldValue, value, "SelectedClassB"));
              RaisePropertyChanged(() => SelectedClassB);
          }
       }
    
       public ClassBViewModel()
       {
          Messenger.Default.Register<PropertyChangedMessage<ClassA>>(this, (message) => SelectedClassA = message.NewValue);
       }
    }
    

    DataGrid 的 ClassBView xaml 看起来仍然相同:

    <DataGrid ItemsSource="{Binding SelectedClassA.CollectionOfClassB}" SelectedItem="{Binding SelectedClassB}">
        //columns defined here
    </DataGrid>
    

    【讨论】:

    • 如果我明白你在说什么,我的三个模型和三个视图模型都将连接到同一个主视图。我想要实现的是每个视图模型都有一个带有主视图的视图,这样我的主视图就不会变得太大。
    • 我使用 MVVM Light 添加了另一个选项。这利用 Messenger 类在 ViewModel 之间进行通信,而无需相互引用。您可以使用普通事件来完成此操作,因为您的 MainViewModel 具有其他 3 个 ViewModel 的实例,但我认为这是一种更好的方法,可以防止它们相互耦合。