2015-10-19 74 views
1

我有一个问题,我有麻烦的措辞正确,因此一直没能找到一个令人满意的答案呢。也许这里的某个人可以指引我走向正确的方向。WPF DataBinding之间ViewModels

我在WPF中使用MVVM来创建类似于UML的建模工具。对于所有的意图和目的,让我们坚持UML类比。

我基本上有4个相关的ViewModels:CanvasViewModel,ClassViewModel,MemberViewModel,TypeViewModel。它们都实现了暴露bool有效属性的相同接口。你可以想象,画布上有1个全局画布,n个类,一个类中有n个成员,每个成员有1个类型。它可以表示像这样的:

Canvas 
{ 
    Person (Class) 
    { 
    Age (Member) 
    { 
     int (Type) 
    } 
    Name (Member) 
    { 
     string (Type) 
    } 
    } 
    House (Class) 
    { 
    Price (Member) 
    { 
     currency (Type) 
    } 
    } 
} 

它实现了画面像这样:(http://www.tutorialspoint.com/uml/images/uml_class_diagram.jpg

我绑定CanvasViewModel.Valid到画布上,如果它是显示一个红色的大探照灯假。 如果它是false,我将ClassViewModel.Valid绑定到类框以执行摆动动画。 我将MemberViewModel.Valid绑定到列表视图,如果它为false,则呈现红色。 我特别将TypeViewModel.Valid绑定到任何东西。

当然,有效属性被实现非常简单(代码未测试):

// CanvasViewModel 
public bool Valid { get { return Classes.All(x => x.Valid); } } 

// ClassViewModel 
public bool Valid { get { return Members.All(x => x.Valid); } } 

// MemberViewModel 
public bool Valid { get { return Type.Valid; } } 

所以用例在此是:用户不小心设置TypeViewModel为“innt”,这是一个无效的类型。我希望所有4个ViewModel都将其有效属性评估为false。我想让这个事件通过所有正确的ViewModel传播(从Type - > Member - > Class - > Canvas)。

如何在地球上我做到这一点,而无需到处折腾我的代码与可怕的线条如

var memberViewModel = new MemberViewModel(member); 
memberViewModel.PropertyChanged += (o, e) => { if (e.PropertyName == "Valid") OnPropertyChanged("Valid"); } 
Members.Add(memberViewModel); 

我不希望我的串在一起的功能是这样。我更喜欢使用干净的绑定,具有清晰的入口/出口,获取/设置或添加/删除功能。

所以是的,不是ViewModel和GUI之间的绑定,而是ViewModels之间的绑定。我试着玩弄OnPropertyChanged和CoerceValue。但他们的目的并不完全清楚。好像在我的情况下,会员OnPropertyChanged的实现应该是这样的

public static void OnPropertyChanged(...) 
{ 
    MyParentClass.CoerceValue(ClassViewModel.ValidProperty); 
} 

我想这不会是太糟糕了,唯独我不知道什么责任CoerceValue居然有这样看来。评估是否在那里进行?该属性是否承担了CoerceValue的返回值? OnPropertyChanged表示“set”,CoerceValue表示常规Property中的“get”,这是否简单?无论如何,我没有得到它的工作,所以我怀疑我正确理解它的目的。所有使用OnPropertyChanged/CoerceValue的示例基本上都是在同一个类中处理DependencyProperties(永远不会跨类)。

任何想法如何解决这个相当普遍的问题?

干杯,

勒内

+1

最终,它是关于属性更改通知传播以及订阅上游PropertyChanged事件的类,并将它们在它们自己的PropertyChanged事件中传播到下游类。你可以使注册合理优雅,但除非你想限制你的设计以至于你可以拥有“一个班级来统治所有人”,否则没有简单的方法。 –

回答

1

处理这种情况的设计模式讨论得很好here。 在你的情况下,我喜欢选项1,在那里你持有对每个孩子的父对象的引用。然后,您将调用属性设置器中从子项更改的父项属性。这可能是最简单的解决方案,但它确实引入了父 - 子耦合,但似乎并不像你所描述的那样具有同样的担忧。我会建议将IValidating传递给子构造函数,并在setter中检查它是否为null。如果您使用mvvm框架,您还可以查看消息总线模式

+0

消息总线被提及的事实证实了我的怀疑,即没有真正优雅的方式来做到这一点。感谢您指点我正确的方向! – Rene

0

使用反射和接口(或某种基类)。使用基类来标识要为其设置值的对象的类型,并迭代到每个类中。一些效果如下:

interface IValid 
    { 
     bool Valid { get; set; } 
    } 

    class Canvas : IValid, INotifyPropertyChanged 
    { 
     private bool valid; 
     public bool Valid 
     { 
      get { return this.valid; } 
      set 
      { 
       this.valid = value; 
       this.OnPropertyChanged("Valid"); 

       this.SetPropertyValues(this); 
      } 
     } 


     public Canvas() 
     { 
      this.Person = new Person(); 
     } 

     public Person Person { get; set; } 


     public event PropertyChangedEventHandler PropertyChanged; 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 

     private void SetPropertyValues(IValid obj) 
     { 
      var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); 

      foreach (var property in properties) 
      { 
       if (property.CanRead) 
       { 
        var validObject = property.GetValue(obj) as IValid; 

        if (validObject != null) 
        { 
         validObject.Valid = obj.Valid; 

         SetPropertyValues(validObject); 
        } 
       } 
      } 
     } 


    } 
    class Person : IValid, INotifyPropertyChanged 
    { 
     private bool valid; 
     public bool Valid 
     { 
      get { return this.valid; } 
      set 
      { 
       this.valid = value; 
       this.OnPropertyChanged("Valid"); 
      } 
     } 


     public Person() 
     { 
      this.Age = new Age(); 
     } 

     public Age Age { get; set; } 



     public event PropertyChangedEventHandler PropertyChanged; 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
    } 

    class Age : IValid, INotifyPropertyChanged 
    { 
     private bool valid; 
     public bool Valid 
     { 
      get { return this.valid; } 
      set 
      { 
       this.valid = value; 
       this.OnPropertyChanged("Valid"); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
    }