无法更新子项列表框

Cannot update subitems ListBox

我有两个 ListBox。一个 ListBox 显示一些项目。每个项目都有一个子项目列表。第二个 ListBox 显示第一个 ListBox 中当前项目的子项目。一个典型的 item/subitem 场景。当我向子项列表添加值时,无法更新第二个 ListBox。我怎样才能强制第二个 ListBox 更新我的子项目?

我的简化 XAML 和视图模型如下。请注意,我的视图模型继承自 Prism 的 BindableBase。在我的视图模型中,我有一种方法可以将值添加到子项列表并使用 RaisePropertyChanged.

更新 Items 属性
<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");
    }
}
class Item
{
    public int ItemValue { get; set; }
    public ObservableCollection<int> SubItems { get;}
      = new ObservableCollection<int>();
}
class MyViewModel : BindableBase
{
    publc ObservableCollection<Item> Items {get;}
        = new ObservableCollection<Item>();        

    public MyViewModel()
    {
        List<Item> myItems = new List<Item>() {  /* a bunch of items */ };

        myItems.ForEach(item => Items.Add(item));
    }

    private Item _currentItem;
    private Item CurrentItem
    {
       get => _currentItem;
       set
       {
          if(Equals(_currentItem, value))
            return;

          _currentItem = value;
          RaisePropertyChanged(nameof(CurrentItem));
       }
    }

    private void AddNewSubItem(object obj)
    {
        CurrentItem.SubItems.Add(123);            
    }
}
<ListBox ItemsSource="{Binding Items}" 
         SelectedItem="{Binding CurrentItem}">
  ********
  ********
</ListBox>

<ListBox ItemsSource="{Binding CurrentItem.SubItems}" >
  ********
  ********
</ListBox>

问题是您的 Item 类型包含 List<int>,它不会通知集合更改,因为它没有实现 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> 来解决问题。

顺便说一句,这同样适用于 ItemValueSubItems 属性以及 INotifyPropertyChanged。这两个属性都有 setters,这意味着它们可以被重新分配,但是绑定也不会得到通知。由于您使用 BindableBase,您可以使用带有支持字段的 SetProperty 方法在设置 属性 时自动引发 PropertyChanged 事件。

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);
   }
}

关于您的项目的评论。您尝试做的是 master-detail view for hierarchical data。由于您已经公开了 ICollectionView,因此您不必显式使用 CurrentItem

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.

有一个special binding syntax, where you can use a /表示当前项目,例如:

<ListBox Grid.Row="1" Name="SubItems" ItemsSource="{Binding Items/SubItems}">

另外一些关于您的视图模型代码的评论。

  • _items 字段初始值设定项是无用的,因为您无论如何都在构造函数中重新分配字段。
  • 如果只使用集合视图而不使用_items,可以在构造函数中使用局部变量。
  • Items的privatesetter不用了,可以去掉
  • Items.CurrentItem 直接转换为 Item 以获得无效转换异常,该异常比您稍后在使用 as 访问值时将获得的空引用异常更具体并获得 null。如果您期望该值可能不是 Item,您可以在 as 之后检查 null 或使用模式匹配来处理这两种情况,成功和错误 (CurrentItem 的类型不正确 Item)。