본문 바로가기

work/vb

SetWindowLong: Apply TreeView Special Effects

Visual Basic Common Control API Routines
SetWindowLong: Apply TreeView Special Effects
   
Posted:   Friday April 17, 1998
Updated:   Tuesday August 12, 2008
   
Applies to:   VB4-32, VB5, VB6 (see prerequisites)
Developed with:   VB5, Windows 95
OS restrictions:   None
Author:   VBnet - Randy Birch
   
 Prerequisites
Errata: The first posting of this page stated that the Bold and FullRowSelect methods require the comctl32.dll installed with IE4 or above. This appears to be incorrect.  The C header files indicate that the bolding method should be available under an IE3.x version of comctl32.dll (but which I couldn't test), but the other effects all require the comctl32.dll from IE4 or greater be installed. The version of comctl32.dll that I used to develop this code was 4.72.3007.0.

VB6 MSCOMCTL.DLL Note: The effects demonstrated here were tested using VB5 Common Controls (SP2). This control is based on the standard Windows Common Controls found in comctl32.dll.

In developing the VB6 common controls, the VB development team stopped using comctl32.dll as its basis for functionality. Instead, the team took the unprecedented steps of removing the VB OCX dependency upon comctl32.dll, and opted (wrongly in this author's view) to rewrite the functionality available in comctl32.dll v4.72 as the new VB6 mscomctl.ocx. This redesigned OCX is not dependant on the original Windows' common control dll, and no longer relies on it for any functionality. And in creating the OCX, its implementation has been tweaked to the point where some methods that do work against the VB5 common control implementation (which uses comctl32.dll) no longer function as expected using the VB6 common control (mscomctl.ocx).

The TreeView BackColor and ForeColor methods detailed here do not work the same under the VB6 mscomctl.dll-version of the common controls. Specifically, the ForeColor method fails to recolor all nodes; only the node losing focus exhibits the changed colour, momentarily, then reverts to the default WindowText colour. The BackgroundColor method will not colour the background area of the icon and text. FullRowSelect is available in the VB6-based control as a property; the API implementation shown here will not work correctly against a coloured background. Font bolding, thankfully, still works as indicated.

This method is intended for Visual Basic 5 or Visual Basic 6 where the Common Control library used is the MSComCtl 5 version (comctl32.ocx). Because the VB6-specific mscomctl.ocx (Common Controls 6) is a complete implementation of comctl32.dll and not reliant on the version of comctl32.dll installed, this routine may not work when applied to a listview created from the VB6-specific mscomctl.ocx.

Enhanced Comctl32 functionality is only available to users with comctl32.dll version 4.70 or greater installed. This dll is typically installed with IE3.x or greater.


This code the first installment in the TreeView Common Control API series, tackles some of the easier treeview messages - those providing 'special effects' without resorting to owner-drawn controls.

The treeview is a special beast ... compared to the listview, in my opinion, a lot more complicated to manipulate via API. Instead of referring to items as simple  indices based on order, each treeview item has an item handle, and it is this that is used when manipulating treeview items (see the code for the 'Bold Selected' option). And, like the listview, some changes to items in the control via API are not reflected in VB's internal collection of treeview items, making maintaining the control's API-affected contents and VB's internal-collection contents of the control cumbersome.

So we start with the easy ones; ones that don't affect the collection.

Windows' common control header contains the definitions of the calls to manipulate aspects of the treeview control.  For applying changes to the colours used in the control, the header file defines the methods as :

#define TreeView_SetBkColor(hwnd, clr) \
   (COLORREF)SNDMSG((hwnd), TVM_SETBKCOLOR, 0, (LPARAM)clr)

#define TreeView_SetTextColor(hwnd, clr) \
   (COLORREF)SNDMSG((hwnd), TVM_SETTEXTCOLOR, 0, (LPARAM)clr)

#define TreeView_GetBkColor(hwnd) \
   (COLORREF)SNDMSG((hwnd), TVM_GETBKCOLOR, 0, 0)

#define TreeView_GetTextColor(hwnd) \
   (COLORREF)SNDMSG((hwnd), TVM_GETTEXTCOLOR, 0, 0)

When using methods that need to deal with the control on a per-item basis (as in the Bold method), the header defines a message to return the control item identifier (hTreeItem) relative to a 'passed relationship' (in wParam):

#define TreeView_GetNextItem(hwnd, hitem, code) \
   (HTREEITEM)SNDMSG((hwnd), TVM_GETNEXTITEM, (WPARAM)code, (LPARAM)(HTREEITEM)(hitem))

These 'relation identifiers' passed in wParam can be one of TVGN_ROOT, TVGN_NEXT, TVGN_PREVIOUS, TVGN_PARENT, TVGN_CHILD, TVGN_FIRSTVISIBLE, TVGN_NEXTVISIBLE, TVGN_PREVIOUSVISIBLE, TVGN_DROPHILITE, and TVGN_CARET.

Finally, note that there is no message is identified providing a means to change the colour of the treeview connecting lines or +- signs; these are based on the system setting for 3D objects. This limits the setting of fore/back colours to ones that not only are visually appealing, but provide enough contrast to clearly identify the tree lines.

 BAS Module Code
Place the following code into the general declarations area of a bas module:

Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright ©1996-2009 VBnet, Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Const GWL_STYLE As Long = (-16)
Public Const COLOR_WINDOW As Long = 5
Public Const COLOR_WINDOWTEXT As Long = 8

Public Const TVI_ROOT   As Long = &HFFFF0000
Public Const TVI_FIRST  As Long = &HFFFF0001
Public Const TVI_LAST   As Long = &HFFFF0002
Public Const TVI_SORT   As Long = &HFFFF0003

Public Const TVIF_STATE As Long = &H8

'treeview styles
Public Const TVS_HASLINES As Long = 2
Public Const TVS_FULLROWSELECT As Long = &H1000

'treeview style item states
Public Const TVIS_BOLD  As Long = &H10

Public Const TV_FIRST As Long = &H1100
Public Const TVM_GETNEXTITEM As Long = (TV_FIRST + 10)
Public Const TVM_GETITEM As Long = (TV_FIRST + 12)
Public Const TVM_SETITEM As Long = (TV_FIRST + 13)
Public Const TVM_SETBKCOLOR As Long = (TV_FIRST + 29)
Public Const TVM_SETTEXTCOLOR As Long = (TV_FIRST + 30)
Public Const TVM_GETBKCOLOR As Long = (TV_FIRST + 31)
Public Const TVM_GETTEXTCOLOR As Long = (TV_FIRST + 32)

Public Const TVGN_ROOT                As Long = &H0
Public Const TVGN_NEXT                As Long = &H1
Public Const TVGN_PREVIOUS            As Long = &H2
Public Const TVGN_PARENT              As Long = &H3
Public Const TVGN_CHILD               As Long = &H4
Public Const TVGN_FIRSTVISIBLE        As Long = &H5
Public Const TVGN_NEXTVISIBLE         As Long = &H6
Public Const TVGN_PREVIOUSVISIBLE     As Long = &H7
Public Const TVGN_DROPHILITE          As Long = &H8
Public Const TVGN_CARET               As Long = &H9

Public Type TV_ITEM
   mask As Long
   hItem As Long
   state As Long
   stateMask As Long
   pszText As String
   cchTextMax As Long
   iImage As Long
   iSelectedImage As Long
   cChildren As Long
   lParam As Long
End Type

Public Declare Function SendMessage Lib "user32" _
   Alias "SendMessageA" _
   (ByVal hwnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    lParam As Any) As Long

Public Declare Function GetWindowLong Lib "user32" _
   Alias "GetWindowLongA" _
   (ByVal hwnd As Long, _
    ByVal nIndex As Long) As Long

Public Declare Function SetWindowLong Lib "user32" _
   Alias "SetWindowLongA" _
   (ByVal hwnd As Long, _
    ByVal nIndex As Long, _
    ByVal dwNewLong As Long) As Long

Public Declare Function GetSysColor Lib "user32" _
   (ByVal nIndex As Long) As Long
 Form Code
Add a treeview (TV1), the common dialog control (CommonDialog1), and five command buttons to a form (Command1, Command2, Command3, Command4 and Command5). Add the following code:

Option Explicit

Private Sub Form_Load()

   Dim nodX As Node
   
  'add some test items 
   Set nodX = TV1.Nodes.Add(, , "R", "Root")
   Set nodX = TV1.Nodes.Add("R", tvwChild, "C1", "Child 1")
   Set nodX = TV1.Nodes.Add("R", tvwChild, "C2", "Child 2")
   Set nodX = TV1.Nodes.Add("R", tvwChild, "C3", "Child 3")
   Set nodX = TV1.Nodes.Add("R", tvwChild, "C4", "Child 4")
   nodX.EnsureVisible
   
   Set nodX = TV1.Nodes.Add("C3", tvwChild, "C31", "Child 3 SubC 1")
   Set nodX = TV1.Nodes.Add("C3", tvwChild, "C32", "Child 3 SubC 2")
   nodX.EnsureVisible
   
   Set nodX = TV1.Nodes.Add("C31", tvwChild, "C321", "Child 3 SubC 1 SubC 1")
   
   Set nodX = TV1.Nodes.Add("C4", tvwChild, "C41", "Child 4 Subchild 1")
   nodX.EnsureVisible
   
   Command1.Caption = "Set Background"
   Command2.Caption = "Set Foreground"
   Command3.Caption = "Bold Selected"
   Command4.Caption = "Full Row Select"
   Command5.Caption = "End"            
   
End Sub


Private Sub Command5_Click()

   Unload Me
   
End Sub


Private Function GetTVBackColour() As Long

   Dim clrref As Long
   Dim hwndTV As Long
   
   hwndTV = TV1.hwnd
   
  'try for the treeview backcolor
   clrref = SendMessage(hwndTV, TVM_GETBKCOLOR, 0, ByVal 0)
   
  'if clrref = -1, then the color is a system color.
  'In theory, system colors need to be Or'd with &HFFFFFF
  'to retrieve the actual RGB value, but not Or'ing 
  'seems to work for me. The default system colour for 
  'a treeview background is COLOR_WINDOW.
   If clrref = -1 Then
      clrref = GetSysColor(COLOR_WINDOW)  ' Or &HFFFFFF
   End If
   
  'one way or another, pass it back
   GetTVBackColour = clrref
   
End Function


Private Function GetTVForeColour() As Long

   Dim clrref As Long
   Dim hwndTV As Long
   
   hwndTV = TV1.hwnd
   
  'try for the treeview text colour
   clrref = SendMessage(hwndTV, TVM_GETTEXTCOLOR, 0, ByVal 0)
   
  'if clrref = -1, then the color is a system color.
  'In theory, system colors need to be Or'd with &HFFFFFF
  'to retrieve the actual RGB value, but not Or'ing 
  'seems to work for me. The default system colour for
  'treeview text is COLOR_WINDOWTEXT.
   If clrref = -1 Then
      clrref = GetSysColor(COLOR_WINDOWTEXT) ' Or &HFFFFFF
   End If
   
  'one way or another, pass it back
   GetTVForeColour = clrref
   
End Function


Private Sub SetTVBackColour(clrref As Long)

   Dim hwndTV As Long
   Dim style As Long
   
   hwndTV = TV1.hwnd
   
  'Change the background
   Call SendMessage(hwndTV, TVM_SETBKCOLOR, 0, ByVal clrref)
   
  'reset the treeview style so the
  'tree lines appear properly
   style = GetWindowLong(TV1.hwnd, GWL_STYLE)
   
  'if the treeview has lines, temporarily
  'remove them so the back repaints to the
  'selected colour, then restore
   If style And TVS_HASLINES Then
      Call SetWindowLong(hwndTV, GWL_STYLE, style Xor TVS_HASLINES)
      Call SetWindowLong(hwndTV, GWL_STYLE, style)
   End If
  
End Sub


Private Sub SetTVForeColour(clrref As Long)

   Dim hwndTV As Long
   Dim style As Long
   
   hwndTV = TV1.hwnd
   
  'Change the background
   Call SendMessage(hwndTV, TVM_SETTEXTCOLOR, 0, ByVal clrref)
   
  'reset the treeview style so the
  'tree lines appear properly
   style = GetWindowLong(TV1.hwnd, GWL_STYLE)
   
  'if the treeview has lines, temporarily
  'remove them so the back repaints to the
  'selected colour, then restore
   If style And TVS_HASLINES Then
      Call SetWindowLong(hwndTV, GWL_STYLE, style Xor TVS_HASLINES)
      Call SetWindowLong(hwndTV, GWL_STYLE, style)
   End If
   
End Sub


Private Sub Command1_Click()

   Dim newclr As Long
   
   With CommonDialog1
      .Flags = cdlCCRGBInit      'using RGB colours
      .Color = GetTVBackColour() 'pre-select the current colour
      .ShowColor                 'get the user's choice
      newclr = .Color            'and assign to a var
   End With
   
   SetTVBackColour newclr        'set the backcolour
   
End Sub


Private Sub Command3_Click()

   Dim TVI As TV_ITEM
   Dim hitemTV As Long
   Dim hwndTV As Long
   
  'get the handle to the treeview item.
  'If the item is selected, use TVGN_CARET.
  'To highlight the first item in the root, use TVGN_ROOT
  'To hilight the first visible, use TVGN_FIRSTVISIBLE
  'To hilight the selected item, use TVGN_CARET
   hwndTV = TV1.hwnd
   hitemTV = SendMessage(hwndTV, TVM_GETNEXTITEM, TVGN_CARET, ByVal 0&)
   
  'if a valid handle get and set the
  'item's state attributes
   If hitemTV > 0 Then
   
      With TVI
         .hItem = hitemTV
         .mask = TVIF_STATE
         .stateMask = TVIS_BOLD
          Call SendMessage(hwndTV, TVM_GETITEM, 0&, TVI)
         
         'flip the bold mask state
         .state = TVIS_BOLD
      End With
      
      Call SendMessage(hwndTV, TVM_SETITEM, 0&, TVI)
 
   End If
   
End Sub


Private Sub Command4_Click()

   Dim hwndTV As Long
   Dim style As Long

  'get the window style
   style = GetWindowLong(TV1.hwnd, GWL_STYLE)
   
  'toggle the fullrow select
   If style And TVS_FULLROWSELECT Then
      style = style Xor TVS_FULLROWSELECT
   Else
      style = style Or TVS_FULLROWSELECT
   End If
   
  'and set it
   Call SetWindowLong(TV1.hwnd, GWL_STYLE, style)
   
End Sub


Private Sub Command2_Click()

   Dim newclr As Long
   
   With CommonDialog1
      .Flags = cdlCCRGBInit      'using RGB colours
      .Color = GetTVForeColour() 'pre-select the current colour
      .ShowColor                 'get the user's choice
      newclr = .Color            'and assign to a var
   End With
   
   SetTVForeColour newclr        'set the text colour
   
End Sub
 Comments
Run the project. Selecting either of the colour options will bring up the common dialog.   Select a colour - either from the available colours or a custom colour. The appropriate part of the treeview will take the selected colour.

If you've installed IE4 (or the newer Java preview), you also have the comctl version required to bold selected items or select using Full Row Select.

At this time, there appears to be no means to italicize individual treeview items, change the font of individual items, set individual item colours, or add images as treeview backgrounds without resorting to subclassing and owner-drawing the control.

In addition, an apparent bug with this control and the full row select will highlight past the left side of a child if that child is the last item in the treeview, as in the illustration above.