ViewModel 之间的 WPF 数据绑定

WPF DataBinding between ViewModels

我有一个问题,我无法正确地表述,因此还没有找到满意的答案。也许这里有人可以指出正确的方向。

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

我基本上有 4 个相关的 ViewModel:CanvasViewModel、ClassViewModel、MemberViewModel、TypeViewModel。它们都实现了相同的接口,暴露了一个 bool Valid 属性.

如您所想,canvas 上有 1 个全局 Canvas,n 个 Class,Class 中有 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绑定到canvas,如果是假的,会显示一个大红色的探照灯。 如果 ClassViewModel.Valid 为假,我将 ClassViewModel.Valid 绑定到 class 框以执行摆动动画。 如果 MemberViewModel.Valid 为假,我将其绑定到列表视图以闪烁红色。 我没有特别绑定 TypeViewModel.Valid。

当然,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 然后将它们的 Valid 属性 评估为 false。我希望此事件通过所有正确的 ViewModels 传播(从类型 -> 成员 -> Class -> Canvas)。

我到底要怎么做才能做到这一点,而不必 fiddle 在我的代码中到处都是可怕的行,例如

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

我不想像这样把我的功能串在一起。我更喜欢干净的绑定,具有清晰的 entry/exit、get/set 或 add/remove 函数。

是的,不是 ViewModel 和 GUI 之间的绑定,而是 ViewModel 之间的绑定。我尝试使用 On属性Changed 和 CoerceValue。但他们的目的对我来说并不完全清楚。在我的例子中,成员 On属性Changed 实现看起来像这样

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

我想这不会太糟糕,除了我不知道 CoerceValue 实际有什么责任。评估在那里进行? 属性 是否假定 CoerceValue 的 return 值?是否像常规 属性 中的 On属性Changed 代表 "set" 和 CoerceValue 代表 "get" 一样简单?无论如何,我没有让它工作,所以我怀疑我是否正确理解了它的用途。所有使用 OnPropertyChanged/CoerceValue 的示例基本上都在同一个 class 中处理 DependencyProperties(从不跨越 class)。

有什么想法可以解决这个相当普遍的问题吗?

干杯,

雷内

使用反射和接口(或某种基础 class)。使用基础 class 来识别您要为其设置值的对象类型,并向下迭代到每个 class。效果为:

   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));
            }
        }
    }

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