Chapter 12. Windows Programming Concepts

WHAT YOU WILL LEARN IN THIS CHAPTER:

  • The basic structure of a window

  • The Windows API and how it is used

  • Windows messages and how you deal with them

  • The notation that is commonly used in Windows programs

  • The basic structure of a Windows program

  • How you create an elementary program using the Windows API and how it works

  • Microsoft Foundation Classes

  • The basic elements of an MFC-based program

  • Windows Forms

  • The basic elements of a Windows Forms application

In this chapter, you will take a look at the basic ideas that are involved in every Windows program in C++. You'll first develop a very simple example that uses the Windows operating system API directly. This will enable you to understand how a Windows application works behinds the scenes, which will be useful to you when you are developing applications using the more sophisticated facilities provided by Visual C++ 2010. You will then see what you get when you create a Windows program using the Microsoft Foundation Classes, better known as the MFC. Finally, you'll create a basic program using Windows Forms that will execute with the CLR, so by the end of the chapter you'll have an idea of what each of the three approaches to developing a Windows application involves.

WINDOWS PROGRAMMING BASICS

With Visual C++ 2010 you have three basic ways of creating an interactive Windows application:

  • Using the Windows API. This is the fundamental interface that the Windows operating system provides for communications between itself and the applications that are executing under its control.

  • Using the Microsoft Foundation Classes, better known as the MFC. This is a set of C++ classes that encapsulate the Windows API.

  • Using Windows Forms. This is a forms-based development mechanism for creating applications that execute with the CLR.

In a way, these three approaches form a progression from the most programming-intensive to the least programming-intensive. With the Windows API, you are writing code throughout — all the elements that make up the GUI for your application must be created programmatically. With MFC applications there's some help with GUI build in that you can assemble controls on a dialog form graphically and just program the interactions with the user; however, you still are involved in a lot of coding. With a Windows Forms application, you can build the complete GUI, including the primary application window, by assembling the controls that the user interacts with graphically. You just place the controls wherever you want them in a form window, and the code to create them is generated automatically. Using Windows Forms is by far the fastest and easiest mechanism for generating an application because the amount of code that you have to write is greatly reduced compared with the other two possibilities. The code for a Windows Forms application also gains all the benefits of executing with the CLR.

Using the MFC involves more programming effort than Windows Forms, but you have more control over how the GUI is created and you end up with a program that will execute natively on your PC. Because using the Windows API directly is the most laborious method for developing an application, 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 principles of the mechanism that all Windows applications use to work with the operating system under the covers. You'll explore the fundamentals involved in all three possibilities for Windows application development in this chapter and investigate using MFC and Windows Forms in more detail later in the book. Of course, it also is possible to develop applications in C++ that do not require the Windows operating system, and games programs often take this approach when the ultimate in graphics performance is required. Although this is itself an interesting topic, it would require a whole book to do it justice, so I won't pursue this topic further.

Before getting to the examples in this chapter, 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 to illustrate the various elements that go to make up a window.

Elements of 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 12-1.

FIGURE 12-1

Figure 12.1. FIGURE 12-1

The example actually generated two windows. The larger window with the menu and the tool bars is the main, or parent window, and the smaller window is a child window of the parent. Although 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, closing the parent window automatically closes the child window as well. 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 with the left mouse button, 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 a good idea always 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.

The client area is the part of the window where you usually want your program to write text or graphics. You 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 above, 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.

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 before I move on. 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; however, in Windows a window is a generic term covering a whole range of entities. In fact, almost any entity that is displayed is a window — for example, a dialog box is a window and each button is also a window. 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 they 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.

Windows Programs and the Operating System

When you write a Windows program, your program is subservient to the operating system and Windows is in control. Your program must not 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 for Windows being in control 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.

Event-Driven Programs

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 required 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 user drags the window of another application that is active alongside your program and this action uncovers part of the client area of the window devoted to your application, your application may need to redraw that part of the window.

Windows Messages

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. Thus a Windows message is simply a record of the data relating to an event, and the messagequeue for an application is just a sequence of such messages waiting to be processed by the application. 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 — many 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, 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 what to do about it.

Fortunately, you don't need to write code to process every message. You can filter out those that are of interest in your program, 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 provided by Windows called DefWindowProc(), which provides default message processing.

The Windows API

All of the communications between any 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 are provided as a standard with the Windows operating system that provides 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 some kinds of 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++ 2010 makes the life of the application developer very much easier. Visual C++ 2010 packages the Windows API in a way that structures the API 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. Also, for applications targeting the CLR, you have a facility called Windows Forms where the code necessary to create a GUI is all created automatically. All you have to do is supply the code necessary to handle the events in the way that your application requires. You'll be creating a Windows Forms application a little later in this chapter.

Visual C++ also provides Application Wizards that create basic applications of various kinds, including MFC and Windows Forms-based 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 much more detail when we get to write some more practical examples using the MFC Application Wizard.

Windows Data Types

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, 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 the types used by Windows, as well as 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.

Notation in Windows Programs

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

This 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 was easily done in the C language, a precursor of C++. 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.

THE STRUCTURE OF A WINDOWS PROGRAM

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 process messages for 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. In fact, Windows also calls WinMain(). This is illustrated in Figure 12-2.

FIGURE 12-2

Figure 12.2. FIGURE 12-2

The function WinMain() communicates with Windows by calling some of the Windows API functions. The same applies to WindowProc(). The integrating factor in your Windows program is Windows itself, which links to both WinMain() and WindowProc(). You will take a look at what the pieces are that make up WinMain() and WindowProc() and then assemble the parts into a working example of a simple Windows program.

The WinMain() Function

The WinMain() function is the equivalent of the main() function in a console program. It's where execution starts and where the basic initialization for the rest of 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, which is new to you. 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.

Note

If you really want to know about calling conventions, they are described in the documentation that comes with Visual C++ 2010. WINAPI is defined as __ stdcall, and putting this modifier before a function name indicates that the standard Windows calling convention is to be used. This requires that the parameters are pushed on the stack in reverse order and that the function called will clean up the stack when it ends. The CALLBACK modifier that you will see later in this chapter is also defined as __stdcall and is therefore the equivalent of WINAPI. The standard C++ calling convention is specified by the __cdecl modifier.

The four arguments that are passed by Windows to your WinMain() function contain important data. The first argument, 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 under Windows 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 shortly, handles are also used to identify all sorts of other things. Of course, all handles in a particular context — application instance handles, for example — need to be different from one another.

The next argument passed to your WinMain() function, hPrevInstance, is a legacy from the 16-bit versions of the Windows operating system, and you can safely ignore it. On current versions of Windows, it is always null. The next argument, lpCmdLine, is a pointer to a string containing the command line that started the program. For instance, if you started it using the Run command from the Start button menu of Windows, the string contains everything that appears in the Open box. Having 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.

The last argument, 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, the shortcut for the program might specify that the program should be minimized when it starts. This argument can take one of a fixed set of values that are defined by symbolic constants such as SW_SHOWNORMAL and SW_SHOWMINNOACTIVE. There are a number of other constants like these that define the way a window is to be displayed, and they all begin SW_. Other examples are SW_HIDE or SW_SHOWMAXIMIZED. 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.

If you want to know what all the other constants are that specify how a window displays, you can find a complete list of the possible values if you search for WinMain in the MSDN Library. You can access the MSDN library online at http://msdn2.microsoft.com/en-us/library/default.aspx.

The function WinMain() in your Windows program needs to do four things:

  • Tell Windows what kind of window the program requires

  • Create the program window

  • Initialize the program window

  • Retrieve Windows messages intended for the program

We will take a look at each of these in turn and then create a complete WinMain() function.

Specifying a Program Window

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. Do not confuse this with a C++ class — the MFC defines a class that represents a window but, this is not the same at all. You need to create a variable of type WNDCLASSEX, and give values to each of its members (just like filling in a form). 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've already registered.

The definition of the WNDCLASSEX structure is as follows:

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

All you need to do now is 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 CS_.

Note

You'll find all the possible constant values for style if you search for WNDCLASSEX in the MSDN Library that you'll find at http://msdn2.microsoft.com/en-us/library.

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 must 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.

The members hIcon, hCursor, and hbrBackground are handles that in turn define the icon that represents the application when minimized, the cursor the window uses, and the background color of the client area of the window. (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 are set using Windows API functions. For example:

WindowClass.hIcon = LoadIcon(0, IDI_APPLICATION);
WindowClass.hCursor = LoadCursor(0, 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 null, in which case Windows would provide the default icon and cursor. If you set hbrBackground to null, 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 zero if there is no menu for the window. 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 = L"OFWin";       // Define window class name
WindowClass.lpszClassName = szAppName;     // Set class name

I have defined szAppName as a Unicode string here. In fact, the LPCTSTR type is defined as const wchar_t* if UNICODE is defined for the application, or const char* if it is not. Thus, the definition for szAppName here assumes a Unicode application.

The last member is hIconSm, which identifies a small icon associated with the window class. If you specify this as null, Windows searches for a small icon related to the hIcon member and uses that.

In fact, the WNDCLASSEX structure replaces another structure, WNDCLASS, that was used for the same purpose. The old structure did not include the cbSize member that stores the size of the structure in bytes or the hIconSm member.

Creating a Program Window

After all the members of your WNDCLASSEX structure have been set to the values required, the next step is to tell Windows about it. You do this using the Windows API function RegisterClassEx(). Given that your structure 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. If you were using the obsolete WNDCLASS structure that I mentioned, you would have to use a different function here, RegisterClass().

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 use the function CreateWindow() for this. The window class that you've already created determines the broad characteristics of a window, and further arguments to the function CreateWindow() add additional characteristics. Because an application may have several windows in general, the function CreateWindow() returns a handle to the window 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 if you want to use them. You will look at a typical use of the CreateWindow() function at this point. This might be:

HWND hWnd;                              // Window handle
...
hWnd = CreateWindow(
        szAppName,                      // the window class name
        "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
        0,                              // No parent window
        0,                              // No menu
        hInstance,                      // Program Instance handle
        0                               // No window creation data
      );

The variable hWnd of type HWND is a 32-bit integer handle to a window, or 64-bit in 64-bit mode. You'll use this variable to record the value that the CreateWindow() function returns that identifies the window. The first argument that you pass to the function is the class name. This is used by Windows to identify the WNDCLASSEX struct that you passed to it previously, in the RegisterClassEx() function call, so that the 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, actually 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 of the window. 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 zero, indicating that the window being created is not a child window (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 zero, 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 zero 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.

Note

The Windows API also includes a CreateWindowEx() function that you use to create a window with extended style information.

After calling the CreateWindow() function, the window now exists but is not yet displayed on the screen. You need to 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 the function CreateWindow(). The second is the value nCmdShow that was passed to WinMain(), and that indicates how the window is to appear onscreen.

Initializing the Program Window

After calling the function ShowWindow(), the window appears onscreen 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 are not considered to be permanent — if you want the client area contents to be retained, you can't afford to 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. (In general, there can be several windows in an application.) The result of the call is that Windows sends a message to your program requesting that the client area be redrawn.

Dealing with Windows Messages

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.

Queued and Non-Queued Messages

I oversimplified Windows messaging when I introduced the idea earlier. 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.

The Message Loop

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, 0, 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 variable msg, which is a struct of type MSG, contains a number of different 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 was now possible. You might assume that it was of type WORD (which is 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.

Note

There are prefixes other than WM for messages destined for other types of windows than a general window.

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 window separately from another. If this argument is 0, 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.

Multitasking

If there are no messages queued, the GetMessage() function does not come back to your program. Windows allows execution to pass to another application, and you 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 their control of the processor from time to time. After your program calls GetMessage(), unless there is a message for your program, 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 in any event. 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 12-3.

FIGURE 12-3

Figure 12.3. FIGURE 12-3

Within the while loop, the first function call to TranslateMessage() requests Windows to do some conversion work for keyboard-related messages. Then the call to the function DispatchMessage() causes Windows to dispatch the message or, in other words, to call the WindowProc() function in your program to process the message. The return from DispatchMessage() does not occur until WindowProc() has finished processing the message. The WM_QUIT message indicates that the program should end, so this results in FALSE being returned to the application that stops the message loop.

A Complete WinMain() Function

You have looked at all the bits that need to go into the function 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 = L"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(0, IDI_APPLICATION);

  // Set window cursor to be the standard arrow
  WindowClass.hCursor = LoadCursor(0, IDC_ARROW);

  // Set gray brush for background color
  WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));

  WindowClass.lpszMenuName = 0;             // No menu
  WindowClass.lpszClassName = szAppName;    // Set class name
  WindowClass.hIconSm = 0;                  // Default small icon

  // Now register our window class
  RegisterClassEx(&WindowClass);
// Now we can create the window
  hWnd = CreateWindow(
          szAppName,                       // the window class name
          L"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
          CW_USEDEFAULT,          // ....
          0,                      // No parent window
          0,                      // No menu
          hInstance,              // Program Instance handle
          0                       // No window creation data
        );

  ShowWindow(hWnd, nCmdShow);     // Display the window
  UpdateWindow(hWnd);             // Cause window client area to be drawn

  // The message loop
  while(GetMessage(&msg, 0, 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
}
                                                                       
A Complete WinMain() Function

How It Works

After declaring the variables you need in the function, all the members of the WindowClass structure are initialized and the window is registered.

The next step is to call the CreateWindow() function to create the data for the physical appearance of the window, based on the arguments passed 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.

Finally, the message loop continues to retrieve messages for the application until a WM_QUIT message is obtained, whereupon 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.

Message Processing Functions

The function WinMain() contained 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.

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 was and which window it was 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 particular window concerned. However, the overall sequence of operations, and the way in which the function WindowProc() analyses an incoming message, is much the same in most application contexts.

The WindowProc() Function

The prototype of our WindowProc() function is:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
                            WPARAM wParam, LPARAM lParam);

The return type is LRESULT, which is a type defined by Windows and is normally equivalent to type long. Because the function is called by Windows through a pointer (you set the pointer up 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 defined by Windows that determines how the function arguments are handled. Therefore, you could use WINAPI here instead of CALLBACK, although the latter expresses better what this function is about. The four arguments that are passed provide information about the particular message causing the function to be called. The meaning of each of these arguments 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, which is passed to the function. In this case, you have only one window, so you can ignore it.

Messages are identified by the value message that is passed to WindowProc(). You can test this value against predefined symbolic constants, each of which relates to 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 a Windows Message

The process of decoding the message that Windows is sending is usually done using a switch statement in the WindowProc() function, based on the value of message. Selecting the message types that you want to process is then just a question of putting a case statement for each case in the switch. The typical structure of such a switch statement, with arbitrary cases included, is as follows:

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 may be hidden from sight in the Windows programs that you will write later using MFC. Each case corresponds to a particular value for the 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 providing default message handling.

In a complex program dealing specifically 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, you won't have to worry about this because it is all taken care of for you and you never see the WindowProc() function. All you need to do is to supply the code to process the particular messages in which you are interested.

Drawing the Window Client Area

Windows sends a WM_PAINT message to the program to signal that the client area of an application should be redrawn. So in your example, you need to draw the text in 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 as follows:

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 type HDC defines what is called 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 provides you with a display context as a return value and requires two arguments to be supplied. The window to which you want to write is identified by the window handle, hWnd, which you pass as the first argument. The second argument is the address of a PAINTSTRUCT variable PaintSt, in which Windows places information about the area 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, which is passed through the second argument as a pointer. You can then use this definition of the client area for your window when you write the text to the window using 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
         L"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 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.

Ending the Program

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 the WindowProc() function. You do this by generating a WM_QUIT message with the following statement:

PostQuitMessage(0);

The argument here is an exit code. This Windows 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 in WinMain() returning FALSE and ending the message loop, so ending the program.

A Complete WindowProc() Function

You have covered all the elements necessary to make up the complete WindowProc() function for your example. The code for the function is as follows:

// Listing OFWIN_2
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
                          WPARAM wParam, LPARAM lParam)
{
HDC hDC;                       // Display context handle
  PAINTSTRUCT PaintSt;           // Structure defining area to be drawn
  RECT aRect;                    // A working rectangle

  switch(message)                // Process selected messages
  {
    case WM_PAINT:                     // Message is to redraw the window
      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
             L"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;

    default:                      // Any other message - we don't
                                  // want to know, so call
                                  // default message processing
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
}
                                                                
A Complete WindowProc() Function

How It Works

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 different messages: WM_PAINT and WM_DESTROY. You hand all other messages back to Windows by calling the DefWindowProc() function in the default case for the switch. 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 you handle, a zero value is returned.

A Simple Windows Program

Because you have written WinMain() and WindowProc() to handle messages, you have enough to create a complete source file for a Windows program using just the Windows API. The complete source file simply consists of an #include directive for the windows.h header file, a prototype for the WindowProc function, and the WinMain and WindowProc functions that you have already seen:

// Ex12_01.cpp   Native windows program to display text in a window
#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
                          WPARAM wParam, LPARAM lParam);

   // Insert code for WinMain() here (Listing OFWIN_1)

   // Insert code for WindowProc() here (Listing OFWIN_2)
                                                               
A Simple Windows Program

Of course, you'll need to create a project for this program, but instead of choosing Win32 Console Application as you've done up to now, you should create this project using the Win32 Project template. You should elect to create it as an empty project and then add the Ex12_01.cpp file to hold the code.

WINDOWS PROGRAM ORGANIZATION

In the previous example, you saw an elementary Windows program that used the Windows API and displayed a short quote from the Bard. It's unlikely to win any awards, being completely free of any useful functionality, but it does serve to illustrate the two essential components of a Windows program: the WinMain() function that provides initialization and setup, and the WindowProc() function that services Windows messages. The relationship between these is illustrated in Figure 12-5.

FIGURE 12-5

Figure 12.5. FIGURE 12-5

It may not always be obvious in the code that you will see, but this structure is at the heart of all Windows programs, including programs written for the CLR. Understanding how Windows applications are organized can often be helpful when you are trying to determine why things are not working as they should be in an application. The WinMain() function is called by Windows at the start of execution of the program, and the WindowProc() function, which you'll sometimes see with the name WndProc(), is called by the operating system whenever a message is to be passed to your application's window. In general, there typically is a separate WindowProc() function in an application for each window in an application.

The WinMain() function does any initialization that's necessary and sets up the window or windows that are the primary interface to the user. It also contains the message loop for retrieving messages that are queued for the application.

The WindowProc() function handles all the messages for a given window that aren't queued, which includes those initiated in the message loop in WinMain(). WindowProc(), therefore, ends up handling both kinds of messages. This is because the code in the message loop sorts out what kind of message it has retrieved from the queue, and then dispatches it for processing by WindowProc(). WindowProc() is where you code your application-specific response to each Windows message, which should handle all the communications with the user by processing the Windows messages generated by user actions, such as moving or clicking the mouse or entering information at the keyboard.

The queued messages are largely those caused by user input from either the mouse or the keyboard. The non-queued messages, for which Windows calls your WindowProc() function directly, are either messages that your program created, typically as a result of obtaining a message from the queue and then dispatching it, or messages that are concerned with window management — such as handling menus and scrollbars, or resizing the window.

THE MICROSOFT FOUNDATION CLASSES

The Microsoft Foundation Classes (MFC) are a set of predefined classes upon which Windows programming with Visual C++ is built. 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++ 2010 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. These derived classes, of course, 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 customize 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.

MFC Notation

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 examples that use MFC.

You'll find that 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 'pointer to long'. This practice of explicitly showing the type of a variable in its name was important in the C environment because of the lack of type checking; because you could determine the type from the name, you had a fair chance of not using or interpreting its value incorrectly. The downside is that the variable names can become quite cumbersome, making the code look more complicated than it really is. 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.

How an MFC Program Is Structured

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.

USING WINDOWS FORMS

A Windows form is an entity that represents a window of some kind. By a window, I mean window in its most general sense, being an area on the screen that can be a button, a dialog, a regular window, or any other kind of visible GUI component. A Windows form is encapsulated by a subclass of the System::Windows::Forms::Form class, but you don't need to worry about this much initially, because all the code to create a form is created automatically. To see just how easy it's going to be, create a basic window using Windows Forms that has a standard menu.

SUMMARY

In this chapter you've seen three different ways of creating an elementary Windows application with Visual C++ 2010. You should now have a feel for the essential differences between these three approaches. In later chapters of the book, you'll be exploring in more depth how you develop applications using the MFC and using Windows Forms.

WHAT YOU LEARNED IN THIS CHAPTER

TOPIC

CONCEPT

Windows API

The Windows API provides a standard programming interface by which an application communicates with the operating system.

The WinMain() function

All Windows applications include a WinMain() function that is called by the operating system to begin execution of the application. The WinMain() function also includes code to retrieve messages from the operating system.

Message processing

The Windows operating system calls a particular function in an application to handle processing of specific messages. An application identifies the message processing function for each window in an application by calling a Windows API function.

The MFC

The MFC consists of a set of classes that encapsulate the Windows API and simplify programming using the Windows API.

Windows Forms applications

A Windows Forms application executes with the CLR. The windows in a Windows Forms application can be created graphically with all the required code being generated automatically.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.50.183