ItemsSource and collections in which only element

2019-08-27 19:05发布

I'm having grief with a ComboBox not reflecting changes in the properties of the collection to which its ItemsSource is bound.

There is a tree made up of a Categories observable collection of Category objects containing Setting objects. Some settings define the presentation names for the domain of values permitted for other settings. These are spread over several categories, but a little magic with LINQ produces an ObservableCollection in which ChannelCategory exposes properties ChannelNumber and ChannelTitle.

ChannelCategory.ChannelTitle is a StringSetting, ChannelNumber is an int, an immutable identity value for which ChannelTitle provides a display string. Omitting abundant but irrelevant other nodes, we have the following:

Channels
  ChannelCategory
    ChannelNumber = 1
    ChannelTitle = "caption for channel one"
  ChannelCategory
    ChannelNumber = 2
    ChannelTitle = "caption for channel two"
  ChannelCategory
    ChannelNumber = 3
    ChannelTitle = "caption for channel three"
...

This Channels collection is prepared and exposed by a property on a class instantiated and added to the window resource dictionary in XAML (accessible as a StaticResource). This arrangement allows me declaratively bind a combobox to Channels

    <ComboBox VerticalAlignment="Center" Grid.Column="2" 
              ItemsSource="{Binding Source={StaticResource cats}, Path=Channels}"
              DisplayMemberPath="ChannelTitle.Editor"
              SelectedValuePath="ChannelNumber"
              SelectedValue="{Binding Editor}"
              />

This all works, but edits elsewhere to the ChannelTitle value are not reflected in the values shown in the combobox list.

Various debugging trickery with breakpoints and the DropDownOpened event allowed me to ascertain that the updates are available from the collection referenced by ItemsSource.

And finally we reach the mystery. Why doesn't the combobox detect changes to the elements of the collection? The collection itself is an ObservableCollection so it should notify property changes.

The elements of the collection are all ChannelCategory which is a DependencyObject, and ChannelCategory.ChannelTitle is a DependencyProperty.

I think the problem is that I'm neither adding nor removing items from the collection, so as far as the collection is concerned it has the same elements and therefore hasn't changed.

Can anyone suggest a strategy for causing changes to ChannelTitle to cause Channels collection to notify change so the combobox updates?


Rachel's suggestion ended up as shown below. In the context of my application there was considerably more complexity because each ChannelCategory owns a collection of settings objects, so the changing value is a property of a an object in a collection that is a property of an object in the collection to which ItemsSource is bound. But the essence of Rachel's suggestion simply needed application at two levels.

public class ObservableChannelCollection : ObservableCollection<ChannelCategory>
{
  protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  {
    if (e.NewItems != null)
      foreach (ChannelCategory channel in e.NewItems)
        channel.PropertyChanged += channel_PropertyChanged;
    if (e.OldItems != null)
      foreach (ChannelCategory channel in e.OldItems)
        channel.PropertyChanged -= channel_PropertyChanged;
    base.OnCollectionChanged(e);
  }
  void channel_PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    OnPropertyChanged(e);
  }
}

1条回答
再贱就再见
2楼-- · 2019-08-27 19:39

ObservableCollection tracks changes to the Collection itself, such as Add, Remove, Reset, etc, but it does not track changes to individual items inside the collection. So if you update the property on an item in an ObservableCollection, the collection doesn't get notified that something has changed.

One alternative is to add an event to the ObservableCollection.CollectionChanged event, and when new items get added, hook up a property change on the new items that will raise the collection changed event.

void MyObservableCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach(MyItem item in e.NewItems)
        {
            MyItem.PropertyChanged += MyItem_PropertyChanged;
        }
    }

    if (e.OldItems!= null)
    {
        foreach(MyItem item in e.OldItems)
        {
            MyItem.PropertyChanged -= MyItem_PropertyChanged;
        }
    }
}

void MyItem_PropertyChanged(object sender, PropertyChange e)
{
    RaisePropertyChanged("MyObservableCollection");
}
查看更多
登录 后发表回答