Customizing Common Controls

Common controls are standard Windows 95/NT GUI components that save you time and users trouble. As an example, Jason describes how you can customize a tree-view control.


September 01, 1997
URL:http://drdobbs.com/windows/customizing-common-controls/184410273

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:

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:

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

Dr. Dobb's Journal September 1997: Creating a Custom Tree Control Using the SDK

Creating a Custom Tree Control Using the SDK

By Jason Clark

Dr. Dobb's Journal September 1997

Example 1: Creating an owner-drawn button.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal September 1997: Creating a Custom Tree Control Using the SDK

Creating a Custom Tree Control Using the SDK

By Jason Clark

Dr. Dobb's Journal September 1997

Figure 1: CRainbowTreeCtrl displays the text for its items in the colors of the rainbow.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal September 1997: Creating a Custom Tree Control Using the SDK

Dr. Dobb's Journal September 1997

Creating a Custom Tree Control Using the SDK


The techniques presented here can be applied to an SDK custom control as well. When using the SDK without MFC, however, some special considerations will apply.

Unlike the MFC approach, each SDK custom control will need to be explicitly subclassed from an existing tree-view control. An in-depth discussion on window subclassing is beyond the scope of this article. For more information on subclassing, however, see "Windows 95 Subclassing and Superclassing," by Jeffrey Richter and Jonathan Locke (Dr. Dobb's Sourcebook, March/April 1996).

Your subclassed window procedure will need to respond to the WM_ PAINT and WM_NOTIFY messages in much the same way that the CCustomTreeCtrl does. Much of the code will port fairly easily, but without MFC, you will not have a simple way of storing the strings for each item.

If you are not using C++, you will probably want to send a WM_DRAWITEM message to the window's parent for each item that needs to be drawn. This means that you need to include the information for redrawing the item in a DRAWITEMSTRUCT. This way, your subclassed control will behave like other owner-draw-capable controls.

-- J.C.


Copyright © 1997, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.