Kyle Marsh
Microsoft Developer Network Technology Group
Created: January 25, 1994
The Platform SDK documentation makes few distinctions between window classes in 32-bit versions of Windows® and window classes in 16-bit versions of Windows. Many developers who read the documentation may assume there are no changes to window classes for Win32 at all. This is not the case. In fact, there are a number of subtle, and not so subtle, differences between the way window classes work under the 16-bit and 32-bit versions of Windows. This article describes how window classes work in Win32, provides the details that are lacking in the Win32 documentation, and tries to clear up the confusion about Win32 window classes.
This article repeats some of the information in my "Classy Windows" article, which describes window classes in 16-bit Windows (Win16). I repeated the information that applies to both platforms because I want this article to stand on its own as a full reference for Win32 window classes. At the same time, I want to help developers who are moving from the 16-bit world to the 32-bit world. I will try to point out the differences between 16-bit and 32-bit versions of Windows as I go along.
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. The 32-bit version of the Microsoft® Windows® operating system also registers classes that can be used by all applications in the system.
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:
Windows provides for three types of window classes:
Windows registers the system global classes for all applications to use. These classes contain the following familiar standard controls:
Windows NT® adds the DDEMLEvent class for DDEML because the DDEML functionality has been incorporated into Windows NT USER.
Windows 95/98 does not register class #32772 because it does not use icon title windows.
All Win32® applications can use system global classes, but applications cannot add or delete these classes.
Applications can change the properties of system global classes by subclassing the classes. In Win32, an application can subclass a system global class without affecting other applications. This is a departure from Win16, where subclassing a system class affected all windows of that class created after the subclassing. Because of this side-effect, Microsoft discouraged developers from subclassing the system classes in Win16.
In Win32, Microsoft encourages the use of subclassing a system class. This technique is a very powerful and convenient way to change the behavior of windows. For example, an application can limit the input that edit controls will accept by subclassing the Edit class and assigning it a new window procedure. After the subclassing, all edit controls created by the application will use the new window procedure instead of the standard edit control window procedure.
The current Win32 platforms (Windows NT and Windows 95) keep the system classes separate for each 32-bit process. How the system classes are implemented does not affect your application. However, because I spent a lot of time figuring this out, I will describe the implementation in this section. Those of you who have a life can skip to the next section without missing anything important.
Windows 95 implementation
System global classes are implemented similarly in Windows 95 and in Windows 3.1. USER creates the system classes when Windows is started. Unlike Windows 3.1, however, Windows 95 takes the following steps when it detects an application subclassing a system class:
All 16-bit applications share the same process. Windows 95 USER performs the procedure above for the 16-bit process, but all 16-bit applications still share the system global classes. Thus, 16-bit applications work in Windows 95 the same way they did in Windows 3.1.
Windows NT implementation
Windows NT does things differently. There are two parts of the Win32 subsystem in Windows NT: a server process and a dynamic-link library (DLL) that runs inside each Win32 process. Windows NT registers the Edit class from the DLL inside each process. This allows all the code that handles edit controls to reside in that DLL. No local procedure calls are needed to handle edit controls, and the system overhead caused by applications' heavy use of these controls is avoided. Because edit controls manipulate only their own data, the impact on system robustness is minimal.
The server process keeps information for each Win32 application, including a list of the application's public and private classes, on the system. When a queue is created for a Win32 thread, which occurs when the thread calls a USER or graphics device interface (GDI) function, USER checks to see if this is the first thread for the process. If it is, USER registers the rest of the system classes. When a class is registered for any process except for the server process itself (USER keeps track of when it is registering classes for itself), the class is added to either the public list or the private list for the process. Effectively, Windows registers the system classes for each process and stores a copy of each system class for each Win32 application. This is a robust implementation, but it does use more memory than implementation in Windows 95. Windows NT also provides a performance advantage over Windows 95 when a system class is subclassed because Windows NT does not need to allocate memory and copy data the way Windows 95 does. Since applications were discouraged from subclassing the system classes in Win16, this speed improvement is probably not a big deal.
In Windows NT, 16-bit applications share the same process, so they share all the system global classes, subclasses and all.
Application global classes are registered with the CS_GLOBALCLASS style.
16-bit versions of Windows such as Windows 3.1 gave real meaning to application global classes. A DLL or application could register an application global class that any other DLL or application on the system could use. Thus, an application global class was a global class registered by an application instead of the system.
Application global classes are quite different in Win32. An application global class is a class that is available to all parts of a process. Now doesn't that sound silly? What does it mean? Basically, it means that an .EXE or DLL can register classes that can be seen by other DLLs or .EXEs within one process. If a DLL registers a class that is not an application global class, only that DLL can use the class. The same rule applies to a class registered by an .EXE file that is not an application global class—the class is available only to the .EXE code.
However, Win32 has a technique that allows custom controls to be implemented within a DLL, and for that DLL to be initialized within each Win32 process. This is a feature that Windows 3.1 does not have. When an application uses a custom control in Windows 3.1, it has two choices: (a) to explicitly load the DLL using the LoadLibrary function, or (b) to link the DLL's import library into the application by calling a function that resides in the DLL. Windows NT has a feature that makes this unnecessary. When a DLL's name is added to the registry key:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\ CurrentVersion\Windows\APPINIT_DLLS
the DLL is loaded whenever a Win32 application is loaded. This is overkill because many Win32 applications will probably not use the custom control. The DLL can register an application global class during its initialization. This class can then be used by every application and DLL within the process. Since this happens for every Win32 process, every application and DLL on the system can use this application global class. This technique is based on Win32's ability to load specified DLLs with every process.
Win32 application local classes are the same as they were in Windows 3.1. They are the most frequently used window classes and are available only to the application or DLL that registers them. Application local classes are registered without the CS_GLOBALCLASS style. Most applications register an application local class for their main window to use.
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, device context (DC) 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 to paint 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. The Win32 SDK documentation says that these styles affect the width of a window, but I can find no reason for this statement. The styles affect only the horizontal position of a window.
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:
This check does not occur in Windows NT, but it does occur in Windows 95.
"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 it 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, all system fonts 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 DC for the windows created from a class.
The CS_OWNDC style brings out a difference between Windows NT and Windows 95. In Windows NT, the Win32 subsystem has the same available address space (4 GB) as any other Windows NT process. The application can use 2 GB of this address space. Each window instance that has the CS_OWNDC style takes up about 800 bytes of storage. Obviously, this is not a problem under Windows NT. However, things are much different under Windows 95. Windows 95 has a 64K local heap for GDI. Although many data items are no longer stored in this local help, DCs still are. This means that each window instance that has the CS_OWNDC style takes up 800 bytes of this precious memory space. As a result, applications that target Windows 95 should use the CS_OWNDC class style sparingly. Windows 95 may move DCs out of the local heap in the future.
Applications that will run only on Windows NT have no reason to use this style because the space benefit is insignificant. However, this style is useful for applications that target Windows 95 because the space benefit is very significant in the 64K GDI local heap of Windows 95.
In Windows 95, all standard Window controls have the CS_PARENTDC style. In Windows NT, the ComboBox control does not have the CS_PARENTDC style, but the other controls do. Thus, an edit control and list box in a dialog box share the same DC—that of the dialog box itself.
The benefits of CS_PARENTDC can be summed up with one word: speed. Windows 3.1 and Windows 95 keep five common DCs in memory (see the next bullet item). Windows 95 keeps its five DCs on a per-thread basis. When a window (for example, a dialog box) has more than five child windows (let's say it has six edit controls), the DC cache loses its effectiveness. For each child window, the application must reinitialize a DC with the clipping region and device origin. If the child windows share their parent's DC, the DC will be found in the cache more often. This saves a lot of time and is why standard Windows controls use CS_PARENTDC. Windows NT uses more than five cached DCs if it needs to, so it may have enough cached DCs to be effective—on the other hand, it may not.
Another outcome of the CS_PARENTDC style is that child windows can draw anywhere in their parent's client area as well as drawing in their own client area. The CTL3D library, which creates 3-D effects around edit controls and list boxes, relies on this behavior. An application should not use CS_PARENTDC if it needs to adjust the mapping modes for different child windows. This will negate much of the benefit and could cause problems ensuring that each child window has the correct mapping mode set.
In Windows NT, there is no preset number of cached DCs. If all the current DCs are in use when the application calls GetDC or BeginPaint, Windows NT will allocate another cache.
Windows 95 still has a cache that contains five DCs. For portability, Win32 applications should attempt to limit their use of common DCs to five, and release a DC as soon as possible.
To bypass the default DC 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 DC.
The ScrollWindow and ScrollWindowEx functions handle DCs 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 in the MSDN Library Archive.
All standard Windows controls, the dialog class, and the desktop class have the CS_DBLCLKS style. 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 within the process 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.
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. If you use a negative value for either field, Windows NT and Windows 95 will not register the class.
In Windows 95 and Windows NT, there is no reason to limit the number of extra bytes for a window or class to a small number such as 40. However, developers should choose reasonable values when determining their extra byte requirements.
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:
If Windows 95 does not find an application global class with the class name, it searches the system class list for the process.
Windows NT uses this procedure for all windows created by the application, including windows created by Windows on the application's behalf, such as dialog boxes.
Windows 95 also uses this procedure for all windows created for or by an application, except for message boxes. When processing a message box, Windows 95 does not search the local class list for the process.
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.
In the 32-bit versions of Windows, you may not subclass a window or class that belongs to another process. All subclassing must be done from within the same process.
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 in Win32" technical article in the MSDN Library.
In my Win16 version of this article, I described a technique called local superclassing, which allows the application to register a new local class with a system class name. Windows finds this new local class before it finds the actual system class. The purpose of local superclassing in Win16 was to find a way to subclass all window instances in a system class without using global subclassing (that is, without subclassing the system class itself). Since subclassing a system class is no longer a problem in Win32, local superclassing should be thrown out of a developer's toolbox. If you need to subclass all window instances in a system class, just go ahead and subclass the system class directly.
If you are already using local subclassing in your Win16-based application and you are too lazy to change your code, you can keep the local subclassing when you port your application to Win32. You can postpone updating the code until you have more time (which may never happen).
Window classes in Win32 have many minor differences from window classes in Win16. Most of these differences (for example, the ability to subclass a system class) add power for the application developer. If you want to exploit window classes in Win32, make sure that the feature you are planing to use is available in both Windows NT and Windows 95.