WPF Custom Control - FilterControl for ListBox/ListView
Introduction
                ComputerCastle.Demo.Controls.FilterControl is a WPF-based Custom Control
                which can be used as quick filter for any ItemsControl like ListBox,
                ListView, DataGrid, etc. This article is to tell you how
                easy to create a Custom Control in WPF using combination of simple framework controls.
The article focus on:
- How to create a Custom Control in WPF briefly
- Explanation about FilterControl
- How to implement FilterControl
                
Background
I have been working in WPF for 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 grown better now. You might find few resemblence 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 (Preferrably 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 ResourceDictionaryfile where you write the Style for theFilterControl.
- 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 developing a Custom Control. Somecase 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:
                
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 fire 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 define 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 stop 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)
                
Here the Custom Filter logic is implemented.
                
Known Issue
- FilterControlfilter only work with- ItemsControlwhich has- ItemsSourceis set. If the- TargetControl- Itemsproperty is used, you need to handle the- Filterevent and write your own filter logic in there and set- IsFilterApplied = True.
History
| Date | Updated Notes | 
| 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. | 
Post Comment
- This is my article posted in code project. How come you using it without requesting permission from me??? 
 Venkatesh Mookkan[14.200.30.*]2014/4/1 20:51:53#1
Venkatesh Mookkan[14.200.30.*]2014/4/1 20:51:53#1