A common request on our forums is how to enable the Search As You Type online example to search in DateTime or Integer properties. Also recently another request was brought to our attention: How to highlight the matching search text in the grid cells. In this blog post I’m going to show you how to achieve these goals.

 

Let’s start our journey, by looking at the MainPage.xaml of the attached sample application:

    1 <UserControl x:Class="DateTimeFiltering.MainPage"

    2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4    xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.GridView"

    5    xmlns:themes="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls"

    6    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

    7    xmlns:local="clr-namespace:DateTimeFiltering"

    8    xmlns:jeff="clr-namespace:JeffWilcox.Samples;assembly=HighlightingTextBlock">

    9 

   10     <UserControl.Resources>

   11         <local:SearchViewModel x:Key="ViewModel" />

   12 

   13         <SolidColorBrush Color="Red" x:Key="HighlightBrush" />

   14         <Style TargetType="jeff:HighlightingTextBlock" x:Key="HighlightTextBlockStyle">

   15             <Setter Property="HighlightBrush" Value="{StaticResource HighlightBrush}" />

   16             <Setter Property="HighlightFontWeight" Value="Bold" />

   17         </Style>

   18 

   19         <DataTemplate x:Key="HighlightCellTemplate">

   20             <jeff:HighlightingTextBlock

   21                Style="{StaticResource HighlightTextBlockStyle}"

   22                HighlightText="{Binding SearchText, Source={StaticResource ViewModel}}" >

   23                 <i:Interaction.Behaviors>

   24                     <local:CellValueBindingBehavior />

   25                 </i:Interaction.Behaviors>

   26             </jeff:HighlightingTextBlock>

   27         </DataTemplate>

   28     </UserControl.Resources>

   29 

   30     <Grid DataContext="{Binding Source={StaticResource ViewModel}}">

   31         <Grid.RowDefinitions>

   32             <RowDefinition Height="Auto" />

   33             <RowDefinition />

   34         </Grid.RowDefinitions>

   35         <Grid.ColumnDefinitions>

   36             <ColumnDefinition Width="Auto" />

   37             <ColumnDefinition />

   38         </Grid.ColumnDefinitions>

   39 

   40         <TextBlock Text="Find:" Margin="5,5,7,5" VerticalAlignment="Center"/>           

   41         <TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Grid.Column="1" Margin="3">

   42             <i:Interaction.Behaviors>

   43                 <local:PropertyChangedUpdateTriggerBehavior />

   44             </i:Interaction.Behaviors>

   45         </TextBox>

   46 

   47         <telerik:RadGridView Name="playersGrid" AutoGenerateColumns="False" ItemsSource="{Binding Items}"

   48            Grid.Row="1" Grid.ColumnSpan="2">

   49             <telerik:RadGridView.Columns>

   50                 <telerik:GridViewDataColumn DataMemberBinding="{Binding Name}" CellTemplate="{StaticResource HighlightCellTemplate}" />

   51                 <telerik:GridViewDataColumn DataMemberBinding="{Binding Established}" CellTemplate="{StaticResource HighlightCellTemplate}" />

   52                 <telerik:GridViewDataColumn DataMemberBinding="{Binding StadiumCapacity}" CellTemplate="{StaticResource HighlightCellTemplate}" />

   53             </telerik:RadGridView.Columns>

   54         </telerik:RadGridView>

   55     </Grid>

   56 </UserControl>

The interesting pieces of code are as follows:

  • Line 11 - SearchViewModel
  • Line 20 – HighlightingTextBlock
  • Line 24 – CellValueBindingBehavior
  • Line 43 – PropertyChangedUpdateTriggerBehavior

 

Let’s look at each one of these.

 

SearchViewModel

