So you’d like to highlight the query terms used in a full text search within your WPF or Silverlight application. It’s a common use case in many web applications, but it’s becoming an increasingly common one in desktop applications as well.
The effect you want to achieve is simple. Given that a user searched for the words highlighting and terms the following snippet of text:
…we found that highlighting query terms made it much easier for users…
Should be displayed like this:
…we found that highlighting query terms made it much easier for users…
Your first instinct might be to use a FlowDocument displayed within a RichTextBox or FlowDocumentViewer. FlowDocuments have a convenient API for applying formatting to words or phrases, but they are heavyweight objects. Since we’ll display many search results on the same screen, the repeated use of FlowDocuments and viewer/editor controls will increase memory use and be a drag on performance. It’s not a good idea to use them within the ItemsControl or ListBox item templates that present search results.
Fortunately, we can get term highlighting to work within a lightweight TextBlock control, if we use a few tricks. Our approach binds the text snippet to ContentControl using a converter that generates XAML objects.
First, let’s assume that your full-text search engine produces a string that contains delimiters to indicate where the highlights should begin and end. Lucene.Net, the content indexing engine we use for Infovark, has a Highlighter plugin that works this way.
In Lucene.Net, the default delimiters used are opening and closing <B></B> tags. We’ll need to change these default settings to use delimiters that won’t cause trouble for XML parsing. (Remember, your text snippet could contain HTML or characters invalid in an XML document, so you’ll need to escape its output. We don’t want our delimiters to get wiped away during the escape process!)
We decided to use the sequences |~S~| and |~E~| as these were highly unlikely to show up naturally in our snippets. Using our example from above, the highlighted snippet from Lucene.Net would look like this:
…we found that |~S~|highlighting|~E~| query |~S~|terms|~E~| made it much easier for users…
Now that we have our search result snippet formatted the way we want, we can build an IValueConverter that generates a TextBlock with internal Run elements. Here’s the full class.
Now I’ll explain what we’re doing in the Convert() method in detail.
First, we check to make sure that we actually have a string object with some text. Then we take that string and run it through the System.Security.SecurityElement.Escape() routine, a handy method in the .NET Framework for replacing invalid XML characters with properly escaped ones.
Next, we replace the start delimiters with opening Run tags. Note that we set a style on this tag using a dynamic resource. This lets to apply whatever style we like to the highlighted words without having to mess with our converter logic. We also replace the end delimiters with a closing Run tag.
Then we wrap the snippet inside a TextBlock. I’ve set the TextWrapping property to Wrap so that our line of text doesn’t run off the edge of the screen in our UI. Note that I’ve also included the XAML namespace; this will become important later when we use the XamlReader.
Now we should have a well-formed XAML string. We feed this string into a StringReader, then an XmlReader, and finally a XamlReader. The XamlReader.Create() method instantiates the framework objects returned by our converter.
With our converter working properly, we can use it in the DataTemplates for our search results like this:
It’s quite a bit of work, but the end result is exactly what we want: a lightweight way of highlighting query terms in search results.
If you’ve got comments or suggestions about this approach, we’d love to hear them.
Leave a Comment