C# ViewModels之间的WPF数据绑定

C# ViewModels之间的WPF数据绑定,c#,wpf,mvvm,data-binding,dependency-properties,C#,Wpf,Mvvm,Data Binding,Dependency Properties,我有一个问题,我很难正确表达,因此还没有找到一个令人满意的答案。也许这里有人能给我指出正确的方向 我正在WPF中使用MVVM来创建一个类似UML的建模工具。出于所有意图和目的,让我们坚持UML类比 我这里基本上有4个相关的视图模型:CanvasViewModel、ClassViewModel、MemberViewModel、TypeViewModel。它们都实现了相同的接口,公开了bool-Valid属性 正如您所想象的,有1个全局画布,画布上有n个类,一个类中有n个成员,每个成员有1个类型。可

我有一个问题,我很难正确表达,因此还没有找到一个令人满意的答案。也许这里有人能给我指出正确的方向

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

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

正如您所想象的,有1个全局画布,画布上有n个类,一个类中有n个成员,每个成员有1个类型。可以这样表示:

Canvas
{
  Person (Class)
  {
    Age (Member)
    {
      int (Type)
    }
    Name (Member)
    {
      string (Type)
    }
  }
  House (Class)
  {
    Price (Member)
    {
      currency (Type)
    }
  }
}
它实现了这样一个画面:()

我绑定CanvasViewModel.Valid到画布,如果为false,则显示一个大的红色搜索灯。 我将ClassViewModel.Valid绑定到类框,以便在其为false时执行摆动动画。 我将MemberViewModel.Valid绑定到listview,如果为false,则闪烁红色。 我绑定了TypeViewModel。没有特别的有效性

当然,Valid属性的实现非常简单(代码未经测试):

因此,这里的用例是:用户意外地将TypeViewModel设置为“innt”,这是一个无效的类型。我希望所有4个ViewModels都将其有效属性计算为false。我希望此事件通过所有正确的视图模型传播(从类型->成员->类->画布)

我究竟如何做到这一点,而不必在代码中到处乱动可怕的行,例如

var memberViewModel = new MemberViewModel(member);
memberViewModel.PropertyChanged += (o, e) => { if ( e.PropertyName == "Valid") OnPropertyChanged("Valid"); }
Members.Add(memberViewModel);
我不想像这样把我的功能串在一起。我更喜欢干净的绑定,带有清晰的入口/出口、get/set或add/remove函数

是的,不是ViewModel和GUI之间的绑定,而是ViewModels之间的绑定。我试着玩弄OnPropertyChanged和ImpresseValue。但我并不完全清楚他们的目的。在我的例子中,一个成员OnPropertyChanged实现看起来像这样

public static void OnPropertyChanged(...)
{
  MyParentClass.CoerceValue(ClassViewModel.ValidProperty);
}
我想这不会太糟糕,除非我不知道强制评估值实际上有什么责任。评估是在那里进行的吗?该属性是否假定了强制值的返回值?它是否像OnPropertyChanged表示“set”和强制值表示“get”一样简单,就像在常规属性中一样?无论如何,我没有让它工作,所以我怀疑我是否正确理解它的用途。所有使用OnPropertyChanged/ImpresseValue的示例基本上都处理同一类中的DependencyProperties(决不跨类)

有没有办法解决这个相当普遍的问题

干杯


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

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

处理这种情况的设计模式讨论得相当好。
在您的例子中,我喜欢选项1,其中在每个子对象中保留对父对象的引用。然后,您将在属性设置器中调用从子级更改的父级属性。这可能是最简单的解决方案,但它确实引入了父子耦合,但看起来你不会像他们描述的那样担心。我建议将一个ivaliding传递到孩子的构造函数中,并在setter中检查它是否为null。如果您使用的是mvvm框架,那么您还可以查看消息总线模式

最终,这都是关于属性更改通知传播和类向上游订阅PropertyChanged事件,并在其自己的PropertyChanged事件中将它们传播到下游的类。你可以让注册变得相当优雅,但是没有一种简单的方法,除非你想限制你的设计,以至于你可以“用一个类来管理所有的注册”。提到消息总线的事实,某种程度上证实了我的怀疑,没有真正优雅的方法可以做到这一点。谢谢你给我指明了正确的方向!
   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));
            }
        }
    }