Special Edition Using Visual C++ 6

Previous chapterNext chapterContents


- B -

Windows Programming Review and a Look Inside CWnd


The Microsoft Foundation Classes were written for one single purpose: to make Windows programming easier by providing classes with methods and data that handle tasks common to all Windows programs. The classes that are in MFC are designed to be useful to a Windows programmer specifically. The methods within each class perform tasks that Windows programmers often need to perform. Many of the classes have a close correspondence to structures and window classes, in the old Windows sense of the word class. Many of the methods correspond closely to API (Application Programming Interface) functions already familiar to Windows programmers, who often refer to them as the Windows SDK or as SDK functions.

Programming for Windows

If you've programmed for Windows in C, you know that the word class was used to describe a window long before C++ programming came to Windows. A window class is vital to any Windows C program. A standard structure holds the data that describes this window class, and the operating system provides a number of standard window classes. A programmer usually builds a new window class for each program and registers it by calling an API function, RegisterClass(). Windows that appear onscreen can then be created, based on that class, by calling another API function, CreateWindow().

A C-Style Window Class

The WNDCLASS structure, which describes the window class, is equivalent to the WNDCLASSA structure, which looks like Listing B.1.

Listing B.1  WNDCLASSA Structure from WINUSER.H

typedef struct tagWNDCLASSA {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
} WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;

WINUSER.H sets up two very similar window class structures: WNDCLASSA for programs that use normal strings and WNDCLASSW for Unicode programs. Chapter 28, "Future Explorations," covers Unicode programs in the "Unicode" section.


TIP: WINUSER.H is code supplied with Developer Studio. It's typically in the folder \Program Files\ Files\Microsoft Visual Studio\VC98\include.

If you were creating a Windows program in C, you would need to fill a WNDCLASS structure. The members of the WNDCLASS structure are as follows:

Window Creation

If you've never written a Windows program before, having to fill out a WNDCLASS structure might intimidate you. This is the first step, though, in Windows programming in C. However, you can always find simple sample programs to copy, like this one:

WNDCLASS wcInit;
 wcInit.style = 0;
 wcInit.lpfnWndProc = (WNDPROC)MainWndProc;
 wcInit.cbClsExtra = 0;
 wcInit.cbWndExtra = 0;
 wcInit.hInstance = hInstance;
 wcInit.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(ID_ICON));
 wcInit.hCursor = LoadCursor (NULL, IDC_ARROW);
 wcInit.hbrBackground = GetStockObject (WHITE_BRUSH);
 wcInit.lpszMenuName = "DEMO";
 wcInit.lpszClassName ="NewWClass";
 return (RegisterClass (&wcInit));


Hungarian Notation

You might wonder what kind of variable name lpszClassName is or why it's wcInit and not just Init. The reason for this is Microsoft programmers use a variable naming convention called Hungarian Notation. It is so named because a Hungarian programmer named Charles Simonyi popularized it at Microsoft (and probably because at first glance, the variable names seem to be written in another language).

In Hungarian Notation, the variable is given a descriptive name, like Count or ClassName, that starts with a capital letter. If it is a multiword name, each word is capitalized. Then, before the descriptive name, letters are added to indicate the variable type--for example, nCount for an integer or bFlag for a Boolean (TRUE or FALSE) variable. In this way, the programmer should never forget a variable type or do something foolish such as pass a signed variable to a function that is expecting an unsigned value.

The style has gained widespread popularity, although some people hate it. If you long for the good old days of arguing where to put the braces, or better still whether to call them brace, face, or squiggle brackets, but can't find anyone to rehash those old wars anymore, you can probably find somebody to argue about Hungarian Notation instead. The arguments in favor boil down to "you catch yourself making stupid mistakes," and the arguments against it to "it's ugly and hard to read." The practical truth is that the structures used by the API and the classes defined in MFC all use Hungarian Notation, so you might as well get used to it. You'll probably find yourself doing it for your own variables, too. The prefixes are as follows:
Prefix
Variable Type
Comment
a
Array

b
Boolean

d
Double

h
Handle

i
Integer
"index into"
l
Long

lp
Long pointer to

lpfn
Long pointer to function

m_
Member variable

n
Integer
"number of"
p
Pointer to

s
String

sz
Zero-terminated string

u
Unsigned integer

C
Class


Many people add their own type conventions to variable names; the wc in wcInit stands for window class.


