Click here to Skip to main content
Click here to Skip to main content
Go to top

Custom Draw TreeView in VB.NET

, 25 May 2005
Rate this:
Please Sign up or sign in to vote.
An owner draw implementation of a VB.NET treeview to show some bold text in nodes.

Introduction

This article shows the owner draw technique for a TreeView using Visual Basic .NET to draw some portions of the text of the nodes in bold font, as shown in the image.

Background

The owner draw technique for Windows Common Controls is well documented in the following MSDN article: Customizing a Control's Appearance Using Custom Draw&, which I recommend to read. The article explains the notification messages, the paint cycles and drawing stages, and provides a C++ example, so I won't repeat it here.

Using the code

A TreeNodeEx class (derived from TreeNode) is provided in the source code, which allows you to specify in the constructor the node text, the initial text position that will use the bold font, and the length of the bold text.

A helper function like this is provided to add nodes to a TreeView:

Private Function AddNodeToTreeView(ByVal colNodes As TreeNodeCollection, _
     ByVal sText As String, ByVal iBoldTextInitialPosition As Integer, _
     ByVal iBoldTextLength As Integer) As TreeNodeEx

   Dim objTreeNodeEx As TreeNodeEx

   objTreeNodeEx = New TreeNodeEx(sText, _
                   iBoldTextInitialPosition, iBoldTextLength)
   colNodes.Add(objTreeNodeEx)

   Return objTreeNodeEx

End Function

A TreeViewEx class (derived from TreeView) is provided too. This class performs the owner draw with the tree nodes. The class is used as follows:

Private m_ctlTreeViewEx As TreeViewEx

Private Sub Form1_Load(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load

   Dim objRootTreeNodeEx As TreeNodeEx

   m_ctlTreeViewEx = New TreeViewEx()
   Me.Controls.Add(m_ctlTreeViewEx)
   m_ctlTreeViewEx.Left = 0
   m_ctlTreeViewEx.Top = 0
   m_ctlTreeViewEx.Dock = DockStyle.Fill

   objRootTreeNodeEx = AddNodeToTreeView(m_ctlTreeViewEx.Nodes, _
                       "This is the first node", 12, 5)

   AddNodeToTreeView(objRootTreeNodeEx.Nodes, "The second node", 4, 6)
   AddNodeToTreeView(objRootTreeNodeEx.Nodes, "Third node", 0, 5)
   AddNodeToTreeView(objRootTreeNodeEx.Nodes, "Node 4", 5, 1)
   AddNodeToTreeView(objRootTreeNodeEx.Nodes, "Last node", -1, 0)

   objRootTreeNodeEx.Expand()

End Sub

Points of Interest

There are some points of interest in the source code:

  • Windows Common Controls send NM_CUSTOMDRAW notifications through WM_NOTIFY messages to the parent window. So, we would need to intercept that message in the parent window, outside of our treeview control, which breaks the encapsulation rules. Fortunately, the .NET Framework allows controls to receive that message "reflected". To do this, the .NET Framework adds the value 0x2000 to the value of the WM_NOTIFY message and sends it to the control. Therefore, the TreeView control can receive the WM_NOTIFY message sent to its parent window using the following code in its own WndProc procedure:
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    
       Const WM_NOTIFY As Integer = &H4E
    
       Dim iResult As Integer
       Dim bHandled As Boolean = False
    
       If m.Msg = (&H2000 Or WM_NOTIFY) Then
       ' It is the reflected WM_NOTIFY message sent to the parent
    
          If m.WParam.Equals(Me.Handle) Then
             iResult = HandleNotify(m)
             m.Result = New IntPtr(iResult)
             bHandled = True
          End If
    
       End If
    
       If Not bHandled Then
          MyBase.WndProc(m)
       End If
    
    End Sub
  • To draw the text of a node, which mixes bold and non-bold portions, we need to draw the initial non-bold portion, the bold portion, and the final non-bold portion. To do this, we need to know the length of each portion in pixels, to set the coordinate X of the next portion, and we need a very accurate measure to avoid "holes" between two portions. It happens that when using the function Graphics.MeasureCharacterRanges to measure drawn strings, some pixels are added to the exact result. Since we need the exact result (in order to draw the next text just after the previous one), we can use the following trick: we measure the length of the text and the length of the text duplicated: since in both cases the extra pixels are added, the difference will be the exact length.

History

  • 24-May-2005. Initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author


Comments and Discussions

 
GeneralException thrown when debugging through VS2005 PinmemberEnriad7-Mar-07 14:08 
QuestionTreeview display width bug PinmemberVaibhav Deshpande23-Oct-06 3:57 
GeneralAny possibility for C# version Pinmemberzhaozh30-Jun-06 10:42 
GeneralRe: Any possibility for C# version PinmemberPerfectlyNormalBeast18-May-09 6:29 
GeneralRe: Any possibility for C# version PinmemberPerfectlyNormalBeast18-May-09 9:47 
QuestionEvents do not fire PinmemberGeorge9876543219-Mar-06 20:26 
AnswerRe: Events do not fire PinmemberGeorge9876543219-Mar-06 20:36 
QuestionAbout Checkbox Pinmemberwhatda10120-Feb-06 12:29 
GeneralFore and back colors PinsussGWSyZyGy30-Sep-05 6:39 
Generalenable regular actions on custom draw treeview PinmemberLothar Felkel13-Aug-05 0:58 
Du to the excellent article I was able to solve my problem with special formatting of tree nodes. However, using regular action like a popup menue or drag and drop are disabled due to a flaw in the WndProc. Although the HandleNotify routine handles only a few events (those return iResult<>0) the WndProc does not forward these, it treats all events as 'custom handled'. To correct this behavior, modify WndProc as follows:
   Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
 
            Const WM_NOTIFY As Integer = &H4E
 
            Dim iResult As Integer
            Dim bHandled As Boolean = False
 
            If m.Msg = (&H2000 Or WM_NOTIFY) Then   ' It is the reflected WM_NOTIFY message sent to the parent
 
                  If m.WParam.Equals(Me.Handle) Then
                        iResult = HandleNotify(m)
                        If iResult <> 0 Then   'fkl 20050812
                              m.Result = New IntPtr(iResult)
                              bHandled = True
                        End If                        'fkl 20050812
 
                  End If
 
            End If
 
            If Not bHandled Then
                  MyBase.WndProc(m)
            End If
 
      End Sub
 
Check, whether iResult is <> 0, before setting bHandled to True and MyBase.WndProc(m) will be called whenever there is no custom interception.
QuestionHow about custom drawn icons? PinmemberAmit Gee7-Aug-05 14:23 
GeneralGreat article - and very timely PinsussMark Mischke7-Jul-05 0:40 
Generalexcellent Pinmemberhenok14-Jun-05 12:56 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 26 May 2005
Article Copyright 2005 by Carlos J. Quintero
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid