6

I'd like to have methodToBeCalledWhenPropertyIsSet() execute when Property in the Model is changed.

How could I do this?

If I understand correctly, I could add MyModel.PropertyChanged += methodToBeCalledWhenPropertyIsSet somewhere in my ViewModel to subscribe to the PropertyChanged event in general but I only care when Property is set

public class ViewModel : INotifyPropertyChanged
{
    ...

    public Model MyModel { get; set; }

    public void methodToBeCalledWhenPropertyIsSet() { }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Model : INotifyPropertyChanged
{
    object _propertyField;
    public object Property
    {
        get
        {
            return _propertyField;
        }
        set
        {
            _propertyField = value;
             OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
user229044
  • 232,980
  • 40
  • 330
  • 338
  • I don't quite understand what you're after. Can you explain in more detail, maybe give some example? – dkozl Jul 09 '14 at 16:17
  • You need to pass the propety name when calling OnPropertyChanged – mclaassen Jul 09 '14 at 16:17
  • use `void methodToBeCalledWhenPropertyIsSet(object sender, PropertyChangedEventArgs e)` then check `if (e.PropertyName=="Property")` – Bolu Jul 09 '14 at 16:19
  • @mclaassen : My OnPropertyChanged method has an attribute [CallerMemberName] that removes the necessity to do that if OnPropertyChanged() is called within the property itself – William Thomas Waller Jul 09 '14 at 16:20
  • @WilliamThomasWaller so what is wrong with subscribing to `PropertyChanged` event? It should be called when property is set – dkozl Jul 09 '14 at 16:23
  • I'm going to try out Bolu's suggestion, because that seems like the right thing to do. – William Thomas Waller Jul 09 '14 at 16:26
  • 1
    @WilliamThomasWaller remember that If you subscribe to the model's events in the VM you need to unsubscribe properly. Otherwise you might get memory leaks. See my answer that does not require any additional event subscription. – Federico Berasategui Jul 09 '14 at 16:28

2 Answers2

14

The INotifyPropertyChanged interface solves this problem. Subscribe your View Model to the Models PropertyChangedEventHandler and filter your results.

public class ViewModel : INotifyPropertyChanged
{
    ...

    public Model MyModel { get; set; }

    public void methodToBeCalledWhenPropertyIsSet() { }

    public event PropertyChangedEventHandler PropertyChanged;

    public ViewModel()
    {
        // MyModel would need to be set in this example.
        MyModel.PropertyChanged += Model_PropertyChanged;
    }

    private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "Property")
        {
             methodToBeCalledWhenPropertyIsSet();
        }
    }
}

In the MVVM pattern the View Model is intended to deal with messy situations like this. This still maintains your abstraction from the model.

Edit As HighCore pointed out, this code doesn't work copy paste. MyModel would need to be instantiated beforehand. I use MEF(http://msdn.microsoft.com/en-us/library/dd460648(v=vs.110).aspx) for this purpose. You can either grab a model class directly or use some kind of factory/manager to get a reference.

Daniel Johnson
  • 351
  • 1
  • 2
  • 11
  • 1
    I did use your solution and it works how I need it to, so +1, but I'm going to try out HighCore's solution also. Thanks! – William Thomas Waller Jul 09 '14 at 16:49
  • 2
    I updated my answer to explain that it isn't intended to be copy pasted into a solution. If MEF or an Inversion of Control container like Unity(https://unity.codeplex.com/) were used, it would solve that problem. I agree that this is strongly typed, but I don't think that is a terrible thing in the View Model. I'm more concerned about getting updates from the Model than typing too strongly. – Daniel Johnson Jul 09 '14 at 16:52
  • Question: Is it better to subscribe once to `PropertyChanged` and then make a switch/case in the ViewModel dispatching by event name OR is it possible to have multiple `PropertyChangedEventHandler` within the model and give them names like `prop1Changed`and `prop2Changed`? Or does that screw up the internal event handling? – mefiX Dec 13 '18 at 11:05
  • @mefiX I don't see why not. I don't have a good setup to test at this moment, but it is just an event your are subscribing to. It shouldn't matter if you have multiple listeners. Depending on your scenario I could see either way being valid. – Daniel Johnson Dec 19 '18 at 00:15
3

One possible, commonly used solution is to wrap the Model's Property with an equivalent property in the ViewModel:

public class ViewModel
{
    public object Property
    {
        get
        {
            return Model.Property;
        }
        set
        {
             Model.Property = value;
             methodToBeCalledWhenPropertyIsSet(); // Here you call your method directly
        }
    }
}

And bind your UI to this property rather than the Model's.

Edit: If the model changes due to UI interaction, then that interaction will occur "thru" the ViewModel. If the model changes due to internal business logic in the model itself, then you'll have no other option but to subscribe to it's PropertyChanged event, but make sure you properly unsubscribe at a later moment. I usually put such subscription / unsubscription code In the setter of the Model property in the VM:

public MyModel Model
{
    get { return _model; }
    set
    {
        if (_model != null)
            _model.PropertyChanged -= OnModelPropertyChanged;

        _model = value;

        if (_model != null
            _model.PropertyChanged += OnModelPropertyChanged;

        NotifyPropertyChange(() => Model);
     } 
}
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • Will ViewModel's Property change when the Model's Property does? I may have abstracted my example too much because now it's hard to describe what I'm actually trying to do lol. I actually have a list of Models and was planning on just having my methodToBeCalled() associated with all of their PropertyChanged events – William Thomas Waller Jul 09 '14 at 16:34
  • +1. I that is a much cleaner method of subscribing/unsubscribing than what I did. Do you have a recommendation on how to handle the PropertyChanged event when you need to react to updates from the Model? – Daniel Johnson Jul 09 '14 at 16:58
  • @wirecat I usually don't do that because my code is largely based on T4 code generation, so all my VMs basically "clone" the Model and add application logic. I don't put business logic into the Model classes because I use them as DTOs, but that's just me. Since my VMs are also platform-agnostic (living in PCLs rather than WPF-specific assemblies), I tend to put all the logic into VMs. – Federico Berasategui Jul 09 '14 at 17:01