Search code examples
.netvb.netwinformsgraphicstreeview

Treeview raising drawnode but some changes won't propagate to the screen


I am working with a treeview in VB with OwnerDrawText as my draw mode. I have handled Me.DrawNode in my treeview to allow me to have highlight specific items (ie keep the last node highlighted and so on). I saw the the drawnode was being call and tried setting all of there backgrounds to a custom color buy doing this

Public Class Form1
    Private nodelist As New List(Of TreeNode)

    Public Sub New()
        InitializeComponent()
        TreeView1.Nodes.AddRange(New TreeNode() {
            New TreeNode("Text of Node 0") With {.Name = "Node0"},
            New TreeNode("Text of Node 1") With {.Name = "Node1"},
            New TreeNode("Text of Node 2") With {.Name = "Node2"},
            New TreeNode("Text of Node 3") With {.Name = "Node3"},
            New TreeNode("Text of Node 4") With {.Name = "Node4"}
        })
    End Sub

    Private Sub myTreeView_DrawNode(ByVal sender As Object, ByVal e As DrawTreeNodeEventArgs) Handles TreeView1.DrawNode
        Console.WriteLine(e.Node.Text & " Rewritten")

        ' Draw the background and node text for a selected node.
        If nodelist.Contains(e.Node) Then
            e.Graphics.FillRectangle(New SolidBrush(Color.Chartreuse), e.Bounds)
            TextRenderer.DrawText(e.Graphics, e.Node.Text, e.Node.NodeFont, e.Bounds,
                                  Color.Black, Color.Empty,
                                  TextFormatFlags.VerticalCenter)
        Else
            e.Graphics.FillRectangle(New SolidBrush(Color.Black), e.Bounds)
            TextRenderer.DrawText(e.Graphics, e.Node.Text, e.Node.NodeFont, e.Bounds,
                                  Color.White, Color.Empty,
                                  TextFormatFlags.VerticalCenter)
        End If
    End Sub

    Private Sub myTreeView_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles TreeView1.MouseDown
        Console.WriteLine("Clicked")
        Dim clickedNode As TreeNode = TreeView1.GetNodeAt(e.X, e.Y)
        nodelist.Add(clickedNode)
        If nodelist.Count > 3 Then
            nodelist.RemoveAt(0)
        End If
        TreeView1.SelectedNode = clickedNode
    End Sub
End Class

This code is supposed to highlight the last three selected nodes. However, if you click all the node, from 0 to 5, you will see it highlights everything. Then if you select node 2 you will see that all Nodes are redrawn (Console.WriteLine shown this), which I assume would mean that Node 1 and 2 should be lose the highlight Color, but they don't.


Solution

  • The drawing part is correct, even though it can be simplified: the same methods can be used for both the background and the Text rendering, to DRY up the code.

    The logic in the MouseDown event needs to be partially refactored, so the Nodes that are selected and unselected are actually added and removed from the collection of Nodes, the nodelist Field.

    The TreeView also needs to be notified of the change, calling its Invalidate() method, so the Control can redraw the new selection of Nodes (Invalidate() causes the Control to repaint itself, raising its Paint or a similar, related, event: the DrawNode event, in this case).

    Simplified drawing method:

    ► Note: Here, I'm using the e.Node.Bounds as the bounding rectangle for both the background and the Text of the Node. e.Bounds can be used instead, if the rectangle seems too tight in the actual implementation. To be tested (it can be considered a matter of preference in this case).

    Private Sub TreeView1_DrawNode(sender As Object, e As DrawTreeNodeEventArgs) Handles TreeView1.DrawNode
        Console.WriteLine(e.Node.Text & " Rewritten")
    
        Dim backColor As Color = Color.Black
        Dim foreColor As Color = Color.White
        If nodelist.Contains(e.Node) Then
            backColor = Color.Chartreuse
            foreColor = Color.Black
        End If
    
        e.Graphics.FillRectangle(New SolidBrush(backColor), e.Node.Bounds)
        TextRenderer.DrawText(e.Graphics, e.Node.Text, e.Node.NodeFont, e.Node.Bounds,
                              foreColor, Color.Transparent, TextFormatFlags.VerticalCenter)
    End Sub
    

    The MouseDown event adds the currently selected node to the nodelist collection if it's not already there, otherwise the node is removed from the collection.
    Another condition is verified before the selected Node can be added to the collection: the collection cannot contain more than 4 items; if the collection is already full, the selected Node is not added.

    Private Sub myTreeView_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles TreeView1.MouseDown
        Dim clickedNode As TreeNode = TreeView1.GetNodeAt(e.X, e.Y)
        If nodelist.Contains(clickedNode) Then
            nodelist.Remove(clickedNode)
        Else
            If nodelist.Count < 4 Then
                nodelist.Add(clickedNode)
                TreeView1.SelectedNode = clickedNode
            End If
        End If
        TreeView1.Invalidate()
    End Sub