Owner Draw Combo and List Boxes Version 2.1
Customise Combo Boxes and List Boxes with this control
Owner draw combo and list boxes are an excellent way to improve the look and feel of your application. However, there is little support for them in Visual Basic. The only owner-draw combo box supplied is the Checked list box style, but this is a preset list box style with no possibility for customisation. Here I provide a new control, completely written in Visual Basic, which does all the hard work of setting up an owner draw combo or list box. It also provides some great looking preset implementations:
Owner Draw Combo Boxes and List Boxes in the Win32 API
Creating owner drawn combo boxes or list boxes is relatively straightforward, but leads to rather a lot of non-Basic style coding. When the control is created, the application should add CBS_OWNERDRAWVARIABLE (for combo boxes) or LBS_OWNERDRAWVARIABLE (for list boxes) to the Windows Style bit (this cannot be done after the control is created, however).
Having done this, Windows no longer draws the control. Instead it sends a WM_DRAWITEM message whenever a portion of the control needs to be repainted. This passes a pointer to a DRAWITEMSTRUCT structure filled out with what needs to be drawn and where:
Type DRAWITEMSTRUCT CtlType As Long ' -Not needed CtlID As Long ' -The ID of the control when created ItemId As Long ' -The ListIndex of the item to draw ItemAction As Long ' -What action is causing the draw call ItemState As Long ' -The state of the item to be drawn hwndItem As Long ' -The hWnd of the window to draw in hDC As Long ' -The DC of the window to draw in rcItem As RECT ' -The bounding rectangle of the item to be drawn itemData As Long ' -The item data of the item to be drawn End Type
You then respond to this message by drawing the item into the DC as required.
In addition, whenever an item is added to the list box, Windows sends a WM_MEASUREITEM message. This contains a pointer to a MEASUREITEMSTRUCT structure that the application can fill in with the size of the item.
Owner Draw Combo and List Boxes in Visual Basic
In practice, doing this in Visual Basic is not simple. Firstly, you cannot take an existing Combo Box or List Box and then add the required owner-draw style bit - it has no effect, and at best will cause the control not to operate correctly.
So instead you have to create a new window by calling CreateWindowEx. This means there is a lot of hard work required to make the control respond like a normal VB control. The worst problem is with focus. Normally you do not need to worry about Focus because VB implements all the methods required to set focus to and from other OCX controls. However, if you create a non-OCX window using CreateWindowEx, VB's default methods have no idea whatsoever how to handle it. Focus continues to work in VB as if the window you created did not exist, except when you click on the window with the mouse, in which case VB fails to understand that any change in focus has occurred.
Focus setting for OCX controls is done properly by implementing the OLE IOLEInPlaceActiveObject interface. However, this interface is resolutely hard-coded into VB UserControl's so it is very hard to get at. The first release of this control attempted to hack around this problem by subclassing the WM_SETFOCUS message and responding in such a way that VB no longer had any control. There were many problems with this, such as the control failing to operate on MDI forms, GotFocus and LostFocus events not firing and VB getting confused about which the ActiveControl was.
Version 2 of the control fixes all the focus problems by implementing the IOLEInPlaceActiveObject interface. It does this using some seriously hardcore code developed by Mike Gainer, Matt Curland and Bill Storage. This replaces the UserControl's standard OLE vtable during the UserControl_Initialise event, allowing the IOLEInPlaceActiveObject interface to be overridden by local VB versions of these functions. Using this in combination with responding to all WM_SETFOCUS and WM_MOUSEACTIVATE messages allows the correct response to focus to be coded in. If you are attempting to create a VB control which uses CreateWindowEx, and your control has to get focus, you need to download and check out this code!
To get the rest of the features working is fairly straightforward (but again rather un-basic like - a lot of copying memory to and from pointers is required), and essentially consists of intercepting WM_COMMAND, WM_MEASUREITEM and WM_DRAWITEM notification events and translating VB's methods (List, ListIndex etc) into the equivalent SendMessage calls.
Using the ODCboLst Control
When you place a new instance of ODCboLst onto a form, the primary items relating to owner draw operation are the ClientDraw and Style properties.
The Style property allows you to choose what type of control you get. This is similar to the Style property of a normal combo box or list box, except that both are provided in the same control, and also simple list boxes are not supported (does anyone ever use these?) The settings are fairly self-explanatory:
The ClientDraw property determines how the control is rendered, and what options you will have to draw into the control. The settings for this are:
These styles will be covered in turn.
In this style, you are not allowed to do any drawing yourself. All drawing is done internally in the control using the default drawing method. The default drawing method supports the following properties for each item in the control:
This mode performs the same as ecdNoClient except that once it has finished painting it raises a DrawItem event which allows you to perform extra drawing on the list items. This event is described in ecdClientDrawOnly next.
In this mode, the control makes no attempt at drawing, except that it selects the font appropriate to the item into the control's drawing DC. It raises a DrawItem event to the owner form, which should draw the item itself. The parameters for the DrawItem event are as follows:
The "Line Style and Texture Pickers" in this section provides sample source on how to use this method. The sample uses GDI methods to the drawing, however you could equally use VB drawing methods to draw into a picture box with AutoRedraw set to True and then use one call to BitBlt to transfer the item to the DrawItem hDC if you are more comfortable with VB's drawing methods.
This style causes ODCboLst to draw a colour picker box. All drawing is done internally in the control using the default drawing method. The drawing method supports the following properties for each item in the control:
This style is the same as ecdColourPickerWithNames except that it shows colour sample boxes only.
This style is the same as ecdColourPicker, except that it preinitialises the colours to the system colours.
This mode performs the same as ecdNoClient except that once it has finished the default paint it additionally paints a box on the right hand side showing the text centring and font sizes. This style is aimed at setting up a paragraph style selector similar to the one in Word.
This mode performs the same as ecdNoClient except the combo/list box is initialised with all the screen and printer fonts on the system and it draws a TT/printer icon next to the font.
Other Additional Properties and Methods
In addition to the owner-draw related properties and methods, the following are also provided: