Together with the TreeView common control, the ListView common control is one of the most important control used to present organized data. Since displaying data in columns is probably the most used features of the ListView control, I will discuss how to implement subitem coloring of cells in a report-view mode ListView Ocx control. In this installment I will discuss the necessary steps to start custom drawing a GB32 Ocx control. The ListView cell coloring is discussed in another entry.
Custom drawing
The common control library supports a technique called 'custom draw' to change the appearance of the elements of some of the controls. The custom draw technique was developed to eliminate the need to fully owner-draw entire controls. Custom draw allows trapping only a small set of the drawing operations, rather than perform all the drawing.
The common controls send custom draw messages as a WM_NOTIFY messages to the parent window. The WM_NOTIFY message specifies a pointer to a NMHDR structure in the
lParam argument. The NMHDR structure specifies the (notification) code, the window handle and ID of the control from which the message originates.
Type NMHDR
hwndFrom As Long
idfrom As Long
code As Long
End Type
The _MessageProc event
In GB32 a parent-window is usually a Form. The WM_NOTIFY message is a sent message and can only be handled in Form's event sub
Form_MessageProc(). The
_MessageProc is invoked from inside the Form's window procedure (defined in the GfaWin23.Ocx) before GB32 processes the message itself. The two additional arguments of the
_MessageProc (
retval% and
ValidRet?) determine whether GB32 will actually continue processing this message in its own window procedure once it returned from
_MessageProc, which could look like this:
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, _
lParam%, retval%, ValidRet?)
Switch Mess
Case WM_NOTIFY
' use Pointer to cast a lParam to a type!
Dim nmhdr As Pointer NMHDR
Pointer nmhdr = lParam
' Custom draw message.
If nmhdr.code == NM_CUSTOMDRAW
' (do something)
EndIf
EndSwitch
EndSub
Noticeable is the elegant way GB32 allows you to cast a pointer to a structure. I explained
Pointers in the past.
It is important to realize that NM_CUSTOMDRAW is sent always, for every common control that supports it. As soon as a common control starts it painting process it starts sending this message. It is up to the developer to tell the common control library to send follow-up messages for each stage in the painting process, or to stop sending notifications after the first one arrived. For performance reasons, the library only sends the message that signals the start of the paint-cycle (CDDS_PREPAINT) and then only sends more messages when you respond with a value "
send-me-more".
Custom drawing Ocx controls
What to do once you intercepted the NM_CUSTOMDRAW message is described in the MSDN. However, the documentation always discusses C-style handling and provides sample code in case one control wants custom drawing. What if the control is a GB32 Ocx and multiple Ocxs want custom drawing? The question arises how to differentiate between the various Ocx controls when we process NM_CUSTOMDRAW?
Option #1 is to store the window handles of the Ocx-controls in global variables and compare these in a (possibly long)
If-Else statement with
nmhdr.hwndFrom. Once you find a match you know what to do. This option is certainly valid, but it is not my intention to show you how to solve this problem the API-way, but to show you the GB32- COM way.
The GB32-COM way
This is option #2. We will use
nmhdr.hwndFrom to get an Ocx object and then use the object to perform general custom drawing for that object type. Since GB32 supports COM interfaces for
ListView,
TreeView,
ToolBar, and
Slider, not all common controls (Header, ToolTip, and ReBar) that support custom drawing can be handled this way. The ReBar common control is not implemented, unfortunately, and Header and ToolTip controls are part of other controls and are not support on their own. So, we need a function that separates the good from the bad.
Function OcxCustomDraw(hWndFrom%, lParam%, _
ByRef retval%, ByRef ValidRet?) As Bool
' Non Ocx controls are rejected immediately, Only OCX() is invoked.
Dim Ob As Object
Set Ob = OCX(hWndFrom)
If IsNothing(Ob)
Return False ' Not an OCX control
ElseIf TypeOf(Ob) Is ListView ' Most often used in custom drawing
Return CustomDrawListView(Ob, lParam%, retval%, ValidRet?) ' Big chance we have a hit.
ElseIf TypeOf(Ob) Is TreeView
Return CustomDrawTreeView(Ob, lParam%, retval%, ValidRet?)
ElseIf TypeOf(Ob) Is ToolBar
Return CustomDrawToolBar(Ob, lParam%, retval%, ValidRet?)
ElseIf TypeOf(Ob) Is Slider
Return CustomDrawSlider(Ob, lParam%, retval%, ValidRet?)
EndIf
' Not a custom draw Ocx control,
' continue processing
' NM_CUSTOMDRAW in Form_MessageProc()
Return False
EndFunc
The window handle in
nmhdr.hwndFrom identifies the common control, so we pass this value in the first parameter
hwndFrom of
the OcxCustomDraw() function. The second parameter is the
lParam value as it is passed to
_MessageProc, holding a pointer to NMHDR. The out-parameters
retval% and
ValidRet? are passed as well, since they must be set once we processed the message. In addition, we make this a Boolean function to signal the caller (
_MessageProc()) to continue processing WM_NOTIFY or to exit the event sub immediately. (The
_MessageProc should not perform any action when the message is handled in
OcxCustomDraw(), but It might do more processing when the
hwndFrom passed in isn't an Ocx object or when NM_CUSTOMDRAW originates from a GB32 Ocx that doesn't support custom drawing. In these cases
OcxCustomDraw() returns
False.)
To check the Ocx type for a given window handle we need a two step process. First, obtain the address of the object that wraps the control using the
OCX(hWnd) function. This is a simple
addrObject = GetWindowLong(hWnd, GWL_USERDATA) call. When no object is attached to the window handle
Ob Is Nothing and we can skip the rest of the function. When the Ob contains a valid Ocx type we test whether it is of type
ListView,
TreeView,
ToolBar, or
Slider using the
TypeOf(O) Is Interfacetype construction. When
True, we execute either one of four Ocx custom draw operations. These functions are defined as follows:
' ListView custom drawing
Function CustomDrawListView(Lv As ListView _
, lParam%, ByRef retval%, ByRef ValidRet?) As Bool
Dim nmcdr As Pointer NMLVCUSTOMDRAW
Pointer nmcdr = lParam
Return False
EndFunc
' TreeView custom drawing
Function CustomDrawTreeView(Treeview As TreeView, _
lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
Dim nmcdr As Pointer NMTVCUSTOMDRAW
Pointer nmcdr = lParam
Return False
EndFunc
' ToolBar custom drawing
Function CustomDrawToolBar(Toolbar As ToolBar, _
lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
Dim nmcdr As Pointer NMTBCUSTOMDRAW
Pointer nmcdr = lParam
Return False
EndFunc
' Slider custom drawing
Function CustomDrawSlider(Slider As Slider, _
lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
Dim nmcdr As Pointer NMCUSTOMDRAW
Pointer nmcdr = lParam
Return False
EndFunc
For a ListView control Ocx the function calls
CustomDrawListView(). When invoking the function the general
Ob variable of type
Object is cast to a
ListView type implicitly. Each of these
CustomDrawType() functions casts the
Object variable
Ob to the appropriate COM type. Inside each function the
lParam is cast to the correct API custom-draw structure (see SDK).
Note - The types are all defined in the
commctrl.inc.lg32 included in the GB32 package. You can either load this library using
$Library or copy-paste the relevant Types.
Final note
The
TypeOf(O) Is Interfacetype construction is a library function that checks the object against the IID value of
InterfaceType. Therefore it calls the
QueryInterface() for that type. This decreases performance of course, although the GB32 implementation of
Is TypeOf is pretty fast.
Next time I discuss how to color subitems in report ListView.