Click here to Skip to main content
Click here to Skip to main content

Subclassing and Owner Drawing, the SDK Way

, 14 Aug 2001
Rate this:
Please Sign up or sign in to vote.
An article describing how to use the Win32 SDK to subclass controls, and to give them a custom appearance using owner draw.

Sample Image - OwnrDrwSubCls.gif

Introduction

In spite of the proliferation of object-oriented libraries for Windows, such as the Microsoft Foundation Classes (MFC), programming with nothing more than the Win32 SDK still remains a popular choice when performance and size are issues. However, the terminology used by the SDK is a bit different than that of MFC, and in some cases can be completely opposite. This article was written in an attempt to explain some of these differences, and to provide a few examples.

Under the hood, MFC is really a normal Win32 SDK application, even if it does not appear to be one. The features that Microsoft has added to MFC over the years to make programming for Windows simpler, has in some cases obscured the underlying principles and even renamed these concepts from what SDK programmers known them as. I myself had this problem recently when reading several of the articles on CodeProject about subclassing controls.

MFC allows you to create a C++ class, which is derived from a base class - subclassing in the C++ definition of the word - to change the behavior and the visual appearance of the control described by the base class. This is very different from the SDK meaning of "subclassing," which is used only to change the behavior, but not the appearance.

As an example: If you look at Chris Maunder's subclassing article, you will notice that he changes one of the style bits in his subclassed button to make it owner-drawn, and then overrides the OnDrawItem virtual function in his derived class. In SDK programming, this is simply implementing an owner drawn control. Hidden by MFC is the fact that the WM_DRAWITEM message is sent to the parent of the control, and the parent is responsible for determining which control is to be drawn, its state, and then drawing it. This is not subclassing; Rather, this is owner-drawing.

On the other hand, if you look at Daniel Kopitchinski's message handling article, the behavior of an edit control is modified by preventing certain keys from getting to the control. Considering that the control does not send every keystroke to its parent in a WM_CHAR message, how can the author be overriding this message handler? This is subclassing in the SDK sense: By changing the function that handles messages for the edit control, the author was able to filter what the control actually received.

Samples And Explanations

What follows is code fragments, taken from the Zip file included with this article that should illustrate both of these concepts from the SDK point of view.

Subclassing, The SDK Way

First, we will examine "true" subclassing. The code is adapted directly from Daniel's, so it should look familiar. When you run the program, you will find that only numbers are recognized by the edit control. The important thing to notice in the dialog callback is the code in the WM_INITDIALOG handler:

g_oldEditProc = (WNDPROC)SetWindowLong(
    GetDlgItem( window, IDC_EDIT ), GWL_WNDPROC, long( EditProc ) );

This is the function responsible for subclassing. The calling format for SetWindowLong is:

DWORD SetWindowLong( HWND  handleOfWindowToSubclass,
                     int   indexOfPropertyToChange,
                     DWORD newValueOfProperty );

The returned value from this function is the old value of the property. The GetDlgItem call retrieves the window handle of a control contained within a dialog box. The constant, GWL_WNDPROC is the index of the WndProc property for the window. Other constants allow the developer to change the style of the window, and more. When each of these building blocks are put together, the result is that the edit control's windows procedure is replaced with a new one, and the old one is saved in a global variable called, g_oldEditproc.

The new edit control callback looks like this:

int __stdcall EditProc( HWND window, UINT message, WPARAM wparam, LPARAM lparam )
{
    //  Keystrokes are intercepted before the edit control sees them.
    if( message == WM_CHAR )
    {
        // Only allow numbers to reach the control.
        if( IsCharNum( char( wparam ) ) )
        {
            return( CallWindowProc( g_oldproc, window, message, wparam, lparam ) );
        }

        // All other characters are dropped.
        else
        {
            return(0);
        }
    }

    // All other messages get through without any modification.
    else
    {
        return( CallWindowProc( g_oldproc, window, message, wparam, lparam ) );
    }
}

First, note that Windows does not provide an IsCharNum function. This is an inline function defined in the source code in the Zip file for convenience.

Just like Daniel's code, the callback traps all characters sent to the edit control, and drops everything except for numbers. This could be the basis for a template that only allows telephone numbers, prices, or social security numbers, for example.

One of the messages that cannot be properly trapped this way, is WM_PAINT. You are welcome to try it, by modifying the subclassing example. An easy test is to paint the background of the control gray. The code to add to EditProc would look like:

if( message == WM_PAINT )
{
    HDC dc = GetDC( window );
    RECT rect; GetWindowRect( window, &rect );
    FillRect( dc, &rect, HBRUSH( GetStockObject( GRAY_BRUSH ) ) );
    ReleaseDC( window, dc );
    return( 0 );
}

All this will do is cause the control to not be painted at all. Subclassing is obviously not going to help in customizing the appearance of the control. That brings this tutorial to its second topic: Allowing a control to be drawn by its parent.

Owner Drawing

First, notice that in the WM_INITDIALOG handler, the same SetWindowLong magic is used to add the BS_OWNERDRAW bit to the control's style. Although it can be set in the resource editor by checking the "Owner Draw" option, this makes it a little cleaner, and allows the code to be used on other button controls without modifying the resource file:

SetWindowLong( GetDlgItem( window, IDC_BUTTON ), GWL_STYLE, 
        GetWindowLong( GetDlgItem( window, IDC_BUTTON ), GWL_STYLE ) | 
        BS_OWNERDRAW );

When any owner-drawn controls need to be updated, Windows sends a WM_DRAWITEM message to the parent window of the control. The parent has the responsibility of determining which child needs to be redrawn, and then performing some appropriate action to ensure that it happens. If the control is not drawn, then it will appear to be invisible. For the purpose of this example, the message handler ensures that only the one button is being drawn, and then dispatches control to the DrawItem function:

if( ( ( DRAWITEMSTRUCT* ) lparam )->hwndItem == GetDlgItem( window, IDC_BUTTON ) )
{
    DrawItem( ( DRAWITEMSTRUCT* ) lparam );
    return( TRUE );
}

Instead of doing the usual "hot tracking" demonstration, in which the background color of the button changes when the mouse is over it, the example program completely changes the appearance of the button to be rounded, instead of the boring standard rectangle. DrawItem calls the DrawRoundedButton function with some flags so the button looks right, given its current state, and then draws the button's text in the center. If the button is disabled, the text is gray. If the button is pressed (selected), then the text is offset by one pixel to give it the impression of depth.

All of the drawing magic is contained in DrawRoundedButton. This function looks at the flags passed by DrawItem to determine if the button is pressed or not, and focused or not, and then draws a series of rounded rectangles nested inside of each other to get the appropriate look. If this code was used in an actual production piece of software, a compatible device context should be created where all of the drawing is done, and then copy the final image to the button's screen device context. This would eliminate the flicker, and be more efficient. But for the purposes of this example, it is interested to set a breakpoint at the beginning of the drawing code, and see how the button is built.

Also of importance in an SDK program, is deleting any GDI objects that are created. If this is not done, the program would suffer from memory leaks that would keep building until memory was exhausted:

pen = CreatePen( PS_NULL, 1, 0 );
        ...
DeleteObject( pen );

Conclusion

Hopefully the sample code and the explanations in this tutorial will clear up some confusion, and help other developers understand the difference between subclassing and owner drawing. Also, knowing what goes on behind all of the "MFC magic" can help to understand MFC a little better.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Paul A. Howes
Software Developer (Senior)
United States United States
Mr. Howes has been programming since he was first introduced to the Apple II+ at the tender age of ten. After briefly dabbling with circuit design as a combined CS/EE major, he came to his senses and completed a pure CS degree at RIT
 
All these years later, the programming bug still bites him each day and causes him to go to work, where he writes software for a living. Now he is even learning Cocoa for the Mac!
 
In his copious amounts of "spare" time, Mr. Howes is learning the art of cabinet-making, flies radio-controlled helicopters and airplanes, and mountain cycles. Next summer he would like to learn how to kayak.
 
Linked In Page: http://www.linkedin.com/pub/3/54a/578

Comments and Discussions

 
Generalsource zip download not working Pinmemberbimal jain1-Jul-08 5:24 
GeneralRe: source zip download not working PinmemberPaul A. Howes1-Jul-08 5:27 
GeneralA bug when handle WM_PAINT PinmemberProgmanEx11-Dec-06 16:32 
GeneralRe: A bug when handle WM_PAINT PinmemberPaul A. Howes10-Feb-07 4:26 
QuestionHow can I prevent my own window from being captured(text)? PinmemberJewel Nandy21-Nov-05 3:44 
AnswerRe: How can I prevent my own window from being captured(text)? PinmemberPaul A. Howes21-Nov-05 10:18 
AnswerRe: How can I prevent my own window from being captured(text)? PinmemberPaul A. Howes22-Nov-05 17:21 
AnswerRe: How can I prevent my own window from being captured(text)? PinmemberProgmanEx11-Dec-06 16:29 
GeneralThe name of th esubclass PinmemberAlex Evans19-Feb-05 19:24 
GeneralGood Article - one small bug tho' PinmemberGoldenBalls3-Dec-03 7:27 
Thanks for the article, it's very informative and helped clear up a few owner draw issues for me.
 
There is one small bug with it tho' that I'm struggle with. When you set a button's style to BS_OWNERDRAW you no longer get the ODS_DEFAULT state for the button in the DRAWITEMSTRUCT. This seems to be because you cannot set default buttons to be owner draw (try to do it in the dialog editor and it greys out the default button option if you select owner draw.
 
In your .exe file everything seems to work OK because your line for drawing the black default border reads:
 
if (btnDefault || focused)
 
the btnDefault is never true but focused is. This is ok when all you have is buttons on the dialog, but if you have an application where there are other controls on the dialog when the user moves focus to another control the focused flag is set to false and your default button loses it's black border.
 
Hope this makes sense...
Do you have any ideas on how to get round this? Is there another way of detecting if a button is a default button?
 
Thanks
GeneralHELP! Owner-Draw Edit Class Pinsussanonymous17-Nov-03 18:58 
GeneralRe: HELP! Owner-Draw Edit Class PinmemberPaul A. Howes18-Nov-03 1:34 
GeneralRe: HELP! Owner-Draw Edit Class Pinsussanonymous18-Nov-03 6:11 
GeneralRe: HELP! Owner-Draw Edit Class PinmemberPaul A. Howes18-Nov-03 7:23 
GeneralRe: HELP! Owner-Draw Edit Class Pinsussanonymous18-Nov-03 10:20 
GeneralRe: HELP! Owner-Draw Edit Class PinsussAlec Turner8-Jan-04 1:50 
GeneralRe: HELP! Owner-Draw Edit Class PinsussAlec Turner8-Jan-04 2:11 
GeneralRe: HELP! Owner-Draw Edit Class PinmemberAndrew Phillips3-Nov-05 14:48 
GeneralSubclassing CBitmapButton in SDK program. PinmemberVikas Mishra22-Sep-03 3:59 
QuestionHow can I make a Dialog has a round rect PinmemberMinhHai10-Jun-03 23:51 
QuestionHow can I make a Dialog has a round rect PinmemberMinhHai10-Jun-03 23:51 
GeneralGive us something new man. PinsussAnonymous13-Aug-02 4:07 
GeneralRe: Give us something new man. PinmemberPaul A. Howes13-Aug-02 8:34 
GeneralRe: Give us something new man. PinmemberDavid Nash9-May-07 23:01 
GeneralNice article, and one thing... PinmemberSiuming28-May-02 18:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411028.1 | Last Updated 15 Aug 2001
Article Copyright 2001 by Paul A. Howes
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid