Behavior for enabling/disabling a WPF ComboBox depending on number of items

102 views Asked by At

Whenever the ItemsSource of a WPF ComboBox changes (i.e. adding or removing elements, or assigning a new Collection), I want it to do the following (pseudo code):

IsEnabled = Items.Count > 1;

if (Items.Count == 1)
    SelectedItem = Items.First();

I'm using MVVM, and I know how to do it in MVVM. But I want to avoid the boilerplate code, and IMHO it is too UI-specific for the ViewModel to care about. So is there a way to specify this in a Behavior<ComboBox>?

I couldn't find a ComboBox event I could subscribe to. If possible, it should work on ObservableCollection as well as ICollectionView or List etc.

2

There are 2 answers

1
mm8 On BEST ANSWER

You could use a Style with triggers:

<Style x:Key="customComboBox" TargetType="ComboBox">
    <Style.Triggers>
        <Trigger Property="HasItems" Value="False">
            <Setter Property="IsEnabled" Value="False" />
        </Trigger>
        <DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}"
                     Value="1">
            <Setter Property="SelectedIndex" Value="0" />
        </DataTrigger>
    </Style.Triggers>
</Style>

If you for example put this in your App.xaml file, you can use it across the whole application, e.g.:

<ComboBox ItemsSource="{Binding SomeCollection}" Style="{StaticResource customComboBox}" />
8
emoacht On

You can capture the change of the source collection from the change of ItemsSource DependencyProperty and the change of the source collection items from CollectionChanged events provided that the source collection implements INotifyCollectionChanged.

using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Microsoft.Xaml.Behaviors;

public class ItemsControlBehavior : Behavior<ItemsControl>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
        dpd?.AddValueChanged(AssociatedObject, OnItemsSourceChanged);

        AddSourceCollection();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
        dpd?.RemoveValueChanged(AssociatedObject, OnItemsSourceChanged);

        RemoveSourceCollection();
    }

    private void OnItemsSourceChanged(object? sender, EventArgs e)
    {
        RemoveSourceCollection();
        AddSourceCollection();
    }

    private INotifyCollectionChanged? _collection;

    private void AddSourceCollection()
    {
        _collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (_collection is not null)
        {
            _collection.CollectionChanged += OnItemsSourceCollectionChanged;
        }
        Apply();
    }

    private void RemoveSourceCollection()
    {
        if (_collection is not null)
        {
            _collection.CollectionChanged -= OnItemsSourceCollectionChanged;
        }
        _collection = null;
    }

    private void OnItemsSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        Apply();
    }

    private void Apply()
    {
        AssociatedObject.IsEnabled = _collection is ICollection { Count: > 1 };

        if (AssociatedObject is Selector selector)
        {
            selector.SelectedIndex = 0;
        }
    }
}