Q:

WPF binding to property of all items in a collection

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?

A:

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 the Tabs 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 the TabsCollectionChanged handler in the Tabs setter.
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)));
        }
    }
}

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

The view model could now be reduced to this:
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)));
        }
    }
}

