Jonathan Wood
Jonathan Wood

Reputation: 67195

Nested view model in WPF

Still trying to learn MVVM and WPF here.

I'm trying to create a complex view model EditArticleViewModel. It has some code that is repeated for similar controls and so I've moved the repeating code into another class. Then, I've added several instances of that other class into EditArticleViewModel.

I will set an instance of EditArticleViewModel as my window's DataContext. And I will bind to things like Categories.Items and Subcategories.SelectedItem.

public class CategoryView
{
    public ObservableCollection<object> Items { /* */ }
    public object SelectedItem { /* ... */ }
}

public class SubcategoryView
{
    public ObservableCollection<object> Items { /* */ }
    public object SelectedItem { /* ... */ }
}

public class EditArticleViewModel : INotifyPropertyChanged
{
    public CategoryView Categories { get; private set; }
    public SubcategoryView Subcategories { get; private set; }

    public EditArticleViewModel()
    {
        Categories = new CategoryView();
        SubcategoryView Subcategories new SubcategoryView();
    }

    // Additional properties and methods here

}

As you can see, my EditArticleViewModel class implements INotifyPropertyChanged so that I can notify the visual elements when something has changed.

My question is about how I notify visual elements about changes within CategoryView and SubcategoryView. Is there a way to notify the window about changes within these classes directly? Or must I raise an event from each class and have EditArticleViewModel handle that event in order to send the appropriate notification?

Any tips appreciated.

Upvotes: 1

Views: 2905

Answers (1)

Karolis Kajenas
Karolis Kajenas

Reputation: 1543

There should only be one ViewModel per View, with an extend that primary ViewModel can contain other "ViewModels".

So when you set DataContext to your primary ViewModel all the content of it will be have a subscription to NotifyPropertyChanged event, thus implementing INotifyPropertyChanged interface in other derived ViewModel will be notified.

I would suggest implementing a base class with INotifyPropertyChanged interface which you could derive from in your other ViewModels.

By having this alteration you should solve the problem you are having:

public class ObservableViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName]string propName = null)
    {
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

public class CategoryView : ObservableViewModelBase
{
  public ObservableCollection<object> Items { /* */ }
  public object SelectedItem { /* ... */ }
}

public class SubcategoryView : ObservableViewModelBase
{
  public ObservableCollection<object> Items { /* */ }
  public object SelectedItem { /* ... */ }
}

public class EditArticleView : ObservableViewModelBase
{
  public CategoryView Categories { get; set; } = new CategoryView();
  public SubcategoryView Subcategories { get; set; } = new SubcategoryView();
}

Regarding ObservableCollection. It will notify view to change only when you add/remove items but it does not notify when content is changed. To update view on item content change you should have something like that:

public class GridRowItemViewModel : ObservableViewModelBase // From previous example.
{
      private string _sampleProp;          
      public string SampleProp
      {
          get
          {
              return _sampleProp;
          }
          set
          {
              _sampleProp = value;
              OnPropertyChanged();
          }
      }
}

And thus your Main ViewModel should look something like this:

public class MainViewModel : ObservableViewModelBase // This is your DataContext.
{
     public ObservableCollection<GridRowItemViewModel> GridCollection { get; set; }
}

EDIT: You cannot bind to fields, WPF does not resolve fields. It can only handle properties. So by creating plain fields of child ViewModels you are getting no where. Change these into properties and you will be able to access its content in the View by the property name.

Upvotes: 3

Related Questions