DCSIMG
January 2009 - Posts - justguy's

January 2009 - Posts

WPF: ComboBox with CheckBoxes as items (it will even update on the fly!)

Hi,

 

Recently I’ve come across something weird… I needed a ComboBox that will allow the user to select multiple items.
The the solution coming to mind is using CheckBoxes. I have found several examples, but neither one displayed the selected items with pretty commas (like this: image ).

 

I’ve decided the best solution willl be taking an example from MSDN and modifying it to suite my needs.

Steps: (actually took ALOT longer and was ALOT harder – learning curves and such)

  1. Created a UserControl.
  2. Added the ComboBox from the MSDN sample.
  3. Created 3 dependency properties:
    • Text – retrieves the text of the selected items
    • ItemsSource – the items to display (currently bound to Title and IsSelected)
    • DefaultText – the text to display if no items were checked
  4. Bound the ItemsSource properties of the UserControl and the ComboBox.
  5. Added a Click event to the CheckBox that refreshes the text field in the ContentPresenter.

Usage

<Window





xmlns:controls="clr-namespace:Controls;assembly=Controls"
<controls:ComboWithCheckboxes
    x:Name="cbLanguages"
    Height="22"
    DefaultText="Choose Subtitles..."
    ItemsSource="{Binding}"
    />

 

Result

image

image

image

Code

 

ComboWithCheckboxes.xaml

<UserControl x:Class="Controls.ComboWithCheckboxes"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="22" Width="120"
    x:Name="UserControl"
    >
    
    <UserControl.Resources>
        <LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FFF" Offset="0.0"/>
                    <GradientStop Color="#CCC" Offset="1.0"/>
                </GradientStopCollection>
            </GradientBrush.GradientStops>
        </LinearGradientBrush>

        <LinearGradientBrush x:Key="NormalBorderBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#CCC" Offset="0.0"/>
                    <GradientStop Color="#444" Offset="1.0"/>
                </GradientStopCollection>
            </GradientBrush.GradientStops>
        </LinearGradientBrush>

        <SolidColorBrush x:Key="GlyphBrush" Color="#444" />

        <LinearGradientBrush x:Key="DarkBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FFF" Offset="0.0"/>
                    <GradientStop Color="#AAA" Offset="1.0"/>
                </GradientStopCollection>
            </GradientBrush.GradientStops>
        </LinearGradientBrush>

        <LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#BBB" Offset="0.0"/>
                    <GradientStop Color="#EEE" Offset="0.1"/>
                    <GradientStop Color="#EEE" Offset="0.9"/>
                    <GradientStop Color="#FFF" Offset="1.0"/>
                </GradientStopCollection>
            </GradientBrush.GradientStops>
        </LinearGradientBrush>

        <SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />

        <SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />

        <SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />

        <SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />

        <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="20" />
                </Grid.ColumnDefinitions>
                <Border
                  x:Name="Border" 
                  Grid.ColumnSpan="2"
                  CornerRadius="2"
                  Background="{StaticResource NormalBrush}"
                  BorderBrush="{StaticResource NormalBorderBrush}"
                  BorderThickness="1" />
                <Border 
                  Grid.Column="0"
                  CornerRadius="2,0,0,2" 
                  Margin="1" 
                  Background="{StaticResource WindowBackgroundBrush}" 
                  BorderBrush="{StaticResource NormalBorderBrush}"
                  BorderThickness="0,0,1,0" />
                <Path 
                  x:Name="Arrow"
                  Grid.Column="1"     
                  Fill="{StaticResource GlyphBrush}"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"
                  Data="M 0 0 L 4 4 L 8 0 Z"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="ToggleButton.IsMouseOver" Value="true">
                    <Setter TargetName="Border" Property="Background" Value="{StaticResource DarkBrush}" />
                </Trigger>
                <Trigger Property="ToggleButton.IsChecked" Value="true">
                    <Setter TargetName="Border" Property="Background" Value="{StaticResource PressedBrush}" />
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter TargetName="Border" Property="Background" Value="{StaticResource DisabledBackgroundBrush}" />
                    <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource DisabledBorderBrush}" />
                    <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
                    <Setter TargetName="Arrow" Property="Fill" Value="{StaticResource DisabledForegroundBrush}" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
        
        <ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
            <Border x:Name="PART_ContentHost" Focusable="False" Background="{TemplateBinding Background}" />
        </ControlTemplate>

        <Style x:Key="{x:Type ComboBoxItem}" TargetType="ComboBoxItem">
            <Setter Property="SnapsToDevicePixels" Value="true"/>
            <Setter Property="OverridesDefaultStyle" Value="true"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ComboBoxItem">
                        <Border 
                          Name="Border"
                          Padding="2"
                          SnapsToDevicePixels="true">
                            <ContentPresenter />
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsHighlighted" Value="true">
                                <Setter TargetName="Border" Property="Background" Value="{StaticResource SelectedBackgroundBrush}"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>

    <ComboBox
        x:Name="CheckableCombo"
        SnapsToDevicePixels="True"
        OverridesDefaultStyle="True"
        ScrollViewer.HorizontalScrollBarVisibility="Auto"
        ScrollViewer.VerticalScrollBarVisibility="Auto"
        ScrollViewer.CanContentScroll="True"
        IsSynchronizedWithCurrentItem="True"
        MinWidth="120"
        MinHeight="20"
        ItemsSource="{Binding ElementName=UserControl, Path=ItemsSource}"
        DataContext="{Binding ElementName=UserControl, Path=DataContext}"
        >
        <ComboBox.ItemTemplate>
            <HierarchicalDataTemplate>
                <CheckBox Content="{Binding Title}"
                          IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
                          Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
                          Click="CheckBox_Click"
                          />
            </HierarchicalDataTemplate>
        </ComboBox.ItemTemplate>

        <ComboBox.Template>
            <ControlTemplate TargetType="ComboBox">
                <Grid>
                    <ToggleButton 
                        Name="ToggleButton" 
                        Template="{StaticResource ComboBoxToggleButton}" 
                        Grid.Column="2" 
                        Focusable="false"
                        IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                        ClickMode="Press">
                    </ToggleButton>
                    <ContentPresenter
                        x:Name="Presenter"
                        IsHitTestVisible="False" 
                        Margin="3,3,23,3"
                        VerticalAlignment="Center"
                        HorizontalAlignment="Left">
                        <ContentPresenter.Content>
                            <TextBlock TextTrimming="CharacterEllipsis"
                                       Text="{Binding Path=Text,Mode=TwoWay,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
                        </ContentPresenter.Content>
                    </ContentPresenter>
                    <!-- Content="{TemplateBinding SelectionBoxItem}" -->
                    <TextBox x:Name="EditableTextBox"
                        Style="{x:Null}" 
                        Template="{StaticResource ComboBoxTextBox}" 
                        HorizontalAlignment="Left" 
                        VerticalAlignment="Center" 
                        Margin="3,3,23,3"
                        Focusable="True" 
                        Background="Transparent"
                        Visibility="Hidden"
                        IsReadOnly="{TemplateBinding IsReadOnly}"/>
                    <Popup 
                        Name="Popup"
                        Placement="Bottom"
                        IsOpen="{TemplateBinding IsDropDownOpen}"
                        AllowsTransparency="True" 
                        Focusable="False"
                        PopupAnimation="Slide">
                        <Grid 
                                  Name="DropDown"
                                  SnapsToDevicePixels="True"                
                                  MinWidth="{TemplateBinding ActualWidth}"
                                  MaxHeight="{TemplateBinding MaxDropDownHeight}">
                            <Border 
                                    x:Name="DropDownBorder"
                                    Background="{StaticResource WindowBackgroundBrush}"
                                    BorderThickness="1"
                                    BorderBrush="{StaticResource SolidBorderBrush}"/>
                            <ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True" DataContext="{Binding}">
                                <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                            </ScrollViewer>
                        </Grid>
                    </Popup>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasItems" Value="false">
                        <Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                    <Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
                        <Setter TargetName="DropDownBorder" Property="CornerRadius" Value="4"/>
                        <Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
                    </Trigger>
                    <Trigger Property="IsEditable"
                   Value="true">
                        <Setter Property="IsTabStop" Value="false"/>
                        <Setter TargetName="EditableTextBox" Property="Visibility"    Value="Visible"/>
                        <Setter TargetName="Presenter" Property="Visibility" Value="Hidden"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </ComboBox.Template>
    </ComboBox>
