Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Customizing Common Controls


Dr. Dobb's Journal September 1997: Customizing Common Controls

Putting owner-draw features to work

Jason develops software for MTE Software and can be reached at [email protected].


Sidebar: Creating a Custom Tree Control Using the SDK

By including Windows 95/NT standard GUI components, collectively called "common" controls, Microsoft saved developers the time and trouble of creating lists, buttons, and similar building blocks. Common controls also promote a standard in user-interface design, which ensures a familiar interface for users that helps them understand and use the software more quickly. (For more information on common controls, see "Windows 95 Common Controls," by Vinod Anantharaman, DDJ, May 1995.)

Still, deviations are sometimes necessary. Consequently, a number of Windows common controls -- buttons, list boxes, combo boxes, and header controls -- support the notion of "owner-draw." The owner-draw feature lets you customize existing controls (or create controls from scratch) for specific applications.

For instance, I was recently given the task of creating a user interface that included a control similar to the familiar Windows tree-view control. The only difference was that each item in the tree needed to display formatted text. This would have been straightforward if the Windows tree-view control supported the notion of owner-draw -- but alas, it does not, and I had to create my own. In this article, I'll share the techniques I developed to create this customized tree-view control.

Using Owner-Draw Controls

When creating an owner-drawn control using the standard Windows SDK, you must:

1. Create the control using CreateWindow (or a dialog resource) with the owner-draw style set. Example 1, for instance, might be used to create an owner-drawn button. The parent window that contains the owner-drawn control must also respond to the WM_DRAWITEM message. This message is sent each time the control needs to be drawn.

2. Send a pointer to a DRAWITEMSTRUCT with the WM_DRAWITEM message. This structure includes information necessary for the application to know exactly how and where to draw the control.

Creating an owner-drawn control using C++/MFC is a similar, but better encapsulated process. As with the SDK approach, the control should be created with the owner-draw style set. This means the owner-draw checkbox in the dialog editor should be checked for this control, or the owner-draw style flag should be included with the call to the control's Create() method. To draw the control, however, MFC takes a slightly different approach from the SDK.

Instead of sending a message to the parent, the message is reflected back to the control, which causes the control to call its own DrawItem function. The DrawItem function in an MFC "owner-drawable" control is virtual and should be overridden in a derived class to implement the drawing of the control. This functionality was my goal in creating the CCustomTreeCtrl.

Using CCustomTreeCtrl

The CCustomTreeCtrl class, derived from the CTreeCtrl class, was designed with compatibility in mind. For most uses of the CTreeCtrl class, you should be able to substitute CCustomTreeCtrl without trouble. However, to create an owner-drawn tree control, you must derive a class from the CCustomTreeCtrl. To do this, your class needs to implement only one function -- DrawItem, a virtual function called by CCustomTreeCtrl each time one or more items in the tree need to be redrawn. The DrawItem function implemented in the CCustomTreeCtrl behaves much like the standard Windows tree-view control. Your DrawItem function, however, doesn't need to be so mundane.

DrawItem is a simple function that accepts four parameters:

  • HTREEITEM, which refers to the item in the tree that currently needs to be drawn.
  • A reference to a CDC object, which is used for all drawings in your function.
  • A reference to a CRect object, which describes the dimensions and location of the item to be drawn.
  • A UINT, which includes flags indicating whether the item is to be drawn selected or unselected, with focus or without.

I have included a dialog-based application (available electronically; see "Availability," page 3) that derives a class called CRainbowTreeCtrl from CCustomTreeCtrl. As Figure 1 shows, this class displays the text for its items in the colors of the rainbow. Although this class is not likely to be generally useful, it does provide a good example of how to create your own DrawItem implementation.

Creating CCustomTreeCtrl

I had two goals in mind when designing the CCustomTreeCtrl class:

  • To create a class that facilitates the creation of tree controls with a custom look.
  • To use as much of the functionality of the existing Windows control as possible.

The reasons for the second goal relate to the reasons for the existence of common controls in the first place. By using built-in Windows functionality, I save development time and trouble, while providing a familiar interface for users.

The implementation of the CCustomTreeCtrl is based on the concept of Windows subclassing. The details of subclassing a window are all but completely hidden from the MFC programmer. However, it is important to understand the basics of this technique. (For more information, see the accompanying text box entitled "Creating a Custom Tree Control Using the SDK.")

All windows in Windows have a window procedure that is called for each message that the window receives. This function's response to these messages defines the behavior of the window. Subclassing a window involves replacing the window's original window procedure with a new one. This procedure responds to the messages it cares about, then calls the original window procedure for all other processing. This adds functionality while retaining old behavior that fits your needs. Subclassing is automatic in MFC. You need only worry about creating message handlers for the messages to which you wish to respond.

