MVVM and INotifyPropertyChanged Issue

本文关键字:Issue INotifyPropertyChanged and MVVM | 更新日期: 2023-09-27 18:08:20

我对MVVM设计有一个很大的问题。我试图抓住我的内部嵌套对象的每一个PropertyChanged,包括他们的嵌套对象的进一步propertchanged,在我的ViewModel,但我不知道如何做到这一点。

下面是我的结构:

class MyVM
{
  public MyVM()
  {
    this.SomeData = new SomeData();
    this.SomeData.NestedObj = new MyNestedDat();
    this.SomeData.Str = "This tiggers propertychanged inside MyDat class";
    // this triggers propertychanged event inside MyNestedDat class
    this.SomeData.NestedObj.Num = 123;
  }
  // and here should be a method where i catch all possibe propertychanges from my nested objets and their nested objets, how do i do that?
  public MyDat SomeData
  {
    get;
    set;
  }
}
class MyDat : INotifyPropertyChanged
{
  private string str;
  public string Str;
  {
    get { return this.str;}
    set
    {
      this.str = value;
      this.PropertyChanged(this, "Str");
    }
  }

  publicMyNestedDat NestedObj
  {
   get;
   set;
  }
}
class MyNestedDat : INotifyPropertyChanged
{
  private int num;
  public int Num
  {
    get{ return this.num;}
    set
    {
      this.num = value;
      this.PropertyChanged(this, "Num");
    }
  }
}

我如何得到这个工作?我真的不知道从哪里开始。

mynestedat类抛出PropertyChanged, MyDat类抛出PropertyChanged,我想在我的视图模型中捕获它们。我怎么能做到呢?

MVVM and INotifyPropertyChanged Issue

在我看来,你所问的问题有一些概念上的错误。想象一下,您得到了一个适用于您的场景的解决方案(您很满意),并考虑以下内容:

  • 如果添加另一层会发生什么?你还期望它能像以前一样工作吗?
  • 属性变化是否应该传播(viewModel1.propA通知viewModel2.PropA)?
  • 属性变化是否应该转换(viewModel1.SomeProp通知ViewModel2.AnotherProp)?
  • 是否关注性能?如果您需要将属性更改事件传播到多个级别,那么这将如何执行?

这应该敲响警钟,当前的方法不是正确的。

你需要的是以松耦合的方式在你的视图模型之间提供通信,这样你的视图模型甚至不需要知道彼此的存在。这样做的美妙之处在于,它不仅可以用于属性更改,还可以用于其他情况。

对于属性更改事件的情况,一个viewModel想要知道何时发生(记住,它可能不是属性更改事件)。这意味着另一个viewModel需要某种方式说"嘿,一个属性已经改变了"(或"我的状态已经改变了","数据库调用已经完成"等)。

现在在c#中,您可以提供提供此功能的事件....除了,现在你的对象知道彼此,这给你留下了你以前遇到过的同样的问题。

为了克服这个问题,您需要另一个对象,一个中介(在本例中我们称之为Messenger),它的唯一目的是处理在对象之间传递的消息,以便它们可以相互忽略。

总体思路是这样的。在提供通知的viewModel中,你可以这样做:

public string MyProp
{
    get { return _myProp; }
    set
    {
        _mProp = value;
        OnPropertyChanged("MyProp");
        Messenger.PostMessage(new VMChangedMessage { ViewModel = this, PropertyName = "MyProp" });
    }
}

在对事件感兴趣的viewModel中你可以这样做:

public class ViewModel2
{
    public ViewModel2()
    {
        Messenger.Subscribe<VMChangedMessage>(handleMessage);
    }
    private void handleMessage(VMChangedMessage msg)
    {
        // Do something with the information here...
    }
}

注意,这两个viewmodel从不相互引用。它们现在是松耦合的。

有许多预先存在的实现已经可用,并且创建自己的实现并不困难(信使基本上保留对某个消息感兴趣的对象列表,并在需要通知感兴趣的各方时迭代该列表)。有一些事情可以以不同的方式实现(有些实现只是传递字符串消息,而不是将信息封装在对象中,有些实现自动处理观察者的清理)。

我建议使用Josh Smiths(优秀的)MVVM Foundation,其中包括一个信使类。它也是开源的,所以你可以看到它是如何工作的。

对于PropertyName应该在PropertyChangedEventArgs中包含什么没有明确的约束。

参见订阅INotifyPropertyChanged获取嵌套(子)对象。

下面是一个例子:

class A : BaseObjectImplementingINotifyPropertyChanged {
  private string m_name;
  public string Name {
    get { return m_name; }
    set {
      if(m_name != value) {
        m_name = value;
        RaisePropertyChanged("Name");
      }
    }
  }
}
class B : BaseObjectImplementingINotifyPropertyChanged {
  private A m_a;
  public A A {
    get { return m_a; }
    set {
      if(m_a != value) {
        if(m_a != null) m_a.PropertyChanged -= OnAPropertyChanged;
        m_a = value;
        if(m_a != null) m_a.PropertyChanged += OnAPropertyChanged;
        RaisePropertyChanged("A");
      }
    }
  }
  private void OnAPropertyChanged(object sender, PropertyChangedEventArgs e) {
    RaisePropertyChanged("A." + e.PropertyName);
  }
}

B b = new B();
b.PropertyChanged += (s, e) => { Console.WriteLine(e.PropertyName); };
b.A.Name = "Blah"; // Will print "A.Name"

最好将ModelViewModel的概念分开。

通过使用比Model平坦的ViewModel对象,可以避免这种情况。使用自动映射工具,如Automapper,然后允许您将Model映射到ViewModel,反之亦然。

https://github.com/AutoMapper/AutoMapper/wiki/Flattening

class MyDatViewModel : INotifyPropertyChanged
{
    public string Str
    {
        // ... Get Set
    }
    public int NestedObjNum
    {
        // ... Get set
    }
}
// Configure AutoMapper
Mapper.CreateMap<MyDat, MyDatViewModel>();
// Perform mapping
MyDatViewModel viewModel = Mapper.Map<MyDat, MyDatViewModel>(someData);