Results 1 to 10 of 10

Thread: Highlighting just certain text in treeView node text

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Jul 2004
    Location
    Kansas, USA
    Posts
    352

    Highlighting just certain text in treeView node text

    I have a treeview that lists matches that are varying degrees of closeness to the original text. The nade is the original search text and the child nodes are the suggested matches. What I want to do is highlight the words in the suggested matches (child nodes) that appear in the parent node.

    Example:

    Parent Node: Testing the ability to match text.
    Child node: Testing my car's ability to accelerate.

    In this example the words "Testing", "ability", "to" would be highlighted in the node text.

    Does someone know a way to highlight just certain words in a treeview node text?
    Thanks,
    Eric
    --------------------------------------------------------------------------------------------------------------------
    VB.net/C# ... Visual Studio 2019
    "None of us are as smart as all of us."

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    You would set the DrawMode property of the TreeView to either OwnerDrawText or OwnerDrawAll, handle the DrawNode event and then use GDI+ to draw the text of the node yourself with the desired highlighting.

    If you use OwnerDrawText then you would be able to use a different text colour for the matching words. I'm not sure whether OwnerDrawText supports using a different background colour or not. If it doesn't and that's what you want then you would have to use OwnerDrawAll, which also means that you need more drawing code.

    I would suggest that you start out by getting the GDI+ stuff working on a simple form. Start with a method like this:
    Code:
    Private Sub DrawTextWithHighlightedWords(text As String, wordsToHighlight As String(), g As Graphics, location As Point)
        'Split text into substrings to highlight and not highlight.
        'Draw substrings using g starting at location.
    End Sub
    You can call that method from the Paint event handler of the form and experiment with your code until you get the effect you want. You can then start calling that method from the DrawNode event of your TreeView.

  3. #3
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    Here's something to start with:
    Code:
    Public Class Form1
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            DrawTextWithHighlightedWords("girls just want to have fun",
                                         {"just", "to", "have"},
                                         e.Graphics,
                                         New PointF(10.0F, 10.0F))
        End Sub
    
        Private Sub DrawTextWithHighlightedWords(text As String, wordsToHighlight As String(), g As Graphics, location As PointF)
            'Split text into substrings to highlight and not highlight.
            Dim wordsWithHighlightStatus = GetWordsWithHighlightStatus(text, wordsToHighlight)
    
            'Draw substrings using g starting at location.
            For Each word In wordsWithHighlightStatus
                g.DrawString(word.Item1,
                             Font,
                             If(word.Item2, Brushes.Red, Brushes.Black),
                             location.X,
                             location.Y)
    
                Dim textSize = g.MeasureString(word.Item1, Font)
    
                location = New PointF(location.X + textSize.Width,
                                      location.Y)
            Next
        End Sub
    
        Private Function GetWordsWithHighlightStatus(text As String, wordsToHighlight As String()) As Tuple(Of String, Boolean)()
            Dim words = text.Split(" "c)
            Dim wordsWithHighlightStatus = Array.ConvertAll(words,
                                                            Function(s) Tuple.Create(s, wordsToHighlight.Contains(s)))
    
            Return wordsWithHighlightStatus
        End Function
    
    End Class

  4. #4
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    Here's something a bit better. The spaces between the words got bigger with each word using MeasureString but MeasureCharacterRanges keeps those spaces even.
    Code:
    Public Class Form1
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            DrawTextWithHighlightedWords("girls just want to have fun",
                                         {"just", "to", "have"},
                                         e.Graphics,
                                         New PointF(10.0F, 100.0F))
        End Sub
    
        Private Sub DrawTextWithHighlightedWords(text As String, wordsToHighlight As String(), g As Graphics, location As PointF)
            'Split text into substrings to highlight and not highlight.
            Dim words = text.Split(" "c)
            Dim wordsWithHighlightStatus = Array.ConvertAll(words,
                                                            Function(s) Tuple.Create(s, wordsToHighlight.Contains(s)))
    
            Dim ranges = GetCharacterRanges(words)
            Dim format As New StringFormat
    
            format.SetMeasurableCharacterRanges(ranges)
    
            Dim regions = g.MeasureCharacterRanges(text,
                                                   Font,
                                                   New RectangleF(location.X, location.Y,
                                                                  ClientSize.Width - location.X,
                                                                  ClientSize.Height - location.Y),
                                                   format)
    
            For i = 0 To wordsWithHighlightStatus.GetUpperBound(0)
                Dim wordWithHighlightStatus = wordsWithHighlightStatus(i)
                Dim region = regions(i)
    
                g.DrawString(wordWithHighlightStatus.Item1,
                             Font,
                             If(wordWithHighlightStatus.Item2, Brushes.Red, Brushes.Black),
                             region.GetBounds(g).Location)
            Next
        End Sub
    
        Private Function GetCharacterRanges(words As String()) As CharacterRange()
            Dim startIndex = 0
            Dim ranges As New List(Of CharacterRange)
    
            For i = 0 To words.GetUpperBound(0)
                If i > 0 Then
                    startIndex += 1
                End If
    
                Dim wordLength = words(i).Length
    
                ranges.Add(New CharacterRange(startIndex, wordLength))
                startIndex += wordLength
            Next
    
            Return ranges.ToArray()
        End Function
    
    End Class

  5. #5
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    This will get you closer but there is still some work to do.
    Code:
    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Dim node1 As New TreeNode("I would just like to go and have a meal")
    
            node1.Nodes.Add(New TreeNode("girls just want to have fun"))
    
            Dim node2 As New TreeNode("Testing the ability to match text")
    
            node2.Nodes.Add(New TreeNode("Testing my car's ability to accelerate"))
    
            TreeView1.Nodes.AddRange({node1, node2})
        End Sub
    
        Private Sub TreeView1_DrawNode(sender As Object, e As DrawTreeNodeEventArgs) Handles TreeView1.DrawNode
            Dim parentNode = e.Node.Parent
    
            If parentNode Is Nothing Then
                e.DrawDefault = True
            Else
                Dim wordsToHighlight = parentNode.Text.Split(" "c)
    
                DrawTextWithHighlightedWords(e.Node.Text, wordsToHighlight, e.Graphics, e.Bounds.Location)
            End If
        End Sub
    
        Private Sub DrawTextWithHighlightedWords(text As String, wordsToHighlight As String(), g As Graphics, location As PointF)
            'Split text into substrings to highlight and not highlight.
            Dim words = text.Split(" "c)
            Dim wordsWithHighlightStatus = Array.ConvertAll(words,
                                                            Function(s) Tuple.Create(s, wordsToHighlight.Contains(s)))
    
            Dim ranges = GetCharacterRanges(words)
            Dim format As New StringFormat
    
            format.SetMeasurableCharacterRanges(ranges)
    
            Dim regions = g.MeasureCharacterRanges(text,
                                                   Font,
                                                   New RectangleF(location.X, location.Y,
                                                                  ClientSize.Width - location.X,
                                                                  ClientSize.Height - location.Y),
                                                   format)
    
            For i = 0 To wordsWithHighlightStatus.GetUpperBound(0)
                Dim wordWithHighlightStatus = wordsWithHighlightStatus(i)
                Dim region = regions(i)
    
                g.DrawString(wordWithHighlightStatus.Item1,
                             Font,
                             If(wordWithHighlightStatus.Item2, Brushes.Red, Brushes.Black),
                             region.GetBounds(g).Location)
            Next
        End Sub
    
        Private Function GetCharacterRanges(words As String()) As CharacterRange()
            Dim startIndex = 0
            Dim ranges As New List(Of CharacterRange)
    
            For i = 0 To words.GetUpperBound(0)
                If i > 0 Then
                    startIndex += 1
                End If
    
                Dim wordLength = words(i).Length
    
                ranges.Add(New CharacterRange(startIndex, wordLength))
                startIndex += wordLength
            Next
    
            Return ranges.ToArray()
        End Function
    
    End Class

  6. #6

    Thread Starter
    Hyperactive Member
    Join Date
    Jul 2004
    Location
    Kansas, USA
    Posts
    352

    Re: Highlighting just certain text in treeView node text

    jmcilhinney. As always. Exceptional. Thanks for all your help over the years. I just learned a whole lot in this post alone.
    Thanks,
    Eric
    --------------------------------------------------------------------------------------------------------------------
    VB.net/C# ... Visual Studio 2019
    "None of us are as smart as all of us."

  7. #7
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    I did notice an odd bug with that code where the text of the second child node was drawn over the first node. I looked a bit closer and that can be alleviated by changing this line:
    Code:
    If parentNode Is Nothing Then
    to this:
    Code:
    If parentNode Is Nothing OrElse e.Bounds.X = -1 Then
    You probably also noticed that the text of the owner-drawn nodes overhangs the selection box a bit. I'm afraid that I don't know how to fix that. It just seems like GDI+ doesn't draw the text the same way as the default control as all the words seem to be just a bit wider. It can be improved a bit by changing this:
    Code:
    Dim format As New StringFormat
    to this:
    Code:
    Dim format = StringFormat.GenericTypographic
    but, to do any better, you may have to do a bit of research on GDI+ and, if possible, how the TreeView is rendered by default.

  8. #8
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Highlighting just certain text in treeView node text

    Quote Originally Posted by jmcilhinney View Post
    You probably also noticed that the text of the owner-drawn nodes overhangs the selection box a bit. I'm afraid that I don't know how to fix that. It just seems like GDI+ doesn't draw the text the same way as the default control as all the words seem to be just a bit wider. It can be improved a bit by changing this:
    I thought that the text in WinForm Controls from .NET 2.0 onwards was rendered by GDI, not GDI+.

    You'd probably get more consistent output using the TextRenderer.MeasureText and TextRenderer.DrawText methods, passing the relevant e.Graphics to the methods' IDeviceContext parameter.

  9. #9
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    Quote Originally Posted by Inferrd View Post
    You'd probably get more consistent output using the TextRenderer.MeasureText and TextRenderer.DrawText methods, passing the relevant e.Graphics to the methods' IDeviceContext parameter.
    I did look at that but there's no equivalent to MeasureCharacterRanges so it would be a bit more complex. If it fixes the issue though, it might be necessary. Decided to have a closer look and, while this is still not perfect, it's a significant improvement:
    Code:
    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Dim node1 As New TreeNode("I would just like to go and have a meal")
    
            node1.Nodes.Add(New TreeNode("girls just want to have fun"))
    
            Dim node2 As New TreeNode("Testing the ability to match text")
    
            node2.Nodes.Add(New TreeNode("Testing my car's ability to accelerate"))
    
            TreeView1.Nodes.AddRange({node1, node2})
        End Sub
    
        Private Sub TreeView1_DrawNode(sender As Object, e As DrawTreeNodeEventArgs) Handles TreeView1.DrawNode
            Dim parentNode = e.Node.Parent
    
            If parentNode Is Nothing OrElse e.Bounds.X = -1 Then
                e.DrawDefault = True
            Else
                Dim wordsToHighlight = parentNode.Text.Split(" "c)
    
                DrawTextWithHighlightedWords(e.Node.Text, wordsToHighlight, e.Graphics, e.Bounds.Location, (e.State And TreeNodeStates.Selected) = TreeNodeStates.Selected)
            End If
        End Sub
    
        Private Sub DrawTextWithHighlightedWords(text As String, wordsToHighlight As String(), g As Graphics, location As Point, selected As Boolean)
            'Split text into substrings to highlight and not highlight.
            Dim words = text.Split(" "c)
            Dim wordsWithHighlightStatus = Array.ConvertAll(words,
                                                            Function(s) Tuple.Create(s, wordsToHighlight.Contains(s)))
    
            For i = 0 To wordsWithHighlightStatus.GetUpperBound(0)
                Dim wordWithHighlightStatus = wordsWithHighlightStatus(i)
                Dim word = wordWithHighlightStatus.Item1
                Dim highlight = wordWithHighlightStatus.Item2
                Dim font = TreeView1.Font
                Dim size = TextRenderer.MeasureText(g,
                                                    word,
                                                    font,
                                                    Drawing.Size.Empty,
                                                    TextFormatFlags.NoPadding)
    
                TextRenderer.DrawText(g,
                                      word,
                                      font,
                                      location,
                                      If(highlight,
                                         Color.Red,
                                         If(selected,
                                            Color.White,
                                            Color.Black)),
                                      TextFormatFlags.NoPadding)
    
                location.Offset(size.Width, 0)
    
                size = TextRenderer.MeasureText(g,
                                                " ",
                                                font,
                                                Drawing.Size.Empty,
                                                TextFormatFlags.NoPadding)
    
                TextRenderer.DrawText(g,
                                      " ",
                                      font,
                                      location,
                                      Color.Black,
                                      TextFormatFlags.NoPadding)
    
                location.Offset(size.Width, 0)
            Next
        End Sub
    
    End Class

  10. #10
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Highlighting just certain text in treeView node text

    This iteration looks pretty darn good:
    Code:
    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Dim node1 As New TreeNode("I would just like to go and have a meal")
    
            node1.Nodes.Add(New TreeNode("girls just want to have fun"))
    
            Dim node2 As New TreeNode("Testing the ability to match text")
    
            node2.Nodes.Add(New TreeNode("Testing my car's ability to accelerate"))
    
            TreeView1.Nodes.AddRange({node1, node2})
        End Sub
    
        Private Sub TreeView1_DrawNode(sender As Object, e As DrawTreeNodeEventArgs) Handles TreeView1.DrawNode
            Dim parentNode = e.Node.Parent
    
            If parentNode Is Nothing OrElse e.Bounds.X = -1 Then
                e.DrawDefault = True
            Else
                Dim wordsToHighlight = parentNode.Text.Split(" "c)
    
                DrawTextWithHighlightedWords(e.Node.Text,
                                             wordsToHighlight,
                                             e.Graphics,
                                             e.Bounds,
                                             (e.State And TreeNodeStates.Selected) = TreeNodeStates.Selected)
            End If
        End Sub
    
        Private Sub DrawTextWithHighlightedWords(text As String, wordsToHighlight As String(), g As Graphics, bounds As Rectangle, selected As Boolean)
            Dim textSize = GetTextSize(g, text)
    
            'Default location is the node bounds location.
            Dim location = bounds.Location
    
            'Add padding to the left of the text, which is half the difference between the node bounds width and the text width.
            location.Offset((bounds.Width - textSize.Width) \ 2, 0)
    
            'Split text into substrings to highlight and not highlight.
            Dim words = text.Split(" "c)
            Dim wordsWithHighlightStatus = Array.ConvertAll(words,
                                                            Function(s) Tuple.Create(s, wordsToHighlight.Contains(s)))
    
            For i = 0 To wordsWithHighlightStatus.GetUpperBound(0)
                Dim wordWithHighlightStatus = wordsWithHighlightStatus(i)
                Dim word = wordWithHighlightStatus.Item1
                Dim highlight = wordWithHighlightStatus.Item2
                Dim size As Size
    
                'Draw a space before all but the first word.
                If i > 0 Then
                    size = GetTextSize(g, " ")
                    DrawText(g,
                             " ",
                             location,
                             Color.Black)
                    location.Offset(size.Width, 0)
                End If
    
                'Draw the current word.
                size = GetTextSize(g, word)
                DrawText(g,
                         word,
                         location,
                         If(highlight,
                            Color.Red,
                            If(selected,
                               Color.White,
                               Color.Black)))
                location.Offset(size.Width, 0)
    
            Next
        End Sub
    
        Private Function GetTextSize(g As Graphics, text As String) As Size
            Return TextRenderer.MeasureText(g,
                                            text,
                                            TreeView1.Font,
                                            Size.Empty,
                                            TextFormatFlags.NoPadding)
        End Function
    
        Private Sub DrawText(g As Graphics, text As String, location As Point, color As Color)
            TextRenderer.DrawText(g,
                                  text,
                                  TreeView1.Font,
                                  location,
                                  color,
                                  TextFormatFlags.NoPadding)
    
        End Sub
    
    
    End Class

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width