This class servers as a view model for the MainPage. Its main purpose is to abstract away the logic for the inline filtering. For this purpose it exposes two properties: SearchText and Items. SearchText is the property that is bound to the TextBox used for searching. The second property Items is the actual collection that the RadGridView is bound to. The trick here is that this property returns a QueryableCollectionView over the original collection. This allow us to control the filtering in the view model using the FilterDescriptors property of the collection view. And this is where the magic happens. In the heart of the SearchViewModel lies the PredicateFilterDescriptor – a custom IFilterDescriptor which implements the heavy lifting for the search. Here is the relevant code:

   51 private void UpdateSearchPredicate()

   52 {

   53     string text = this.SearchText;

   54 

   55     Expression<Func<Club, bool>> predicate =

   56         c => string.IsNullOrEmpty(text)

   57                 ? true

   58                 : c.Name.ToString().ToLower().Contains(text) ||

   59                    c.Established.ToLongDateString().ToLower().Contains(text) ||

   60                    c.StadiumCapacity.ToString().ToLower().Contains(text);

   61 

   62     this.filter.Predicate = predicate;

   63 }

You can see how we are creating an expression which will call ToString() on each property of the Club object and then verify if this string contains our SearchText. If this expression returns true the item will be visible in the RadGridView, otherwise it will be filtered out. Explaining you how PredicateFilterDescriptor is implemented is a subject of a blog post on its own, so I’m not going to go deep in the rabbit hole.

 

HighlightingTextBlock

For the purpose of the sample we need a way to define a custom cell template. It should somehow have a text block which will highlight the searched text. But how to achieve this? Fortunately Jeff Wilcox comes to rescue with his HighlightingTextBlock. This custom control allow you to display a text and highlight portions of it using the HightlightText property. Thanks Jeff, this is exactly what we need. On line 22 you can see how the HightlightText is bound to the SearchText property of the SearchViewModel.

 

CellValueBindingBehavior

This is a custom attached behavior. Its purpose is to bind the text property of the HighlightingTextBlock to the value of its parent cell. We need this because RadGridView is smart and when you have a custom cell template the DataContext of the cell is the data item itself, not the value of its property. Here is the interesting code:

public class CellValueBindingBehavior : LoadBehavior<FrameworkElement>

{

    protected override void OnAssociatedObjectLoaded()

    {

        base.OnAssociatedObjectLoaded();

 

        var cell = this.AssociatedObject.ParentOfType<GridViewCell>();

        if (cell != null)

        {

            this.AssociatedObject.SetBinding(HighlightingTextBlock.TextProperty, new Binding("Value") { Source = cell, Mode = BindingMode.TwoWay });

        }

    }

 

    protected override void OnDetaching()

    {

        this.AssociatedObject.ClearValue(HighlightingTextBlock.TextProperty);

 

        base.OnDetaching();

    }

}

Note: This behavior is not needed in WPF because you can use RelativeSource=FindAncestor. Currently Silverlight did not have this comfortable feature.

 

PropertyChangedUpdateTriggerBehavior

Again we need this because of the limitation in the SIlverlight platform. This behavior enables the TextBox to update the item bound to its Text property on every key stroke. This mimics the WPF’s UpdateSourceTrigger=PropertyChanged setting of the binding. You can see how we have achieved this in Silverlight:

public class PropertyChangedUpdateTriggerBehavior : Behavior<TextBox>

{

    protected override void OnAttached()

    {

        this.AssociatedObject.TextChanged += OnTextBoxTextChanged;

    }

 

    void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)

    {

        var bindingExpression = this.AssociatedObject.ReadLocalValue(TextBox.TextProperty) as BindingExpression;

        if (bindingExpression != null)

        {

            bindingExpression.UpdateSource();

        }

    }

 

    protected override void OnDetaching()

    {

        this.AssociatedObject.TextChanged -= OnTextBoxTextChanged;

    }

}

 

This was the last interesting piece of code for today. Hope you will find it useful.

 

You can play with the example bellow:

 

You can download a demo project for Silverlight: Highlight_Search_SL.

You can download a demo project for WPF: Highlight_Search_WPF
(.NET 4.5_HighlightSearch_WPF)

Have fun.


Related Posts

Comments

Comments are disabled in preview mode.