In this chapter we
analyse the code required for a simple windows program based on
a custom window class. The purpose of the program is to show you
how message are distributed and graphical output is performed.
The only thing the program actually does is to count the number
of paint messages the window is receiving in order to demonstrate
which events cause a repaint message.
Contents of this chapter
To compile and modify the program you need the following files:
Example1.C - the main code file
Example.RC - the main resource
file
Example.DEF - the module definition
file, required only for Win16
Versinfo.RC
- Information about program version,
Included by Example.RC
Example1.h - Header file
Example1.ICO - Icon file containing program icon in binary
form
Ex1W16.MAK - Project file for Visual C++ 1.x
Ex1W32.MAK - Project file for Visual C++ 2.x
Example1.IDE - Project file for Borland C++ 4.x for both
Win16 and Win32
Ex1W16.EXE - 16-bit executable file for Windows 3.x
Ex1W32.EXE - 32-bit executable file for Windows 95/ Windows
NT
Click here to download the archive to your computer.
Let's have a look at the program entry point, the procedure WinMain. This is where it all starts and what we get is four parameters.
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
The first parameter hInst is a handle for the current instance of our program. Now what do we mean by instance? As you know, you can start most windows programs multiple times and you will get another window every time. Take Notepad e.g. and start it 10 times say. You've now got 10 instances of Notepad and each of them are independent from each other. Internally Windows loads the program's code only once in order to save memory space, but it gives every instance its own data area and stack and you do not have to bother about whether there is one or many instances of your program.
This handle is very important as we will need it later on as a parameter for various functions and so we store it in a global variable.
hInstance=hInst;
Many people would tell you its bad to use global variables at all and generally I agree. Global variables cause a number of problems and make programs less understandable and maintainable. In this case however its different. Basically this is because it is not really a variable but more of a constant, since it will not change at any time. Unfortunately C does not offer a feature to set the value of a variable only once but since we set it right at the beginning and we know it wont change all we have to make sure is that we do not change it by mistake.
The second parameter of WinMain is hPrevInst. Under Win16 this is a handle to a previous instance, if any, otherwise it is NULL. Basically this handle indicates in which memory segment the data of the previous instance is located. Under Win32 things have changed a bit. First there is no memory segmentation any more and second instances are more protected against each other which makes this parameter obsolete and it will thus always be NULL. The only function that makes use of this handle under Win16 is GetInstanceData, but since there is no such handle in Win32 it has been deleted and so its better to forget about it straight way.
However in Win16 this handle has another function - the detection of the first instance of a program which is the case when hPrevInst is NULL. This is important because only the first instance must register user defined window classes that the program wants to use (we will come to that in a minute). Again in Win32 a program does not know whether it is the first instance and thus it must always register the window classes required.
We use the following code to detect whether we are the first instance and if so we call our own function to register the window classes. Since in Win32 hPrevInst is always NULL this will also work fine for 32-bit programs. A description of what RegisterWindowClasses does can be found in the following paragraph.
if (hPrevInst == NULL) if (!RegisterWindowClasses(hInstance)) return FALSE; // registration failed!
After the window class has been registered it is now time to create our application window. For that we use the function CreateWindow and if the window can be created as specified we will receive a window handle in return. The CreateWindow function requires a number of parameter which I will not explain in detail since a precise description is provided in the online help. The window will have the title "My first Windows Program" and its size will be 400 by 300 pixels. With the CW_USEDEFAULT parameters we leave it up to Windows where on the screen the window will be shown later on.
// Create our Main Application Window hAppWnd=CreateWindow(szWndClassName,"My First Windows Program", WS_OVERLAPPEDWINDOW|WS_HSCROLL|WS_VSCROLL, CW_USEDEFAULT,CW_USEDEFAULT,400,300, NULL,NULL,hInstance,NULL );
As it is always good practise to check the return value of API functions we now check whether the window handle is valid. If it is not i.e. if the window handle is NULL we terminate the program by returning FALSE.
if (!hAppWnd) return FALSE; // Create Window failed
The window is now created but it is not visible on the screen yet (Note: if you want a window to be visible immediately after creation add the window style WS_VISIBLE). To display the window we call ShowWindow which requires the window handle and a flag indicating how the window should be shown. The latter can be for example SW_SHOWNORMAL if the window should be shown in its given size, SW_SHOWMINIMIZED or SW_SHOWMAXIMIZED if it should be initially an icon or full screen or any other of the SW_ constants defined in windows.h (see function ShowWindow in the SDK help file).
For our main window however we should not set this ourselves but use the parameter nCmdShow which is the last parameter of the WinMain procedure. Usually this will be set to SW_SHOWNORMAL but the user can specify otherwise in the program manager. Afterwards we force the window to paint its contents by calling UpdateWindow.
ShowWindow(hAppWnd,nCmdShow); UpdateWindow(hAppWnd);
Now that the window is created we come to the most important part of the WinMain procedure: the message loop. This is a simple while loop that will poll message form the message queue and dispatch them to the associated windows for further processing.
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); /* translates virtual key codes */ DispatchMessage(&msg); /* dispatches message to window */ }
There is another parameter in the WinMain procedure that I have not talked about yet. lpCmdLine is a pointer to a zero terminated string with the command line parameters specified in the program manger of Win 3.x or the shortcut properties of Win 95 respectively. Unlike in DOS or UNIX programs the parameters are not split up automatically i.e. if you call your program e.g. with "C:\TEST\MYPROG.EXE -f hello.tst -n" lpCmdLine will point to a string containing "-r hello.tst -n".
Although command line options are pretty unimportant in a GUI environment anyway there is one thing the lpCmdLine should always be used for: if your program deals with files you should always check whether lpCmdLine contains a file name and if so open it. This is important since many users start programs by double clicking on a document file in the file manager or the explorer. Provided that the file extension of the document file is assigned to your application, these programs will then call your program and specify the documents file name in the lpCmdLine Parameter.
In the description of the WinMain procedure above I have missed to explain what RegisterWindowClasses actually does. This function is defined next in the C file as follows:
static BOOL RegisterWindowClasses(HINSTANCE hFirstInstance)
This function, which is called from the WinMain for the first instance in a Win16 program and for every instance in a Win32 program, requires a handle to the instance and returns a boolean parameter indicating whether the function was successful (return value is TRUE) or not (return value is FALSE). I also added the keyword static which makes the function invisible i.e. inaccessible outside this module. It is good practise to declare every function that is not called from outside as static as you can find out more easily where this function is called from and you can use the function name again in other modules.
Lets just discuss what a window class is. A window class defines general properties for all windows that are derived from this class i.e. once a class has been defined you can create any number of windows based on this description and they will all have certain things in common like the background color, the icon that is shown when the window is minimised or most important the Windows procedure which handles all events concerning the window.
In order to register a window class we need to fill a structure of type WNDCLASS which is defined in windows.h. For that we define a local variable of this type and set the name of the new window class.
{ WNDCLASS wc; wc.lpszClassName = szWndClassName; // Name of the Window Class
The name we set here for our class can be virtually any name that takes our fancy except the name of predefined window classes. Predefined classes are BUTTON, STATIC, LISTBOX, COMBOBOX, SCROLLBAR and EDIT which are the names of all standard controls provided by Windows. The class you register will be available for your application only and be invisible to others; hence you also do not need to worry whether the name you are giving your class has already been taken by another application.
The name is not given here directly but it is hidden in the constant string array szWndClassName which I have defined at the beginning of the file as:
static const char szWndClassName[]={ "MYWNDCLASS" };
Hence the name of the class we are registering is MYWNDCLASS. We need this name also for the function CreateWindow which is the reason why I have put it in a variable. Again as we only need the string in this module it is saver to define it as static which makes it invisible to other source code modules.
Next we set the instance handle and some class style flags. A concise description of possible class style flags can be found in the online help. The style I used here forces the window to redraw completely every time the size of the window changes. You might want to try out what effect it has if you do not specify these flags. For that set wc.style to zero and recompile the program.
wc.hInstance = hFirstInstance; // Handle of program instance wc.style = CS_HREDRAW|CS_VREDRAW; // Combination of Class Styles
Now we need to specify the address of a function handling all the events concerning the window. This function is called the windows procedure and you'll find the body for the function in the following paragraph. Since all windows derived from this class share the same window procedure we have a problem if more than one window per program instance is derived from this class. How can we give each window its own private data? The answer lies in the cbWndExtra parameter which gives the number of bytes allocated for each window to store user defined data. In C++ this is e.g. used to store a pointer to a C++ object. To set and retrieve the data Windows offers the functions SetWindowWord and SetWindowLong or GetWindowWord and GetWindowLong respectively.
wc.lpfnWndProc = AppWndProc; // Address of Window Procedure wc.cbClsExtra = 0; // Extra Bytes allocated for this Class wc.cbWndExtra = 0; // Extra Bytes allocated for each Window
The rest of the structure members defines the basic appearance of the windows created from this class. First we load the Icon that is displayed when the window is minimised from the resource file. LoadIcon does the job by returning a handle to the icon. To identify the resource we use the macro MAKEINTRESOURCE which is also defined in windows.h and which takes a numeric value uniquely identifying a resource. Its up to us to assign numbers or names to resources so I have defined ICON_APPWND in the example1.h header file as:
#define ICON_APPWND 100
If you want to have your own cursor when the user is in the window you can use LoadCursor accordingly. In this case we use one of Windows' standard cursors which is why we set the instance handle to NULL. Finally we set the background colour to white and the menu name to NULL.
wc.hIcon = LoadIcon(hFirstInstance, MAKEINTRESOURCE(ICON_APPWND)); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL;
Now that all members of the structure describing the window class have been set we register the window by passing a pointer to the structure on to windows.
if (!RegisterClass(&wc)) /* Register the class */ return FALSE; /* RegisterClass failed */
When your program gets more complex you might need to register other windows classes.
Where all the code we have looked at so far is executed in the first few milliseconds after program execution we come now to the heart of your program, the windows procedure. As I mentioned before this is where Windows sends all events concerning the window to and where we can decide how to react to them. This is also the place where you will extend your program in order to add more functionality.
Let's have a look at the general structure of a window procedure first:
LRESULT FAR PASCAL WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { case WM_... /* process a message */ break; default: /* let Windows handle the message */ return DefWindowProc(hWnd,msg,wParam,lParam); } return 0L; }
As the window procedure is called by Windows it must always be defined as PASCAL i.e. it must use the Pascal calling convention. The keyword FAR is important for Win16 only but its no mistake to specify it for Win32 either. The window procedure receives four parameters from Windows and returns a 32 bit integer value.
The first parameter is the handle of the window which is exactly the one we received from the CreateWindow procedure in the WinMain. The second parameter is a message number indicating the type of event that has occurred. The value and meaning of the last two parameters are depending on the message.
All window messages are defined in windows.h and they all begin with the prefix WM_. There are hundreds of messages and you will never need to process all of them in a window procedure. Here are some of the most often used which you can look up in the help file.
WM_CREATE WM_PAINT WM_DESTROY WM_SIZE WM_LBUTTONDOWN WM_ACTIVATE WM_COMMAND
All messages that you do not process must be passed on to the default message handler of windows. To do that the function DefWindowProc is called in the default branch of the switch statement.
Now let's look how the Window procedure for our sample application has been implemented:
LRESULT FAR PASCAL AppWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) { static int iPaintCount; // Count the number of Paint Messages received static HBRUSH hFillBrush; // handle to a brush which we use for filling the ellipse
First we define a variable which we use later on to count the number of WM_PAINT messages that have been received. This variable must be defined as static in order to preserve its value when we return control to Windows. Otherwise its value would be some random number every time Windows calls the window procedure. And we need another static variable to store the handle of the brush we want to fill the ellipse with.
Now we filter the messages and the first message we're going to process is WM_CREATE:
switch(msg) { case WM_CREATE: iPaintCount=0; hFillBrush=CreateSolidBrush(RGB(0,255,255)); // Create a cyan brush break;
This message is received only once and that is when the window is created. We receive this message from Windows as a result of the call to CreateWindow we made in the procedure WinMain. So at this point, the program will still be in the WinMain waiting for CreateWindow to return, which will only be the case after we finished processing our WM_CREATE message. This message is now used for initialising variables and data structures and allocating all kinds of resources required by the window. In our case we set the counter variable to zero and create a brush that we will use for filling the ellipse. From CreateSolidBrush we receive a handle to a brush of the desired color which is specified in terms of its red, green and blue content using the RGB macro. This handle is a resource allocated by windows on behalf of your application and must be freed later on using DeleteObject.
The next message in line is the WM_COMMAND message. It deals with menu commands only. By examining the wParam parameter we can determine which menu command the user selected. All menu commands are associated with a number which is defined in the example1.h file; all beginning with IDM_ standing for IDentifier Menu.
case WM_COMMAND: switch(wParam) { case IDM_ABOUT: DialogBox(hInstance,MAKEINTRESOURCE(DLG_ABOUT),hWnd,AboutDlgProc); break; case IDM_QUIT: DestroyWindow(hWnd); break; } break;
There are only two commands in the menu the About and the Quit command. If the user decides to quit, we simply destroy the window using DestroyWindow. The WM_DESTROY message will then do everything else that is necessary. If the user selected the about command the DialogBox command is used to display a windows defined in the resource file. This is described in more detail in the Dialogue Example.
In order to force a repaint of the window we allow the user to press the left mouse button. This is again easy to detect as we will be sent the following message:
case WM_LBUTTONDOWN: // Invalidate the window's client area InvalidateRect(hWnd,NULL,FALSE); break;
There are other messages similar to that indicating other mouse events such as WM_LBUTTONUP, WM_MOUSEMOVE etc.
What we do here is not repainting the window directly but rather invalidating the window's client area. The client area is the area of the window inside its borders i.e. all the space that is at your disposal and that you are responsible for drawing. InvalidateRect takes three parameters: the handle of the windows which client area you want to invalidate, the part of the client area you want to invalidate, and a flag indicating whether the invalidated parts should be erased first, or whether you want to paint over the existing contents. The part you want to invalidate is specified by a pointer to a rectangle containing the boundaries but is here (and in most cases this is absolutely sufficient) set to NULL which means that the entire client area is invalidated.
Now how does that help us? InvalidateRect will add a rectangle to the window's update region and post a WM_PAINT message to the window. If many events happen simultaneously each requesting to repaint the window, then if you'd do it directly you might end up, drawing the same thing several times. With this mechanism all requests are collected and processed later. Additionally this also takes into account, which part of the window is visible in case of overlapping windows. Window's will automatically minimise the area that must be repainted.
Now there has got to be some code to actually draw the contents of the window and the time and place for that is the WM_PAINT message. This message is received first when the window becomes visible and then every time that Windows wants us to repaint the entire window or a portion of it, which can virtually be any time. Since the whole purpose of the program is to give you an idea when and how often this message is received we increase the value of our counter every time and display its value. Now how do we do that?
case WM_PAINT: { PAINTSTRUCT ps; HDC hdc; // Increase the Paint Message Counter iPaintCount++; // Get the handle to the Windows's Display Context hdc=BeginPaint(hWnd,&ps); // Now Paint the Windows's contents PaintAppWindow(hWnd,hdc,iPaintCount,hFillBrush); // Call EndPaint to release the DC and validate the client area EndPaint(hWnd,&ps); } break;
First we allocate a structure of type PAINTSTRUCT which we let Windows fill with values by calling BeginPaint. The meaning of the structure members is of minor importance at the moment and what we're really after is the handle to the display context (this is what HDC stands for). This handle is given to us in return to the call to BeginPaint and it is actually also contained in the PAINTSTRUCT structure. Fair enough, you might say, but what the heck is a display context?
In brief a display context is something that tells Windows where any output the window wishes to make has to go on the screen and where the borders of the area are. In fact you won't be able to draw or write anything in your window without having a valid handle to such a display context. All output is managed by the Graphic Device Interface (GDI) subsystem of Windows and I will tell you more about display contexts and the GDI in the next section.
So now that we've got the handle to display context we can actually draw our message which we do using the function PaintAppWindow which I will explain in the next paragraph. After we have done that, we must call EndPaint which tells Windows that we have finished painting and that the contents of the window are now in a valid state. End Paint will also free the display context i.e. the handle becomes invalid and we can't use it any more.
Processing the WM_PAINT message and calling BeginPaint and EndPaint is the single most important thing a window procedure has to provide. However messing around with it can cause a lot of trouble so I better warn you of some of the most dangerous pitfalls a beginner can step into:
The last message we have to process for our example application is the WM_DESTROY message and you probably guess that this is sent when the window is destroyed.
case WM_DESTROY: DeleteObject(hFillBrush); // Delete the fill brush (never forget!) PostQuitMessage(0); // Tell Windows we want to terminate break;
First we destroy the brush we allocated in the WM_CREATE message. If you have more resources allocated in the WM_CREATE message such as memory or file handles, this is the place and time to free them.
Now Windows does not know, that the destruction of this window means the end of our program. So what we have to do here in order to tell Windows that we want to terminate our application is to call PostQuitMessage. Window will then put a WM_QUIT message in our message queue and that will give our call to GetMessage in the procedure WinMain a return value of zero. If you look at the message loop we have defined in the WinMain again you will see that a return value of zero terminates the loop and the procedure WinMain returns. Now none of our code is executed any more and Window will remove our application from memory. Failing to call PostQuitMessage would keep our application waiting for a message but since there is no window any more there won't be any more messages. Unfortunately the only way to get rid of such an application (if you do not execute it from the IDE) is to reboot Windows.
That was heavy going now wasn't it? But do not worry if you do not understand it completely. If you stick to the example and do it like that you can't go wrong.
Once you have received the WM_DESTROY there is nothing you can do to keep the window alive. So what if we want to ask the user for confirmation first when he/ she closes the window?. In this case you have to process the WM_CLOSE message which you receive prior to WM_DESTROY. The example in the help file shows how to do that.
The only thing that is left for us to do now is to let Windows handle all messages that we did not handle. This is done by calling DefWindowProc in the default branch of the switch statement. For all the messages we have processed we return a value of zero.
default: // We didn't process the message so let Windows do it return DefWindowProc(hWnd,msg,wParam,lParam); } return 0L; }
static void PaintAppWindow(HWND hWnd,HDC hdc,int iPaintCount,HBRUSH hEllipseBrush)
This function is declared as static since it will only be called within this module. It is passed the handle of the window, the handle of the display context, the count number of messages received so far, and a handle to a brush which we use to fill the ellipse. First we need to allocate the following local variables:
{ char szText[50]; // Array holding the string displayed RECT rcWnd; // Dimensions of the Windows's client area HBRUSH hOrgBrush; // Original Brush of display context
The first thing we do now, is to get the coordinates of the window's client area and store them in rcWnd which is a structure containing the left, top, bottom and right boundaries of the rectangle.
GetClientRect(hWnd,&rcWnd);
Usually the upper left coordinate will be at 0/0 unless you change the windows origin. Hence the right and bottom values will contain the width and height of the client area.
We can now use this information to draw an ellipse touching each side of the window. Before we actually draw it though, we've got to select the fill brush we want to use into our display context.
All drawing primitives in Windows i.e. lines, rectangles, ellipses, arcs are always carried out with the pen and brush that is currently selected into the display context. The default is a black pen and a white brush. To change that you can call SelectObject and give it another pen, brush, color palette etc. SelectObject automatically detects the type of object you specified and replaces the current one. It also returns a handle to the object that was previously selected and this is a very important value to remember, since you are responsible to undo all changes before you release the display context again using EndPaint or ReleaseDC. Failing to restore the display context is a major offence and will most likely be punished by the need to reboot your system. The effect does not occur immediately though but after hours of use. This is why these bugs are very difficult to find and require special tools to detect such as BoundsChecker.
All right, this is how we do it now:
hOrgBrush=SelectObject(hdc,hEllipseBrush); // select the brush Ellipse(hdc,rcWnd.left,rcWnd.top,rcWnd.right,rcWnd.bottom); // Draw the ellipse SelectObject(hdc,hOrgBrush); // deselect the brush
Now all we need to do is write the text into the centre of the window. To do that we use wsprintf to generate a string consisting of the text and the number value of iPaintCount. Then we set the background mode of the display context to transparent (otherwise it would be white) and draw the text using DrawText.
wsprintf(szText,"Paint Count: %d - Click inside window to repaint!",iPaintCount); SetBkMode(hdc,TRANSPARENT); // Make text transparent DrawText(hdc,szText,lstrlen(szText),&rcWnd,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
DrawText has several options that are all given with the last parameter and which can be combined using a binary or. See the description in the help file for details. Another option for outputting text is the function TextOut.