Click here to Skip to main content
15,879,071 members
Articles / Desktop Programming / WPF
Tip/Trick

WPF TextBlock Highlighter

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
9 Feb 2018CPOL1 min read 25.5K   1K   14   6
How to highlight TextBlock text using attached properties
Image 1

Introduction

I'll highlight here, no pun intended, how you can go about highlighting the text in a TextBlock using attached properties, which enable setting of the text to highlight and its background and foreground color.

TextBlockHighlighter

TextBlockHighlighter is a class containing definitions of attached properties for highlighting text in a TextBlock. The first property, Selection, is used to specify which text in the TextBlock will be highlighted. Highlighting is done in the property's callback method by making use of Inlines.

C#
public static readonly DependencyProperty SelectionProperty =
    DependencyProperty.RegisterAttached("Selection", typeof(string), typeof(TextBlockHighlighter),
        new PropertyMetadata(new PropertyChangedCallback(SelectText)));

private static void SelectText(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d == null) return;
    if (!(d is TextBlock)) throw new InvalidOperationException("Only valid for TextBlock");

    TextBlock txtBlock = d as TextBlock;
    string text = txtBlock.Text;
    if (string.IsNullOrEmpty(text)) return;

    string highlightText = (string)d.GetValue(SelectionProperty);
    if (string.IsNullOrEmpty(highlightText)) return;

    int index = text.IndexOf(highlightText, StringComparison.CurrentCultureIgnoreCase);
    if (index < 0) return;

    Brush selectionColor = (Brush)d.GetValue(HighlightColorProperty);
    Brush forecolor = (Brush)d.GetValue(ForecolorProperty);

    txtBlock.Inlines.Clear();
    while (true)
    {
        txtBlock.Inlines.AddRange(new Inline[] {
                 new Run(text.Substring(0, index)),
                 new Run(text.Substring(index, highlightText.Length)) {Background = selectionColor,
                     Foreground = forecolor}
        });

        text = text.Substring(index + highlightText.Length);
        index = text.IndexOf(highlightText, StringComparison.CurrentCultureIgnoreCase);

        if (index < 0)
        {
            txtBlock.Inlines.Add(new Run(text));
            break;
        }
    }
}
VB.NET
Public Shared ReadOnly SelectionProperty As DependencyProperty =
        DependencyProperty.RegisterAttached("Selection", GetType(String), GetType(TextBlockHighlighter),
                                            New PropertyMetadata(New PropertyChangedCallback
                                           (AddressOf SelectText)))

Private Shared Sub SelectText(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If d Is Nothing Then Exit Sub
    If TypeOf d IsNot TextBlock Then Throw New InvalidOperationException("Only valid for textBlock")

    Dim txtBlock As TextBlock = CType(d, TextBlock)
    Dim text As String = txtBlock.Text
    If String.IsNullOrEmpty(text) Then Exit Sub

    Dim highlightText As String = CStr(d.GetValue(SelectionProperty))
    If String.IsNullOrEmpty(highlightText) Then Exit Sub

    Dim index = text.IndexOf(highlightText, StringComparison.CurrentCultureIgnoreCase)
    If index < 0 Then Exit Sub

    Dim selectionColor As Brush = CType(d.GetValue(HighlightColorProperty), Brush)
    Dim forecolor As Brush = CType(d.GetValue(ForecolorProperty), Brush)

    txtBlock.Inlines.Clear()
    While True
        txtBlock.Inlines.AddRange(New Inline() {
                                      New Run(text.Substring(0, index)),
                                      New Run(text.Substring(index, highlightText.Length)) _
                                      With {.Background = selectionColor, .Foreground = forecolor}
                                      })

        text = text.Substring(index + highlightText.Length)
        index = text.IndexOf(highlightText, StringComparison.CurrentCultureIgnoreCase)

        If index < 0 Then
            txtBlock.Inlines.Add(New Run(text))
            Exit While
        End If
    End While
End Sub

Because the attached property is meant for exclusive use on a TextBlock, an exception is thrown if you attempt to use it on any other element.

Image 2
Error message displayed in XAML designer

There are two other attached properties which can be used for setting the background and foreground color of the highlighted text.

C#
public static readonly DependencyProperty HighlightColorProperty =
    DependencyProperty.RegisterAttached("HighlightColor", typeof(Brush), typeof(TextBlockHighlighter),
        new PropertyMetadata(Brushes.Yellow, new PropertyChangedCallback(HighlightText)));
...

public static readonly DependencyProperty ForecolorProperty =
    DependencyProperty.RegisterAttached("Forecolor", typeof(Brush), typeof(TextBlockHighlighter),
        new PropertyMetadata(Brushes.Black, new PropertyChangedCallback(HighlightText)));
VB.NET
Public Shared ReadOnly HighlightColorProperty As DependencyProperty =
    DependencyProperty.RegisterAttached("HighlightColor", GetType(Brush), GetType(TextBlockHighlighter),
                                        New PropertyMetadata(Brushes.Yellow,
                                                             New PropertyChangedCallback
                                                             (AddressOf SelectText)))
...

Public Shared ReadOnly ForecolorProperty As DependencyProperty =
    DependencyProperty.RegisterAttached("Forecolor", GetType(Brush), GetType(TextBlockHighlighter),
                                        New PropertyMetadata(Brushes.Black,
                                                             New PropertyChangedCallback
                                                             (AddressOf SelectText)))

Usage

To use the attached properties just add a reference to the namespace containing the class with the properties and apply them to a TextBlock element.

XML
<TextBlock Text="Awesome"
           local:TextBlockHighlighter.Selection="awe"
           local:TextBlockHighlighter.HighlightColor="Aquamarine"
           local:TextBlockHighlighter.Forecolor="Red"/>

You can also bind the attached properties: In the downloadable sample project, the TextBlockHighlighter.Selection property is bound to a property of type string which acts as a filter for the elements displayed in an ItemsControl. The TextBlock on which the property is applied is the content of the ItemsControl's data template.

XML
<ItemsControl Margin="10,10,10,0" Background="White"
              ItemsSource="{Binding Words}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Margin="10,2" FontWeight="Bold"
                       Text="{Binding}" 
                       local:TextBlockHighlighter.Selection="{Binding DataContext.Filter,
                Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window}}"
                       local:TextBlockHighlighter.HighlightColor="LightGreen"
                       local:TextBlockHighlighter.Forecolor="Teal"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Conclusion

Hopefully, the information provided in this tip was useful, or will be useful to you at some point. The approach I have taken here, using Inlines and a string to define the selection, was inspired by a suggestion on an article on the same subject. Kudos to Bruce Greene on the nice suggestion.

History

  • 9th February, 2018: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Kenya Kenya
Experienced C# software developer with a passion for WPF.

Awards,
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • CodeProject MVP 2021

Comments and Discussions

 
Questiongreat job Pin
Shuang Zhou3-Feb-24 19:08
Shuang Zhou3-Feb-24 19:08 
QuestionBreaks when used in a DataGrid DataGridTemplateColumn Pin
altocumulus18-Apr-19 8:26
altocumulus18-Apr-19 8:26 
GeneralRe: Breaks when used in a DataGrid DataGridTemplateColumn Pin
Meshack Musundi28-May-19 22:11
professionalMeshack Musundi28-May-19 22:11 
PraiseCool Code Pin
msbsoftware10-Apr-19 4:29
msbsoftware10-Apr-19 4:29 
QuestionGood Job Pin
Bob Ranck12-Feb-18 6:25
Bob Ranck12-Feb-18 6:25 
GeneralRe: Good Job Pin
Meshack Musundi12-Feb-18 8:52
professionalMeshack Musundi12-Feb-18 8:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.