Wednesday, December 17, 2008

Highlighting databound information in Silverlight

As I was playing around with the Live Search SDK, to investigate integration in different applications, I ran into this issue. I wanted to display bound data and highlight it as well. I started digging and couldn't find a feasible solution on the web, so I came up with my own.

Let me start with a screenshot of the finished (well, sort of :-)) product:
What you are looking at is basically a textbox to input a query, a listbox to display the titles with highlights and a textblock to display some status information.
The highlighting information is supplied by Live search. It surrounds any term that needs highlighting with some special characters, one for the start and one for the end.
I figured that to get the highlighting, I would use a textblocks inlines property and add different runs for non-highlighted and highlighted text. The questions remained, when and where? My first order of business was to get the listbox to display data and I would figure it out later.
Here is the XAML for the listbox:

<ListBox x:Name="ResultsListBox" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<search:HighlightTextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

As you can see I decided to use a usercontrol I would work on. The reason I used a usercontrol and not just derive from the TextBox is pretty straight forward, the TextBox class is sealed (Why, Microsoft, Why?!).
So a simple usercontrol that wraps the TextBox, is my starting point. The XAML for this is so easy that I haven't posted it here (Cause you are all an intelligent bunch, right? ;-) )

Next thing was to get the text into the textbox trough databinding. I simply added a dependency property to the user control. The code is largely generated from the snippet for it (propdp) and looks like this:

public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}

// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(string.Empty, TextPropertyChanged));


The alert reader may have spotted the extra parameter in the PropertyMetadata constructor at the end here. It points to an eventhandler that will handle the actual highlighting as our textproperty is changed.
The reason for this, in short, is that whenever databinding happens, the actual property setter is never used. Instead the SetValue method is called with the dependency property object. So the only insertion point for custom code is in an eventhandler attached to the dependency property. A better explanation can be found in this great article by Bryant Likes.


So here is the code I use to highlight my text and display it in the wrapped TextBox control:


private void SetText(string value)
{
innerTextBlock.Inlines.Clear();

string remainder = value;
while (remainder.Contains('\uE000'))
{
int highlightStart = remainder.IndexOf('\uE000');
int highlightEnd = remainder.IndexOf('\uE001');

string beforeHighlight = remainder.Substring(0, highlightStart);
Run beforeHighlightRun = new Run();
beforeHighlightRun.Text = beforeHighlight;
innerTextBlock.Inlines.Add(beforeHighlightRun);

string highlight = remainder.Substring(highlightStart + 1, highlightEnd - highlightStart - 1);
Run highlightRun = new Run();
highlightRun.Text = highlight;
highlightRun.FontWeight = FontWeights.Bold;
innerTextBlock.Inlines.Add(highlightRun);

remainder = remainder.Substring(highlightEnd + 1);
}
Run finalRun = new Run();
finalRun.Text = remainder;
innerTextBlock.Inlines.Add(finalRun);
}

private static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
HighlightTextBlock textBlock = d as HighlightTextBlock;
if (textBlock != null)
{
textBlock.SetText(e.NewValue.ToString());
}
}

So whenever the Text property of my usercontrol is changed, it calls the SetText method, which in turn processes the set value into a Inlines collection, based on the provided highlighting information.

There are some obvious improvements to this code, like providing a starting and closing character property and some style to apply for highlighting, but you get the picture.

I hope this was another helpful article. Please let me know what you think about it and if you have any questions, I'm always happy to help. The comment box is waiting for you.

5 comments:

  1. hey can u please attach source code.

    ReplyDelete
  2. hi,

    this post is very good and i have same requirement.
    i have been working on this from past 2 days but i am not able to get.
    as i am new to silvrlight can u please upload sample project.

    thank you

    ReplyDelete
  3. Hi harsha,

    Thanks for your comment. Unfortunately I do no longer have this source available. Is there any other way I can help you out?

    Greets,
    Jonathan

    ReplyDelete
  4. Hi, what's 'innerTextBlock'. This isn't declared anywhere.

    ReplyDelete
  5. Hi,

    As I stated earlier, I don't have this source available anymore. However, I'm convinced it has to be a TextBlock, declared in the XAML.

    HTH.
    Greets,
    Jonathan

    ReplyDelete