2

I have a big problem with MVVM design. I am trying to catch every PropertyChanged of my inner nested objects, including futhermore propertchanged of their nested objects, inside my ViewModel but I dont know how to do it.

Here is my structure:

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

How do i get this to work? I am really clueless where to start.

MyNestedDat class throws PropertyChanged, MyDat class throws propertychanged and i want to catch them all inside my viewmodel. How can i do that?

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
snowy hedgehog
  • 652
  • 1
  • 7
  • 23
  • 5
    You can do that by subscribing to the `PropertyChanged` event – Daniel Hilgarth Apr 09 '13 at 14:27
  • Yea but if i have a complex structure it gets messy with all the add event handler and remove event handler is there a clean way to do this? – snowy hedgehog Apr 09 '13 at 14:33
  • Why do you want to do that? Never run into a situation where this was required. – JMan Apr 09 '13 at 14:35
  • Constructs like "a.b.c.d = 123" are an anti-pattern ans nearly always indicate a design problem. Probably if you fix it, you found the solution for this question as well. – stijn Apr 09 '13 at 14:43
  • 1
    You would benefit by using more loosely coupled communication between your viewModels using a message bus / event aggregation. – Benjamin Gale Apr 09 '13 at 14:51
  • @Benjamin tell me more about loosely coupled communication I dont know what that is. – snowy hedgehog Apr 09 '13 at 16:20
  • @DanielHilgarth Ok, if not then take my example and show how is that not complicated???? – snowy hedgehog Apr 09 '13 at 16:22
  • Have you looked at PropertyObserver? http://joshsmithonwpf.wordpress.com/2009/07/11/one-way-to-avoid-messy-propertychanged-event-handling/ – Suresh Apr 09 '13 at 17:50

3 Answers3

1

There is no clear constraint about what PropertyName should contains in PropertyChangedEventArgs.

See Subscribe to INotifyPropertyChanged for nested (child) objects.

Here is an example :

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"
Community
  • 1
  • 1
Nicolas Repiquet
  • 9,097
  • 2
  • 31
  • 53
  • 1
    Oo - No offence, but this is really a weird coding style. I'm not sure, if you're code is valid or not. But I'm missing some closing curved brackets...I guess... – DHN Apr 09 '13 at 14:53
  • @DHN What's wrong with my "coding style" ? :D Corrected the two missing curly braces. – Nicolas Repiquet Apr 09 '13 at 14:57
  • `What's wrong with my "coding style"` - Are you kidding? `m_` prefixing field names? egyptian braces?!? this looks like java... if it weren't for the `get` `set`... – Federico Berasategui Apr 09 '13 at 15:52
  • So basically for every nested object property's getter and setter i remove and add handlers everytime.. Thats not really very clean to me. Is there a better solution than this??? That removing and adding handlers doesnt look simple to me for a problem who solution shall be kinda very simple, dont you think the same? – snowy hedgehog Apr 09 '13 at 16:23
1

The best thing to do here is to separate the idea of a Model and a ViewModel.

By having a ViewModel object that is flatter than the Model you can avoid this scenario. Using an automatic mapping tool like Automapper then allows you to map the Model to the ViewModel and vice versa.

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);
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
  • With flattening you mean to create every property twice, one in entity and one in viewmodel? I dont really like that either.. Whenever i change something i have to change it everywhere else. Is there another solution. Maybe there is a pattern i dont know of. Its a common problem of MVVM I guess and there must be something smooth and smiple to solve this and not instead creating every property twice.. i like your solution though but lets see if there is something better – snowy hedgehog Apr 09 '13 at 16:25
  • Do you need every property in the ViewModel? Yes you do have to duplicate some properties. /shrug – Cameron MacFarland Apr 09 '13 at 17:09
1

In my opinion there are a few conceptual things wrong with what you are asking. Just imagine you get a solution that works for your scenario (that you are happy with) and consider the following:

  • What happens if another layer is added? do you still expect it to work the same?
  • Should property changes be propagated (viewModel1.propA notifies viewModel2.PropA)?
  • Should property changes be transformed (viewModel1.SomeProp notifies ViewModel2.AnotherProp)?
  • Is performance a concern? how will this perform if you need to propagate the property changed events through many levels?

This should be raising alarm bells that the current approach is not the right path to tread.

What you need is a way to provide communication between your viewModels in a loosely coupled way so that you viewModels do not even need to know about each others existence. The beauty of this is that this will also work in other situations not just for property changes.

For your case of property changed events, one viewModel wants to know when something happens (it could be something other than a property changed event, remember). This means the other viewModel needs some way of saying "Hey, a property has changed" (or "My state has changed", "That database call has finished" etc).

Now in C# you can provide events which provide this feature....except, now your objects know about each other which leaves you with the same problem you had before.

To overcome this problem you need another object, a mediator (lets call it Messenger in this example), whose sole purpose is to handle the message passing between the objects so that they can live in ignorance of each other.

The general idea is this. In the viewModel that provides notifications you might do something like this:

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

And in the viewModel that is interested in the event you might do something like this:

public class ViewModel2
{
    public ViewModel2()
    {
        Messenger.Subscribe<VMChangedMessage>(handleMessage);
    }

    private void handleMessage(VMChangedMessage msg)
    {
        // Do something with the information here...
    }
}

Notice that the two viewModels never reference each other. They are now loosely-coupled.

There are a number of pre-existing implementations already available and it isn't difficult to create your own (the messenger basically keeps a list of objects that are interested in a certain message and iterates the list when it needs to notify the interested parties). There are a few things that can be implemented differently (some implementations just pass string messages around rather than encapsulating the information in objects, and some handle the clean-up of observers automatically).

I would recommend using Josh Smiths (excellent) MVVM Foundation which includes a messenger class. It's also open source so you can see how it works.

Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100