Jakob Busk Sørensen
Jakob Busk Sørensen

Reputation: 6091

Make binding work on property in a different class

So I have a class MainViewModel in which I have a button. The button navigates to a view which has its own view model, let's call in ListViewModel. It resides inside MainViewModel. It has an ObservableCollection called WorkOrders.

In my main view model, I have a property, which returns the number of items, in the list in my ListViewModel. However, if I bind my button text to this property (NumberOfWorkOrders), then nothing happens, when WorkOrders.Count() changes. Even if I call OnPropertyChanged("NumberOfWorkOrders").

However, it does work, if I bind to the identical property inside the ListViewModel. How come it does not work, with the property in the MainViewModel? Is it because the notification from INotifyPropertyChanged does not work in a different view model?

Button binding which DOES NOT work (uses property from MainViewModel)

<Button 
    Content="{Binding NumberOfWorkOrders}" 
    ContentStringFormat="WorkOrders ({0})" />

Button binding which DOES work (uses property from ListViewModel)

<Button 
    DataContext="{Binding LVM}"
    Content="{Binding NumberOfWorkOrders}" 
    ContentStringFormat="WorkOrders ({0})" />

MainViewModel.cs

public class MainViewModel : BindableBase
{
    // INotifyPropertiesChanged is implemented in BindableBase

    private ListViewModel listViewModel = new ListViewModel();

    // This is where I would like to bind my button text to
    public int NumberOfWorkOrders
    {
        get { return listViewModel.WorkOrders.Count(); }
    }

    // I would prefer not to have this
    public ListViewModel LVM
    {
        get { return listViewModel; }
    }
}

ListViewModel.cs

public class ListViewModel : BindableBase
{
    // INotifyPropertiesChanged is implemented in BindableBase

    public ObservableCollection<WorkOrder> WorkOrders
    {
        get; set;
    }

    // I would prefer to use the version of this in the MainViewModel
    public int NumberOfWorkOrders
    {
        get { return listViewModel.WorkOrders.Count(); }
    }

    public void RefreshWorkOrders()
    {
        (...) // Load work orders and add them to collection

        OnPropertyChanged("NumberOfWorkOrders");
    }
}

Upvotes: 0

Views: 151

Answers (2)

Sinatr
Sinatr

Reputation: 22008

You are running into this problem , where you have aggregated property which required additional job: you will have to subscribe to ListViewModel.PropertyChanged and rise notification for MainViewModel.NumberOfWorkOrders property:

public class MainViewModel : BindableBase
{
    readonly ListViewModel listViewModel = new ListViewModel();
    public int NumberOfWorkOrders => listViewModel.WorkOrders.Count();

    public MainViewModel()
    {
        // since you already rise notification in ListViewModel
        // listen to it and "propagate"
        listViewModel.PropertyChanged += (s, e) =>
        {
            if(e.PropertyName == nameof(ListViewModel.NumberOfWorkOrders))
                OnPropertyChanged(nameof(NumberOfWorkOrders));
        };
    }
}

First button (which does NOT work) has MainViewModel as data context, binding will only listen for notification in this class.

As a simple fix you can include LVM in Path of binding. Binding is smart and will start listening to both events: of main view model and of that instance given by LVM property:

<Button Content="{Binding LVM.NumberOfWorkOrders}" ... />

you can delete NumberOfWorkOrders from MainViewModel then.

Upvotes: 1

andnik
andnik

Reputation: 2804

The reason your MainViewModel binding doesn't work, because you call OnPropertyChanged("NumberOfWorkOrders") from ListViewModel context. I would suggest in MainViewModel to sign to changes in listViewModel.WorkOrders, and fire OnPropertyChanged("NumberOfWorkOrders") from MainViewModel, when WorkOrders changed. You need to look into documentation of ObservableCollection to find how to sign for collection changed notifications.

Upvotes: 0

Related Questions