【问题标题】:WPF DataBinding between ViewModelsViewModel 之间的 WPF 数据绑定
【发布时间】:2015-10-19 14:40:14
【问题描述】:

我有一个问题,我无法正确措辞,因此还没有找到令人满意的答案。也许这里有人可以为我指明正确的方向。

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

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

正如你想象的那样,有 1 个全局 Canvas,画布上有 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 绑定到列表视图以闪烁红色。 我将 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。我希望这个事件通过所有正确的 ViewModel 传播(从类型 -> 成员 -> 类 -> 画布)。

我究竟如何做到这一点,而不必在我的代码中到处摆弄诸如

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

我不想像这样把我的功能串在一起。我更喜欢干净的绑定,具有明确的进入/退出、获取/设置或添加/删除功能。

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

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

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

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

干杯,

雷内

【问题讨论】:

  • 最终都是关于属性更改通知传播和类订阅上游的 PropertyChanged 事件并在它们自己的 PropertyChanged 事件中将它们传播到下游的类。您可以使注册相当优雅,但没有一种简单的方法,除非您想限制您的设计,以至于您可以拥有“一个类来统治它们”。

标签: c# wpf mvvm data-binding dependency-properties


【解决方案1】:

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

【讨论】:

  • 提到消息总线的事实有点证实了我的怀疑,即没有真正优雅的方式来做到这一点。感谢您为我指明正确的方向!
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 2011-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    相关资源
    最近更新 更多