从嵌套的ItemsControls中指定完整绑定路径

Nuc*_*mer 7 wpf xaml binding datagrid mvvm

当最外层控件的DataContext发生更改时,我遇到了使用ItemSources的嵌套控件的问题.内部控件似乎更新以反映新的DataContext,但它有一些"Ghost"绑定仍然绑定到旧的DataContext.

我怀疑拥有DataTemplates的嵌套控件会阻止内部控件的绑定在外部控件的DataContext更改时更新.我在某处读到只有绑定才响应从PATH中明确定义的对象引发的PropertyChanged事件.

我的问题是:如何使用ItemsSources从nexted控件中完全定义绑定PATH?就我而言:

<DataGrid name="OuterGrid" ItemsSource={Binding SelectedSchool.Classes}"> 
   <ItemsControl ItemsSource={Binding Students}">
      <ComboBox SelectedItem={Binding Grade}" />
   </ItemsControl>
</DataGrid>
Run Code Online (Sandbox Code Playgroud)

我想完全指定内部ComboBox的SeletedItem PATH,类似于以下内容,但是我需要将它绑定到集合中的特定项(而不仅仅是索引0处的那个).

<ComboBox SelectedItem="{Binding ElementName=OuterGrid, 
     Path=DataContext.SelectedSchool.Classes[0].Students[0].Grade}" />
Run Code Online (Sandbox Code Playgroud)

我在下面有一个更详细的问题示例,我无法发布实际代码或描述我正在使用的ACTUAL对象(安全原因),所以我试图用最简单的方式来理解它.


模型:

我有一个相当复杂的Biz对象,其中包含其他对象的集合.集合中的项目也包含集合.

  • 学校有很多课程
  • 课程有很多学生
  • 每个学生都有一个班级的字母等级.
  • 每所学校的可能的字母等级列表是不同的.

每个类(包括我的ViewModel)都实现了INotifyPropertyChanged,每个集合都是一个ObservableCollection.


视图模型:

我的ViewModel具有以下特性:

  • 一个ObservableCollection of Schools ...(AllSchools).
  • 选定的学校
  • 布尔值(IsEditing)
  • ObservableCollection可能的等级(当IsEditing发生变化时会更新,并且基于所选择的学校).

这里要注意的重要一点是,不同的学校可能有不同的可能成绩(即一个可能有A +,A和A-而另一个只有A).


XAML:

我有一个Datagrid绑定到我的ViewModel的AllSchools集合和ViewModel的SelectedSchool属性.当用户双击一行时,事件处理程序通过更改ViewModel的IsEditing属性(编辑面板的Visibily绑定到IsEditing属性)打开所选学校的"编辑面板".在编辑面板中,我有一个Datagrid(绑定到所选学校的Classes集合),在Datagrid内部我有一个带有ItemsControlTemplatedColumn(绑定到当前Class的学生的集合).每个学生都有一个ComboBox,用于学生在课堂上的成绩.ComboBox的ItemsSource是ViewModel的PossibleGrades集合.


问题:

问题在于,当SelectedSchool发生变化时,之前在SelectedSchool中的任何学生,其字母成绩不适用于新选择的学校,突然将其成绩设置为null(因为ComboBox的ItemsSource不再具有成绩) .

在视觉上,一切似乎都很好.编辑面板正确显示所选学校的属性,并在SelectedSchool属性更改时更新.但是如果我重新打开第一所学校的编辑面板,那么组合框中没有一个选择了值,因为当我选择第二所学校时它们都被设置为null.

它就像旧的ComboBoxes仍然有他们的Bindings连接,即使他们不再出现在屏幕上.但是,如果只影响以前的SelectedSchool(不是之前的那个).

Nuc*_*mer 2

感谢@OmegaMan 对绑定幕后发生的事情的描述。

我基本上通过创建一个级联 PropertyChanged 事件的接口来解决这个问题。

public interface ICascadePropertyChanged: INotifyPropertyChanged
{
    void CascadePropertyChanged();
}
Run Code Online (Sandbox Code Playgroud)

然后,我修改了 ModelBase 和 CollectionBase 类,通过使用 Refection 在子属性上递归调用 CascadePropertyChanged() 来实现所述接口。

public class ModelCollection<M>  : ObservableCollection<M>, 
    ICascadePropertyChanged where M: ModelBase
{
    ...
    public void CascadePropertyChanged()
    {
        foreach (M m in this)
        {
             if (m != null)
             {
                 m.CascadePropertyChanged();
             }
        }
    }
}

public abstract class ModelBase: ICascadePropertyChanged
{
    ...
    public void CascadePropertyChanged()
    {
      var properties = this.GetType().GetProperties()
          .Where( p => HasInterface(p.PropertyType, typeof(ICascadePropertyChanged));

      // Cascade the call to each sub-property.
      foreach (PropertyInfo pi in properties)
      {
        ICascadePropertyChanged obj = (ICascadePropertyChanged)pi.GetValue(this);
        if (obj  != null)
        {
            obj.CascadePropertyChanged();
        }
      }
      RaisePropertyChanged();
   }
}
Run Code Online (Sandbox Code Playgroud)

我不得不凭记忆重新输入,所以请原谅打字错误。