Click here to Skip to main content
Click here to Skip to main content

WPF Custom Control - FilterControl for ListBox/ListView

By , 26 Apr 2011
 

Table of Contents 

Introduction

ComputerCastle.Demo.Controls.FilterControl is a WPF-based Custom Control which can be used as a quick filter for any ItemsControl like ListBox, ListView, DataGrid, etc. This article tells you how easy it is to create a Custom Control in WPF using a combination of simple framework controls.

The article focuses on:

  • How to create a Custom Control in WPF briefly
  • Explanation about FilterControl
  • How to implement FilterControl

Picture1.png

Background

I have been working in WPF for the past two and half years. When I started developing in WPF, it was hard for me to adopt to the new technology. I searched everywhere especially in CodeProject to learn WPF. With the help of Josh Smith, Sacha Barber, Nishant and few more people here, now my WPF knowledge has grown better now. You might find a little resemblance of their style in the code.

How to Create a Custom Control in WPF

There are many articles in CodeProject discussing about Custom Control in WPF. So, I will explain in brief.

  • Open Visual Studio (Preferably Visual Studio 2010 as it is the latest)
  • Create a New WPF Project (FilterControlDemo)
  • Add a Class file and name it as FilterControl
  • Finish the FilterControl
  • Add a ResourceDictionary file where you write the Style for the FilterControl.
  • Make sure you merge all the resource dictionaries together and set the ThemeInfo in the AssemblyInfo.cs file.
[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]

Using the Code

The main class is FilterControl which is derived from System.Windows.Controls.Control. It is very important to determine the base class when you are developing a Custom Control. In some cases, you just require plain Control's feature but most of the cases you might require feature of on existing control like TextBox, Button, ListBox, etc. I could have used TextBox as base class. But TextBox properties and methods are too much for my Control. The XAML syntax is:

<ctrl:FilterControl Height="25" Header="Type here to filter" DockPanel.Dock="Top"
                            TargetControl="{Binding ElementName=listBox}"
                            FilterTextBindingPath="Name"/>

And the class diagram is:

ClassDiagram1.png

 

FilterControl Members

Name Description
FilterFiringInterval Sets/Gets the interval of time to filter the filter action. The interval should be in milliseconds. The default value is 300 ms.
FilterOnEnter Filter action will be fired once you hit Enter key if it is set to True
FilterText Sets/Gets the filter text.
FilterTextBindingPath Use while TargetControl is bound.
Header Info to show the user if the Focus is not in.
TargetControl Used to bind the Target control to which filter has to be applied.

FilterControl Style

<Style TargetType="{x:Type egl:FilterControl}">
        <Setter Property="Background" Value="{DynamicResource 
		{x:Static SystemColors.WindowBrushKey}}"/>
        <Setter Property="FilterOnEnter" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>

                <ControlTemplate TargetType="{x:Type egl:FilterControl}">
                    <Border x:Name="border" CornerRadius="3,3,3,3"
                            Background="#ADADAD">
                		<Grid Margin="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <Border Background="{TemplateBinding Background}" 
				Grid.ColumnSpan="3" CornerRadius="3" 
				VerticalAlignment="Stretch" 
				HorizontalAlignment="Stretch" />
                            <Image Source="/ComputerCastle.Demo;
				component/Resources/Search.png"/>
                			<TextBox Style="{StaticResource FilterTextBoxStyle}"
                                     x:Name="PART_FilterBox"  AutoWordSelection="True"
                                     Grid.Column="1"
                                     Margin="0,1,0,1" VerticalAlignment="Center"
                                     Text="{Binding RelativeSource=
					{RelativeSource AncestorType=
					{x:Type egl:FilterControl}}, 
					Path=FilterText, Mode=TwoWay, 
					UpdateSourceTrigger=PropertyChanged}"/>
					<TextBlock x:Name="PART_Header" 
					Text="{TemplateBinding Header}" 
					VerticalAlignment="Center" 
					HorizontalAlignment="Left" Margin="2,0,0,0"
                                       Grid.Column="1"
                                       IsHitTestVisible="False" Foreground="#ADADAD"/>
                			<Button x:Name="PART_ClearButton" Grid.Column="2" 
					Margin="0,0,4,0" 
					Style="{StaticResource ClearButtonStyle}"
                                    Visibility="Collapsed"/>
                		</Grid>
                	</Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsFocused" Value="True" 
				SourceName="PART_FilterBox" >
                            <Setter Property="Background" 
				Value="{StaticResource FocusBackground}" 
				TargetName="border" />
                        </Trigger>
                        <Trigger Property="IsFocused" Value="True" 
				SourceName="PART_ClearButton" >
                            <Setter Property="Background" 
				Value="{StaticResource FocusBackground}" 
				TargetName="border" />
                        </Trigger>
                        <Trigger Property="IsFocused" Value="True" >
                            <Setter Property="Background" 
				Value="{StaticResource FocusBackground}" 
				TargetName="border" />
                        </Trigger>                       
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>               
    </Style>

