We step right into the drawing stage of custom draw Ocx controls. I discussed the message handling of custom draw for Ocx controls in a previous entry Custom Draw for Ocx controls. Note this is GFABASIC 32 specific implementation, other languages might use a different method.
Using the correct Ocx type
We left off with the function that invokes a specific subroutine when the NM_CUSTOMDRAW message has come from a ListView, TreeView, ToolBar, or Slider Ocx control. This function, which I called OcxCustomDraw() to emphasize we are processing GB32-Ocx specific custom drawing, passes the general Control variable as the first parameter, by value. In the process of passing, the type of COM variable is changed to the required COM interface.
Note - In VB/GB32 the interface name is the name of a COM object without the leading I. Here we have an Control variable, which is a pointer (4-bytes) addressing the memory occupying a structure for an Control type. When we pass the variable by value, we put the contents of the Control variable on the stack and interpret it in the called subroutine as an address of a (I)ListView type (interface ). Any actions performed on the local Ocx variable will instruct the compiler to use COM syntax for the type (I)ListView. Confusing? Probably worth a new blog entry ...
The drawing process
I'll be short about interpreting the NM_CUSTOMDRAW draw message data passed in the lParam argument. You'll find it in the SDK or MSDN. First we need to cast the pointer in lParam to the custom draw structure for a ListView control.
' ListView custom drawing Function CustomDrawListView(Lv As ListView _ , lParam%, ByRef retval%, ByRef ValidRet?) As Bool Dim nmcdr As Pointer NMLVCUSTOMDRAW Pointer nmcdr = lParam
Now we can access the structure-members using the dot-operator syntax. For a ListView's subitem to draw, we must respond to three draw stages; CDDS_PREPAINT, CDDS_ITEMPREPAINT, and CDDS_ITEMPREPAINT | CDDS_SUBITEM. That is it.
Let us start with CDDS_PREPAINT. This notification is sent at the beginning of the drawing of the row. Each row sends it once only. You must response with the correct value to specify what you want next. Well, both GB32 as ourselves want more (sub)item draw messages, so we must respond with CDRF_NOTIFYITEMDRAW. Since GB32 already returns this value, our custom draw function may skip this message (although it wouldn't hurt if you didn't).
Switch nmcdr.nmcd.dwDrawStage Case CDDS_PREPAINT 'retval = CDRF_NOTIFYITEMDRAW 'ValidRet? = True Return True
We return from this function with True to let the Form's event sub _MessageProc() know that we handled this message and that it can leave the event sub without further processing.
The next drawing stage is CDDS_ITEMPREPAINT. When GB32 receives this message it fills in the appropriate NMLVCUSTOMDRAW members on return. The common control library than uses the selected colors and fonts for drawing. After it processes this message, GB32 returns with a value indicating it is ready with this list item (row). We trap this message before GB32 handles it an specify that we want additional custom draw messages for each subitem of the row.
Case CDDS_ITEMPREPAINT retval = CDRF_NOTIFYSUBITEMDRAW ValidRet? = True Return True
Because CDDS_ITEMPREPAINT returned CDRF_NOTIFYSUBITEM the common library will start sending notification messages, in the form CDDS_ITEMPREPAINT, before it starts drawing each of the subitems.
3 CDDS_ITEMPREPAINT + CDDS_SUBITEM
To indicate subitem drawing the control library adds CDDS_SUBITEM to the value. So, in the next stage we must process, we must fill in the members of NMLVCUSTOMDRAW that the control library will use to draw. More precisely, we can fill in the .clrText and .clrTextBk fields for the colors and select a new font in the hDC. This is exactly the behavior of GB32. So, we will let GB32 process the message first.
Case CDDS_ITEMPREPAINT | CDDS_SUBITEM ' Set back to the ListView control default colors for each subitem. nmcdr.clrText = CLR_NONE nmcdr.clrTextBk = CLR_NONE ' Let GB32 copy the ListItem properties of the row (color _and_ font) ' Go through the ListView window procedure, so forward the WM_NOTIFY message ' (as OCM_NOTIFY) to the ListView's window procedure. nmcdr.nmcd.dwDrawStage = CDDS_ITEMPREPAINT retval = SendMessage(Lv.hWnd, WM_NOTIFY + $2000, 0, *nmcdr) ValidRet? = True ' Finally, we get a chance to overrule the color settings: Local Int Row = nmcdr.nmcd.dwItemSpec + 1 ' 0 to 1 -based Local Int Column = nmcdr.iSubItem + 1 ' 0 to 1 -based If lv4_Colors(Row, Column).Fore <> CLR_NONE nmcdr.clrText = lv4_Colors(Row, Column).Fore EndIf If lv4_Colors(Row, Column).Back <> CLR_NONE nmcdr.clrTextBk = lv4_Colors(Row, Column).Back EndIf Return True ' Notify the Form_MessageProc EndSwitch ' dwDrawStage
After overruling the colors the subitem is drawn. Unfortunately, the function uses a global array named lv4_Colors() of type SubItemColors. The following is from the frm1_Load() sub event, lv4 is the Ocx ListView in report mode.
Type SubItemColors - Int Fore, Back EndType Local Int Rows = lv4.ListItems.Count Local Int Columns = lv4.ColumnHeaders.Count Global lv4_Colors(Rows, Columns) As SubItemColors ' The array MUST be initialized with the default ListView system colors CLR_NONE (= -1). ' Whenever a subitem cell color must be reset to the default value, ' simply set the Fore and Back members to CLR_NONE. MemLFill ArrayAddr(lv4_Colors()), ArraySize(lv4_Colors()), CLR_NONE ' Cell (1,1) is left-top-most cell lv4_Colors(1, 1).Fore = $23FFFF lv4_Colors(1, 1).Back = $777777 lv4_Colors(2, 1).Fore = $FF lv4_Colors(2, 1).Back = RGB(0, 255, 0)
The CLR_NONE value indicates the use of the ListView default colors. It is the clue to all drawing.
The integration in GB32 custom drawing might be complicated. If so ask me.