19 January 2010

ListView Subitems Custom Draw

Just before Christmas 2009 Peter Heinzig mailed me a copy of his "listView_CellColor" program. He was experimenting with Custom Draw for common controls to provide cell coloring for subitems in a ListView in report-mode. To be honest, I never used custom draw, so I had some catching up to do. After reading the SDK I checked the GB32 disassembly to check if GB32 uses custom draw. Bingo! It does use it with the ListView Ocx control to implement the ListItem's properties ForeColor, BackColor, and Font. Anyone trying to implement custom draw for a ListItem object could get in problems with GB32's implementation, so it is time to implement a custom draw routine for ListView controls taking the GB32 implementation into account.

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
'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.

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.

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.

' 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
If lv4_Colors(Row, Column).Back <> CLR_NONE
nmcdr.clrTextBk = lv4_Colors(Row, Column).Back

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
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.

13 January 2010

Swap Arrays by ArrPtr()

Didn't GFABASIC for Atari ST support swapping (or passing) arrays by pointer? I picked up my copy of the book 'GFABASIC' by Frank Ostrowski (1987) to check if I remembered correctly. Indeed, some kind of swapping by pointer is supported. The QuickSort program FO presented on page 22 passes a string array to the sort routine by using *a$(), which is equal to ArrPtr(a$()). To access the array locally in the procedure, the array descriptor passed is swapped with a the descriptor of array a$(). Given a pointer to an array descriptor the Atari ST Swap command supported the following syntax:

Swap *ardescriptor%, a$()   ! Atari ST GFABASIC

Unfortunately, the Swap command of GB32 isn't that flexible. The arguments of the Swap command need to be of the same kind. For instance:

Swap a%(), b%()
Swap f#, g#
Swap udt1, udt2

Even a user-defined type can be swapped! Swap works well as long as the data type of both operands is the same. But I have a need to store a pointer (Long Integer) to an array of data and later on use that pointer to access the array's data. So, I want to store an ArrPtr() and swap that pointer with an (empty) array. For instance:

Swap arrdesc%, d()    // not supported

After disassembling the code GB32 generates for Swap a(), b() it is easy to see, that GB32 implements the array swap by passing the pointers of the descriptors of these arrays. Internally, a Swap a(), b() is actually a Swap ArrPtr(a()), ArrPtr(b()).

Also, the disassembly reveals the Asm scall name for the library function. Strangely, the name is pgMinBottom, obviously an error in the naming. It is now easy to create a custom routine to swap two arrays by descriptor.

Proc ArrPtrSwap(ArrPtr1%, ArrPtr2%)
  . push [ArrPtr1%], [ArrPtr2%]
  . scall pgMinBottom   ' GB32 naming error

Given some arrays, the procedure might be invoked like this:

Dim g%(2), a%(5)
Dim ptrToa% = *a()
Dim ptrTog% = ArrPtr(g())

ArrPtrSwap(ptrToa%, *g%())
ArrPtrSwap *a%(), *g%()
ArrPtrSwap *a%(), ptrTog%

Now you can initialize an array, store a pointer to it in the .WhatsThisHelpID property of an OCX control, and use it later on by swapping it with an empty array. I now can finally conclude my series on custom drawing of single SubItems of a ListView.Listitem object.

11 January 2010

The .Tag property

In COM all strings are UNOCODE BSTRs. GB32 converts a string to UNICODE before it calls the set_Tag() function. When the property .Tag is set with a literal string constant, the compiler optimizes string assignment by converting the literal string to an UNICODE string before hand. For instance, the assignment Ocx.Tag = "Hello" is stored as:

Ocx.Tag = "H" #0 "e" #0 "l" #0 "l" #0 "0" #0 #0

When the .Tag property assignment is actually executed during runtime, the UNICODE string is passed to the set_Tag() function of the Ocx/COM object, and the BSTR is stored with the COM object for later access. But something is breaking the process ...

GB32 stores ANSI code
The rationale behind GB32's implementation of UNICODE vs. ANSI isn't very clear. IMHO the most simple, effective, and fastest procedure would be to store the pointer to the UNICODE string as it is passed to the Tag property. But GB32 converts it (back) to ANSI and stores a pointer to the ANSI-string memory. Of course, GB32 strings might be as large as physical memory and storing a UNICODE string doubles the memory necessary. But that couldn't be the reason for this behavior, could it?

Anyway, so far so good. It is a fact of life. If you like you can store binary data in a string and  assign it to a .Tag property. But can you get it out of the Tag property? The answer is No! When the BSTR passed to the set_Tag() function is copied to ANSI, GB32 only stores the pointer to the converted ANSI string (a char* in C/C++). It doesn't save the length of the passed BSTR. So, how does GB32 know how many characters to return from get_Tag()? It doesn't and simply invokes a call to the C-library function strlen(char*), which returns at first occurrence of '\0'.

Binary data cannot be stored in a .Tag property.

08 January 2010

The OCX() return type

What exactly does the OCX() function return? It got me thinking when I was developing the custom draw program. Since GB32 seems to name all COM objects Ocx objects, I expected it to return a general Object type (which isn't exactly wrong). But it doesn't return an Object type. Remember that a DisAsm or Collection COM object aren't created using the Ocx command, but using a New clause in the Dim command. These are not Ocx objects; Ocx objects are COM wrappers for controls only, that is anything with a window handle. Consequently, strictly spoken, the OCX() function returns a Control object.

Any COM type can be cast back to an Object. The only actions performed on an Object are late bound properties and methods. An Object is an IDispatch interface only.
Next in hierarchy is a Control object. It embeds the IUnknown and IDispatch interfaces, but it also includes data for GB32 to maintain the windows/controls. Every Ocx ( = control) includes (embeds) a Control object. Since there is no interface type-info for a Control object (use OleView.exe to inspect COM types), the properties and methods are invoked through IDispatch, like with the Object type.
Finally, there is an interface definition for an Ocx control. The Ocx object is based on the Control object. In addition, each Ocx object has its own control specific data. A Control object is an OCX without the specific control information and without an interface.

Object -> Control -> Ocx
Control = OCX(hWnd)

As you can see, there is nothing wrong to store the return value of OCX() in an Object variable, but strictly speaking it should be a Control type variable.

For a non-Ocx COM object the hierarchy is

Object -> GB32 COM Object type (Collection, DisAsm, etc).

For these types there is no OCX() function (because it depends on a window handle) and should not be cast to a Control object.

06 January 2010

Custom Drawing Ocx Controls

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.
  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
    ' 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)
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?)

  ' Not a custom draw Ocx control, 
' continue processing
  ' NM_CUSTOMDRAW in Form_MessageProc()
  Return False
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
' 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
' 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
' 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
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.