Highlighting Inline Search for RadGridView for Silverlight

Monday, January 18, 2010 by Stefan Dobrev | Comments 13

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 the source here.

 

Have fun.

13 Comments

  • Vlad 01 Feb 2010
    The WPF version can be found here :)
  • Jax 17 Jun 2010
    mild bug - typing "ar" should filter only Arsenal - but it doesn't
  • Stefan Dobrev 17 Jun 2010
    Hi Jax,

    You can see that the predicate is lowering only the values of the properties and not the search string itself. Actually 'ar' is working fine, but 'Ar' not. This can be easily changed by lowering the search string as well.
  • Sal 31 Mar 2011
    Well this is fantastic if you are using MVVM but why not making it the simple way and just showing how to do it without any framework in mind? I mean, KISS... I am having a hard time just trying to adapt it to a simple grid withouth MVVM... simplicity... a grid, a cell that takes the highlightsearchtext, an associated search box and that's it! Why complicating it this way?
  • Stefan Dobrev 31 Mar 2011
    Hi Sal,

    The main idea behind implementing the sample this ways is to make the code more reusable and illustrate how you can structure it in a more decoupled way. You are right that everything can be implemented in the code-behind, but eventually your code will become a mess. Maybe for a simple blog post it does not make sense, but people tend to copy and paste the code from posts like this directly into their code bases, without ever thinking how the stuff works.

    Just my two cents.
  • Almond 20 Apr 2011
    hello to all.

    Would like to ask if there is version of this for EF.  Using the DomainServices?

    I would really like to implement such using those feature.

    Thanks in advance.
  • tzuhsun 05 May 2011
    You mentioned the behavoiur 'CellValueBindingBehavior' is no need in WPF, can I have the sample code for that in WPF?
  • tzuhsun 05 May 2011
    You mentioned the behavoiur 'CellValueBindingBehavior' is no need in WPF, can I have the sample code for that in WPF?
  • Stefan Dobrev 05 May 2011
    Hi tzuhsun,

    You can use RelativeSource FindAncestor binding in WPF to achieve this.

    Note: RelativeSource binding will also be available in Silverlight 5.

    Hope this helps.
  • tzuhsun 05 May 2011
    What I found in silverlight version is the GridViewCell Value will bind to Text property, i try to convert it in WPF

    <DataTemplate x:Key="HighlightCellTemplate"
                <jeff:HighlightingTextBlock  
                    Style="{StaticResource HighlightTextBlockStyle}"  
                    HighlightText="{Binding SearchText, Source={StaticResource ViewModel}}"              
                    Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=telerik:GridViewCell}, Path=Value, Converter={local:ObjectToStringConverter}}>"  
     

    and I got the empty grid, may I know which parts go wrong?
  • Stefan Dobrev 05 May 2011
    Hi tzuhsun,

    This one works for me:
    Text="{Binding Value, RelativeSource={RelativeSource FindAncestor, AncestorType=telerik:GridViewCell}, Mode=TwoWay}" 

    Maybe you can check what is happening in your ObjectToStringConverter class.
  • tzuhsun 05 May 2011
    Thanks Stefan !

    I found the reason, I missing the 'Mode=TwoWay' in xaml

    Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=telerik:GridViewCell}, Path=Value, Converter={local:ObjectToStringConverter}, Mode=TwoWay}" 

    This is work for me, but I thought the binding of cell.value is two way by default?
  • tzuhsun 06 May 2011
    The default binding should depend on 'Text' but not cell Value, since it is not default to TwoWay now I know why.

    Thanks again.

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)