The new vbAccelerator Site - more VB and .NET Code and Controls
Source Code
1 VB 5 Custom Controls &nbsp


NOTE: this code has been superceded by the version at the new site.



&nbsp

Owner Draw Combo and List Boxes Version 2.1

Owner Draw Combo List Control Demonstration

UpdatedUpdated!
30 May 1999: The control has now been updated to version 2.1 Read about the updates to this control.

Download the control (54kb)
Download the demonstration project source code (requires the control first) (33kb)
Download the client draw demonstration (requires the control first) (43kb)
Download the entire source code for the control and demonstrations (224kb)

&nbsp Source Code Note &nbsp
&nbsp This OCX is a binary compatible component which works with all other samples. If you compile your own copy of this OCX yourself please make sure you change the name. See disclaimer and license for more details. &nbsp
&nbsp Before you Begin &nbsp
&nbsp These projects require the SSubTmr.DLL component. Make sure you have loaded and registered this before trying any project. &nbsp
&nbsp This project requires the Ole Guids and Interface Definitions type library (OleGuids.TLB) when running in the IDE. Make sure you have downloaded and registered this before trying the project. &nbsp

Download/View the ODCboLst interface documentation (22kb)

Overview
Owner draw combo and list boxes are an excellent way to improve the look and feel of your application. However, there is precious 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 5, which does all the hard work of setting up an owner draw combo or list box. It also provides some great looking preset implementations:

  • Choosing colours
  • Choosing system colours
  • Choosing fonts
  • Drawing combo or list boxes with icons, indentations and different font and fore/back colours for each item
  • Selecting paragraph styles, similar to the paragraph picker in Word 97

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.

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
&nbsp &nbsp CtlType As Long &nbsp &nbsp ' -Not needed
&nbsp &nbsp CtlID As Long &nbsp &nbsp ' -The ID of the control when created
&nbsp &nbsp ItemId As Long &nbsp &nbsp ' -The ListIndex of the item to draw
&nbsp &nbsp ItemAction As Long &nbsp &nbsp ' -What action is causing the draw call
&nbsp &nbsp ItemState As Long &nbsp &nbsp ' -The state of the item to be drawn
&nbsp &nbsp hwndItem As Long &nbsp &nbsp ' -The hWnd of the window to draw in
&nbsp &nbsp hDC As Long &nbsp &nbsp ' -The DC of the window to draw in
&nbsp &nbsp rcItem As RECT &nbsp &nbsp ' -The bounding rectangle of the item to be drawn
&nbsp &nbsp itemData As Long &nbsp &nbsp ' -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
That is the overview. 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 - this has no effect in Win32, 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 (if not a little 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:

  • ecsDropDownCombo = 0
  • ecsDropDownList = 2
  • ecsListBox = 4
  • ecsListBoxMultiSelect = 5
  • ecsListBoxChecked = 6
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:
  • ecdNoClientDraw = 0
  • ecdDefaultDrawThenClient = 1
  • ecdClientDrawOnly = 2
  • ecdColourPickerWithNames = 3
  • ecdColourPickerNoNames = 4
  • ecdSysColourPicker = 5
  • ecdParagraphStyles = 6
  • ecdFontPicker = 7
These styles will be covered in turn.

ecdNoClientDraw
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:

&nbsp
&nbsp ItemForeColor(n) Sets/gets the colour text will be drawn in for the item. If set to -1, the control's text colour will be used. &nbsp
&nbsp ItemBackColor(n) Sets/gets the back colour for the item. If set to -1, the control's back colour will be used. &nbsp
&nbsp ItemFont(n) Sets/gets the font for the item. If this is not specified, or set to nothing, the control's font will be used. &nbsp
&nbsp ItemIcon(n) Sets/gets the zero-based index of an image within an ImageList to be drawn for the item. In order for this to work, the control's ImageList property must be initialised. Set the property either to a Visual Basic ImageList control, or to a valid ImageList handle. &nbsp
&nbsp ItemIndent(n) Sets/gets the indentation, in pixels, from the left margin which will be left before the item is drawn. &nbsp
&nbsp ItemHeight(n) The height of the item in pixels. If not specified, the default of 20 pixels will be used. &nbsp
&nbsp ItemXAlign(n) Sets/gets the horizontal alignment of the item's text within its boundary. The default is left aligned (eixLeft). &nbsp
&nbsp ItemYAlign(n) Sets/gets the vertical alignment of the item's text within its boundary. The default is top aligned (eixTop). If you set the vertical alignment to centred or vertical, only one line of text will be shown. &nbsp
&nbsp ItemUnderline(n) Whether a ruling will be drawn underneath the item (spans the whole control width regardless of indentation). &nbsp
&nbsp ItemOverline(n) Whether a ruling will be drawn over the item (spans the whole control width regardless of indentation). &nbsp
&nbsp



Here is a sample of the control in this mode:

ODCboLst Default Draw Sample

&nbsp
&nbsp

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

ecdClientDrawOnly
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:

&nbsp
&nbsp Index As Long The ListIndex of the item to draw. &nbsp
&nbsp hDC As Long The hDC to draw the item into. &nbsp
&nbsp bSelected As Boolean Whether the item is selected or not. &nbsp
&nbsp bEnabled As Boolean Whether the item is enabled or not. &nbsp
&nbsp LeftPixels As Long The left-hand corner of the area occupied by the item in pixels. &nbsp
&nbsp TopPixels As Long The top corner of the area occupied by the item in pixels. &nbsp
&nbsp RightPixels As Long The right-hand corner of the area occupied by the item in pixels. &nbsp
&nbsp BottomPixels As Long The bottom corner of the area occupied by the item in pixels. &nbsp
&nbsp hFntOld As Long This is the handle to the font in the control before ODCboLst.OCX selected the appropriate font. &nbsp
&nbsp
Check the client draw sample source for examples of how to use this. This 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 DC if you are more comfortable with VB's drawing methods.

ecdColourPickerWithNames
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:

&nbsp
&nbsp ItemBackColor(n) Sets/gets the colour that will appear in the colour sample box next to each item in the combo box. &nbsp
&nbsp



Here is a sample of the control in this mode:

Colour Picker with Names Sample

ecdColourPickerNoNames
This style is the same as ecdColourPickerWithNames except that it shows colour sample boxes only:

Colour Picker Without Names Sample

ecdSysColourPicker
This style is the same as ecdColourPicker, except that it preinitialises the colours to the system colours:

System Colour Picker

ecdParagraphStyles
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:

Paragraph Styles Mode

ecdFontPicker
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:

Font Picker Mode

&nbsp
&nbsp

Other Additional Properties and Methods
In addition to the owner-draw related properties and methods, the following are also provided:

Sub AddItemAndData(sItem As String, [lIconIndex As Long = -1], [lIndent As Long], [lForeColour As OLE_COLOR = -1], [lBackColour As OLE_COLOR = -1], [lItemData As Long], [lExtraData As Long], [lHeight As Long = -1], [eTextXAlign As EItemXAlign = eixLeft], [eTextYAlign As EItemYAlign = eixTop], [fntThis As StdFont])
This method allows you to add an item but also set the extended properties of each list item at the same time. If you have a lot of items to add, and you are setting one or more extended properties, it will be considerably quicker to use this method than the AddItem method followed by the individual extended properties.

Sub InsertItem(sItem As String, lIndex As Long)
This method is the same as AddItem except it inserts after the index lIndex.

Sub InsertItemAndData(sItem As String, lIndex As Long, [lIconIndex As Long = -1], [lIndent As Long], [lForeColour As OLE_COLOR = -1], [lBackColour As OLE_COLOR = -1], [lItemData As Long], [lExtraData As Long], [lHeight As Long], [eTextXAlign As EItemXAlign = eixLeft], [eTextYAlign As EItemYAlign = eixTop], [fntThis As StdFont])
This method is the same as AddItemAndData except it inserts after the index lIndex.

Property ImageList As Variant
Assigns a VB ImageList control or valid handle to an ImageList to the control. This provides a source for icons under the default drawing scheme.

Property DropDownWidth As Long (combo box styles only)
Allows the drop down size to be changed. Set to > 0 to set the width of the drop down portion of the combo box in pixels, or to
Property ExtendedUI As Boolean (ecsDropDownList Style only)
Set to True if you wish the combo box to drop down in response to the Down Arrow rather than the F4 key.

Function FindItemIndex(sToFind As String, [bExactMatch As Boolean = False]) As Long
Attempts to find the item matching sToFind in the control. If bExactMatch is True, the control will search for an exact match, otherwise it will match the characters in sToFind against the first characters in the list items. Returns the ListIndex of the item if found, -1 otherwise.

Property MaxLength as Long (ecsDropDownCombo Style only)
Sets the maximum number of characters which can be typed into the edit box section of the combo box.

Sub SelectRange(IndexStart As Long, IndexEnd As Long, bState As Boolean) (ecsListBoxMultiSelect and ecsListBoxChecked only)
Sets the selection state for the items from IndexStart to IndexEnd to bState.

Sub ShowDropDown(bState As Boolean) (Combo boxes only)
Drops down a combo when bState is true, closes it when bState is false.

Sub ShowDropDownAtPosition(XPixels As Long, YPixels As Long, [WidthPixels As Long], [HeightPixels As Long]) (Combo boxes only)
Same as ShowDropDown except it only allows the drop down to be shown and additionally allows the drop down to be positioned. Use this to draw the drop down portion of a combo box in response to e.g. clicked a command button.

Additional Events

DropDown (Combo Boxes only)
Raised when the combo is dropped down.

CloseUp (Combo Boxes only)
Raised when the combo is closed.

DrawItem
See description above.

MeasureItem
See description above.

&nbsp
&nbsp


TopBack to top

Source Code - What We're About!Back to Source Code

&nbsp


NOTE: this code has been superceded by the version at the new site.



AboutContributeSend FeedbackPrivacy

Copyright 1998-1999, Steve McMahon ( steve@vbaccelerator.com). All Rights Reserved.
Last updated: 30 May 1999