I have two ListBox
es. One ListBox
displays some items. Each item has a list of subitems. The second ListBox
displays the subitems of the current item in the first ListBox
. A typical item/subitem scenario. When I add a value to the subitem list, I cannot get the second ListBox
to update. How can I force the second ListBox
to update my subitems?
My simplified XAML and view model are below. Note that my view model inherits from Prism's BindableBase
. In my view model I have a method to add a value to the subitems list and update the Items
property with RaisePropertyChanged
.
<ListBox Name="Items" ItemsSource="{Binding Items}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ItemValue, Mode=TwoWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="SubItems" ItemsSource="{Binding Items.CurrentItem.SubItems}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
class Item
{
public int ItemValue { get; set; }
public List<int> SubItems { get; set; }
}
class MyViewModel : BindableBase
{
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public MyViewModel()
{
List<Item> myItems = new List<Item>() { /* a bunch of items */ };
_items = new ObservableCollection<Item>(myItems);
Items = new ListCollectionView(_items);
}
public ICollectionView Items { get; private set; }
private void AddNewSubItem(object obj)
{
Item currentItem = Items.CurrentItem as Item;
currentItem.SubItems.Add(123);
RaisePropertyChanged("Items");
}
}
The issue is that your Item
type contains a List<int>
, which does not notify on collection changes, because it does not implement the INotifyCollectionChanged
interface. Consequently, bindings do not get notified to update their values in the user interface when the collection is modified. Using a collection that implements this interface, e.g. an ObservableCollection<int>
will solve the issue.
By the way, the same applies to ItemValue
and SubItems
properties as well with INotifyPropertyChanged
. Both properties have setters, which means they can be reassigned, but the bindings will not get notified then either. Since you use BindableBase
, you can use the SetProperty
method with backing fields to automatically raise the PropertyChanged
event when a property is set.
public class Item : BindableBase
{
private int _itemValue;
private ObservableCollection<int> _subItems;
public int ItemValue
{
get => _itemValue;
set => SetProperty(ref _itemValue, value);
}
public ObservableCollection<int> SubItems
{
get => _subItems;
set => SetProperty(ref _subItems, value);
}
}
A remark about your project. What you try to do is a master-detail view for hierarchical data. Since you already expose an ICollectionView
, you do not have to use the CurrentItem
explicitly.
When the source is a collection view, the current item can be specified with a slash (/). For example, the clause Path=/ sets the binding to the current item in the view. When the source is a collection, this syntax specifies the current item of the default collection view.
There is a special binding syntax, where you can use a /
to indicate the current item, e.g.:
<ListBox Grid.Row="1" Name="SubItems" ItemsSource="{Binding Items/SubItems}">
Additionally a few remarks about your view model code.
_items
field initializer is useless, as you reassign the field in the constructor anyway._items
, you can use a local variable in the constructor.Items
is not used, you can remove it.Items.CurrentItem
directly to Item
to get an invalid cast exception that is more specific that the null reference exception that you would get later when accessing the value from using as
and getting null
. If you expect that the value could be something else than an Item
, you could check for null
after as
or use pattern matching to handle both cases, success and error (CurrentItem
is not of type Item
) appropriately.