Filling the wcInit structure and calling RegisterClass is standard stuff: registering a class called NewWClass with a menu called DEMO and a WindProc called MainWndProc. Everything else about it is ordinary to an experienced Windows C programmer. After registering the class, when those old-time Windows programmers wanted to create a window onscreen, out popped some code like this:

 HWND hWnd;
 hInst = hInstance;
 hWnd = CreateWindow (
 "NewWClass",
 "Demo 1",
 WS_OVERLAPPEDWINDOW,
 CW_USEDEFAULT,
 CW_USEDEFAULT,
 CW_USEDEFAULT,
 CW_USEDEFAULT,
 NULL,
 NULL,
 hInstance,
 NULL);
 if (! hWnd)
 return (FALSE);
 ShowWindow (hWnd, nCmdShow);
 UpdateWindow (hWnd);

This code calls CreateWindow(), then ShowWindow(), and UpdateWindow(). The parameters to the API function CreateWindow() are as follows:

CreateWindow()returns a window handle--everybody calls his window handles hWnd--and this handle is used in the rest of the standard code. If it's NULL, the window creation failed. If the handle returned has any non-NULL value, the creation succeeded and the handle is passed to ShowWindow() and UpdateWindow(), which together draw the actual window onscreen.


Handles

A handle is more than just a pointer. Windows programs refer to resources such as windows, icons, cursors, and so on, with a handle. Behind the scenes there is a handle table that tracks the resource's address as well as information about the resource type. It's called a handle because a program uses it as a way to "get hold of" a resource. Handles are typically passed to functions that need to use resources and are returned from functions that allocate resources.

There are a number of basic handle types: HWND for a window handle, HICON for an icon handle, and so on. No matter what kind of handle is used, remember that it's a way to reach a resource so that you can use the resource.


Encapsulating the Windows API

API functions create and manipulate windows onscreen, handle drawing, connect programs to Help files, facilitate threading, manage memory, and much more. When these functions are encapsulated into MFC classes, your programs can accomplish these same basic Windows tasks, with less work on your part.

There are literally thousands of API functions, and it can take six months to a year to get a good handle on the API, so this book doesn't attempt to present a minitutorial on the API. In the "Programming for Windows" section earlier in this chapter, you were reminded about two API functions, RegisterClass() and CreateWindow(). These illustrate what was difficult about C Windows programming with the API and how the MFC classes make it easier. Documentation on the API functions is available on MSDN, which comes with Visual C++.

Inside CWnd

CWnd is an enormously important MFC class. Roughly a third of all the MFC classes use it as a base class--classes such as CDialog, CEditView, CButton, and many more. It serves as a wrapper for the old-style window class and the API functions that create and manipulate window classes. For example, the only public member variable is m_hWnd, the member variable that stores the window handle. This variable is set by the member function CWnd::Create() and used by almost all the other member functions when they call their associated API functions.

You might think that the call to the API function CreateWindow() would be handled automatically in the CWnd constructor, CWnd::CWnd, so that when the constructor is called to initialize a CWnd object, the corresponding window on the screen is created. This would save you, the programmer, a good deal of effort because you can't forget to call a constructor. In fact, that's not what Microsoft has chosen to do. The constructor looks like this:

CWnd::CWnd()
{
 AFX_ZERO_INIT_OBJECT(CCmdTarget);
}

AFX_ZERO_INIT_OBJECT is just a macro, expanded by the C++ compiler's preprocessor, that uses the C function memset to zero out every byte of every member variable in the object, like this:

#define AFX_ZERO_INIT_OBJECT(base_class) 
¬ memset(((base_class*)this)+1, 0, sizeof(*this) 
¬ - sizeof(class base_class));

The reason why Microsoft chose not to call CreateWindow() in the constructor is that con-structors can't return a value. If something goes wrong with the window creation, there are no elegant or neat ways to deal with it. Instead, the constructor does almost nothing, a step that essentially can't fail, and the call to CreateWindow() is done from within the member function Cwnd::Create()or the closely related CWnd::CreateEx(), which looks like the one in Listing B.2.

Listing B.2  CWnd::CreateEx() from WINCORE.CPP

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName, DWORD dwStyle,
 int x, int y, int nWidth, int nHeight,
 HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
 // allow modification of several common create parameters
 CREATESTRUCT cs;
 cs.dwExStyle = dwExStyle;
 cs.lpszClass = lpszClassName;
 cs.lpszName = lpszWindowName;
 cs.style = dwStyle;
 cs.x = x;
 cs.y = y;
 cs.cx = nWidth;
 cs.cy = nHeight;
 cs.hwndParent = hWndParent;
 cs.hMenu = nIDorHMenu;
 cs.hInstance = AfxGetInstanceHandle();
 cs.lpCreateParams = lpParam;
 if (!PreCreateWindow(cs))
 {
 PostNcDestroy();
 return FALSE;
 }
 AfxHookWindowCreate(this);
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
 cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
 cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
#ifdef _DEBUG
 if (hWnd == NULL)
 {
 TRACE1("Warning: Window creation failed: Â
 GetLastError returns 0x%8.8X\n",
 GetLastError());
 }
#endif
 if (!AfxUnhookWindowCreate())
 PostNcDestroy();
 // cleanup if CreateWindowEx fails too soon
 if (hWnd == NULL)
 return FALSE;
 ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
 return TRUE;
}


TIP: WINCORE.CPP is code supplied with Developer Studio. It's typically in the folder \Program Files\Microsoft Visual Studio\VC98\mfc\src.

This sets up a CREATESTRUCT structure very much like a WNDCLASS and fills it with the parameters that were passed to CreateEx(). It calls PreCreateWindow, AfxHookWindowCreate(), ::CreateWindow(), and AfxUnhookWindowCreate() before checking hWnd and returning.


TIP: The AFX prefix on many useful MFC functions dates back to the days when Microsoft's internal name for its class library was Application Framework. The :: in the call to CreateWindow identifies it as an API function, sometimes referred to as an SDK function in this context. The other functions are member functions of CWnd that set up other background boilerplate for you.

On the face of it, there doesn't seem to be any effort saved here. You declare an instance of some CWnd object, call its Create() function, and have to pass just as many parameters as you did in the old C way of doing things. What's the point? Well, CWnd is really a class from which to inherit. Things become much simpler in the derived classes. Take CButton, for example, a class that encapsulates the concept of a button on a dialog box. A button is just a tiny window, but its behavior is constrained--for example, the user can't resize a button. Its Create() member function looks like this:

BOOL CButton::Create(LPCTSTR lpszCaption, DWORD dwStyle,
 const RECT& rect, CWnd* pParentWnd, UINT nID)
{
 CWnd* pWnd = this;
 return pWnd->Create(_T("BUTTON"), lpszCaption, dwStyle, rect, pParentWnd, nID);
}

That amounts to a lot fewer parameters. If you want a button, you create a button and let the class hierarchy fill in the rest.

Getting a Handle on All These MFC Classes

There are more than 200 MFC classes. Why so many? What do they do? How can any normal human keep track of them and know which one to use for what? Good questions. Questions that will take a large portion of this book to answer. The first half of this book presents the most commonly used MFC classes. This section looks at some of the more important base classes.

CObject

Figure B.1 shows a high-level overview of the inheritance tree for the classes in MFC. Only a handful of MFC classes do not inherit from CObject. CObject contains the basic functionality that all the MFC classes (and most of the new classes you create) will be sure to need, such as persistence support and diagnostic output. As well, classes derived from CObject can be contained in the MFC container classes, discussed in Appendix F, "Useful Classes."

FIG. B.1 Almost all the classes in MFC inherit from CObject.

CCmdTarget

Some of the classes that inherit from CObject, such as CFile and CException, and their derived classes don't need to interact directly with the user and the operating system through messages and commands. All the classes that do need to receive messages and commands inherit from CCmdTarget. Figure B.2 shows a bird's-eye view of CCmdTarget's derived classes, generally called command targets.

FIG. B.2 Any class that will receive a command must inherit from CCmdTarget.

CWnd

As already mentioned, CWnd is an extremely important class. Only classes derived from CWnd can receive messages; threads and documents can receive commands but not messages.


TIP: Chapter 3, "Messages and Commands," explores the distinction between commands and messages. Chapter 4, "Documents and Views," explains documents, and Chapter 27, "Multitasking with Windows Threads," explains threads.

CWnd provides window-oriented functionality, such as calls to CreateWindow and DestroyWindow, functions to handle painting the window onscreen, processing messages, talking to the Clipboard, and much more--almost 250 member functions in all. Only a handful of these will need to be overridden in derived classes. Figure B.3 shows the classes that inherit from CWnd; there are so many control classes that to list them all would clutter up the diagram, so they are lumped together as control classes.

All Those Other Classes

So far you've seen 10 classes in these three figures. What about the other 200+? You'll meet them in context throughout the book. If there's a specific class you're wondering about, check the index. Check the online help, too, because every class is documented there. Also, don't forget that the full source for MFC is included with every copy of Visual C++. Reading the source is a hard way to figure out how a class works, but sometimes you need that level of detail. 

FIG. B.3 Any class that will receive a message must inherit from CWnd, which provides lots of window-related functions.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.