Click here to Skip to main content
15,870,324 members
Articles / Desktop Programming / MFC
Article

Subclassing and Owner Drawing, the SDK Way

Rate me:
Please Sign up or sign in to vote.
4.88/5 (17 votes)
14 Aug 20016 min read 201.2K   4.6K   72   29
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


Written By
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 Pin
bimal jain1-Jul-08 4:24
bimal jain1-Jul-08 4:24 
This is the exact article i was lookin for. only prolem is i m not able to see the full source.

I m new to this web site. I m able to download the project demo but unable to download source zip file
GeneralRe: source zip download not working Pin
Paul A. Howes1-Jul-08 4:27
Paul A. Howes1-Jul-08 4:27 
GeneralA bug when handle WM_PAINT Pin
ProgmanEx11-Dec-06 15:32
ProgmanEx11-Dec-06 15:32 
GeneralRe: A bug when handle WM_PAINT Pin
Paul A. Howes10-Feb-07 3:26
Paul A. Howes10-Feb-07 3:26 
QuestionHow can I prevent my own window from being captured(text)? Pin
Jewel Nandy21-Nov-05 2:44
Jewel Nandy21-Nov-05 2:44 
AnswerRe: How can I prevent my own window from being captured(text)? Pin
Paul A. Howes21-Nov-05 9:18
Paul A. Howes21-Nov-05 9:18 
AnswerRe: How can I prevent my own window from being captured(text)? Pin
Paul A. Howes22-Nov-05 16:21
Paul A. Howes22-Nov-05 16:21 
AnswerRe: How can I prevent my own window from being captured(text)? Pin
ProgmanEx11-Dec-06 15:29
ProgmanEx11-Dec-06 15:29 
GeneralThe name of th esubclass Pin
Alex Evans19-Feb-05 18:24
Alex Evans19-Feb-05 18:24 
GeneralGood Article - one small bug tho' Pin
GoldenBalls3-Dec-03 6:27
GoldenBalls3-Dec-03 6:27 
GeneralHELP! Owner-Draw Edit Class Pin
Anonymous17-Nov-03 17:58
Anonymous17-Nov-03 17:58 
GeneralRe: HELP! Owner-Draw Edit Class Pin
Paul A. Howes18-Nov-03 0:34
Paul A. Howes18-Nov-03 0:34 
GeneralRe: HELP! Owner-Draw Edit Class Pin
Anonymous18-Nov-03 5:11
Anonymous18-Nov-03 5:11 
GeneralRe: HELP! Owner-Draw Edit Class Pin
Paul A. Howes18-Nov-03 6:23
Paul A. Howes18-Nov-03 6:23 
GeneralRe: HELP! Owner-Draw Edit Class Pin
Anonymous18-Nov-03 9:20
Anonymous18-Nov-03 9:20 
GeneralRe: HELP! Owner-Draw Edit Class Pin
alecturner8-Jan-04 0:50
alecturner8-Jan-04 0:50 
GeneralRe: HELP! Owner-Draw Edit Class Pin
alecturner8-Jan-04 1:11
alecturner8-Jan-04 1:11 
GeneralRe: HELP! Owner-Draw Edit Class Pin
Andrew Phillips3-Nov-05 13:48
Andrew Phillips3-Nov-05 13:48 
GeneralSubclassing CBitmapButton in SDK program. Pin
Vikas Mishra22-Sep-03 2:59
Vikas Mishra22-Sep-03 2:59 
QuestionHow can I make a Dialog has a round rect Pin
MinhHai10-Jun-03 22:51
MinhHai10-Jun-03 22:51 
QuestionHow can I make a Dialog has a round rect Pin
MinhHai10-Jun-03 22:51
MinhHai10-Jun-03 22:51 
GeneralGive us something new man. Pin
Anonymous13-Aug-02 3:07
Anonymous13-Aug-02 3:07 
GeneralRe: Give us something new man. Pin
Paul A. Howes13-Aug-02 7:34
Paul A. Howes13-Aug-02 7:34 
GeneralRe: Give us something new man. Pin
David Nash9-May-07 22:01
David Nash9-May-07 22:01 
GeneralNice article, and one thing... Pin
nilaysoft28-May-02 17:45
nilaysoft28-May-02 17:45 

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

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