You can find the wrox.com code downloads for this chapter on the Download Code tab at www.wrox.com/go/beginningvisualc. The code is in the Chapter 11 download and individually named according to the names throughout the chapter.
This chapter will take you on a tour of the basic ideas that are involved in every Windows program in C++. You’ll first develop a simple example that uses the Windows operating system API directly. This will enable you to understand how a Windows application works behind the scenes, which will be useful when you are developing applications using the more sophisticated facilities provided by Visual C++. Next you will see what you get when you create a Windows program using the Microsoft Foundation Classes (MFC) that encapsulates Win32 capabilities.
The Windows API is referred to as WinAPI or Win32, the latter being a slightly dated term since the availability of 64-bit versions of Windows. When you are developing an application with the Windows API, you are writing code at a relatively low level throughout — all the elements that make up the GUI for your application must be created programmatically by calling operating system functions. With MFC applications you are using a set of standard classes that insulate you from the Windows API and make coding much easier. There’s also some help with GUI creation in that you can assemble controls on a dialog form graphically and just program the interactions with the user; however, you are still involved in a lot of coding.
Using the Windows API directly is the most laborious method for developing an application so I won’t go into this in detail. However, you will put together a basic Windows API application so you’ll have an opportunity to understand the mechanism that all Windows applications use under the covers to work with the operating system. Of course, it also is possible to develop applications in C++ that do not require the Windows operating system, and games sometimes take this approach. Many games use DirectX, which is a Windows specific graphics library. Although this is an interesting topic, it would require a whole book to do it justice, so I won’t pursue it further.
Before getting to the examples I’ll review the terminology that is used to describe an application window. You have already created a Windows program in Chapter 1 without writing a single line of code yourself, and I’ll use the window generated by this example to illustrate the elements that go to make up a window.
You will inevitably be familiar with most, if not all, of the principal elements of the user interface to a Windows program. However, I will go through them anyway just to be sure we have a common understanding of what the terms mean. The best way to understand what the elements of a window can be is to look at one. An annotated version of the window displayed by the example that you saw in Chapter 1 is shown in Figure 11-1.
The example generated two windows. The larger window with the menu and the toolbars is the main application window, or parent window, and the smaller window is a child window of the parent. The child window can be closed without closing the parent window by double-clicking the title bar icon that is in the upper-left corner of the child window or by clicking the Close button in the upper-right corner of the child window. Closing the parent window automatically closes the child window. This is because the child window is owned by and dependent upon the parent window. In general, a parent window may have a number of child windows, as you’ll see.
The most fundamental parts of a typical window are its border, the title bar that shows the name that you give to the window, the title bar icon that appears at the left end of the title bar, and the client area, which is the area in the center of the window not used by the title bar or borders. You can get all of these created for free in a Windows program. As you will see, all you have to do is provide some text for the title bar.
The border defines the boundary of a window and may be fixed or resizable. If the border is resizable, you can drag it to alter the size of the window. The window also may possess a size grip, which you can use to alter the size of a window. When you define a window, you can modify how the border behaves and appears if you want. Most windows will also have the maximize, minimize, and close buttons in the upper-right corner of the window. These allow the window to be increased to full screen size, reduced to an icon, or closed.
When you click the title bar icon, it provides a standard menu for altering or closing the window called the system menu or control menu. The system menu also appears when you right-click the title bar of a window. Although it’s optional, it is always a good idea to include the title bar icon in any main windows that your program generates. Including the title bar icon provides you with a very convenient way of closing the program when things don’t work during debugging, which is the process by which you find and eliminate errors in your code.
The client area is the part of the window where you usually want your program to write text or graphics. You can address the client area for this purpose in exactly the same way as the yard that you saw in Figure 7-1, in Chapter 7. The upper-left corner of the client area has the coordinates (0, 0), with x increasing from left to right, and y increasing from top to bottom.
The menu bar is optional in a window but is probably the most common way to control an application. Each menu in the menu bar displays a drop-down list of menu items when you click it. The contents of a menu and the physical appearance of many objects that are displayed in a window, such as the icons on the toolbar that appear in Figure 11-1, the cursor, and many others, are defined by a resource file. You will see many more resource files when we get to write some more sophisticated Windows programs.
A ribbon is an alternative to the menu bar. The latest editions of Microsoft Word and Microsoft Excel provide a ribbon as the primary mechanism for navigating around application functions. The MFC provides an extensive set of classes for creating a ribbon but I don’t have the space to go into these.
The toolbar provides a set of icons that usually act as alternatives to the menu options that you use most often. Because they give a pictorial clue to the function provided, they can often make a program easier and faster to use.
I’ll mention a caveat about terminology that you need to be conscious of. Users tend to think of a window as the thing that appears on the screen with a border around it, and, of course, it is, but it is only one kind of window. In Windows a window is a generic term covering a whole range of entities. Many entities that are displayed are windows — for example, a dialog is a window and each toolbar and dockable menu bar are also windows. I will generally use terminology to refer to objects that describe what they are, buttons, dialogs, and so on, but you need to have tucked in the back of your mind that many of them are windows too, because you can do things to them that you can do with a regular window — you can draw on a button, for instance.
When you write a Windows program, your program is subservient to the operating system and Windows is in control. Your program cannot deal directly with the hardware, and all communications with the outside world must pass through Windows. When you use a Windows program, you are interacting primarily with Windows, which then communicates with the application program on your behalf. Your Windows program is the tail, Windows is the dog, and your program wags only when Windows tells it to.
There are a number of reasons why this is so. First and foremost, because your program is potentially always sharing the computer with other programs that may be executing at the same time, Windows has to have primary control to manage the sharing of machine resources. If one application were allowed to have primary control in a Windows environment, this would inevitably make programming more complicated because of the need to provide for the possibility of other programs, and information intended for other applications could be lost. A second reason is that Windows embodies a standard user interface and needs to be in charge to enforce that standard. You can only display information on the screen using the tools that Windows provides, and then only when authorized.
You have already seen in Chapter 1 that a Windows program is event-driven, so a Windows program essentially waits around for something to happen. A significant part of the code for a Windows application is dedicated to processing events that are caused by external actions of the user, but activities that are not directly associated with your application can nonetheless require that bits of your program code are executed. For example, if the operating system determines that your application window needs to be redrawn because it is no longer valid, it will send a message to you application to signal that your application must redraw the part of the application window that has been exposed.
Events in a Windows application are occurrences such as the user clicking the mouse or pressing a key, or a timer reaching zero. The Windows operating system records each event in a message and places the message in a message queue for the program for which the message is intended. A Windows message is simply a record of the data relating to an event, and the message queue for an application is just a sequence of such messages waiting to be processed. By sending a message, Windows can tell your program that something needs to be done, or that some information has become available, or that an event such as a mouse click has occurred. If your program is properly organized, it will respond in the appropriate way to the message. There are many different kinds of messages and they can occur very frequently — several times per second when the mouse is being dragged for example.
A Windows program must contain a function specifically for handling these messages. The function is often called WndProc()
or WindowProc()
, although it doesn’t have to have a particular name because Windows accesses the function through a pointer to a function that you supply. So the sending of a message to your program boils down to Windows calling a function that you provide that is typically called WindowProc()
, and passing any necessary data to your program by means of arguments to this function. Within your WindowProc()
function, it is up to you to work out what the message is from the data supplied and decide what to do about it.
Fortunately, you don’t need to write code to process every message that Windows sends to your application. You can filter out those that are of interest, deal with those in whatever way you want, and pass the rest back to Windows. You pass a message back to Windows by calling a standard function called DefWindowProc()
, which provides default message processing.
All of the communications between a Windows application and Windows itself use the Windows application programming interface, otherwise known as the Windows API. This consists of literally hundreds of functions that come as a standard with the Windows operating system that provide the means by which an application communicates with Windows, and vice versa. The Windows API was developed in the days when C was the primary language in use, long before the advent of C++, and for this reason structures rather than classes are frequently used for passing data between Windows and your application program.
The Windows API covers all aspects of the communications between Windows and your application. Because there is such a large number of functions in the API, using them in the raw can be very difficult — just understanding what they all are is a task in itself. This is where Visual C++ makes the life of the application developer very much easier. Visual C++ packages the Windows API in a way that structures the functions in an object-oriented manner, and provides an easier way to use the interface in C++ with more default functionality. This takes the form of the Microsoft Foundation Classes, MFC.
Visual C++ also provides Application Wizards that create basic applications of various kinds, including MFC applications. The Application Wizard can generate a complete working application that includes all of the boilerplate code necessary for a basic Windows application, leaving you just to customize this for your particular purposes. The example in Chapter 1 illustrated how much functionality Visual C++ is capable of providing without any coding effort at all on your part. I will discuss this in more detail when we get to write some more examples using the Application Wizard.
Windows defines a significant number of data types that are used to specify function parameter types and return types in the Windows API. These Windows-specific types also propagate through to functions that are defined in MFC. Each of these Windows types will map to some C++ type, but because the mapping between Windows types and C++ types can change over time, you should always use the Windows type where this applies. For example, in the past the Windows type WORD
has been defined in one version of Windows as type unsigned short
and in another Windows version as type unsigned int
. On 16-bit machines these types are equivalent, but on 32-bit machines they are decidedly different so anyone using the C++ type rather than the Windows type could run into problems.
You can find the complete list of Windows data types in the documentation, but here are a few of the most common you are likely to meet:
BOOL or BOOLEAN |
A Boolean variable can have the values TRUE or FALSE . Note that this is not the same as the C++ type bool , which can have the values true or false . |
BYTE |
An 8-bit byte. |
CHAR |
An 8-bit character. |
DWORD |
A 32-bit unsigned integer that corresponds to type unsigned long in C++. |
HANDLE |
A handle to an object — a handle being a 32-bit integer value that records the location of an object in memory, or 64-bit when compiling for 64-bit. |
HBRUSH |
A handle to a brush, a brush being used to fill an area with color. |
HCURSOR |
A handle to a cursor. |
HDC |
Handle to a device context — a device context being an object that enables you to draw on a window. |
HINSTANCE |
Handle to an instance. |
LPARAM |
A message parameter. |
LPCTSTR |
LPCWSTR if _UNICODE is defined, otherwise LPCSTR . |
LPCWSTR |
A pointer to a constant null-terminated string of 16-bit characters. |
LPCSTR |
A pointer to a constant null-terminated string of 8-bit characters. |
LPHANDLE |
A pointer to a handle. |
LRESULT |
A signed value that results from processing a message. |
WORD |
A 16-bit unsigned integer, so it corresponds to type unsigned short in C++. |
I’ll introduce any other Windows types we are using in examples as the need arises. All Windows types and the prototypes of the Windows API functions are contained in the header file windows.h
, so you need to include this header file when you put your basic Windows program together.
In many Windows programs, variable names have a prefix, which indicates what kind of value the variable holds and how it is used. There are quite a few prefixes and they are often used in combination. For example, the prefix lpfn
signifies a long pointer to a function. A sample of the prefixes you might come across is:
PREFIX | MEANING |
---|---|
b | a logical variable of type BOOL , equivalent to int |
by | type unsigned char ; a byte |
c | type char |
dw | type DWORD , which is unsigned long |
fn | a function |
h | a handle, used to reference something |
i | type int |
l | type long |
lp | long pointer |
n | type unsigned int |
p | a pointer |
s | a string |
sz | a zero terminated string |
w | type WORD , which is unsigned short |
The use of these prefixes is called Hungarian notation. It was introduced to minimize the possibility of misusing a variable by interpreting it differently from how it was defined or intended to be used. Such misinterpretation is easily done in the C language. With C++ and its stronger type checking you don’t need to make such a special effort with your notation to avoid such problems. The compiler always flags an error for type inconsistencies in your program, and many of the kinds of bugs that plagued earlier C programs can’t occur with C++.
On the other hand, Hungarian notation can still help to make programs easier to understand, particularly when you are dealing with a lot of variables of different types that are arguments to Windows API functions. Because Windows programs are still written in C, and, of course, because parameters for Windows API functions are still defined using Hungarian notation, the method is still used quite widely.
You can make up your own mind as to the extent to which you want to use Hungarian notation, as it is by no means obligatory. You may choose not to use it at all, but in any event, if you have an idea of how it works, you will find it easier to understand what the arguments to the Windows API functions are. There is a small caveat, however. As Windows has developed, the types of some of the API function arguments have changed slightly, but the variable names that are used remain the same. As a consequence, the prefix may not be quite correct in specifying the variable type.
For a minimal Windows program that just uses the Windows API, you will write two functions. These are a WinMain()
function, where execution of the program begins and basic program initialization is carried out, and a WindowProc()
function that is called by Windows to pass messages to the application. The WindowProc()
part of a Windows program is usually the larger portion because this is where most of the application-specific code is, responding to messages caused by user input of one kind or another.
Although these two functions make up a complete program, they are not directly connected. WinMain()
does not call WindowProc()
, Windows does. Windows also calls WinMain()
. This is illustrated in Figure 11-2.
The WinMain()
function communicates with Windows by calling Windows API functions. The same applies to WindowProc()
. The integrating factor in your Windows desktop application is Windows itself, which links to both WinMain()
and WindowProc()
. After looking into what the pieces are that make up WinMain()
and WindowProc()
you will assemble them into a working example of a simple Windows program.
The WinMain()
function is the equivalent of main()
in a console program. It’s where execution starts and where the basic initialization for the program is carried out. To allow Windows to pass data to it, WinMain()
has four parameters and a return value of type int
. Its prototype is:
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
Following the return type specifier, int
, you have a specification for the function, WINAPI
. This is a Windows-defined macro that causes the function name and arguments to be handled in a special way that is specific to Windows API functions. This is different from the way functions are normally handled in C++. The precise details are unimportant — this is simply the way Windows requires things to be, so you need to put the WINAPI
macro name in front of the names of functions called by Windows.
The four arguments that are passed to your WinMain()
function contain important data:
hInstance
is of type HINSTANCE
, which is a handle to an instance — an instance here being a running program. A handle is an integer value that identifies an object of some kind — in this case, the instance of the application. The actual integer value of a handle is not important. There can be several programs in execution at any given instant. This raises the possibility of several copies of the same application being active at once, and this needs to be recognized. Hence, the hInstance
handle identifies a particular copy. If you start more than one copy of a program, each one has its own unique hInstance
value. As you will see, handles are used to identify all sorts of other things.hPrevInstance
is a legacy from the old 16-bit versions of the Windows operating system, and you can safely ignore it. On current versions of Windows, it is always nullptr
.lpCmdLine
is a pointer to a string containing the command line that started the program. This pointer allows you to pick up any parameter values that may appear in the command line. The type LPSTR
is another Windows type, specifying a 32-bit (long) pointer to a string, or 64-bit when compiling in 64-bit mode. There is also a version of WinMain()
that accepts LPWSTR
for Unicode builds.nCmdShow
determines how the window is to look when it is created. It could be displayed normally or it might need to be minimized; for example, if the shortcut for the program specifies that the program should be minimized when it starts. This argument can take one of a fixed set of int
values that are defined by symbolic constants such as SW_SHOWNORMAL
and SW_SHOWMAXIMIZED
. There are nine other constants like these that define the way a window is to be displayed, and they all begin with SW_
. You don’t usually need to examine the value of nCmdShow
. You typically pass it directly to the Windows API function responsible for displaying your application window.The WinMain()
function in your program needs to do four things:
We will take a look at each of these in turn and then create a complete WinMain()
function.
The first step in creating a window is to define just what sort of window it is that you want to create. Windows defines a special struct
type called WNDCLASSEX
to contain the data specifying a window. The data that is stored in an instance of the struct
defines a window class, which determines the type of window. You need to create a variable of type WNDCLASSEX
, and give values to each of its members. After you’ve filled in the variables, you can pass it to Windows (via a function that you’ll see later) to register the class. When that’s been done, whenever you want to create a window of that class, you can tell Windows to look up the class that you have already registered.
The definition of the WNDCLASSEX
structure is:
struct WNDCLASSEX
{
UINT cbSize; // Size of this object in bytes
UINT style; // Window style
WNDPROC lpfnWndProc; // Pointer to message processing function
int cbClsExtra; // Extra bytes after the window class
int cbWndExtra; // Extra bytes after the window instance
HINSTANCE hInstance; // The application instance handle
HICON hIcon; // The application icon
HCURSOR hCursor; // The window cursor
HBRUSH hbrBackground; // The brush defining the background color
LPCTSTR lpszMenuName; // A pointer to the name of the menu resource
LPCTSTR lpszClassName; // A pointer to the class name
HICON hIconSm; // A small icon associated with the window
};
You construct an object of type WNDCLASSEX
in the way that you saw when I discussed structures, for example:
WNDCLASSEX WindowClass; // Create a window class object
You can now fill in values for the members of WindowClass
. They are all public
by default because WindowClass
is a struct
. Setting the value for the cbSize
member of the struct
is easy when you use the sizeof
operator:
WindowClass.cbSize = sizeof(WNDCLASSEX);
The style
member of the struct
determines various aspects of the window’s behavior, in particular, the conditions under which the window should be redrawn. You can select from a number of options for this member’s value, each defined by a symbolic constant beginning with CS_
.
Where two or more options are required, the constants can be combined to produce a composite value using the bitwise OR operator, |
. For example:
WindowClass.style = CS_HREDRAW | CS_VREDRAW;
The option CS_HREDRAW
indicates to Windows that the window is to be redrawn if its horizontal width is altered, and CS_VREDRAW
indicates that it is to be redrawn if the vertical height of the window is changed. In the preceding statement, you have elected to have the window redrawn in either case. As a result, Windows sends a message to your program indicating that you should redraw the window whenever the width or height of the window is altered by the user. Each of the possible options for the window style is defined by a unique bit in a 32-bit word being set to 1. That’s why the bitwise OR is used to combine them. These bits indicating a particular style are usually called flags. Flags are used very frequently, not only in Windows but also in C++, because they are an efficient way of representing and processing features that are either there or not, or parameters that are either true or false.
The member lpfnWndProc
stores a pointer to the function in your program that handles messages for the window you create. The prefix to the name signifies that this is a long
pointer to a function. If you followed the herd and called the function to handle messages for the application WindowProc()
, you would initialize this member with the statement:
WindowClass.lpfnWndProc = WindowProc;
The next two members, cbClsExtra
and cbWndExtra
, allow you to ask that extra space be provided internally to Windows for your own use. An example of this could be when you want to associate additional data with each instance of a window to assist in message handling for each window instance. Normally you won’t need extra space allocated for you, in which case you set the cbClsExtra
and cbWndExtra
members to zero.
The hInstance
member holds the handle for the current application instance, so you should set this to the hInstance
value that was passed to WinMain()
by Windows:
WindowClass.hInstance = hInstance;
The members hIcon, hCursor
, and hbrBackground
are handles that in turn reference objects that represent:
As you saw earlier, a handle is just a 32-bit integer (or 64-bit when compiled in 64-bit mode) used as an ID to represent something. These members are set using Windows API functions. For example:
WindowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
WindowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
All three members are set to standard Windows values by these function calls. The icon is a default provided by Windows and the cursor is the standard arrow cursor used by the majority of Windows applications. A brush is a Windows object used to fill an area, in this case the client area of the window. The function GetStockObject()
returns a generic type for all stock objects, so you need to cast it to type HBRUSH
. In the preceding example, it returns a handle to the standard gray brush, and the background color for our window is thus set to gray. This function can also be used to obtain other standard objects for a window, such as fonts, for example. You could also set the hIcon
and hCursor
members to nullptr
, in which case Windows would provide the default icon and cursor. If you set hbrBackground
to nullptr
, your program is expected to paint the window background, and messages are sent to your application whenever this becomes necessary.
The lpszMenuName
member is set to the name of a resource defining the window menu, or to nullptr
if there is no menu for the window:
WindowClass.lpszMenuName = nullptr;
You will look into creating and using menu resources when you use the AppWizard.
The lpszClassName
member of the struct
stores the name that you supply to identify this particular class of window. You would usually use the name of the application for this. You need to keep track of this name because you will need it again when a window is created. This member would therefore be typically set with the statements:
static LPCTSTR szAppName {_T("OFWin")}; // Define window class name
WindowClass.lpszClassName = szAppName; // Set class name
I have defined szAppName
using the _T()
macro that is defined in the tchar.h
header. The LPCTSTR
type is defined as const wchar_t*
if UNICODE
is defined for the application, or const char*
if it is not. The _T()
macro will create a string of the correct type automatically.
The last member is hIconSm
, which identifies a small icon associated with the window class. If you specify this as nullptr
, Windows searches for a small icon related to the hIcon
member and uses that.
After you have set the members of your WNDCLASSEX
structure to the values required, the next step is to tell Windows about it. You do this by calling the Windows API function RegisterClassEx()
. Given that your WNDCLASSEX
structure object is WindowClass
, the statement to do this would be:
RegisterClassEx(&WindowClass);
Easy, isn’t it? The address of the struct
is passed to the function, and Windows extracts and squirrels away all the values that you have set in the structure members. This process is called registering the window class. Just to remind you, the term class here is used in the sense of classification and is not the same as the idea of a class
in C++, so don’t confuse the two. Each instance of the application must make sure that it registers the window classes that it needs.
After Windows knows the characteristics of the window that you want, and the function that is going to handle messages for it, you can go ahead and create it. You call the CreateWindow()
function for this. The window class that you have created determines the broad characteristics of the application window, and further arguments to CreateWindow()
add additional characteristics. Because an application may have several windows in general, CreateWindow()
returns a handle to the window it created that you can store to enable you to refer to that particular window later. There are many API calls that require you to specify the window handle as a parameter. Let’s look at a typical use of CreateWindow()
. This might be:
HWND hWnd; // Window handle
...
hWnd = CreateWindow(
szAppName, // the window class name
_T("A Basic Window the Hard Way"),// The window title
WS_OVERLAPPEDWINDOW, // Window style as overlapped
CW_USEDEFAULT, // Default screen position of upper left
CW_USEDEFAULT, // corner of our window as x,y.
CW_USEDEFAULT, // Default window size, width...
CW_USEDEFAULT, // ...and height
nullptr, // No parent window
nullptr, // No menu
hInstance, // Program Instance handle
nullptr // No window creation data
);
The hWnd
variable of type HWND
is a 32-bit integer handle to a window, or 64-bit in 64-bit mode. You use this variable to record the window handle that CreateWindow()
returns. The first argument that you pass to CreateWindow()
is the class name. This is used to identify the WNDCLASSEX
object that you passed to the operating system previously in the RegisterClassEx()
function call, so that information from this struct
can be used in the window creation process.
The second argument to CreateWindow()
defines the text that is to appear on the title bar. The third argument specifies the style that the window has after it is created. The option specified here, WS_OVERLAPPEDWINDOW
, combines several options. It defines the window as having the WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX
, and WS_MAXIMIZEBOX
styles. This results in an overlapped window, which is a window intended to be the main application window, with a title bar and a thick frame, which has a title bar icon, system menu, and maximize and minimize buttons. A window that you specify as having a thick frame has borders that can be resized.
The next four arguments determine the position and size of the window on the screen. The first two are the screen coordinates of the upper-left corner of the window, and the second two define the width and height. The value CW_USEDEFAULT
indicates that you want Windows to assign the default position and size for the window. This tells Windows to arrange successive windows in cascading positions down the screen. CW_USEDEFAULT
only applies to windows specified as WS_OVERLAPPED
.
The next argument value is nullptr
, indicating that the window being created is not a child window (i.e., not a window that is dependent on a parent window). If you wanted it to be a child window, you would set this argument to the handle of the parent window. The next argument is also nullptr
, indicating that no menu is required. You then specify the handle of the current instance of the program that was passed to the program by Windows. The last argument for window creation data is nullptr
because you just want a simple window in the example. If you wanted to create a multiple-document interface (MDI) client window, the last argument would point to a structure related to this. You’ll learn more about MDI windows later in the book.
After you call CreateWindow()
, the window exists but is not yet displayed on the screen. You must call another Windows API function to get it displayed:
ShowWindow(hWnd, nCmdShow); // Display the window
Only two arguments are required here. The first identifies the window and is the handle returned by CreateWindow()
. The second is the nCmdShow
value that was passed to WinMain()
, which indicates how the window is to appear on-screen.
After calling ShowWindow()
, the window appears on-screen but still has no application content, so you need to get your program to draw in the client area of the window. You could just put together some code to do this directly in the WinMain()
function, but this would be most unsatisfactory: in this case, the contents of the client area would not be permanent — if you want the client area contents to be retained, you can’t just output what you want and forget about it. Any action on the part of the user that modifies the window in some way, such as dragging a border or dragging the whole window, typically requires that the window and its client area are redrawn.
When the client area needs to be redrawn for any reason, Windows sends a particular message to your program, and your WindowProc()
function needs to respond by reconstructing the client area of the window. Therefore, the best way to get the client area drawn in the first instance is to put the code to draw the client area in the WindowProc()
function and get Windows to send the message requesting that the client area be redrawn to your program. Whenever you know in your program that the window should be redrawn (when you change something, for example), you need to tell Windows to send a message back to get the window redrawn.
You can ask Windows to send your program a message to redraw the client area of the window by calling another Windows API function, UpdateWindow()
. The statement to accomplish this is:
UpdateWindow(hWnd); // Cause window client area to be drawn
This function requires only one argument: the window handle hWnd
, which identifies your particular program window. The result of the call is that Windows sends a message to your program requesting that the client area be redrawn.
The last task that WinMain()
needs to address is dealing with the messages that Windows may have queued for your application. This may seem a bit odd because I said earlier that you needed the function WindowProc()
to deal with messages, but let me explain a little further.
I oversimplified Windows messaging when I introduced the idea. There are, in fact, two kinds of Windows messages:
There are queued messages that Windows places in a queue, and the WinMain()
function must extract these messages from the queue for processing. The code in WinMain()
that does this is called the message loop. Queued messages include those arising from user input from the keyboard, moving the mouse, and clicking the mouse buttons. Messages from a timer and the Windows message to request that a window be redrawn are also queued.
There are non-queued messages that result in the WindowProc()
function being called directly by Windows. A lot of the non-queued messages arise as a consequence of processing queued messages. What you are doing in the message loop in WinMain()
is retrieving a message that Windows has queued for your application and then asking Windows to invoke your WindowProc()
function to process it. Why can’t Windows just call WindowProc()
whenever necessary? Well, it could, but it just doesn’t work this way. The reasons have to do with how Windows manages multiple applications executing simultaneously.
As I said, retrieving messages from the message queue is done using a standard mechanism in Windows programming called the message pump or message loop. The code for this would be:
MSG msg; // Windows message structure
while(GetMessage(&msg, nullptr, 0, 0) == TRUE) // Get any messages
{
TranslateMessage(&msg); // Translate the message
DispatchMessage(&msg); // Dispatch the message
}
This involves three steps in dealing with each message:
GetMessage()
— Retrieves a message from the queue.TranslateMessage()
— Performs any conversion necessary on the message retrieved.DispatchMessage()
— Causes Windows to call the WindowProc()
function in your application to deal with the message.The operation of GetMessage()
is important because it has a significant contribution to the way Windows works with multiple applications, so we should explore it in a little more detail.
The GetMessage()
function retrieves a message queued for the application window and stores information about the message in the variable msg
, pointed to by the first argument. The msg
variable is a struct
of type MSG
that contains members that you are not accessing here. Still, for completeness, the definition of the structure looks like this:
struct MSG
{
HWND hwnd; // Handle for the relevant window
UINT message; // The message ID
WPARAM wParam; // Message parameter (32-bits)
LPARAM lParam; // Message parameter (32-bits)
DWORD time; // The time when the message was queued
POINT pt; // The mouse position
};
The wParam
member is an example of a slightly misleading Hungarian notation prefix that I mentioned is now possible. You might assume that it was of type WORD
(a 16-bit unsigned integer
), which used to be true in earlier Windows versions, but now it is of type WPARAM
, which is a 32-bit integer value.
The exact contents of the wParam
and lParam
members are dependent on what kind of message it is. The message ID in the member message
is an integer value and can be one of a set of values that are predefined in the header file, windows.h
, as symbolic constants. Message IDs for general windows all start with WM_
; typical examples are shown in the following table. General windows messages cover a wide variety of events and include messages relating to mouse and menu events, keyboard input, and window creation and management.
ID | DESCRIPTION |
---|---|
WM_PAINT |
The window should be redrawn. |
WM_SIZE |
The window has been resized. |
WM_LBUTTONDOWN |
The left mouse button is down. |
WM_RBUTTONDOWN |
The right mouse button is down. |
WM_MOUSEMOVE |
The mouse has moved. |
WM_CLOSE |
The window or application should close. |
WM_DESTROY |
The window is being destroyed. |
WM_QUIT |
The program should be terminated. |
The function GetMessage()
always returns TRUE
unless the message is WM_QUIT
to end the program, in which case the value returned is FALSE
, or unless an error occurs, in which case the return value is -1
. Thus, the while
loop continues until a quit message is generated to close the application or until an error condition arises. In either case, you need to end the program by passing the wParam
value back to Windows in a return
statement.
The second argument in the call to GetMessage()
is the handle of the window for which you want to get messages. This parameter can be used to retrieve messages for one application window separately from another. If this argument is nullptr
, as it is here, GetMessage()
retrieves all messages for an application. This is an easy way of retrieving all messages for an application regardless of how many windows it has. It is also the safest way because you are sure of getting all the messages for your application. When the user of your Windows program closes the application window for example, the window is closed before the WM_QUIT
message is generated. Consequently, if you only retrieve messages by specifying a window handle to the GetMessage()
function, you cannot retrieve the WM_QUIT
message and your program is not able to terminate properly.
The last two arguments to GetMessage()
are integers that hold minimum and maximum values for the message IDs you want to retrieve from the queue. This allows messages to be retrieved selectively. A range is usually specified by symbolic constants. Using WM_MOUSEFIRST
and WM_MOUSELAST
as these two arguments would select just mouse messages, for example. If both arguments are zero, as you have them here, all messages are retrieved.
If there are no messages queued, the GetMessage()
function does not return control to your program. Windows allows execution to pass to another application, and you will only get a value returned from calling GetMessage()
when a message appears in the queue.
This mechanism was fundamental in enabling multiple applications to run under older versions of Windows and is referred to as cooperative multitasking because it depends on concurrent applications giving up control of the processor from time to time. After your program calls GetMessage()
, unless there is a message for it another application is executed and your program gets another opportunity to do something only if the other application releases the processor, perhaps by a call to GetMessage()
when there are no messages queued for it, but this is not the only possibility.
With current versions of Windows, the operating system can interrupt an application after a period of time and transfer control to another application. This mechanism is called pre-emptive multitasking because an application can be interrupted at any time. With pre-emptive multitasking, however, you must still program the message loop in WinMain()
using GetMessage()
as before, and make provision for relinquishing control of the processor to Windows from time to time in a long-running calculation (this is usually done using the PeekMessage()
API function). If you don’t do this, your application may be unable to respond to messages to repaint the application window when these arise. This can be for reasons that are quite independent of your application — when an overlapping window for another application is closed, for example.
The conceptual operation of the GetMessage()
function is illustrated in Figure 11-3.
Within the while
loop, the first call to TranslateMessage()
requests Windows to do some conversion work for keyboard-related messages. Then the call to DispatchMessage()
causes Windows to dispatch the message, which it does by calling the WindowProc()
function in your program to process the message. The return from DispatchMessage()
does not occur until WindowProc()
has finished processing the message. A WM_QUIT
message indicates that the program should end, so this results in FALSE
being returned to the application, which stops the message loop.
You have looked at all the bits that need to go into WinMain()
so now you can assemble them into a complete function:
// Listing OFWIN_1
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX WindowClass; // Structure to hold our window's attributes
static LPCTSTR szAppName {_T("OFWin")}; // Define window class name
HWND hWnd; // Window handle
MSG msg; // Windows message structure
WindowClass.cbSize = sizeof(WNDCLASSEX); // Set structure size
// Redraw the window if the size changes
WindowClass.style = CS_HREDRAW | CS_VREDRAW;
// Define the message handling function
WindowClass.lpfnWndProc = WindowProc;
WindowClass.cbClsExtra = 0; // No extra bytes after the window class
WindowClass.cbWndExtra = 0; // structure or the window instance
WindowClass.hInstance = hInstance; // Application instance handle
// Set default application icon
WindowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
// Set window cursor to be the standard arrow
WindowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
// Set gray brush for background color
WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
WindowClass.lpszMenuName = nullptr; // No menu
WindowClass.lpszClassName = szAppName; // Set class name
WindowClass.hIconSm = nullptr; // Default small icon
// Now register our window class
RegisterClassEx(&WindowClass);
// Now we can create the window
hWnd = CreateWindow(
szAppName, // the window class name
_T("A Basic Window the Hard Way"), // The window title
WS_OVERLAPPEDWINDOW, // Window style as overlapped
CW_USEDEFAULT, // Default position of upper left
CW_USEDEFAULT, // corner of our window as x,y...
CW_USEDEFAULT, // Default window size
CW_USEDEFAULT, // ....
nullptr, // No parent window
nullptr, // No menu
hInstance, // Program Instance handle
nullptr // No window creation data
);
ShowWindow(hWnd, nCmdShow); // Display the window
UpdateWindow(hWnd); // Redraw window client area
// The message loop
while(GetMessage(&msg, nullptr, 0, 0) == TRUE) // Get any messages
{
TranslateMessage(&msg); // Translate the message
DispatchMessage(&msg); // Dispatch the message
}
return static_cast<int>(msg.wParam); // End, so return to Windows
}
You need a WindowProc()
implementation to make this into a working Windows application. We will look at that after we explore how this code works.
After declaring the variables that you need in WinMain()
, all the members of the WindowClass
structure are initialized and the window is registered. Next you call the CreateWindow()
function to create the data for the physical appearance of the window based on the arguments and the data established in the WindowClass
structure that was previously passed to Windows using the RegisterClassEx()
function. The call to ShowWindow()
causes the window to be displayed according to the mode specified by nCmdShow
, and the UpdateWindow()
function signals that a message to draw the window client area should be generated.
The message loop retrieves messages until a WM_QUIT
message is issued for the application. When this occurs, the GetMessage()
function returns FALSE
and the loop ends. The value of the wParam
member of the msg
structure is passed back to Windows in the return
statement.
The WinMain()
function contains nothing that was application-specific beyond the general appearance of the application window. All of the code that makes the application behave in the way that you want is included in the message processing part of the program. This is the function WindowProc()
that you identify to Windows in the WindowClass
structure. Windows calls this function each time a message for your main application window is dispatched. Because you identify your WindowProc()
function to Windows by a function pointer, you can use any name for the function that you like. I’ll continue to call it WindowProc()
.
This example is simple, so you will be putting all the code to process messages in the one function, WindowProc()
. More generally, though, the WindowProc()
function is responsible for analyzing what a given message is and which window it is destined for and then calling one of a whole range of functions, each of which would be geared to handling a particular message in the context of the window concerned. However, the overall sequence of operations and the way in which WindowProc()
analyzes an incoming message is much the same in most application contexts.
The prototype of the WindowProc()
function is:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
The return type is LRESULT
, which is a Windows type that is normally equivalent to type long
. Because the function is called by Windows through a pointer (you set up the pointer with the address of WindowProc()
in WinMain()
in the WNDCLASSEX
structure), you need to qualify the function as CALLBACK
. I mentioned this specifier earlier, and its effect is the same as the WINAPI
specifier that determines how the function arguments are handled. You could use WINAPI
here instead of CALLBACK
, although the latter expresses better what this function is about. The four arguments that are passed to WindowProc()
provide information about the message causing the function to be called. The meaning of each argument is described in the following table:
ARGUMENT | MEANING |
---|---|
HWND hWnd |
A handle to the window in which the event causing the message occurred. |
UINT message |
The message ID, which is a 32-bit value indicating the type of message. |
WPARAM wParam |
A 32-bit (or 64-bit in 64-bit mode) value containing additional information depending on what sort of message it is. |
LPARAM lParam |
A 32-bit (or 64-bit in 64-bit mode) value containing additional information depending on what sort of message it is. |
The window that the incoming message relates to is identified by the first argument, hWnd
. In this case, you have only one window, so you can ignore it.
Messages are identified by the message
value that is passed to WindowProc()
. You can test this value against predefined symbolic constants, each of which identifies a particular message. General windows messages begin with WM_
, and typical examples are WM_PAINT
, which corresponds to a request to redraw part of the client area of a window, and WM_LBUTTONDOWN
, which indicates that the left mouse button was pressed. You can find the whole set of these by searching for WM_
in the MSDN Library.
Decoding the message that Windows sends is usually done using a switch
statement in the WindowProc()
function, based on the value of message
. You select the message types that you want to process by putting a case
statement for each message ID in the switch
. The typical structure of such a switch
statement is:
switch(message)
{
case WM_PAINT:
// Code to deal with drawing the client area
break;
case WM_LBUTTONDOWN:
// Code to deal with the left mouse button being pressed
break;
case WM_LBUTTONUP:
// Code to deal with the left mouse button being released
break;
case WM_DESTROY:
// Code to deal with a window being destroyed
break;
default:
// Code to handle any other messages
}
Every Windows program has something like this somewhere, although it will be hidden from sight in the Windows programs that you will write later using MFC. Each case corresponds to a particular message ID and provides suitable processing for that message. Any messages that a program does not want to deal with individually are handled by the default statement, which should hand the messages back to Windows by calling DefWindowProc()
. This is the Windows API function that provides default message handling.
In a complex program dealing with a wide range of possible Windows messages, this switch
statement can become large and rather cumbersome. When you get to use the Application Wizard to generate a Windows application that uses the MFC, you won’t have to worry about this because it is all taken care of for you and you never see the WindowProc()
function. You just need to supply the code to process the messages in which you are interested.
Windows sends a WM_PAINT
message to the program to signal that the client area should be redrawn. So you need to draw the client area of the window in response to the WM_PAINT
message.
You can’t go drawing in the window willy-nilly. Before you can write to the application window, you need to tell Windows that you want to do so, and get Windows’ authority to go ahead. You do this by calling the Windows API function BeginPaint()
, which should only be called in response to a WM_PAINT
message. It is used like this:
HDC hDC; // A display context handle
PAINTSTRUCT PaintSt; // Structure defining area to be redrawn
hDC = BeginPaint(hWnd, &PaintSt); // Prepare to draw in the window
The HDC
type represents a handle to a display context, or more generally a device context. A device context provides the link between the device-independent Windows API functions for outputting information to the screen or a printer, and the device drivers that support writing to the specific devices attached to your PC. You can also regard a device context as a token of authority that is handed to you on request by Windows and grants you permission to output some information. Without a device context, you simply can’t generate any output.
The BeginPaint()
function returns a handle to a display context. It requires two arguments. The first argument is a handle, hWnd
, for the window to which you want to write. The second argument is the address of a PAINTSTRUCT
variable PaintSt
, in which Windows will place information about the area that is to be redrawn in response to the WM_PAINT
message. I will ignore the details of this because you are not going to use it. You will just redraw the whole of the client area. You can obtain the coordinates of the client area in a RECT
structure with the statements:
RECT aRect; // A working rectangle
GetClientRect(hWnd, &aRect);
The GetClientRect()
function supplies the coordinates of the upper-left and lower-right corners of the client area for the window specified by the first argument. These coordinates are stored in the RECT
structure aRect
that you create, and you pass the address of this structure as the second argument. You can use aRect
to identify the area in the client area where the text is to be written by the DrawText()
function. Because your window has a gray background, you should alter the background of the text to be transparent, to allow the gray to show through; otherwise, the text appears against a white background. You can do this with this API function call:
SetBkMode(hDC, TRANSPARENT); // Set text background mode
The first argument identifies the device context and the second sets the background mode. The default option is OPAQUE
.
You can now write the text with the statement:
DrawText(hDC, // Device context handle
_T("But, soft! What light through yonder window breaks?"),
-1, // Indicate null terminated string
&aRect, // Rectangle in which text is to be drawn
DT_SINGLELINE| // Text format - single line
DT_CENTER| // - centered in the line
DT_VCENTER // - line centered in aRect
);
The first argument to the DrawText()
function is your certificate of authority to draw on the window, the display context hDC
. The next argument is the text string that you want to output. You could equally well have defined this in a variable and passed the pointer to the text as the second argument in the function call. The next argument, with the value −1
, signifies that your string is terminated with a null character. If it weren’t, you would put the count of the number of characters in the string here. The fourth argument is a pointer to a RECT
structure defining a rectangle in which you want to write the text. In this case, it is the whole client area of the window defined in aRect
. The last argument defines the format for the text in the rectangle. Here you have combined three specifications with a bitwise OR (|
). The string is written as a single line, with the text centered on the line and the line centered vertically within the rectangle. This places it nicely in the center of the window. There are also a number of other options, which include the possibility to place text at the top or the bottom of the rectangle, and to left- or right-justify it.
After you have written all that you want to display, you must tell Windows that you have finished drawing the client area. For every BeginPaint()
function call, there must be a corresponding EndPaint()
function call. Thus, to end processing of the WM_PAINT
message, you need the statement:
EndPaint(hWnd, &PaintSt); // Terminate window redraw operation
The hWnd
argument identifies your program window, and the second argument is the address of the PAINTSTRUCT
structure that was filled in by the BeginPaint()
function.
You might assume that closing the window closes the application, but to get this behavior, you actually have to add some more code. The reason that the application won’t close by default when the window is closed is that you may need to do some clearing up. It is also possible that the application may have more than one window. When the user closes the window by double-clicking the title bar icon or clicking the Close button, this causes a WM_DESTROY
message to be generated. Therefore, to close the application, you need to process the WM_DESTROY
message in WindowProc()
. You do this by generating a WM_QUIT
message:
PostQuitMessage(0);
The argument here is an exit code. This API function does exactly what its name suggests: it posts a WM_QUIT
message in the message queue for your application. This results in the GetMessage()
function that is called in WinMain()
returning FALSE
and ending the message loop, thus ending the program.
You have covered all the elements necessary to assemble the complete WindowProc()
function for the example. The code for the function is:
// Listing OFWIN_2
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message) // Process selected messages
{
case WM_PAINT: // Message is to redraw the window
HDC hDC; // Display context handle
PAINTSTRUCT PaintSt; // Structure defining area to be drawn
RECT aRect; // A working rectangle
hDC = BeginPaint(hWnd, &PaintSt);// Prepare to draw the window
// Get upper left and lower right of client area
GetClientRect(hWnd, &aRect);
SetBkMode(hDC, TRANSPARENT); // Set text background mode
// Now draw the text in the window client area
DrawText(
hDC, // Device context handle
_T("But, soft! What light through yonder window breaks?"),
-1, // Indicate null terminated string
&aRect, // Rectangle in which text is to be drawn
DT_SINGLELINE| // Text format - single line
DT_CENTER| // - centered in the line
DT_VCENTER); // - line centered in aRect
EndPaint(hWnd, &PaintSt); // Terminate window redraw operation
return 0;
case WM_DESTROY: // Window is being destroyed
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Apart from the last statement, the entire function body is just a switch
statement. A particular case
is selected based on the message ID that is passed to the function through the message
parameter. Because this example is simple, you need to process only two types of message: WM_PAINT
and WM_DESTROY
. You hand all other messages back to Windows by calling the DefWindowProc()
function after the switch
statement. The arguments to DefWindowProc()
are those that were passed to the function, so you are just passing them back as they are. Note the return
statement at the end of processing each message type. For the messages that you handle, a zero value is returned.
The Microsoft Foundation Classes (MFC) are a set of predefined classes that make it easy to develop Windows desktop applications with Visual C++. These classes represent an object-oriented approach to Windows programming that encapsulates the Windows API. MFC does not adhere strictly to the object-oriented principles of encapsulation and data hiding, principally because much of the MFC code was written before such principles were well established.
The process of writing a Windows program involves creating and using MFC objects or objects of classes derived from MFC. In the main, you’ll derive your own classes from MFC, with considerable assistance from the specialized tools in Visual C++ that make this easy. The objects of these MFC-based class types incorporate member functions for communicating with Windows, for processing Windows messages, and for sending messages to each other. Your derived classes inherit all of the members of their base classes. These inherited functions do practically all of the general grunt work necessary for a Windows application to work. All you need to do is to add data and function members to the classes to provide the application-specific functionality that you need in your program. In doing this, you’ll apply most of the techniques that you’ve been grappling with in the preceding chapters, particularly those involving class inheritance and virtual functions.
All the classes in MFC have names beginning with C, such as CDocument
or CView
. If you use the same convention when defining your own classes, or when deriving them from those in the MFC library, your programs will be easier to follow. Data members of an MFC class are prefixed with m_
. I’ll also follow this convention in the MFC examples.
You’ll find that the MFC uses Hungarian notation for many variable names, particularly those that originate in the Windows API. As you recall, this involves using a prefix of p
for a pointer, n
for an unsigned int, l
for long, h
for a handle, and so on. The name m_lpCmdLine
, for example, refers to a data member of a class (because of the m_
prefix) that is of type ‘long pointer to a string’. Because C++ has strong type checking that picks up the sort of misuse that used to happen regularly in C, this kind of notation isn’t essential, so I won’t use it generally for variables in the examples in the book. I will, however, retain the p
prefix for pointers and some of the other simple type denotations because this helps to make the code more readable.
You know from Chapter 1 that you can produce a Windows program using the Application Wizard without writing a single line of code. Of course, this uses the MFC library, but it’s quite possible to write a Windows program that uses MFC without using the Application Wizard. If you first scratch the surface by constructing the minimum MFC-based program, you’ll get a clearer idea of the fundamental elements involved.
The simplest program that you can produce using MFC is slightly less sophisticated than the example that you wrote earlier in this chapter using the raw Windows API. The example you’ll produce here has a window, but no text displayed in it. This is sufficient to show the fundamentals, so try it out.
In this chapter you’ve seen two ways of creating an elementary Windows application with Visual C++. You should now have a feel for the relationship between these two approaches. In subsequent chapters you’ll be exploring in more depth how you develop applications using the MFC.
TOPIC | CONCEPT |
---|---|
Windows API | The Windows API provides a standard programming interface by which a desktop application communicates with the Windows operating system. |
Windows messages | Windows communicates with a desktop application by passing messages to it. A message typically indicates that an event of some kind has occurred that requires action on the part of the application. |
Windows application structure | All Windows desktop applications must include two functions, WinMain() and WindowProc() , that will be called by the operating system. You can use any name you like for the WindowProc() function. |
The WinMain() function |
The WinMain() function is called by the operating system to begin execution of an application. The function includes code to set up the initial conditions required by the application, to specify the application window, and to retrieve messages from the operating system that are intended for the application. |
The WindowProc() function |
The Windows operating system calls a particular function that is usually called WindowProc() to process messages. A desktop application identifies the message processing function for each application window in WinMain() by passing a pointer to the message processing function to a Windows API function as part of a WNDCLASSEX structure. |
The Microsoft Foundation Classes | The MFC consists of a set of classes that encapsulate the Windows API and greatly simplify the development of Windows desktop applications. |
18.224.31.50