Before creating the functionality for the "new" tree control, it was important to understand how the existing control works. This required some digging. There were several ways of finding this out, including using the WinSpy++ utility that comes with Visual C++. However, the most revealing approach was to create a simple class, derived from CTreeCtrl, for which I created message handlers for some key messages. The messages I tried were WM_PAINT and WM_LBUTTONDOWN. The code for these handlers did nothing more than call the default handlers in the base class. By placing break points in these handlers and watching the tree control, however, I was able to ascertain when and how the control was redrawn. This was important, because this was the functionality that I wanted to modify. From this I discovered that all drawing for the standard tree-view control is done in response to WM_PAINT messages.

This meant I would need only respond to the WM_PAINT message to create tree controls with a custom look. I would be free to let the existing control handle mouse and keyboard messages. The CCustomTreeCtrl should then respond to user input in nearly the same manner as the standard windows control.

Painting CCustomTreeCtrl

At first glance, the drawing or "painting" of the control seemed like an easy enough task. However, there were some obstacles to overcome.

The most difficult part of handling the WM_PAINT message was making use of the existing control's paint functionality. The standard Windows tree-view control contains some visual functionality (and code) that I did not want to modify. Specifically, I wanted to allow the existing control to manage the drawing of anything to the left of the actual item text -- tree lines, tree expand and collapse buttons, and item images. To make use of this existing code, I had no choice but to call the base class implementation of OnPaint to handle the default drawing of the control. I do this before I draw a single pixel of custom painting.

Calling the default handler for WM_ PAINT created a major obstacle. Unfortunately, when calling the default paint code, there is no way to execute only the portion that you wish to use. This means that along with the lines, buttons, and images, you will also get the item text -- like it or not. This may not seem like a problem at first, because your DrawItem code will effectively cover any item text painted by the default WM_PAINT handler. However, this can cause an unsettling flicker, especially on slower machines.

The cleanest solution was to adjust the invalid rect for the control to only include the portion that I wished to be painted. This seemed to be a simple and efficient way to limit the default functionality. Unfortunately, this solution was unacceptable because of the nonrectangular shape of the default painting I wished to use. However, this solution may be suitable for visually customizing other common controls.

The solution I eventually chose presented a new problem. I decided that the text portion of each tree control item would consist of nothing but spaces. This way, when the default WM_ PAINT handler drew the text for an item, it would be drawing nothing. This solution would have been perfect, except that many custom tree-control implementations will still want to use the text portion of the tree items in their custom drawing.

I solved this by storing the text for each item manually. The text is stored using a CMap object that maps CString objects to HTREEITEM handles. Of course, this approach made it necessary for me to overload any function in CTreeCtrl (including SetItemText, GetItemText, InsertItem, GetItem, and SetItem) that deals with the text of a tree-control item. For each of these functions, I call the base class routines, substituting the manually stored text where appropriate. This approach does have a downside. Since the overloaded functions are not virtual in the CTreeCtrl class, a pointer to a CCustomTreeCtrl object is stored in a CTreeCtrl pointer variable, and the correct functions are not automatically called.

Aside from item text, the WM_PAINT handler draws two other things I didn't want displayed -- the inverted selection bar and focus rect. I avoided both by removing the focus from the control's window before calling the default handler, then restoring it later (if it had focus in the first place). However, the standard Windows tree-view control generates a WM_PAINT message when it loses focus. By removing the focus while the control was repainting itself, I caused Windows to recursively call my OnPaint handler. Since this was undesirable, I created a private Boolean variable named m_bNoPaint, which (if True upon entering OnPaint) returns without repainting the control. I then set this variable to True before removing the focus from the window. This solved the problem of the focus rect and selected item.

The remainder of the OnPaint handler does little more than iterate through the visible items in the control, and call DrawItem for any control whose rect falls within the invalid rect for the control. There is, however, a default feature of the standard Windows tree-view control that needed a minor modification -- the tooltip that automatically appears over a tree-control item.

Tree controls automatically display a tooltip containing the text of an item if the entire item does not fit horizontally in the control's client area. This functionality is not directly relevant to the CCustomTreeCtrl because the tree-control item may be a graphic instead of text. The tooltip was easily suppressed by overloading the virtual handler OnNotify, and immediately returning True without calling the base class handler. This approach was more desirable than using the undocumented tree-control style TVS_NOTOOLTIPS, because a class derived from CCustomTreeCtrl can reenable tooltips by implementing its own OnNotify function.

Creating Other Custom Controls

The techniques I used to create the CCustomTreeCtrl can be applied to the modification of other standard Windows controls. It is important that the existing control handle all of its drawing in response to WM_PAINT messages. If this is the case for the control in question, then it is likely that you will be able to use these techniques to modify the look of the control to your specific needs.

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.