Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

WPF Custom Control - FilterControl for ListBox/ListView

Rate me:
Please Sign up or sign in to vote.
4.82/5 (35 votes)
26 Apr 2011CPOL4 min read 154.5K   3.1K   89   52
WPF Custom Control FilterControl - Can be used along with any ItemsControl

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.
C#
[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]

Using the Code

The main class is FilterControl which is derived from <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.control.aspx" target="_blank">System.Windows.Controls.Control</a>. 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:

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

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

XML
<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 <a href="http://msdn.microsoft.com/en-us/library/system.windows.templatepartattribute.aspx" target="_blank">TemplatePartAttribute</a>. 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. <a href="http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.gettemplatechild.aspx" target="_blank">GetTemplateChild</a> (is a protected internal function) function is used in that case like below:

C#
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 <a href="http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.aspx" target="_blank">DependencyProperty</a> which would help in Binding or normal property. A <a href="http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.aspx" target="_blank">DependencyProperty</a> is defined as:

C#
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 <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.aspx" target="_blank">ItemsControl</a> 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.

C#
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:

C#
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 <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemssource.aspx" target="_blank">ItemsSource</a>.

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 <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.aspx" target="_blank">ItemsControl</a> which has <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemssource.aspx" target="_blank">ItemsSource</a> 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)


Written By
Team Leader
Australia Australia
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 worked as a Team Leader in a private software development company @Chennai, India for 8+years.

Now working as a Consultant @Sydney, Australia.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Halil ibrahim Kalkan11-May-11 4:29
Halil ibrahim Kalkan11-May-11 4:29 
GeneralRe: My vote of 5 Pin
Venkatesh Mookkan11-May-11 4:31
Venkatesh Mookkan11-May-11 4:31 
GeneralMy vote of 5 Pin
Kannan-CP27-Apr-11 6:20
Kannan-CP27-Apr-11 6:20 
GeneralRe: My vote of 5 Pin
Venkatesh Mookkan27-Apr-11 16:02
Venkatesh Mookkan27-Apr-11 16:02 
QuestionClear Button Pin
Matthias26-Apr-11 3:57
Matthias26-Apr-11 3:57 
AnswerRe: Clear Button Pin
Venkatesh Mookkan26-Apr-11 4:27
Venkatesh Mookkan26-Apr-11 4:27 
AnswerRe: Clear Button Pin
Venkatesh Mookkan26-Apr-11 15:53
Venkatesh Mookkan26-Apr-11 15:53 
AnswerRe: Clear Button Pin
Matthias26-Apr-11 20:43
Matthias26-Apr-11 20:43 
QuestionA very good article! Pin
cesarin16-Apr-11 8:56
cesarin16-Apr-11 8:56 
AnswerRe: A very good article! Pin
Venkatesh Mookkan17-Apr-11 17:36
Venkatesh Mookkan17-Apr-11 17:36 
GeneralExcellent! Pin
John Adams6-Apr-11 9:26
John Adams6-Apr-11 9:26 
GeneralRe: Excellent! Pin
Venkatesh Mookkan6-Apr-11 16:25
Venkatesh Mookkan6-Apr-11 16:25 
GeneralMy vote of 5 Pin
Tawani Anyangwe1-Apr-11 4:06
Tawani Anyangwe1-Apr-11 4:06 
GeneralRe: My vote of 5 Pin
Venkatesh Mookkan3-Apr-11 16:27
Venkatesh Mookkan3-Apr-11 16:27 
GeneralMy Vote of 5 Pin
RaviRanjanKr22-Mar-11 4:02
professionalRaviRanjanKr22-Mar-11 4:02 
GeneralRe: My Vote of 5 Pin
Venkatesh Mookkan22-Mar-11 4:03
Venkatesh Mookkan22-Mar-11 4:03 
GeneralNot a bad effort Pin
Sacha Barber21-Mar-11 21:01
Sacha Barber21-Mar-11 21:01 
GeneralRe: Not a bad effort Pin
Venkatesh Mookkan21-Mar-11 21:58
Venkatesh Mookkan21-Mar-11 21:58 
GeneralMy vote of 5 Pin
CS201121-Mar-11 16:24
professionalCS201121-Mar-11 16:24 
GeneralRe: My vote of 5 Pin
Venkatesh Mookkan21-Mar-11 16:27
Venkatesh Mookkan21-Mar-11 16:27 
GeneralGood! Pin
Rajesh R Subramanian21-Mar-11 9:51
professionalRajesh R Subramanian21-Mar-11 9:51 
GeneralRe: Good! Pin
Venkatesh Mookkan21-Mar-11 18:09
Venkatesh Mookkan21-Mar-11 18:09 
GeneralGood one Pin
thatraja21-Mar-11 7:40
professionalthatraja21-Mar-11 7:40 
GeneralRe: Good one Pin
Venkatesh Mookkan21-Mar-11 18:09
Venkatesh Mookkan21-Mar-11 18:09 
GeneralMy vote of 5 Pin
Abhinav S21-Mar-11 6:14
Abhinav S21-Mar-11 6:14 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.