【问题标题】:MVVM and INotifyPropertyChanged IssueMVVM 和 INotifyPropertyChanged 问题
【发布时间】:2013-04-01 00:53:28
【问题描述】:

我对 MVVM 设计有一个大问题。我试图在我的 ViewModel 中捕获我的内部嵌套对象的每个 PropertyChanged,包括它们嵌套对象的进一步属性更改,但我不知道该怎么做。

这是我的结构:

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

我如何让它工作?我真的不知道从哪里开始。

MyNestedDat 类抛出 PropertyChanged,MyDat 类抛出 propertychanged,我想在我的视图模型中捕获它们。我该怎么做?

【问题讨论】:

  • 您可以通过订阅PropertyChanged 事件来做到这一点
  • 是的,但如果我有一个复杂的结构,所有的添加事件处理程序和删除事件处理程序都会变得混乱,有没有一种干净的方法可以做到这一点?
  • 你为什么要这样做?永远不要遇到需要这样做的情况。
  • 像“a.b.c.d = 123”这样的结构是一种反模式,几乎总是表明存在设计问题。如果你修复它,你可能也找到了这个问题的解决方案。
  • 您将受益于使用消息总线/事件聚合在视图模型之间使用更松散耦合的通信。

标签: c# wpf mvvm


【解决方案1】:

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

  • 如果添加另一层会怎样?您还希望它能够以同样的方式工作吗?
  • 是否应该传播属性更改(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...
    }
}

请注意,两个视图模型从不相互引用。它们现在是松耦合的。

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

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

【讨论】:

    【解决方案2】:

    对于PropertyName 应在PropertyChangedEventArgs 中包含的内容没有明确的限制。

    Subscribe to INotifyPropertyChanged for nested (child) objects

    这是一个例子:

    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"
    

    【讨论】:

    • Oo - 没有冒犯,但这确实是一种奇怪的编码风格。我不确定你的代码是否有效。但我错过了一些右弯括号......我猜......
    • @DHN 我的“编码风格”有什么问题? :D 更正了两个缺少的花括号。
    • What's wrong with my "coding style" - 你在开玩笑吗? m_ 前缀字段名称?埃及牙套?!?这看起来像java...如果不是getset...
    • 所以基本上对于每个嵌套对象属性的 getter 和 setter,我每次都删除和添加处理程序。这对我来说并不是很干净。有没有比这更好的解决方案???删除和添加处理程序对我来说看起来并不简单,解决方案应该很简单,你不这么认为吗?
    【解决方案3】:

    这里最好的做法是将 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);
    

    【讨论】:

    • 扁平化是指每个属性创建两次,一个在实体中,一个在视图模型中?我也不是很喜欢那样。每当我改变某些东西时,我必须在其他任何地方改变它。有没有别的解决办法。也许有一种我不知道的模式。我猜它是 MVVM 的一个常见问题,必须有一些平滑和简单的方法来解决这个问题,而不是创建每个属性两次。我喜欢你的解决方案,但让我们看看是否有更好的解决方案
    • 您需要 ViewModel 中的每个属性吗?是的,您必须复制一些属性。 /耸肩
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-25
    • 2011-06-03
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    • 2016-12-19
    • 1970-01-01
    相关资源
    最近更新 更多