WPF绑定到集合中所有项的属性

我需要绑定到bool属性,只有当集合中的某个属性为true时才是true。

这是绑定:

 

和viewmodel:

 public class MainWindowViewModel : INotifyPropertyChanged { private ObservableCollection _tabs; public ObservableCollection Tabs { get { return _tabs; } set { if (value != _tabs) { _tabs = value; NotifyPropertyChanged(); } } } 

Tab类还具有属性更改通知:

 public class Tab : INotifyPropertyChanged { public bool IsBusy { get{...} set{...NotifyPropertyChanged();} } 

这是转换器:

 public class BusyTabsToStateConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var tabs = value as ObservableCollection; return tabs.Any(tab => tab.IsBusy); } } 

问题是,当Tab.IsBusy更改时,绑定源不会被通知,因为它绑定到可观察集合而不是IsBusy属性。

IsBusy中任何项目的IsBusy属性发生变化时,有没有办法正确地触发通知?

您可以在MainWindowViewModel中拥有一个AnyTabBusy属性,而不是绑定转换器,PropertyChanged事件处理程序会触发更改通知,当它们被添加到集合中或从集合中删除时,它们将附加或分离到Tabs集合中的各个元素。

在下面的示例中, Tabs属性是只读的。 如果它必须是可写的,则必须在Tabs setter中附加和分离TabsCollectionChanged处理程序。

 public class MainWindowViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public ObservableCollection Tabs { get; } = new ObservableCollection(); public bool AnyTabBusy { get { return Tabs.Any(t => t.IsBusy); } } public MainWindowViewModel() { Tabs.CollectionChanged += TabsCollectionChanged; } private void TabsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (Tab tab in e.NewItems) { tab.PropertyChanged += TabPropertyChanged; } break; case NotifyCollectionChangedAction.Remove: foreach (Tab tab in e.OldItems) { tab.PropertyChanged -= TabPropertyChanged; } break; default: break; } } private void TabPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Tab.IsBusy)) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AnyTabBusy))); } } } 

如果要使此代码可重用,可以将其放入派生的集合类中,如下所示,您可以在其中附加ItemPropertyChanged事件的处理程序。

 public class ObservableItemCollection : ObservableCollection where T : INotifyPropertyChanged { public event PropertyChangedEventHandler ItemPropertyChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { base.OnCollectionChanged(e); switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (INotifyPropertyChanged item in e.NewItems) { item.PropertyChanged += OnItemPropertyChanged; } break; case NotifyCollectionChangedAction.Remove: foreach (INotifyPropertyChanged item in e.OldItems) { item.PropertyChanged -= OnItemPropertyChanged; } break; default: break; } } private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) { ItemPropertyChanged?.Invoke(this, e); } } 

视图模型现在可以简化为:

 public class MainWindowViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public ObservableItemCollection Tabs { get; } = new ObservableItemCollection(); public bool AnyTabBusy { get { return Tabs.Any(t => t.IsBusy); } } public MainWindowViewModel() { Tabs.ItemPropertyChanged += TabPropertyChanged; } private void TabPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Tab.IsBusy)) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AnyTabBusy))); } } } 

我已经接受了@Clemens的回答 ,并转换为一种扩展方法,可以更容易地在多个集合中使用。 它将采用PropertyChangedEventHandler并在添加和删除集合中的项目时自动添加和删除它。 如果您对此进行投票,请同时向@ Clemens投票,因为这是基于他的工作。

注意不要使用匿名方法作为PropertyChanged处理程序(这适用于所有事件处理程序,而不仅仅是此解决方案),而不采取特殊预防措施 ,因为它们很难删除。

(注意:这需要C#7,因为它使用本地函数来更轻松地处理CollectionChanged处理程序的委托。)

 public static class ObservableCollectionExtensions { public static Hook RegisterPropertyChangeHook(this ObservableCollection collection, PropertyChangedEventHandler handler) where TList : INotifyPropertyChanged { void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (TList item in e.NewItems) { item.PropertyChanged += handler; } break; case NotifyCollectionChangedAction.Remove: foreach (TList item in e.OldItems) { item.PropertyChanged -= handler; } break; default: break; } } return new Hook(collection, Collection_CollectionChanged); } public class Hook where TList : INotifyPropertyChanged { internal Hook(ObservableCollection collection, NotifyCollectionChangedEventHandler handler) { _handler = handler; _collection = collection; collection.CollectionChanged += handler; } private NotifyCollectionChangedEventHandler _handler; private ObservableCollection _collection; public void Unregister() { _collection.CollectionChanged -= _handler; } } } 

你可以像这样使用它:

 void Main() { var list = new ObservableCollection(); list.RegisterPropertyChangeHook(OnPropertyChange); var animal = new Animal(); // Has a "Name" property that raises PropertyChanged list.Add(animal); animal.Name="Charlie"; // OnPropertyChange called list.Remove(animal); animal.Name="Sam"; // OnPropertyChange not called } private void OnPropertyChange(object sender, PropertyChangedEventArgs e) { Console.WriteLine($"property changed: {e.PropertyName}"); } 

如果您希望能够取消注册挂钩,请执行以下操作:

 var hook = list.RegisterPropertyChangeHook(OnPropertyChange); hook.Unregister(); 

由于扩展方法类不支持generics,取消注册最终比我预期的更棘手。 它使用“ memento ”模式返回一个可用于以后取消注册的对象。

要将模型中的通知传播到模型集合,您需要在Collection本身中具有Notifiable属性。

也许你可以扩展ObservableCollection并在其中有一个可以通知UI的属性

不幸的是,没有办法免费获得这个。 我会在MainWindowViewModel上创建一个IsBusy属性。 设置选项卡时,为集合更改添加一个侦听器,并使其更新IsBusy属性。