Filtering Items in a WPF ComboBox

I was building a WPF application for a client which had a few ComboBoxes with large amounts of items as options in them.  The client asked for the users to have the ability to filter the items in the drop down, but still force them to choose an item in the list.  A search of the internet yielded some solutions, but each solution required me to set the ComboBox.IsEditable property to “True”.  In each solution I found and tried doing this always made the user experience a little weird, allowing them to type in whatever they wanted, but still only allowing them to choose an item within the list.

UnFiltered-FilteringComboBox

FilteringComboBox

What I did to finally solve the problem was actually pretty simple and I was surprised none of the solutions I came across didn’t go this route.  I created a new UserControl to handle this implementation so it would be easier to reuse throughout the application and others in the future. 

The first part of it was creating a new Style for the ComboBox, so that inside the Popup (PART_Popup) portion of the ControlTemplate can include my filtering TextBox.

<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
    <Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=MainGrid}">
        <Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
            <Grid RenderOptions.ClearTypeHint="Enabled">
                <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                    <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=DropDownBorder}" Height="{Binding ActualHeight, ElementName=DropDownBorder}" Width="{Binding ActualWidth, ElementName=DropDownBorder}"/>
                </Canvas>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <DockPanel LastChildFill="True" Margin="0,5,10,5" Grid.Row="0">
                        <TextBlock DockPanel.Dock="Left" Text="Filter:" Margin="0,0,5,0" />
                        <TextBox x:Name="DropDownFilterTextBox" />
                    </DockPanel>
                    <ScrollViewer x:Name="DropDownScrollViewer" Grid.Row="1">
                        <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </ScrollViewer>
                </Grid>
            </Grid>

        </Border>
    </Microsoft_Windows_Themes:SystemDropShadowChrome>
</Popup>

The other piece of this is filtering the items in the within the ComboBox as they type in the filter TextBox.  To do this, I attach to the TextChanged event on it.

protected void DropDownFilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    TextBox textBox = ((TextBox)sender);

    ComboBox cbo = ((TextBox)sender).TemplatedParent as ComboBox;
    cbo.Items.Filter += a =>
    {
        if (a.ToString().ToLower().StartsWith(textBox.Text.ToLower()))
        {
            return true;
        }
        return false;
    };
}

Get the VS 2010 Samples Solution for this blog which includes the WPF ComboBox with an items Filter

WPF DataGrid, Read-Only Row

A common requirement for a DataGrid is to have entire rows that are read-only.  You may have a list of records in your DataGrid and some need to be locked for various reasons.  Unfortunately, in the XAML definition of a DataGrid, there isn’t a way to mark a row as being Read-Only.  To accomplish this without tying my view to my model or rewriting the DataGrid, I’m utilizing the BeginningEdit event, the Tag property of the DataGridRow, and created a style for the DataGridRow with a DataTrigger to put everything together.

First thing I did was to make sure my Model had an IsReadOnly property to latch onto, in the attached sample code, I did this by adding the property to one of the Customer in the Northwind database . 

CustomerReadOnlyProperty

Next, using a Trigger in my DataGridRow style, if the data item’s IsReadOnly property evaluates to “True” then I set the DataGridRow ‘s Tag to a string I can look for in my BeginningEdit event handler.  In the sample project attached and in the code snippets below, I used the string “ReadOnly”.  For a visual cue, I also set the HeaderTemplate to show an image to indicate that the row is locked. 

XAML Styles
  1. <!– Row Header Template to show a Locked icon.–>
  2. <DataTemplate x:Key="rowHeaderTemplate">
  3.     <StackPanel Orientation="Horizontal">
  4.         <Image x:Name="editImage" Source="Images/lock.gif" Width="16" Margin="1,0" />
  5.     </StackPanel>
  6. </DataTemplate>
  7. <!– DataGridRow Style –>
  8. <Style x:Key="ReadOnlyCheck" TargetType="{x:Type DataGridRow}">
  9.     <Style.Triggers>
  10.         <DataTrigger Binding="{Binding IsReadOnly}" Value="True">
  11.             <Setter Property="Tag" Value="ReadOnly" />
  12.             <Setter Property="HeaderTemplate" Value="{StaticResource rowHeaderTemplate}">
  13.             </Setter>
  14.         </DataTrigger>
  15.     </Style.Triggers>
  16. </Style>

 

CustomerReadOnlyVisual

Finally, in my BeginningEdit event handler for my DataGrid, I look at the Row going into Edit Mode, if its Tag property has been set to “ReadOnly” I cancel the event so it never makes it into edit mode.  This enforces the IsReadOnly property from my mode.

BeginningEdit Event Handler
  1. /// <summary>
  2. /// Event Handler for BeginningEdit Event of DataGrid.  If the Row entering into Edit Mode has a Tag set to "ReadOnly",
  3. /// cancel the edit.
  4. /// </summary>
  5. /// <param name="sender">Reference to the DataGrid</param>
  6. /// <param name="e">Instance of DataGridBeginningEditEventArgs</param>
  7. private void dataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
  8. {
  9.     if (((DataGridRow)e.Row).Tag != null && ((DataGridRow)e.Row).Tag.ToString() == "ReadOnly")
  10.     {
  11.         e.Cancel = true;
  12.     }
  13. }

 

Sample Code Download

WPF MarkupExtension Class

This week I was going through StackOverflow, looking for anything I could help with and cam across someone asking about how to bind to an attribute of a class member in XAML.  I wrote a small example IValueConverter class they could use to accomplish the task which was the only way I knew of that it could be done.  Then, another poster (H.B.) solved the task using something I’d never heard of before…the MarkupExtension class.

Using the example H.B. gave, creating a class to display the DisplayName attribute of a property, you can see that this approach gets the job done just as well as a converter, but to me, it looks like it solves the problem in a much more direct way than a converter.  The converters are generally used to convert a value from one Type to another Type, for instance, from a boolean to a value of System.Windows.Visibility or from a Color to a Brush.

To create a MarkupExtension class, you derive from the abstract MarkupExtension class.  There is one method to override, called ProvideValue.  A default constructor is required unless you intend to only support attribute usages of the class.  If you want to support arguments in the usage, might also need to define additional constructors to match the settable properties.

Attached is a sample WPF application with code that implements the MarkupExtension and IValueConverters I’ve mentioned in this short write-up.

Sample Code