</UserControl>

 

ComboWithCheckboxes.xaml.cs

usingSystem.Windows;

namespace Controls
{
    /// <summary>
    ///
Interaction logic for ComboWithCheckboxes.xaml
  
/// </summary>
  
public partial classComboWithCheckboxes
  
{
        #regionDependency Properties
        /// <summary>
        ///
Gets or sets a collection used to generate the content of the ComboBox
      
/// </summary>
      
public objectItemsSource
        {
            get{ return(object)GetValue(ItemsSourceProperty); }
            set
          
{
                SetValue(ItemsSourceProperty, value);

                SetText();
            }
        }

        public static readonlyDependencyPropertyItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(object), typeof(ComboWithCheckboxes), newUIPropertyMetadata(null));

        /// <summary>
        ///
Gets or sets the text displayed in the ComboBox
      
/// </summary>
      
public stringText
        {
            get{ return(string)GetValue(TextProperty); }
            set{ SetValue(TextProperty, value); }
        }

        public static readonlyDependencyPropertyTextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(ComboWithCheckboxes), newUIPropertyMetadata(string.Empty));


        /// <summary>
        ///
Gets or sets the text displayed in the ComboBox if there are no selected items
      
/// </summary>
      
public stringDefaultText
        {
            get{ return(string)GetValue(DefaultTextProperty); }
            set{ SetValue(DefaultTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DefaultText.  This enables animation, styling, binding, etc...
      
public static readonlyDependencyPropertyDefaultTextProperty =
            DependencyProperty.Register("DefaultText", typeof(string), typeof(ComboWithCheckboxes), newUIPropertyMetadata(string.Empty));
        #endregion


        public
ComboWithCheckboxes()
        {
            InitializeComponent();
        }

        /// <summary>
        ///
Whenever a CheckBox is checked, change the text displayed
      
/// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
      
private voidCheckBox_Click(objectsender, RoutedEventArgse)
        {
            SetText();
        }

        /// <summary>
        ///
Set the text property of this control (bound to the ContentPresenter of the ComboBox)
      
/// </summary>
      
private voidSetText()
        {
            this.Text = (this.ItemsSource != null) ?
                this.ItemsSource.ToString() : this.DefaultText;

            // set DefaultText if nothing else selected
          
if(string.IsNullOrEmpty(this.Text))
            {
                this.Text = this.DefaultText;
            }
        }
    }
}

That’s it!

Adi.

Posted by justguy | 74 comment(s)
תגים:,