Kyle Marsh
Microsoft Developer Network Technology Group
Created: June 23, 1993
Most developers consider window classes a necessary evil. They grab the RegisterClass function from GENERIC or from another starter application, fiddle with the values a little, and then move on. In this article, we will explore the features of window classes and explain how they can benefit an application.
Specifically, we will discuss:
A window class contains information that determines how a window looks and behaves. Every window belongs to a window class. Before you create a window, you must register a class for the window. You may register a window class for exclusive use by your application or for general use by all applications in the system. The Microsoft® Windows™ operating system also registers classes that can be used by all applications in the system.
Windows provides for three types of window classes:
When Windows starts, it creates the system global classes for all applications. These classes contain the following familiar standard controls:
The system global classes also include some less familiar classes:
All Windows-based applications can use the system global classes. An application cannot add classes to, or remove classes from, the system global classes. An application can globally subclass a system class, but subclassing is not recommended because it affects all applications in the system. System global classes are destroyed when Windows shuts down.
Application global classes are registered by a dynamic-link library (DLL) or an application, and are available to all applications. These classes are frequently used in the creation of custom controls. When a DLL implements a custom control, it registers an application global class that can be used by all of its client applications. When an application or DLL that registers an application global class exits or is unloaded, the class is automatically removed. Application global classes are registered with the CS_GLOBALCLASS style.
The most frequently used window classes are application local classes, which are available only to the application or DLL that registers them. Application local classes are registered without the CS_GLOBALCLASS style.
Let's look at the information a class stores. Here is the window class structure:
typedef struct tagWNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASS;
where:
Field | Description |
style | Defines features such as window alignment, display context allocation, and double-click processing. |
lpfnWndProc | Points to the window procedure that processes messages for the windows in the class. |
cbClsExtra | Specifies the amount of extra memory, in bytes, that Windows should reserve for the class. |
cbWndExtra | Specifies the amount of extra memory, in bytes, that Windows should reserve for each window in the class. |
hInstance | Identifies the application or DLL that registered the class. |
hIcon | Defines the icon Windows displays when a window belonging to the class is minimized. |
hCursor | Defines the cursor for windows belonging to the class. |
hbrBackground | Defines the color and pattern Windows uses to fill the client area when the application opens or paints a window belonging to the class. |
lpszMenuName | Specifies the default menu for windows in the class that do not explicitly define menus. |
lpszClassName | Identifies the name of the class. |
These fields are discussed at length in the following sections.
The style field in the WNDCLASS structure assigns class styles. These styles determine the behavior of windows created from that class. You can use any combination of the values described in the following sections.
When you use these styles, Windows aligns the windows belonging to the class so that either the client area of the window (CS_BYTEALIGNCLIENT) or the entire window (CS_BYTEALIGNWINDOW) is aligned on byte boundaries. That is, Windows adjusts the horizontal position of the window so that either the client area's or the entire window's left coordinate is evenly divisible by 8. For example, let's see where Windows would place a window with a sizing frame that is 4 pixels wide:
Original window location | Placement with CS_BYTEALIGNWINDOW | Placement with CS_BYTEALIGNCLIENT |
0,y | 0,y | 4,y |
1,y | 0,y | 4,y |
2,y | 0,y | 4,y |
3,y | 0,y | 4,y |
4,y | 8,y | 4,y |
5,y | 8,y | 4,y |
6,y | 8,y | 4,y |
7,y | 8,y | 4,y |
8,y | 8,y | 12,y |
9,y | 8,y | 12,y |
10,y | 8,y | 12,y |
11,y | 8,y | 12,y |
12,y | 16,y | 12,y |
13,y | 16,y | 12,y |
14,y | 16,y | 12,y |
15,y | 16,y | 12,y |
16,y | 16,y | 20,y |
The CS_BYTEALIGNCLIENT and CS_BYTEALIGNWINDOW styles have no effect under two circumstances:
"For a window with the WS_OVERLAPPED or WS_THICKFRAME style, the DefWindowProc function handles a WM_WINDOWPOSCHANGING message by sending a WM_GETMINMAXINFO message to the window. This is done to validate the new size and position of the window and to enforce the CS_BYTEALIGNCLIENT and CS_BYTEALIGN [sic] client styles." (Italics are mine.)
When DefWindowProc receives a WM_WINDOWPOSCHANGING message, it does send the window a WM_GETMINMAXINFO message, but does not enforce the byte alignment styles. To ensure byte alignment with the CS_BYTEALIGNCLIENT and CS_BYTEALIGNWINDOW styles, an application must adjust its horizontal position whenever:
CS_BYTEALIGNCLIENT and CS_BYTEALIGNWINDOW were once instrumental in getting the best performance for an application, especially in versions of Windows before 3.0. In those days, the system fonts all had fixed widths, and Windows could optimize text display on byte-aligned windows. This benefit vanished with Windows version 3.0.
An application that uses the BitBlt function to copy pixels from one window to another window or from a source rectangle in a window to a target rectangle in the same window can set the CS_BYTEALIGNCLIENT style for better performance. By aligning the client area on a byte boundary, the application can ensure that BitBlt operations occur on byte-aligned rectangles. BitBlt operations on byte-aligned rectangles are considerably faster than BitBlt operations on rectangles that are not byte aligned. However, byte alignment affects only the left side of a window. To take maximum advantage of byte alignment, the application must also byte-align the width of the window.
As the number of displays and video cards that are capable of 256 or more colors increases, the need for byte alignment diminishes. 256-color display drivers are already byte aligned. In fact, some 16-color drivers are byte aligned as well. One of my computers has an ATI Graphics Ultra Pro display driver. While researching the byte-alignment style bits, I set my driver to 16 colors and expected to see the position of my window change depending on the style bits I used. Much to my surprise, the alignment bits had no effect. After much bewilderment, I discovered that the ATI video driver uses 8 bits per pixel for its 16-color mode. Switching to the standard VGA 16-color driver produced the window-alignment positioning I expected. I don't know how common using 8 bits per pixel for 16-color mode is, but this is an indication that the byte-alignment styles are losing their importance.
If you use both styles, CS_BYTEALIGNWINDOW overrides CS_BYTEALIGNCLIENT. The dialog class has the CS_BYTEALIGNWINDOW style.
These styles determine the default display context for the windows created from a class.
To bypass the default display context for a window, applications can use the GetDCEx function and specify a flag of DCX_CACHE. This function overrides the CS_OWNDC and CS_CLASSDC styles and returns a common display context.
The ScrollWindow and ScrollWindowEx functions handle display contexts in different ways:
The CS_DBLCLKS style causes Windows to detect a double-click (the user clicking the left mouse button twice in quick succession) for an application. Here is how Windows responds to a double-click event:
Other messages may be mixed within these message sequences, so an application should not rely on the messages being contiguous.
An application can detect a double-click event without using the CS_DBLCLKS style. See Dr. GUI's "Simulating Mouse Button Clicks" article on the Microsoft Developer Network CD (Technical Articles, Ask Dr. GUI, Ask Dr. GUI #5).
All standard Windows controls have the CS_DBLCLKS style except for static controls, the dialog class, and the desktop class. Custom controls must have this style in order to work with the Dialog Editor.
CS_GLOBALCLASS is the only style that applies only to the window class and not to individual windows created from the class. Windows stores classes with the CS_GLOBALCLASS style as application global classes. These classes are available to all applications and DLLs; they are not available exclusively to the application or DLL that registered the class. An application global class is most commonly used for custom controls that are implemented in a DLL. The class is destroyed when the application or DLL that created the class exits or is unloaded, or when the UnregisterClass function is called. All windows created from an application global class must be closed before the application that registered the class exits (this happens automatically for DLLs).
The CS_HREDRAW style causes a window to be completely redrawn whenever its horizontal size (width) changes. The CS_VREDRAW style causes a window to be completely redrawn whenever its vertical size (height) changes. Buttons and scroll bars have these styles.
A window that belongs to a class with the CS_NOCLOSE style does not have the Close command in its System menu. In Windows version 3.0, multiple-document interface (MDI) child windows ignore the CS_NOCLOSE style. This problem was fixed in Windows version 3.1.
Menus, dialog boxes, and combo list boxes have the CS_SAVEBITS style. When you use this style for a window, Windows saves a bitmap copy of the screen image that the window obscures. First, Windows asks the display driver to save the bits. If the display driver has enough memory, it saves the bits for Windows. If the display driver does not have enough memory, Window saves the bits itself as a bitmap in global memory and also uses some of User's local heap for housekeeping structures for each window. When the application removes the window, Windows can restore the screen image quickly by using the stored bits.
The effectiveness of the CS_SAVEBITS style can be difficult to gauge. CS_SAVEBITS improves performance for temporary windows such as menus and dialog boxes. However, a significant amount of overhead is involved in storing the bits, especially if Windows stores the bits instead of the display driver. The benefit to using CS_SAVEBITS really depends on what happens to the area under the window. If the area is complex and requires a lot of effort to redraw, storing the bits is probably easier than redrawing the screen. On the other hand, if the area under the window can be redrawn quickly or changes a lot while it is obscured, the effort to save the bits can hurt overall performance.
The lpfnWndProc field in the WNDCLASS structure contains the address of the window procedure for all windows belonging to the class. Windows sends all messages pertaining to windows created from the class to this procedure. The window procedure provides the functionality for these windows. An application can use the SetClassLong function to change the window procedure address for all classes. This process is called subclassing (more specifically, global subclassing). When an application changes the address, all windows created after the SetClassLong call use the new window procedure address. Windows created before the SetClassLong call continue to use the original window procedure, and are unaffected by the global subclass.
When an application or DLL subclasses a window or set of windows, it must export the new window procedure in its module definition file. An application must use MakeProcInstance to get the procedure address and pass it to SetClassLong. An application can also use compiler switches on newer compilers (for example, the C/C++ version 7.0 or Visual C++ version 1.0 compiler) to export window procedures without calling MakeProcInstance.
The cbClsExtra and cbWndExtra fields in the WNDCLASS structure specify the number of extra bytes Windows will allocate for each class (cbClsExtra) and for each instance of a window from the class (cbWndExtra). These fields must be set to zero if an application does not require extra class or window bytes. If you accidentally use an unreasonably large value for one of these fields, Windows uses that value to allocate extra bytes. I suggest that you use no more than 4 extra bytes per class or window. If you use a higher value, Windows will allocate the extra bytes from User's heap, and will thus run out of system resources quickly. If your application needs more space, you should allocate a block from either the local heap or the global heap, and then store a pointer or handle to the block in the extra bytes. For example, the toolbar sample uses the following code to store the data for each toolbar instance:
#ifdef WIN32
#define SETWINDOWPOINTER(hwnd, name, p) SetWindowLong(hwnd, 0, (WORD)p)
#define GETWINDOWPOINTER(hwnd, name) ((name)GetWindowLong(hwnd, 0))
#define ALLOCWINDOWPOINTER(name, size) ((name)malloc(size))
#define FREEWINDOWPOINTER(p) free(p)
#else
#define SETWINDOWPOINTER(hwnd, name, p) SetWindowWord(hwnd, 0, (WORD)p)
#define GETWINDOWPOINTER(hwnd, name) ((name)GetWindowWord(hwnd, 0))
#define ALLOCWINDOWPOINTER(name, size) ((name)LocalAlloc(LPTR, size))
#define FREEWINDOWPOINTER(p) LocalFree((HLOCAL)p)
#endif
LRESULT CALLBACK ToolbarWndProc(HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam)
{
BOOL fSameButton;
PTBBUTTON ptbButton;
PTBHDR pTBHdr;
int iPos;
BYTE fsState;
pTBHdr = GETWINDOWPOINTER(hWnd, PTBHDR);
switch (wMsg) {
case WM_CREATE:
.
.
.
// Create the state data for this toolbar.
pTBHdr = ALLOCWINDOWPOINTER(PTBHDR, sizeof(TBHDR)-sizeof(TBBUTTON));
if (!pTBHdr)
return -1;
// The structure is initialized to all NULL when created.
pTBHdr->hwndCommand = lpcs->hwndParent;
SETWINDOWPOINTER(hWnd, PTBHDR, pTBHdr);
break;
case WM_DESTROY:
if (pTBHdr) {
.
.
.
FREEWINDOWPOINTER(pTBHdr);
SETWINDOWPOINTER(hWnd, PTBHDR, 0);
}
.
.
.
break;
case WM_PAINT:
ToolbarPaint(hWnd, pTBHdr);
break;
.
.
.
}
If an application registers a dialog box created with the CLASS statement, it must set cbWndExtra to DLGWINDOWEXTRA—the Dialog Manager requires these extra bytes.
The hInstance field in the WNDCLASS structure identifies the module for the class. This field must be an instance handle and must not be NULL.
The hIcon field in the WNDCLASS structure identifies the icon for the class. An application generally uses LoadIcon to retrieve a handle either to a standard Windows icon (such as IDI_APPLICATION) or to a custom icon. If hIcon is set to NULL, the application draws its own icon when Windows sends the application a WM_ICONERASEBKGND message.
The hCursor field in the WNDCLASS structure specifies the cursor for all windows belonging to the class. When you use this field, Windows changes the system cursor to the class cursor whenever the system cursor moves into a window from the class. An application generally uses the LoadCursor function to load either a standard system cursor (normally IDC_ARROW) or a custom cursor for the application. Applications can change cursors whenever required by calling the SetCursor function. If the class is not assigned a cursor (that is, hCursor is set to NULL), the application must set the cursor whenever the system cursor moves into the window.
The hbrBackground field in the WNDCLASS structure identifies the class background brush. You can specify either a handle to the physical brush to be used for painting the background or a color value. If you assign a color value, you must use one of the standard system colors listed below plus 1. (For example, COLOR_BACKGROUND + 1 specifies the system background color.)
COLOR_ACTIVEBORDER | COLOR_HIGHLIGHTTEXT |
COLOR_ACTIVECAPTION | COLOR_INACTIVEBORDER |
COLOR_APPWORKSPACE | COLOR_INACTIVECAPTION |
COLOR_BACKGROUND | COLOR_INACTIVECAPTIONTEXT |
COLOR_BTNFACE | COLOR_MENU |
COLOR_BTNSHADOW | COLOR_MENUTEXT |
COLOR_BTNTEXT | COLOR_SCROLLBAR |
COLOR_CAPTIONTEXT | COLOR_WINDOW |
COLOR_GRAYTEXT | COLOR_WINDOWFRAME |
COLOR_HIGHLIGHT | COLOR_WINDOWTEXT |
If you assign a color value, you must cast it to an HBRUSH type:
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
When the hbrBackground field is set to NULL, the application must paint its own background whenever Windows sends it a WM_PAINT message. The application can determine when the background needs painting by processing the WM_ERASEBKGND message or by testing the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.
The lpszMenuName field in the WNDCLASS structure identifies the menu for the class. The class menu is added to each window created from the class unless another menu is specified when the window is created with CreateWindow or CreateWindowEx. The lpszMenuName field points to a null-terminated string that specifies the resource name of the class menu (as the name appears in the resource file). If an integer is used to identify the menu, the application can use the MAKEINTRESOURCE macro. If lpszMenuName is NULL, windows belonging to this class have no default menu.
The lpszClassName field in the WNDCLASS structure identifies the class name. Class names must be unique within each class type for an application. Thus, all local classes within an application must have unique names, but two applications may have local classes with the same name. For example, two applications can have separate "MainWnd" classes. Global class names must be unique across global classes and system classes. For example, an application can register a local class with the name "Edit", but cannot create a global class with that name.
When an application creates a window with a specified class, Windows uses the following procedure to find the class:
Windows uses this procedure for all windows created by the application, including windows created by Windows on the application's behalf, such as dialog boxes. It is possible, then, to override system global classes without affecting other applications. (I will discuss this in the "Local Superclassing" section later in this article.)
Once you register a class, there is usually little you need to do with it except create windows from the class. However, you may want to access class information, subclass the class, or superclass the class, as described in the following sections.
If you want to examine or change the information for a class, you may use the following functions:
nClassExtra = GetClassWord(hwnd, GCW_CBCLSEXTRA );
The term subclassing describes the process of substituting one window procedure for another. Instance subclassing (or subclassing a window) uses the SetWindowLong function to change the window procedure for a particular window instance. Global subclassing (or subclassing a class) uses the SetClassLong function to substitute a new window procedure for the window procedure in a class.
Superclassing involves creating a new class that uses the window procedure of an existing class for basic functionality.
For more information on subclassing and superclassing, see the "Safe Subclassing" article on the Microsoft Developer Network CD (Technical Articles, Windows Articles, Window Manager Articles).
You can use superclassing to effectively subclass all windows in a global class for an application. (This aspect of superclassing is not discussed in the "Safe Subclassing" article.) For example, if an application needs to subclass all of the list boxes it creates, it must call SetWindowLong after creating each list box. Determining where an application creates a list box can involve significant code and effort. Moreover, if the need to subclass the list boxes is eliminated, all the subclassing code must be ripped out.
Another way to accomplish the same objective is to superclass the list box class. Normally, superclassing registers a class with a new name so that the application can create windows from either the new superclass or the original class. In local superclassing, however, the application registers a new local class with the original class name ("Listbox"), which is actually a system class name. In this case, Windows searches local classes for "Listbox" before searching global or system classes. As a result, Windows creates all list boxes for the application from the local class, effectively subclassing all list boxes for the application. The following code illustrates local superclassing for an edit control:
WNDPROC lpfnEditClassProc = NULL;
LRESULT CALLBACK LocalEdit_WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
if (msg == WM_CHAR ) {
MessageBeep(-1);
}
return CallWindowProc(lpfnEditClassProc, hwnd, msg, wParam, lParam);
}
BOOL CreateLocalEditClass()
{
WNDCLASS wc;
if ( lpfnEditClassProc == NULL ) {
GetClassInfo(NULL, "Edit", &wc);
lpfnEditClassProc = (WNDPROC) wc.lpfnWndProc;
wc.lpfnWndProc = LocalEdit_WndProc;
wc.lpszClassName = "Edit";
wc.hInstance = _hInstance;
if (!RegisterClass(&wc))
return FALSE;
return TRUE;
}
}
void DeleteLocalEditClass()
{
if ( lpfnEditClassProc != NULL ) {
UnregisterClass("Edit", _hInstance);
lpfnEditClassProc = NULL;
}
}
Note that none of the style bits needed changing because the system classes do not have the CS_GLOBALCLASS style set. Windows manages these classes differently from classes registered by other applications. If an application locally superclasses an application global class, the CS_GLOBALCLASS style must be removed.
The edit control represents a special case for local superclassing. Edit controls can be created with a different instance handle that is passed to CreateWindow or CreateWindowEx. Individual instance handles allow each edit control to have a separate local heap, so it can accommodate the maximum number of characters. If you use a different instance handle for an edit control, Windows creates the control from the system class—not from the local superclass. By default, a dialog box uses a different instance handle for edit controls. The dialog box must use the DS_LOCALEDIT dialog style as follows to superclass its edit controls locally:
IDD_LOCAL DIALOG DISCARDABLE 0, 0, 241, 108
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
WS_CAPTION | WS_SYSMENU | DS_LOCALEDIT
CAPTION "Local SuperClassed Controls"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,185,6,50,14
PUSHBUTTON "Cancel",IDCANCEL,185,23,50,14
EDITTEXT IDC_EDIT1,65,5,115,14,ES_AUTOHSCROLL
LTEXT "Numbers",IDC_STATIC,30,5,30,8
EDITTEXT IDC_EDIT2,65,31,115,14,ES_AUTOHSCROLL
LTEXT "Letters",IDC_STATIC,36,31,24,8
EDITTEXT IDC_EDIT3,65,57,115,14,ES_AUTOHSCROLL
LTEXT "Both",IDC_STATIC,44,57,16,8
EDITTEXT IDC_EDIT4,65,83,115,14,ES_AUTOHSCROLL
LTEXT "Regular",IDC_STATIC,33,83,27,8
END
If an application wants to locally superclass edit controls that do not appear in a dialog box, the application must create the edit controls with the application's instance.
If you're using local superclassing in your application, you should be aware of the following issues: