Mark Mercer
Mark Mercer

Reputation: 115

WPF, Update ComboBox ItemsSource when it's DataContext changes

I have two classes A and B which both implement an interface IThingWithList.

public interface IThingWithList
{
  ObservableCollection<int> TheList;
}

TheList in A contains 1, 2, 3 TheList in B contains 4, 5, 6

I have a controller class which has a list of IThingWithList which contains A and B

public class MyControllerClass
{
  public ObservableCollection<IThingWithList> Things { get; } = new ObservableCollection<IThingWithList>() { A, B };

  public IThingWithList SelectedThing { get; set; }
}

Now, in xaml I have two ComboBoxes as follows

<ComboBox
  ItemsSource="{Binding MyController.Things}"
  SelectedValue="{Binding MyController.SelectedThing, Mode=TwoWay}" />

<ComboBox
  DataContext="{Binding MyController.SelectedThing}"
  ItemsSource="{Binding TheList}" />

The first ComboBox controls which (A or B) is the data context of the second combo box.

Problem:

When I select A or B from the first ComboBox The list items of the second ComboBox are not updated.

What I have tried:

Making both A and B ObservableObjects

Making IThingWithList implement INotifyPropertyChanged

Adding UpdateSourceTrigger to the ItemsSource Bindings

Scouring Google.

Upvotes: 0

Views: 407

Answers (1)

Ginger Ninja
Ginger Ninja

Reputation: 797

Here is how I typically do the ViewModel (in your case "Controller") Base Class in order to get the functionality you are looking for:

This is the base class that all VMs derive from.

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void SetAndNotify<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
    {
        if (Equals(property, value))return;

        property = value;
        this.OnPropertyChanged(propertyName);
    }

}

Here is how I would adjust your ControllerClass:

public class MyControllerClass : ViewModelBase
{
    private ObservableCollection<IThingWithList> _things;
    public ObservableCollection<IThingWithList> Things
    {
        get => _things;
        set { SetAndNotify(ref _things, value); }
    }

    private IThingWithList _selectedthing;
    public IThingWithList SelectedThing
    {
        get => _selectedThing;
        set{SetAndNotify(ref _selectedThing, value);}
    }
}

Now adjust your XAML to have the DataContext of the container set instead of each Control (makes life easier)

<UserControl xmlns:local="clr-namespace:YourMainNamespace">
<UserControl.DataContext>
  <local:MyControllerClass/>
</UserControl.DataContext>
  <StackPanel>
    <ComboBox
      ItemsSource="{Binding Things}"
      SelectedValue="{Binding SelectedThing, Mode=TwoWay}" />

    <!-- ComboBox ItemsSource="See next lines" /-->
  </StackPanel>
</Window>

You can change SelectedThing to be an ObservableCollection or you can have a second object that is the list and updates accordingly:

//Add into MyControllerClass

public MyInnerThingList => SelectedThing.TheList;

//Edit the SelectedThing to look like:
private IThingWithList _selectedthing;
public IThingWithList SelectedThing
{
    get => _selectedThing;
    set
    {
        SetAndNotify(ref _selectedThing, value);           
        RaisePropertyChanged(nameof(MyInnerThingList));
    }
}

Then change the binding to: <ComboBox ItemsSource="{Binding MyInnerThingList, Mode=OneWay}" />

You may also want to add a SelectedMyInnerThing property, but not sure if that is needed.

Upvotes: 0

Related Questions