Tatranskymedved
Tatranskymedved

Reputation: 4381

C# - How to handle model's property change to invoke notify in ViewModel

Following MVVM architecture, we have a view with 2 DataGrids whose data are related and view-model having ObservableCollection<Model>.

Model have boolean property, based on which is one of the grid enabled/disabled. Everything works well and when I change the selected Model, the grid becames unusable. However, when I change the property, the Notify of the customer property IsSelectedModelChecked is not invoked (When I check/uncheck the checkbox in 1st grid, I need to invoke Notify over the property IsSelectedModelChecked).

Q: How am I able to invoke property change on Model's property change?

I've checked several questions, but none of them answered mine. I'd like to provide some ideas from top of my head, but I don't have a clue. Thanks


<unnecessary code ommited>

XAML:

<DataGrid
      ItemsSource="{Binding Models}"
      SelectedItem="{Binding SelectedModel}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name"
                 Binding="{Binding Name}"/>
        <DataGridCheckBoxColumn Header="Check"
                 Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}">
    </DataGrid.Columns>
</DataGrid>

<DataGrid
      IsEnabled="{Binding IsSelectedModelChecked}"/>

Model:

public class Model : Microsoft.VisualStudio.PlatformUI.ObservableObject
{
    private string mName = @"<Name>";
    public string Name
    {
        get { return mName; }
        set { SetProperty(ref mName, value); }
    }

    private bool mIsChecked = false;
    public bool IsChecked
    {
        get { return mIsChecked; }
        set { SetProperty(ref mIsChecked, value); }
    }
}

View-Model:

public class ViewModel : Microsoft.VisualStudio.PlatformUI.ObservableObject
{
    private Model mSelectedModel;
    public Model SelectedModel
    {
        get { return mSelectedModel; }
        set
        {
            SetProperty(ref mSelectedModel, value);
            NotifyPropertyChanged(nameof(IsSelectedModelChecked));
        }
    }

    private ObservableCollection<Model> mModels = new ObservableCollection<Model>();
    public ObservableCollection<Model> Models
    {
        get { return mModels; }
        set { SetProperty(ref mModels, value); }
    }

    public bool IsSelectedModelChecked => SelectedModel?.IsChecked ?? false;
}

Upvotes: 0

Views: 565

Answers (2)

Tatranskymedved
Tatranskymedved

Reputation: 4381

This is an extension to Haukinger's answer. He mentions issue with memory leak, that can occur when the ViewModel instance will be destroyed, but the model will exist and the handler will be still attached to the events.

In such case, WPF has implemented something called Weak Event Pattern, which allows to handle such situation. For the property change, there is existing PropertyChangedEventManager that implements such Weak Event Pattern. The code should be written as:

public Model SelectedModel
{
    get { return mSelectedModel; }
    set
    {
        if (mSelectedModel != null)
            PropertyChangedEventManager.RemoveHandler(mSelectedModel, OnSelectedModelPropertyChanged, "");

        SetProperty(ref mSelectedModel, value);

        if (mSelectedModel != null)
            PropertyChangedEventManager.AddHandler(mSelectedModel, OnSelectedModelPropertyChanged, "");

        NotifyPropertyChanged(nameof(IsSelectedModelChecked));
    }
}

private void OnSelectedModelPropertyChanged( object sender, PropertyChangedEventArgs args)
{
    if (args.PropertyName == nameof(Model.IsChecked))
        NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}

Upvotes: 0

Haukinger
Haukinger

Reputation: 10883

You have to listen for your SelectedModel's PropertyChanged event.

I.e.

public Model SelectedModel
{
    get { return mSelectedModel; }
    set
    {
        if (mSelectedModel != null)
            mSelectedModel.PropertyChanged -= OnSelectedModelPropertyChanged;
        SetProperty(ref mSelectedModel, value);
        if (mSelectedModel != null)
            mSelectedModel.PropertyChanged += OnSelectedModelPropertyChanged;
        NotifyPropertyChanged(nameof(IsSelectedModelChecked));
    }
}

private void OnSelectedModelPropertyChanged( object sender, PropertyChangedEventArgs args )
{
    if (args.PropertyName == nameof(Model.IsChecked))
        NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}

Also, you will want to take care about leaking the view model instance if your model instance lives longer than the view model.

Upvotes: 1

Related Questions