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



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

这就是绑定:

<tk:BusyIndicator IsBusy="{Binding Tabs, Converter={StaticResource BusyTabsToStateConverter}}">

视图模型:

public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<Tab> _tabs;
public ObservableCollection<Tab> 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<Tab>;
return tabs.Any(tab => tab.IsBusy);
}
}

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

当集合中任何项的IsBusy属性发生更改时,是否有方法使通知正确触发?

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

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

public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Tab> Tabs { get; } = new ObservableCollection<Tab>();
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<T>
: ObservableCollection<T> 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<Tab> Tabs { get; }
= new ObservableItemCollection<Tab>();
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,并在添加和删除集合中的项目时自动将其添加和删除。如果你投赞成票,请up-vote@Clemens答案也是,因为这是基于他的工作。

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

(注意:这需要C#7,因为它使用了一个本地函数来简化处理CollectionChanged处理程序的委托。)

public static class ObservableCollectionExtensions
{
public static Hook<TList> RegisterPropertyChangeHook<TList>(this ObservableCollection<TList> 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<TList>(collection, Collection_CollectionChanged);
}
public class Hook<TList> where TList : INotifyPropertyChanged
{
internal Hook(ObservableCollection<TList> collection, NotifyCollectionChangedEventHandler handler)
{
_handler = handler;
_collection = collection;
collection.CollectionChanged += handler;
}
private NotifyCollectionChangedEventHandler _handler;
private ObservableCollection<TList> _collection;
public void Unregister()
{
_collection.CollectionChanged -= _handler;
}
}
}

你可以这样使用它:

void Main()
{
var list = new ObservableCollection<Animal>();
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();

由于扩展方法类不支持泛型,注销比我预期的要棘手。它使用"memento"模式返回一个对象,以后可以使用该对象注销。

要将通知从模型传播到模型的集合,您需要在集合本身中有一个Notificationable属性。

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

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

相关内容

  • 没有找到相关文章

最新更新