How to display search results in a WPF items control with highlighted query terms

11,889

Solution 1

I took dthrasers answer and took out the need for an XML parser. He does a great job explaining each of the pieces in his blog, However this didn't require me to add any extra libraries, here's how I did it.

Step one, make a converter class:

class StringToXamlConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string input = value as string;
        if (input != null)
        {
            var textBlock = new TextBlock();
            textBlock.TextWrapping = TextWrapping.Wrap;
            string escapedXml = SecurityElement.Escape(input);
            
            while (escapedXml.IndexOf("|~S~|") != -1) {
                //up to |~S~| is normal
                textBlock.Inlines.Add(new Run(escapedXml.Substring(0, escapedXml.IndexOf("|~S~|"))));
                //between |~S~| and |~E~| is highlighted
                textBlock.Inlines.Add(new Run(escapedXml.Substring(escapedXml.IndexOf("|~S~|") + 5,
                                          escapedXml.IndexOf("|~E~|") - (escapedXml.IndexOf("|~S~|") + 5))) 
                                          { FontWeight = FontWeights.Bold, Background= Brushes.Yellow });
                //the rest of the string (after the |~E~|)
                escapedXml = escapedXml.Substring(escapedXml.IndexOf("|~E~|") + 5);
            }

            if (escapedXml.Length > 0)
            {
                textBlock.Inlines.Add(new Run(escapedXml));                      
            }
            return textBlock;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException("This converter cannot be used in two-way binding.");
    }
}

Step two: Instead of a TextBlock use a ContentBlock. Pass in the string (you would of used for your textBlock) to the content block, like so:

<ContentControl
               Margin="7,0,0,0"
               HorizontalAlignment="Left"
               VerticalAlignment="Center"
               Content="{Binding Description, Converter={StaticResource CONVERTERS_StringToXaml}, Mode=OneTime}">
</ContentControl>

Step three: Make sure the test you pass in is tokenized with |~S~| and |~E~|. And let the highlighting begin!

Notes:
You can change the style in the run to determine what and how your text is highlighted
Make sure you add your Converter class to your namespace and resources. This might also require a rebuild to get working.

Solution 2

I found a way to apply highlighting to search results using a custom IValueConverter. The converter takes a text snippet, formats it into valid XAML markup, and uses a XamlReader to instantiate the markup into framework objects.

The full explanation is rather long, so I've posted it to my blog: Highlighting Query Terms in a WPF TextBlock

Solution 3

A TextBlock can contain multiple Runs in its Inlines collection. You can build it in code or in XAML:

<TextBlock>
    <Run>... these </Run>
    <Run FontWeight="Bold">results</Run>
    <Run> were found...</Run>
</TextBlock>
Share:
11,889
dthrasher
Author by

dthrasher

Senior Solution Architect at EPAM Systems, specializing in Microsoft.NET and Sitecore. Former co-founder of Infovark, Inc.

Updated on July 23, 2022

Comments

  • dthrasher
    dthrasher almost 2 years

    I'd like to display search results within a WPF ItemsControl with the query terms highlighted.

    The search engine I use, Lucene.Net with the Highlighter plugin, returns strings with the query terms marked up like so:

    ...these <Bold>results</Bold> were found to be statistically significant...
    

    I can instruct the Highlighter plugin to use any set of markup tags to wrap a query term. I'm not limited to the <Bold> tag in the example above. For WPF, I'd likely make these <Run/> elements with a style attached.

    The challenge is to take the string I've been given and render it as if it were "actual XAML" within the datatemplate I'm using for search results. In other words, I want to see something like this:

    ...these results were were found to be statistically significant...

    But I'm struggling with how to combine databinding with dynamic rendering of an XAML string within the datatemplate. What's the best approach here?

    1. Use a UserControl to display each search result and call XamlReader.Load() from the codebehind?
    2. Construct a FlowDocument containing the search result strings and display the results with a FlowDocumentScrollViewer?
    3. Something else entirely...?
  • dthrasher
    dthrasher over 13 years
    My question probably wasn't clear enough. The tricky part is that I need to change the string into XAML at run time, not compile time.
  • Mart
    Mart over 13 years
    Maybe I missed something but is looks feasible to me to build an XAML string like in my example (with some regular expression) and use your first approach. The solution I suggested was to build a TextBlock at runtime and populate its Inlines collection with Runs. Adding a style to the highlighted Runs is a solution to replace FontWeight="Bold".
  • dthrasher
    dthrasher about 13 years
    Thanks, @Mart. Your suggestion put me on the right track. My answer describes the approach I used.
  • Rand Random
    Rand Random over 9 years
    Getting a 500 Http error, could you (or someone else) fix the link?
  • dthrasher
    dthrasher about 9 years
    Whoops. Sorry about that. Got my WordPress site sorted again.
  • ardila
    ardila about 5 years
    Link is dead again. Edited the link to point to a cached version of the page, fyi.