Windows Events
She knows herself to be at the mercy of events, and she knows by now that events have no mercy.
— Margaret Atwood, The Blind Assassin
As discussed in previous tutorials, Windows is an event-based operating system and even though a DirectX application usually does not need Windows messages to get its job done, it still needs to handle some events:
Important Messages
WM_DESTROY
So far only the WM_DESTROY message, which is sent when a window is flagged for destruction, has been dealt with by using PostQuitMessage to put a WM_QUIT message in the message queue.
WM_CLOSE
The WM_CLOSE message indicates that the user is trying to close the application. If so desired, the user could be asked for confirmation whether he really intended to stop playing this wonderful game.
And if you are wondering who Cosmo is:
Are you really sure you want to stop playing with Cosmo?
WM_MENUCHAR
This is the most important event to handle. This message is sent when a menu is active and the user presses a key that does not correspond to any mnemonic or accelerator key.
Since the beep when a non-mnemonic key is pressed is terribly annoying, and since usually games do not work with menus, we beg Windows to please, please don’t beep at us by deceiving the system by telling it that the close the menu button was actually pressed. This will be extremely useful when switching to a full-screen application, as hearing that annoying beep each time I pressed alt+enter surely had a detrimental effect on my physical well-being! Leave now and never come back!
WM_ACTIVATE
This message is sent when a window gets activated; it has the following parameters:
The fActive variable is defined by the following parameters:
- WA_CLICKACTIVE: activated by a mouse click.
- WA_ACTIVE: the window has been activated by some means besides the mouse (i.e. keyboard).
- WA_INACTIVE: the window is being deactivated.
The fMinimized variable indicates whether the window was minimized and hwndPrevious is the handle to the window being activated or deactivated.
This message is useful, since it allows keeping track of the state of the game window. When the window becomes inactive, the game should be paused; after all, the hero should not be chomped on by some monsters after the player minimized the window to check his favourite math blog! When, and if, the window is activated again, the game should resume. To assure this functionality, a boolean has been added to the DirectXApp class, called isPaused, which is true if and only if the application is paused.
Obviously, when the window becomes deactivated, the isPaused is set to true, and when the window becomes active (again), this variable is set to false. In the main game loop isPaused is used to concede resources to the CPU while the game is inactive, i.e. the game simply does nothing while paused:
WM_SIZE
This is another important message for games to handle, as it gets invoked each time the user resizes the game window. Each time that happens, the game universe must be scaled to fit the new dimensions of the client area. For now, it is enough to register the size changes. Later, when DirectX is up and running as well, Direct3D will be used to do the actual resizing.
For this purpose, there is a new function in the DirectXApp class, the onResize function, which gets called each time resizing occured; it does nothing yet though, besides printing a message to the log file.
The WM_SIZE message has the following parameters:
The low and high word of lParam simply indicate the new client dimensions whereas the fwSizeType parameter explains what kind of resizing happened, according to the following values:
- SIZE_MAXHIDE: this message gets sent to all pop-up windows when some other window is maximized.
- SIZE_MAXIMIZED: window has been maximized.
- SIZE_MAXSHOW: this message is sent to all pop-up windows when some other window has been restored to its former size.
- SIZE_MINIMIZED: window has been minimized.
- SIZE_RESTORED: window has been resized, but neither SIZE_MINIMIZED nor SIZE_MAXIMIZED apply.
To keep track of its state, the Window class uses three new boolean members, namely isMinimized, isMaximized and isResizing. They are true if and only the window is minimized, maximized respectively being resized.
Here is a barebone code to handle size changes:
Note that while the window is being dragged around (else if (isResizing)) the game is not being resized. It would actually be completely useless to do so, as while the window is being dragged, Windows continuously sends WM_SIZE messages: it would be extremely slow and trivially pointless to respond to all of them (and to constantly change the game graphics), and thus it seems much wiser to simply wait until the dragging is done and to only then do all the resizing that must be done.
To notice whether the window is being resized, the following messages must be handled:
WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE
If the user drags the edges of the window (to make it smaller or larger) a WM_ENTERSIZEMOVE message is sent once the dragging starts and a WM_EXITSIZEMOVE once the dragging is done (and the window is resized).
In this case, it is advised to respond exactly as above. Please note that while the window is being dragged around, the application is paused.
WM_GETMINMAXINFO
This message is sent when the window is about to be changed in size. The lParam parameter of this message holds a pointer to a MINMAXINFO structure. The minimum tracking width (x member) and the minimum tracking height (y member) of the window are stored in ptMinTrackSize. By setting those values to 200 each, it can be assured that the window cannot be resized to be smaller than 200 x 200.
Putting it all together
Here is the new message procedure function of the Window class:
The code is available from here.
Here is the log file from simply starting the game and closing the window, without interacting with it.
Exercises
Exercise 1
Explain line number 2 in the above log file.
Exercise 2
Change the program code to open more than one window, more precisely, create and open a window of each one of those colours: white, black, gray, light gray and dark gray, and figure out a way to close one window at a time and to only exit the program once every single window is closed.
And then Bill Gates said there be Windows everywhere!
By now I am really excited; we covered a lot of ground, we learned about error handling, multitasking and thread-safe logging. We learned quite a bit about the architecture of Windows, and we can define, register and create our own windows as well as write our own event handlers. Furthermore, we had first contact with the powerful scripting language Lua. All in all, I think we have done an excellent job so far.
In the next tutorial, we will learn how to add a high-precision timer to our application, which will prove invaluable for using actual physics in a game. We will also come back to an earlier discussion about how to implement a robust game loop.
References
Literature
(in alphabetic order)
- Microsoft Developer Network (MSDN)
- Tricks of the Windows Game Programming Gurus, by André LaMothe
Photos
- Wikipedia
- Josiane Bellot