I need to bind to a bool property, that is only true, when one of the properties in the collection is true.
This is the binding:
<tk:BusyIndicator IsBusy="{Binding Tabs, Converter={StaticResource BusyTabsToStateConverter}}">
And the viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<Tab> _tabs;
public ObservableCollection<Tab> Tabs
{
get
{ return _tabs; }
set
{
if (value != _tabs)
{
_tabs = value;
NotifyPropertyChanged();
}
}
}
The Tab
class also has property change notification:
public class Tab : INotifyPropertyChanged
{
public bool IsBusy { get{...} set{...NotifyPropertyChanged();} }
This is the converter:
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);
}
}
The problem is, when Tab.IsBusy
changes the binding source is not notified, because it is bound to the observable collection and not to the IsBusy
property.
Is there a way to make the notification trigger correctly when the IsBusy
property on any of the items in the collection changes?
There's no way to get this for free unfortunately. I would create an IsBusy property on MainWindowViewModel. When Tabs is set, add a listener for collection changes and have that update the IsBusy property.
I've taken @Clemens' answer, and converted in to an extension method that could make it easier to use on multiple collections. It will take an
PropertyChangedEventHandler
and automatically add and remove it from the items in the collection as they are added and removed. If you up-vote this, please up-vote@Clemens' answer also, since it is based on his work.Be careful not use use an anonymous method as your
PropertyChanged
handler (this goes for all event handlers in general, not just this solution) without taking special precautions, as they can be difficult to remove.(Note: this requires C# 7, as it uses a local function to make dealing with the
CollectionChanged
handler's delegate easier.)You can use it like this:
If you want to be able to unregister the hook, do this:
Unregistering ended up being trickier than I expected, due to extension method classes not supporting generics. It uses the "memento" pattern to return an object that you can use to unregister later.
To propagate notification from Model to Collection of Model, You need to have a Notifiable property in Collection itself.
Maybe you can extend the ObservableCollection and have a Property in that which can notify the UI
Instead of a Binding Converter, you could have a
AnyTabBusy
property in MainWindowViewModel, for which a change notification is fired by a PropertyChanged event handler, which is attached or detached to individual elements from theTabs
collection when they are added to or removed from the collection.In the example below, the
Tabs
property is readonly. If it has to be writeable, you would have to attach and detach theTabsCollectionChanged
handler in the Tabs setter.If you want to make this code reusable, you could put it into a derived collection class like shown below, where you could attach a handler for an
ItemPropertyChanged
event.The view model could now be reduced to this: