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.
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