从嵌套ItemsControl内指定完整绑定路径

当最外层控件的DataContext发生更改时,我遇到了带有ItemSources的嵌套控件的问题。 内部控件似乎更新以反映新的DataContext,但它像有一些仍与旧的DataContext绑定的“Ghost”绑定。

我怀疑拥有DataTemplates的嵌套控件会在外部控件的DataContext发生更改时阻止内部控件的绑定更新。 我读过的地方只有绑定只响应从PATH中定义的对象引发的PropertyChanged事件。

我的问题是:如何从ItemsSources的nexted控件中完全定义一个绑定PATH? 在我的情况下:

<DataGrid name="OuterGrid" ItemsSource={Binding SelectedSchool.Classes}"> 
   <ItemsControl ItemsSource={Binding Students}">
      <ComboBox SelectedItem={Binding Grade}" />
   </ItemsControl>
</DataGrid>

我想完全指定内部ComboBox的SeletedItem PATH,如下所示,但我需要将它绑定到集合中的特定项目(而不仅仅是索引0处的那个)。

<ComboBox SelectedItem="{Binding ElementName=OuterGrid, 
     Path=DataContext.SelectedSchool.Classes[0].Students[0].Grade}" />

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


模型:

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

  • 学校有许多课程
  • 班上有很多学生
  • 每个学生都有一个班级的字母等级。
  • 每个学校可能的字母等级列表不同。
  • 每个类(包括我的ViewModel)实现INotifyPropertyChanged,每个集合都是一个ObservableCollection。


    视图模型:

    我的ViewModel有以下propeties:

  • 学校的ObservableCollection ...(AllSchools)。
  • SelectedSchool
  • 布尔型(IsEditing)
  • 可能成绩的ObservableCollection(在IsEditing更改时更新,并基于Selected School)。
  • 这里需要注意的是,不同的学校可能有不同的成绩(即可能有A +,A和A-,而另一个只有A)。


    XAML:

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


    问题:

    问题在于,当SelectedSchool发生变化时,之前SelectedSchool中任何一个学生的信函成绩不存在的新学校突然将他们的字母等级设置为空(因为ComboBox的ItemsSource不再具有成绩) 。

    在视觉上,一切似乎都很好。 编辑面板正确显示所选学校的属性,并在SelectedSchool属性更改时更新。 但是,如果我重新打开第一所学校的编辑面板,则没有任何组合框具有已选择的值,因为当我选择第二所学校时,它们全都设置为空。

    即使它们不再显示在屏幕上,它与旧的ComboBoxes一样仍然将它们的Bindings连接起来。 但如果只影响以前SelectedSchool(不是之前的那个)。


    它像旧的ComboBoxes仍然有他们的绑定挂钩

    你越来越暖和....

    但它像是有一些“Ghost”绑定,仍然绑定到旧的DataContext。

    更像僵尸,或者真的孤儿。 让我解释。

    在一天结束时,绑定只是反映了一个命名实例引用的xaml编译器,如果它应用也会查找来自InotifyPropertyChange消息。 记住这一点,只是一个参考点。

    现在我们知道这些数据是分层的,但是像逻辑一样,绑定是一个残酷的女主人; 它并不在乎。 让我们看看您的示例的顶级绑定目标:

     <DataGrid name="OuterGrid" ItemsSource={Binding SelectedSchool.Classes}">   
    

    问题在于,当SelectedSchool发生变化时,之前SelectedSchool中的任何学生的信函成绩不存在于新选择的学校中,

    学校改变了,但你不是对学校有约束力,而是以前提到SelectedSchool.Classes这个子对象。 因此,上面的变化不会慢慢下降,并且引用实际上仍然有效并且没有改变 。 但在视觉上,你已经改变了影响旧数据的组合框......。


    我建议您查看绑定,删除xxxx.yyyyy并只在发生期望的分层更改时专注于提供xxxxyyyy ; 然后实现一个系统,通知属性被同时更改和通知; 请记住适当的xaml绑定到顶级和直接子级别。

    因此,也许创建一个实现了的包装INotifyPropertyChange识别当前,在人造例如,学校还对副引用,并当上的变化,包装是聪明足够多的改变子引用相匹配的顶级变化,并做了级联通知在顶级制定者:

     class MyWrapper : INotifyPropertyChange
     {
       public TheXXX XXXX
       {
          get { return _xxxx; }
          set
              {
                 _xxxx = value;
                 NotifyChange("XXXX");
                 _yyyy = _XXXX.YYYY;
                 NotifyChange("YYYY");
                _zzzz = _XXXX.ZZZZ;
                 NotifyChange("ZZZZ");
                 ...
              }
    
        ...
      }
    

    感谢@OmegaMan了解绑定背后的情况。

    我基本上通过创建级联PropertyChanged事件的接口来解决它。

    public interface ICascadePropertyChanged: INotifyPropertyChanged
    {
        void CascadePropertyChanged();
    }
    

    然后,我修改了我的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();
       }
    }
    

    我不得不从记忆中重新输入,请原谅错误。

    链接地址: http://www.djcxy.com/p/87851.html

    上一篇: Specify Full Binding Path from within nested ItemsControls

    下一篇: Extract rows from a data.frame based on common values with a list