views:

488

answers:

5

Let's say in some abstract ViewModel base-class I have a plain-old property as follows:

public Size Size
{
    get { return _size; }
    set
    {
        _size = value;
        OnPropertyChanged("Size");
    }
}

I then create a more specific ViewModel, inheriting from the previous one, which contains the following property:

public Rect Rectangle
{
    get { return new Rect(0, 0, _size.Width, _size.Height); }
}

Now, in some View class I bind to the aforementioned ViewModel's Rectangle property. Everything works fine, until I change the size. When Size changes, Rectangle doesn't know about it and the change doesn't propagate to the View. And since Rectangle is in the child class, I can't simply add an OnPropertyChanged("Rectangle") to the Size setter.

Now imagine that I have many different properties like Rectangle, that all depend on base-class properties, and that none of these changes are being propagated. I need some lightweight and elegant way of chaining change notifications, preferably one that doesn't require a lot of code and doesn't force me into using dependency properties.

Obviously there are a lot of ugly solutions here- what I am looking for is something clean and clever. It seems to me this would be a very common scenario, and it seems to me there might be an MVVM-friendly way of doing this.

+2  A: 

You can simply override OnPropertyChanged in the derived ViewModel like so:

protected override void OnPropertyChanged(string propertyName) {
    base.OnPropertyChanged(propertyName);
    if (propertyName == "Size") {
        base.OnPropertyChanged("Rectangle");
    }
}

Another possibility... A while back I put together a pretty nice ViewModel base class that supports attributes on properties like:

[DependsOn("Size")]
public Rect Rectangle {
    get { new Rect(0,0,Size.Width, Size.Height); }
}

Then the ViewModel base class collects these DependsOnAttribute's at runtime and in its OnPropertyChanged method it basically just looks to see what other properties need to be invalidated when a property change occurs.

Josh Einstein
Overriding OnPropertyChanged is definitely a solution, but not an elegant one. I like your second suggestion better, though.
Charlie
Not all solutions call for elegance. Overriding OnPropertyChanged is very straightforward and I still do that in many cases unless the dependencies get hairy which led me to do the 2nd approach.
Josh Einstein
You are right, but remember in my question I did specifically say, "Now imagine that I have many different properties like Rectangle, that all depend on base-class properties, and that none of these changes are being propagated." So the assumption is that the hierarchy is already getting hairy.
Charlie
To be honest, I'm not completely satisfied with my attribute-based approach either. I have been toying with a DSL to create ViewModels that picks up on which properties should trigger dependent property changes but I haven't gotten that to anywhere near a satisfactory point.
Josh Einstein
A: 

A clean MVVM way would be to use a Messenger subscribe/notify mechanism (like in Josh Smith's MvvmFoundation)

Create a singleton Messenger object somewhere - the main App class is always a good place for this

public partial class App : Application
{
    private static Messenger _messenger;
    public static Messenger Messenger
    {
        get
        {
            if (_messenger == null)
            {
                _messenger = new Messenger();
            }
            return _messenger;
        }
    }
}

In the Size setter from the base class, notify changes:

public Size Size
{
    get { return _size; }
    set
    {
        _size = value;
        OnPropertyChanged("Size");

        App.Messenger.NotifyColleagues("SIZE_CHANGED");
    }
}

Now you can let your inherited ViewModel's listen for these changes, and raise PropertyChanged events as appropriate...

public MyViewModel : MyViewModelBase
{
    public MyViewModel()
    {
        App.Messenger.Register("SIZE_CHANGED", () => OnPropertyChanged("Rectangle"));
    }
}

Of course - you can add as many subscriptions to this message as you need - one for each property that needs changes to be notified back to the View...

Hope this helps :)

IanR
This just does exactly the same as overriding OnPropertyChanged for the child in the parent, not sure it really makes it any cleaner
AWC
This isn't a bad idea at all, but I want something a little more lightweight.
Charlie
A: 

maybe because im a VB guy, but in your Rectangle code it looks like you are accessing the private _size declaration instead of the Public Size property which would not fire the OnPropertyChanged event to alert the view.

Also I may be off base, but shouldnt Rectangle be an actual Object while Size is a property of that object? Maybe that is what you are doing..some C# methodologies are still really foreign to me.

ecathell
I am using the private _size declaration but it's irrelevant. That is a getter, not a setter. The view needs to be notified that Rectangle is changing when Size changes, not when Rectangle is being accessed.
Charlie
+3  A: 

I recently blogged about this exact problem. I include a [DependsUpon("Size")] attribute with the Rectangle. I REALLY like this approach, because it keeps the dependency knowledge with the code that creates the dependency, not the other way around.

Take a look: http://houseofbilz.com/archive/2009/11/14/adventures-in-mvvm----dependant-properties-with-inotifypropertychanged.aspx

Brian Genisio
Great idea. I changed it a bit to create a dictionary at construction time (for performance reasons), but the concept is sound.
Charlie
+1  A: 

I use Josh Smith's PropertyObserver, which you can get from his MVVM Foundation library at http://mvvmfoundation.codeplex.com/.

Usage:

_viewmodel_observer = new PropertyObserver<OtherViewModel>(_OtherViewModel)
   .RegisterHandler(m => m.Size, m => RaisePropertyChanged(Rectangle);

Brian's attribute approach is nice too. One thing I like about PropertyObserver is that I can execute arbitrary code; allowing me to check conditions which may make me avoid the raise or perform other actions all together.

Tom