Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Nullable ComboBox for Silverlight and WPF

0.00/5 (No votes)
30 Mar 2011 1  
This post describes a simple and easy way to change the current behavior of your Silverlight or WPF ComboBox by using an attached property and a ComboBox style.

Introduction

This article describes a simple and easy way to change the current behavior of your Silverlight or WPF ComboBox by using an attached property and a ComboBox style.

Here is a live example of the Silverlight version.

All that you need to do is set the IsNullable property to True on the ComboBox that you want to have this behavior:

<ComboBox ItemsSource="{Binding Source={StaticResource ViewModel}, Path=Actors}"
          library:Combobox.IsNullable="True" />

Using the code

First, you need to change the default ComboBox style so that it will contain the X button. You can find the default style for Silverlight here and for WPF here. You need to make the following changes to the Template property of the ComboBox:

Silverlight ComboBox:
<ContentPresenter x:Name="ContentPresenter" Margin="{TemplateBinding Padding}"  
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
    <TextBlock Text=" " />
</ContentPresenter>
<Button x:Name="PART_ClearButton" Visibility="Collapsed" 
    HorizontalAlignment="Right" Margin="0,0,20,0">
    <Path Data="M0,0 L1.6,0 L3,1.56 L4.4,0 L6,0 L3.8,2.5 L6,5 L4.4,5 L3,
                3.49 L1.59,5 L-4.2E-09,5 L2.18,2.5 z" 
      Fill="#CC111111" Height="5" 
      Stretch="Fill" Width="7"/>
</Button>
WPF ComboBox:
<TextBox x:Name="PART_EditableTextBox" Style="{x:Null}"
        Template="{StaticResource ComboBoxTextBox}"
        HorizontalAlignment="Left"
        VerticalAlignment="Bottom"
        Margin="3,3,23,3"
        Focusable="True"
        Background="Transparent"
        Visibility="Hidden"
        IsReadOnly="{TemplateBinding IsReadOnly}" />
<Button x:Name="PART_ClearButton" 
        Visibility="Collapsed" 
        HorizontalAlignment="Right" 
        Margin="0,0,22,0">
    <Path Data="M0,0 L1.6,0 L3,1.56 L4.4,0 L6,0 L3.8,2.5 L6,5 L4.4,
                5 L3,3.49 L1.59,5 L-4.2E-09,5 L2.18,2.5 z" 
      Fill="#CC111111" Height="5" 
      Stretch="Fill" Width="7"/>
</Button>

When you have completed these changes, you need to create the IsNullable attached property that will do all the work. When this property is set to True on a ComboBox, it will enable the nullable functionality.

The nullable functionality is obtained by using these two functions:

private static void ApplyIsNullable(ComboBox comboBox)
{
    var isNullable = GetIsNullable(comboBox);
    var clearButton = (Button)GetClearButton(comboBox);
    if (clearButton != null)
    {
        clearButton.Click -= clearButton_Click;
        clearButton.Click += clearButton_Click;

        if (isNullable && comboBox.SelectedIndex != -1)
        {
            clearButton.Visibility = Visibility.Visible;
        }
        else
        {
            clearButton.Visibility = Visibility.Collapsed;
        }
    }
}
 
private static void clearButton_Click(object sender, RoutedEventArgs e)
{
    var clearButton = (Button)sender;
    var parent = VisualTreeHelper.GetParent(clearButton);

    while (!(parent is ComboBox))
    {
        parent = VisualTreeHelper.GetParent(parent);
    }

    var comboBox = (ComboBox)parent;
    //clear the selection
    comboBox.SelectedIndex = -1;
}

The ApplyIsNullable function refreshes the X button state and is called whenever the selection of the ComboBox changes. The clearButton_Click function clears the ComboBox selection and is called when someone clicks the X button.

Before we finish, let's make the X button look better by adding a MouseOver animation by using this custom button style:

<Style x:Key="ClearSelectionButtonStyle" TargetType="Button">
    <Setter Property="Background" Value="#FF3C688D"/>
    <Setter Property="BorderBrush" Value="#FF617584"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Width" Value="15"/>
    <Setter Property="Height" Value="15"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Background="Transparent">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="0" 
                                          Storyboard.TargetProperty="(UIElement.Visibility)" 
                                          Storyboard.TargetName="MouseOverElement">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="0" 
                                           Storyboard.TargetProperty="(UIElement.Visibility)" 
                                           Storyboard.TargetName="MouseOverElement">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled"/>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused"/>
                            <VisualState x:Name="Unfocused"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="MouseOverElement" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        Background="#FFC8E4ED" BorderBrush="#FF3F6A8E" 
                        Visibility="Collapsed"/>
                    <ContentPresenter x:Name="contentPresenter" 
                      ContentTemplate="{TemplateBinding ContentTemplate}" 
                      Content="{TemplateBinding Content}" 
                      HorizontalAlignment="Center" 
                      Margin="{TemplateBinding Padding}" 
                      VerticalAlignment="Center"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

That's it! :)

History

  • Version 1.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here