Code Walkthrough

<TextBox Style="{StaticResource FilterTextBoxStyle}"
                  x:Name="PART_FilterBox"  AutoWordSelection="True"
                  Grid.Column="1"
                  Margin="0,1,0,1" VerticalAlignment="Center"
                  Text="{Binding RelativeSource={RelativeSource AncestorType=
		{x:Type egl:FilterControl}}, Path=FilterText, Mode=TwoWay, 
		UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock x:Name="PART_Header" Text="{TemplateBinding Header}" 
		VerticalAlignment="Center" HorizontalAlignment="Left" Margin="2,0,0,0"
                            Grid.Column="1"
                            IsHitTestVisible="False" Foreground="#ADADAD"/>
<Button x:Name="PART_ClearButton" Grid.Column="2" Margin="0,0,4,0" 
			Style="{StaticResource ClearButtonStyle}"
                        Visibility="Collapsed"/>

In the above XAML, certain elements like TextBox, Button are defined as TemplateParts and these parts are referred in the FilterControl class's TemplatePartAttribute. This is called as "named parts" of the template. When you are customizing this control's template and the name doesn't match, your control will not work properly (Reference for Custom Templates). To make it standard, I am using "PART_" as prefix.

The parts are accessed inside the control class and used further. Normally, if the parts are used throughout the control, a private variable is defined and assigned the value on OnApplyTemplate() overridden method. GetTemplateChild (is a protected internal function) function is used in that case like below:

textBlock = GetTemplateChild(PART_Header) as TextBlock;

filterBox = GetTemplateChild(PART_FilterBox) as TextBox;

You can add your Properties to the class as you like. These properties can be a DependencyProperty which would help in Binding or normal property. A DependencyProperty is defined as:

public string FilterText
{
    get
    {
        return (string)GetValue(FilterTextProperty);
    }
    set
    {
        SetValue(FilterTextProperty, value);
    }
}

public static readonly DependencyProperty FilterTextProperty =
	DependencyProperty.Register("FilterText", typeof(string),
	typeof(FilterControl), new PropertyMetadata(string.Empty));

Working

Once you placed the FilterControl in the Window, you can directly bind the ItemsControl to the control or you can write your own Custom Filter logic inside the Filter event.

Filter event triggers after the user stops typing the text in the filter field. The     FilterFiringInterval is used at this time to avoid performance issue while typing. You can change the interval to any.

The ApplyFilterOnTarget is called after the Filter event fired, if the FilterEventArgs.IsFilterApplied is False. User can set IsFilterApplied to True if they wrote their own custom filter logic. In that case, TargetControl binding is not necessary.

private void ApplyFilterOnTarget()
    {
        if (TargetControl == null || TargetControl.ItemsSource == null)
            return;

        ICollectionView collectionView = CollectionViewSource.GetDefaultView
					(TargetControl.ItemsSource);

        if (collectionView == null)
        {
            throw new InvalidOperationException
		("The TargetConrol should use ICollectionView as ItemSource.");
        }


        if (string.IsNullOrEmpty(this.FilterTextBindingPath))
        {
            throw new InvalidOperationException("FilterTextBindingPath is not set.");
        }

        collectionView.Filter = (m => (GetDataValue
	<string>(m, this.FilterTextBindingPath).IndexOf
	(filterBox.Text, StringComparison.InvariantCultureIgnoreCase) > -1));
        }

Here:

collectionView.Filter = (m => (GetDataValue
	<string>(m, this.FilterTextBindingPath).IndexOf
	(filterBox.Text, StringComparison.InvariantCultureIgnoreCase) > -1));

