Oblomingo
Oblomingo

Reputation: 698

How to bind to child view model property in WPF application?

I am developing WPF application with Prism MVVM framework. And I doesn't know how properly pass data between parent and child view models.

I have 2 view models - ParentViewModel and inner ChildViewModel.

public class ParentViewModel
{
    public ParentViewModel
    {
        ChildViewModel = new ChildViewModel(params);
    }

        private ChildViewModel _childViewModel;
        public ChildViewModel ChildViewModel
        {
            get { return _childViewModel; }
            set
            {
                SetProperty(ref _childViewModel, value);
            }
        }

        //This is does not work
        public int SelectedChildNumber
        {
            return _childViewModel.SelectedNumber;
        }
}

public class ChildViewModel
{
    public ChildViewModel
    {
        _numbers = new List<int>();
    }

    private List<int> _numbers;
    public List<int> Numbers
    {
        get { return _numbers; }
        set
        {
            SetProperty(ref _numbers, value);
        }
    }

    private int _selectedNumber;
    public int SelectedNumber
    {
        get { return _selectedNumber; }
        set
        {
            SetProperty(ref _selectedNumber, value);
        }
    }
}

I want to get and use selected value from child view model. My approach doesn't work - SelectedChildNumber doesn't want to refresh if SelectedNumber changes in ChildViewModel.

UPDATE: Ok, What if I have ChildViewModel collection in ParentViewModel. One of this ChildViewModels have property IsSelected equals true. How to get this one selected view model from collection?

public class ParentViewModel
{
    public ParentViewModel
    {
        Items = GetItems();
    }

    private ObservableCollection<ChildViewModel> _items;
    public ObservableCollection<ChildViewModel> Items
    {
        get
        {
            return _items;
        }
        set
        {
            SetProperty(ref _items, value);
        }
    }
}

public class ChildViewModel
{
    public ChildViewModel
    {
    }

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            SetProperty(ref _isSelected, value);
        }
    }
}

How to get selected view model? Maybe use a converter?

<someUserControl DataContext="{Binding ParentViewModel.Items, Converter={x:Static c:GetSelectedItemConverter.Instance}}" />

In converter I can find selected item. Or this is bad idea?

UPDATE 2:

Ok, I beat this problem with Ed Plunkett help. Final version should be:

public class ParentViewModel
{
    public ParentViewModel
    {
        Items = GetItems();
        foreach (var item in Items)
        {
            item.PropertyChanged += ChildViewModel_PropertyChanged;
        }
    }

    private ObservableCollection<ChildViewModel> _items;
    public ObservableCollection<ChildViewModel> Items
    {
        get
        {
            return _items;
        }
        set
        {
            SetProperty(ref _items, value);
        }
    }

    private ChildViewModel _selectedChild;
    public ChildViewModel SelectedChild
    {
        get { return _selectedChild; }
        set
        {
            SetProperty(ref _selectedChild, value);
        }
    }

    private void ChildViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var child = (ChildViewModel)sender;
        if (e.PropertyName == nameof(ChildViewModel.IsSelected) && child.IsSelected)
        {
            SelectedChild = child;
        }
    }
} 

Upvotes: 3

Views: 4839

Answers (1)

Bind directly to the child property:

<ListBox 
    ItemsSource="{Binding ChildViewModel.Numbers}"
    SelectedItem="{Binding ChildViewModel.SelectedNumber}"
    />

<Label Content="{Binding ChildViewModel.SelectedNumber}" />

That's the name of the parent's ChildViewModel property in the binding path, not the type. The Binding now knows to listen to the ChildViewModel object for PropertyChanged notifications regarding SelectedNumber and Numbers.

The reason your version doesn't work is that the parent does not raise PropertyChanged when SelectedChildNumber changes. In fact, the parent doesn't know when it changes any more than the UI does. The parent could handle the child's PropertyChanged event, and sometimes that's done.

public ParentViewModel()
{
    ChildViewModel = new ChildViewModel(params);

    //  Handle child's PropertyChanged event
    ChildViewModel.PropertyChanged += ChildViewModel_PropertyChanged;
}

private void ChildViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    var child = (ChildViewModel)sender;

    if (e.PropertyName == nameof(ChildViewModel.SelectedNumber))
    {
        //  Do stuff
    }
}

But you don't need to do that for cases like this one.

ChildViewModel.Numbers should probably be ObservableCollection<int>, not List<int>. That way, if you add more numbers to it or remove any, the UI will automatically be notified by the collection and the ListBox will automatically update itself accordingly.

Upvotes: 4

Related Questions