ViewModels之间的WPF DataBinding

时间:2015-10-19 14:40:14

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

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

我在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绑定到类框,如果它是假的则做一个摆动动画。 如果它是false,我将MemberViewModel.Valid绑定到listview以闪烁红色。 我特意将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传播(来自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代表常规属性中的“get”吗?无论如何,我没有让它工作,所以我怀疑我是否正确理解它的目的。使用OnPropertyChanged / CoerceValue的所有示例基本上都处理同一个类中的DependencyProperties(从不跨越类)。

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

干杯,

2 个答案:

答案 0 :(得分:1)

很好地讨论了处理这种情况的设计模式here。 在您的情况下,我喜欢选项1,您可以在其中保存对每个子项中父对象的引用。然后,您将调用从属性设置器中的子项更改的父级属性。它可能是最简单的解决方案,但它确实引入了亲子耦合,但它似乎并不像他们所描述的那样具有相同的关注点。我建议将IValidating传递给子的构造函数,并在setter中将其检查为null。如果您正在使用mvvm框架

,您还可以查看消息总线模式

答案 1 :(得分: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));
            }
        }
    }