Search code examples
wpfcomboboxcompositecollection

Binding/Triggering "Select all"-CheckBox ComboBoxItem in WPF


I'm trying to make a WPF CustomControl CheckComboBox with a "Select All" item in addition to a user defined list of items. When "Select All" is selected, all items in the list should be checked accordingly. How can I act to the "Select All" item being clicked? I have tried a lot of things, but the property "SelectAll" in the CheckComboBox.cs is never entered.

This is my current code.

Generic.xaml

<Style TargetType="{x:Type local:CheckComboBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CheckComboBox}">
                    <ComboBox SelectedItem="{TemplateBinding SelectedItem}"
                              SelectedValue="{TemplateBinding SelectedValue}"
                              SelectedValuePath="{TemplateBinding SelectedValuePath}"
                              DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
                              IsTextSearchEnabled="{TemplateBinding IsTextSearchEnabled}"
                              ItemTemplate="{TemplateBinding ItemTemplate}"
                              x:Name="InnerComboBox" >

                        <ComboBox.Resources>
                            <ResourceDictionary>
                                <CheckBox x:Key="allItem" Content="All" IsChecked="{Binding SelectAll, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
                                <CollectionViewSource x:Key="items" Source="{Binding ComboBoxItems, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
                            </ResourceDictionary>
                        </ComboBox.Resources>

                        <ComboBox.ItemsSource>
                            <CompositeCollection>
                                <ComboBoxItem Content="{Binding Source={StaticResource allItem}}"/>
                                <CollectionContainer Collection="{Binding Source={StaticResource items}}" />
                            </CompositeCollection>
                        </ComboBox.ItemsSource>

                    </ComboBox>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Text}" VerticalAlignment="Center" />
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

CheckComboBox.cs

   public class CheckComboBox : ComboBox
    {
        public class CheckComboBoxItem
        {
            public CheckComboBoxItem(bool isSelected, string text)
            {
                IsSelected = isSelected;
                Text = text;
            }
            public bool IsSelected { get; set; }
            public string Text { get; set; }
        }

        static CheckComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckComboBox), new FrameworkPropertyMetadata(typeof(CheckComboBox)));
        }

        public static readonly DependencyProperty ComboBoxItemsProperty =
            DependencyProperty.Register("ComboBoxItems", typeof (ObservableCollection<CheckComboBoxItem>), typeof (CheckComboBox), new PropertyMetadata(default(ObservableCollection<CheckComboBoxItem>)));

        public ObservableCollection<CheckComboBoxItem> ComboBoxItems
        {
            get { return (ObservableCollection<CheckComboBoxItem>) GetValue(ComboBoxItemsProperty); }
            set { SetValue(ComboBoxItemsProperty, value); }
        }

        public static readonly DependencyProperty SelectAllProperty =
            DependencyProperty.Register("SelectAll", typeof (bool), typeof (CheckComboBox), new PropertyMetadata(default(bool)));

        public bool SelectAll
        {
            get { return (bool) GetValue(SelectAllProperty); }
            set
            {
                foreach (var item in ComboBoxItems)
                {
                    item.IsSelected = value;
                }
                SetValue(SelectAllProperty, value);
            }
        }
    }
}

Setting test data:

ObservableCollection<CheckComboBox.CheckComboBoxItem> checkComboBoxItems = new ObservableCollection<CheckComboBox.CheckComboBoxItem>();
checkComboBoxItems.Add(new CheckComboBox.CheckComboBoxItem(false, "Generation 0"));
checkComboBoxItems.Add(new CheckComboBox.CheckComboBoxItem(true, "Generation 1"));
checkComboBoxItems.Add(new CheckComboBox.CheckComboBoxItem(false, "Generation 2"));
checkComboBox1.ComboBoxItems = checkComboBoxItems;

Edit: Replaced the SelectAll DependencyProperty in CheckComboBox.cs with the following code, but OnSelectAll is not entered. The SelectAll combobox does not trigger the binding for some reason.

public static readonly DependencyProperty SelectAllProperty =
        DependencyProperty.Register("SelectAll",
                    typeof (bool),
                    typeof (CheckComboBox),
                    new FrameworkPropertyMetadata(false,
                        FrameworkPropertyMetadataOptions.AffectsRender,
                        new PropertyChangedCallback(OnSelectAll)));

private static void OnSelectAll(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    CheckComboBox checkComboBox = (CheckComboBox)d;
    foreach (var item in checkComboBox.ComboBoxItems)
    {
        item.IsSelected = (bool) e.NewValue;
    }
}

public bool SelectAll
{
    get { return (bool) GetValue(SelectAllProperty); }
    set { SetValue(SelectAllProperty, value); }
}    

Solution

  • Finally figured out how to trigger the "SelectAll" property. Notice the:

    <ComboBoxItem>
        <CheckBox ... />
    </ComboBoxItem>
    

    Generic.xaml

    ...
    <ComboBox.Resources>
        <ResourceDictionary>
            <CollectionViewSource x:Key="items" Source="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
        </ResourceDictionary>
    </ComboBox.Resources>
    
    <ComboBox.ItemsSource>
        <CompositeCollection>
            <ComboBoxItem>
                <CheckBox Content="All" IsChecked="{Binding SelectAll, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
            </ComboBoxItem>
            <CollectionContainer Collection="{Binding Source={StaticResource items}}" />
        </CompositeCollection>
    </ComboBox.ItemsSource>
    ...
    

    CheckComboBox.cs

    public class CheckComboBox : ComboBox
    {
        public class CheckComboBoxItem : ModelBase
        {
            public CheckComboBoxItem(bool isSelected, string text)
            {
                IsSelected = isSelected;
                Text = text;
            }
    
            private bool _isSelected;
            public bool IsSelected 
            { 
                get { return _isSelected; }
                set { Set(() => IsSelected, ref _isSelected, value); }
            }
    
            private string _text;
            public string Text 
            { 
                get { return _text; }
                set { Set(() => Text, ref _text, value); }
            }
        }
    
        static CheckComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckComboBox), new FrameworkPropertyMetadata(typeof(CheckComboBox)));
        }
    
        public static readonly DependencyProperty SelectAllProperty =
            DependencyProperty.Register("SelectAll",
                                        typeof (bool),
                                        typeof (CheckComboBox),
                                        new FrameworkPropertyMetadata(false,
                                            FrameworkPropertyMetadataOptions.AffectsRender,
                                            new PropertyChangedCallback(OnSelectAll)));
    
        private static void OnSelectAll(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CheckComboBox checkComboBox = (CheckComboBox)d;
            IEnumerable<CheckComboBoxItem> items = (IEnumerable<CheckComboBoxItem>) checkComboBox.ItemsSource;
            foreach (var item in items)
            {
                item.IsSelected = (bool) e.NewValue;
            }
        }
    
        public bool SelectAll
        {
            get { return (bool) GetValue(SelectAllProperty); }
            set { SetValue(SelectAllProperty, value); }
        }
    }
    

    Now I just have to figure out how to automatically de-select the "Select All" check box when another item is de-selected.