|
Owner Draw Combo and List Boxes Version 2.1
Updated! 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)
  |
Source Code Note
|
  |
  |
|
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.
|
  |
  |
Before you Begin
|
  |
  |
|
These projects require the SSubTmr.DLL component. Make sure you have loaded and registered this before trying any project.
|
  |
  |
|
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.
|
  |
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
    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
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:
|
  |
  |
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.
|
  |
  |
ItemBackColor(n)
|
Sets/gets the back colour for the item. If set to -1, the control's back colour will be used.
|
  |
  |
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.
|
  |
  |
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.
|
  |
  |
ItemIndent(n)
|
Sets/gets the indentation, in pixels, from the left margin which will be left before the item is drawn.
|
  |
  |
ItemHeight(n)
|
The height of the item in pixels. If not specified, the default of 20 pixels will be used.
|
  |
  |
ItemXAlign(n)
|
Sets/gets the horizontal alignment of the item's text within its boundary. The default is left aligned (eixLeft).
|
  |
  |
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.
|
  |
  |
ItemUnderline(n)
|
Whether a ruling will be drawn underneath the item (spans the whole control width regardless of indentation).
|
  |
  |
ItemOverline(n)
|
Whether a ruling will be drawn over the item (spans the whole control width regardless of indentation).
|
  |
  |
Here is a sample of the control in this mode:
ODCboLst Default Draw Sample
|
  |
  |
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:
|
  |
  |
Index As Long
|
The ListIndex of the item to draw.
|
  |
  |
hDC As Long
|
The hDC to draw the item into.
|
  |
  |
bSelected As Boolean
|
Whether the item is selected or not.
|
  |
  |
bEnabled As Boolean
|
Whether the item is enabled or not.
|
  |
  |
LeftPixels As Long
|
The left-hand corner of the area occupied by the item in pixels.
|
  |
  |
TopPixels As Long
|
The top corner of the area occupied by the item in pixels.
|
  |
  |
RightPixels As Long
|
The right-hand corner of the area occupied by the item in pixels.
|
  |
  |
BottomPixels As Long
|
The bottom corner of the area occupied by the item in pixels.
|
  |
  |
hFntOld As Long
|
This is the handle to the font in the control before ODCboLst.OCX selected the appropriate font.
|
  |
  |
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:
|
  |
  |
ItemBackColor(n)
|
Sets/gets the colour that will appear in the colour sample box next to each item in the combo box.
|
  |
  |
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
|
  |
  |
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.
|
  |
  |
Back to top
Back to Source Code
|
  |