从嵌套ItemsControls

时间:2015-09-16 16:15:16

标签: wpf xaml mvvm binding datagrid

当最外层控件的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>

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

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

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

MODEL:

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

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

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

视图模型:

我的ViewModel具有以下功能:

  • ObservableCollection of Schools ...(AllSchools)。
  • A SelectedSchool
  • 布尔值(IsEditing)
  • 可能的等级的ObservableCollection(当IsEditing改变时会更新,并且基于所选择的学校)。

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

XAML:

我有一个 Datagrid 绑定到我的ViewModel的AllSchools集合和ViewModel的SelectedSchool属性。当用户双击一行时,事件处理程序会打开一个&#34;编辑面板&#34;通过更改ViewModel的IsEditing属性来选择学校(编辑面板的Visibily绑定到IsEditing属性)。在编辑面板中,我有一个Datagrid(绑定到所选学校的Classes集合),在Datagrid内部我有一个 TemplatedColumn ,带有 ItemsControl (绑定到集合)当前班级的学生)。对于每个学生,该班级的学生成绩都有 ComboBox 。 ComboBox的ItemsSource是ViewModel的PossibleGrades集合。

问题:

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

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

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

2 个答案:

答案 0 :(得分:2)

  

它就像旧的ComboBoxes仍然有他们的Bindings连接

你变暖了......

  

但它有一些&#34; Ghost&#34;绑定仍然绑定到旧的DataContext。

更像是僵尸,还是真正的孤儿。让我解释一下。

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

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

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

问题在于,当SelectedSchool发生变化时,以前被选中的学校中的任何学生都会为新选择的学校提供​​不存在的字母等级,

学校发生了变化,但你没有约束 School ,而是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");
             ...
          }

    ...
  }

答案 1 :(得分:2)

感谢@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();
   }
}

我不得不从记忆中重新输入这个,所以请原谅错别字。