is the code used to filter the collection bounded to TargetControl's ItemsSource.

And finally, it would work like below:

Here the ListBox is directly bound to the FilterControl. (Sample Application attached.)

Picture2.png

Here the Custom Filter logic is implemented.

Picture3.png

Known Issues

  • FilterControl filter only works with ItemsControl which has ItemsSource set. If the TargetControlItems property is used, you need to handle the Filter event and write your own filter logic in there and set IsFilterApplied = True.

History

Date Updated Notes
April 27, 2011 Fixed the issue in Clear button clicked
April 25, 2011 Formatted the article with table of contents
April 1, 2011 More information about deriving from a control is explained
References link to the keywords/framework classes
March 18, 2011 First version published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Venkatesh Mookkan
Team Leader
India India
Member
He is from a small village in Tirunelveli, TamilNadu (India). He is fun loving guy. He like to travel a lot. Bikes/Cars/Mobiles are his fantasies.
 
He started his career as a BPO Data Entry Assistant and got chance to work as an ASP.NET Programmer in the same company after a year. And he move to the next company in 2006 as Software Engineer.
 
He is working as a Team Leader in a private software development company @Chennai, India. For the past 5 years, he learnt many things in .NET. He worked as ASP.NET developer and now working as WPF Application Developer in the same company for past two years.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCool Control - How can I make it sort the list so I get the best matches first?memberErik Rude6 Jun '12 - 2:36 
AnswerRe: Cool Control - How can I make it sort the list so I get the best matches first?memberErik Rude26 Jul '12 - 23:06 
GeneralRe: Cool Control - How can I make it sort the list so I get the best matches first?memberVenkatesh Mookkan4 Sep '12 - 16:32 
QuestionHow filtering DataGridmemberLasoty12 Apr '12 - 22:36 
AnswerRe: How filtering DataGridmemberVenkatesh Mookkan23 Jul '12 - 7:15 
QuestionGreat articlegroupzhujinlong1984091320 Sep '11 - 22:08 
AnswerRe: Great articlememberVenkatesh Mookkan21 Sep '11 - 3:02 
GeneralMy vote of 5memberHalil ibrahim Kalkan11 May '11 - 4:29 
GeneralRe: My vote of 5memberVenkatesh Mookkan11 May '11 - 4:31 
GeneralMy vote of 5memberKannan-CP27 Apr '11 - 6:20 
GeneralRe: My vote of 5memberVenkatesh Mookkan27 Apr '11 - 16:02 
QuestionClear ButtonmemberMatthias26 Apr '11 - 3:57 
AnswerRe: Clear ButtonmemberVenkatesh Mookkan26 Apr '11 - 4:27 
AnswerRe: Clear ButtonmemberVenkatesh Mookkan26 Apr '11 - 15:53 
Matthias wrote:
I can see that search text is cleared, but the filtering event is not fired. I expected that the complete list will be visible than again (no filtering). Is this the behavior you wanted ? It is not what I expected as user.

The Clear button action is not handled in the demo code. I have updated the demo source with the fix. You can download the source now and try it out.

Venkatesh Mookkan (My Recent Article: WPF Custom Control - FilterControl for ListBox/ListView)

AnswerRe: Clear ButtonmemberMatthias26 Apr '11 - 20:43 
QuestionA very good article!membercesarin16 Apr '11 - 8:56 
AnswerRe: A very good article!memberVenkatesh Mookkan17 Apr '11 - 17:36 
GeneralExcellent!memberJohn Adams6 Apr '11 - 9:26 
GeneralRe: Excellent!memberVenkatesh Mookkan6 Apr '11 - 16:25 
GeneralMy vote of 5memberTawani Anyangwe1 Apr '11 - 4:06 
GeneralRe: My vote of 5memberVenkatesh Mookkan3 Apr '11 - 16:27 
GeneralMy Vote of 5memberRaviRanjankr22 Mar '11 - 4:02 
GeneralRe: My Vote of 5memberVenkatesh Mookkan22 Mar '11 - 4:03 
GeneralNot a bad effortmvpSacha Barber21 Mar '11 - 21:01 
GeneralRe: Not a bad effortmemberVenkatesh Mookkan21 Mar '11 - 21:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 26 Apr 2011
Article Copyright 2011 by Venkatesh Mookkan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid