A Windows program with a window and dealing with Messages |
In this section we will write a windows program that displays a window and deals with messages.
By now you know how windows works, know how to declare WinMain, and understand the messaging system. Now to put it all into practice.
We will create two routines in this program - our WinMain, which will be an expanded version of our previous one, and a Window call back procedure.
What is a Window callback procedure?
Each window we define has a window callback procedure. A window callback procedure is simply a procedure that is assigned to a window, and is called everytime that window is sent a message.
Lets say I declare a window, and assign it a call back procedure called Callback. Everytime a message is sent to our window, such as WM_PAINT, Callback() is called with certain parameters, and these parameters are used to deal with the message using the code you created. These parameters are passed to the application from the operating system.
The callback function used is called WindowProc. It is defined as follows:
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam );
The return value LRESULT holds the return value from the message processing, and and depends on the message sent.
CALLBACK indicates this is a callback function.
HWND identifies the current window (we already discussed HWND earlier).
uMsg specifies the message being processed, such as WM_PAINT - there are heaps of messages, and I will not list them here.
WPARAM and LPARAM specify additional message information, and are dependant on the type of message being sent.
Dealing with these messages
Within our WindowProc(), we have a switch statement based on our uMSG parameter to deal with each statement. The three messages we will be dealing with in this application are WM_PAINT, WM_KEYDOWN, and WM_DESTROY.
The WM_PAINT message
WM_PAINT is sent to the application when there is a need to redraw all of or part of the application window. This can be sent from many situations, such as UpdateWindow(), RedrawWindow(), or when the application receives the message from the operating system after the user performs such a task as minimizing and maximizing. The LPARAM and WPARAM for WM_PAINT contain no values, as they are not used in redrawing the screen. Inside our little case statement for WM_PAINT, we will use the routines BeginPaint() and EndPaint(). The first, BeginPaint(), accepts 2 parameters, and returns one. The first is the HWND of the window (we already know what a HWND is), and the second is a variable of type PAINTSTRUCT that will receive the information about the window. A PAINTSTRUCT is a structure that contains information about the application that can be used to repaint the application window. BeginPaint() returns a value of type HDC, which is a handle to a Device Context. Device Contexts are a big topic to explain, and are only used in this application in this one area, and so I will be explaining them in the next tutorial, where they will play a much larger role. The purpose of BeginPaint() begin paint is to prepare the window to be repainted by storing the necessary information in the PAINTSTRUCT variable. After calling of BeginPaint(), we do all our work to draw whatever is desired in the window, then we call EndPaint(), passing it the HWND of the window that was repainted, and the PAINTSTRUCT variable that received the information from BeginPaint(). The function of EndPaint() is to mark the end of the redrawing of the window, and remember: it is required for each call to BeginPaint(), but only after window redrawing is complete.
The WM_KEYDOWN message
The WM_KEYDOWN message is sent to the application when a non-system key was pressed. A non-system key is one that is pressed when ALT is not pressed eg "A" is a non-system key, where as "ALT-A" is a system key. When the WM_KEYDOWN message is sent, the WPARAM contains the keycode of the key pressed. For example, if ESCAPE was pressed, WPARAM would contain the value VK_ESCAPE. The LPARAM contains extended information about the keypress, such as the repeat rate. This is a more advanced topic, and as such, the contents of LPARAM and its functions will not be discussed here.
In our program, we will be processing the WM_KEYDOWN message, looking for when ESCAPE has been pressed. We will make our program quit after the user has pressed Escape. To do this, in our case statement for WM_KEYDOWN, we will call the function DestroyWindow(). This function destroys the window, and then sends the WM_DESTROY message to the application. This function accepts one parameter - the HWND of the window that is being destroyed.
The WM_DESTROY message
The WM_DESTROY message is sent when a window is being destroyed (ie when the user has indicated they wish to quit the program, or the program has exited of its own accord). In this situation, lParam and wParam contain no values. When responding to a WM_DESTROY message, we will call the function PostQuitMessage(). This function takes one parameter - an integer value indicating the value that will be returned to the operating system. When this function is called, it tells Windows that it is ready to quit. It works in a similar method to the exit() function used by DOS applications.
Opening A Window
Before I show you how to capture messages, I will show you how to open a window. No point in trying to keep one open with messaging without knowing how to open one.
First, we declare a variable of the type WNDCLASS, a structure that holds all the information about our window. Then we register the window with RegisterClass(), then the create the window with CreateWindow(), then we show the window with ShowWindow(), then we call UpdateWindow() to send our first WM_PAINT message.
The WNDCLASS datatype
Firstly, lets look at WNDCLASS more closely. The WNDCLASS contains 10 members. They are style, lpfnWndProc, cbClsExtra, cbWndExtra, hInstance, hIcon, hCursor, hbrBackground, lpszMenuName, lpszClassName.
Style specifies the style of the window class, and because its values are so many, I wont explain them all here. You can have more than one value, combining them by using the bitwise OR operator. The ones we will be using are CS_HREDRAW, CS_VREDRAW and CS_DBLCLKS. CD_HREDRAW means we want to redraw the entire window if a movement or size adjustment changes the width of the client area. CS_VREDRAW redraws the entire window if a movement or size adjustment changes the height of the client area. CS_DBLCLKS sends double-click messages to the window procedure when the user double-clicks the mouse while the cursor is within a window belonging to the class.
The next member of WNDCLASS, lpfnWndProc, simply points to our window callback function, which we discussed earlier on.
The next member, cbClsExtra, specifies how many bytes to allocate after the structure. We will set this to 0. Don't worry about this for now, it will be explained in a later tute; it is irrelevant at this time.
The next member, cbWndExtra, specifies how many bytes to allocate after the window instance. We will initialise this to 0. Don't worry about this one either it will be explained in a later tute.
Our next WNDCLASS member, hInstance, identifies the instance of the window.
hIcon identifies the window icon, and hCursor identifies the window cursor. These must be the handle of an icon or cursor resource (I will explain resources in a later tutorial). If these members are NULL, the application must draw them when necessary. For the purposes of this tutorial, we will have Windows define our icon and cursor. To load our icon we will call the function LoadIcon(), passing it a NULL, to indicate that a standard cursor is being loaded, and a value that corresponds to the icon we are loading. In this case, we will specify IDI_APPLICATION , which indicates the default application icon. For the loading of our cursor we will use, you guessed it, LoadCursor(), once again passing NULL as the first parameter, to indicate that a standard arrow is to be loaded. The second value, which identifies the cursor name, will be IDC_ARROW, which represents the standard arrow.
hbrBackground Identifies the background brush. A brush is a graphical tool that keeps store of the current drawing colour and style. For example, we can set the brush to a solid fill in colour green, and when drawing is performed, it will be done with a green solid fill. This value can be a colour, or a brush type. We will draw our window using a white brush. To specify this, we assign hbrBackground the value returned by a call to the function GetStockObject(). This is a function that returns the type of brush we are requesting, and takes a single integer parameter, specifying the type of brush we wish to select. In this case, we will want a brush of type WHITE_BRUSH.
lpszMenuName points to a string that specifies the resource name of the menu. I will go into this in a later tutorial. For now, we will set this to NULL, which means no menu.
lpszClassName Points to a string that is used for the window class name.
Registering the window
After declaring this, then we call RegisterClass(), passing our WNDCLASS structure as a parameter. This function registers the window with the operating system and sets it up to call CreateWindow().
Creating the window
Then we call CreateWindow(). This takes the following parameters:
lpClassName - Name of a registered WNDCLASS structure. We will pass this as WS_EX_TOPMOST, which is a macro that passes the topmost WNDCLASS structure passed, which will be the one we just registered.
lpWindowName - Points to a string that
specifies the window name.
dwStyle - Specifies the style of the window being created. Can be a
combination of many values. Ours will be of style WS_OVERLAPPED, which gives it a
title bar and border.
x, y - Specify the initial horizontal and vertical position of the window.
nWidth, nHeight - Specifies the width and height of the window.
hWndParent - Identifies the handle of the owner or parent window being created.
hMenu - identifies a menu to be used with the window. If we set this value to NULL, then we can use the menu we referred to in our WNDCLASS structure.
hInstance - Identifies the instance of the window.
lpParam - Points to a value passed to the window through the CREATESTRUCT structure referenced by the lParam parameter of the WM_CREATE message. We will set this to NULL. Sorry if this is confusing, I will try to explain it better at a later date.
This function will also return a HWND to gives us the window handle.
hbrBackground
After this, we call ShowWindow(), and pass it the HWND returned to us by CreateWindow(), and then the style of the window, which will be SW_SHOWNORMAL, which displays a window the way it would normally be. Then we call UpdateWindow(), and pass to it the HWND we received before.
Now, if we left it like this, we would see the window once, and it would close and the program would exit. We need to capture those messages!
Capturing messages
Here is a code fragment to capture messages.
MSG Message;GetMessage hangs around letting all other open windows do their thing until it receives a message meant for your application. It then places this information in its first parameter, a variable of type MSG, which contains all the information about the received message. The next parameter, which we will set to 0, tells which window is receiving this message. Our window is first in the queue, and so we set it to 0. The next two parameters, which are both set to 0, are used as message filters, indicating the window whose messages are to be received, which in this case is 0.
After the message has been received, we then call TranslateMessage(), and pass it our MSG variable. This is used to translate the messages sent by the operating system and turn them into messages understood by the program, such as WM_PAINT and WM_DESTROY.
Finally, we use DispatchMessage(), passing it our MSG variable, to send the message off to our operating system.
Putting it all together
So, a step-by-step run through of our program is now in order
We enter our WinMain
We setup our WNDCLASS structure to customise our window
We then register our window with RegisterClass()
We create our window with CreateWindow()
We show our window with ShowWindow()
We update our window with UpdateWindow()
We then enter our messaging loop
We use GetMessage() to get our messages
We translate our message with TranslateMessage()
We then use DispatchMessage() to send our message off to our callback procedure
Our callback procedure deals with the message
We go back to 8 and keep doing this until told to quit
And there you have it. Hopefully, this version of the document is much better than the old one.
Download source code and executable here