JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
  Previous Section Next Section

Handling Important Events

As you've been painfully learning, Windows is an event-based operating system. Responding to events is one of the most important aspects of a standard Windows program. This next section covers some of the more important events that have to do with window manipulation, input devices, and timing. If you can handle these basic events, you'll have more than you need in your Windows arsenal to handle anything that might come up as part of a DirectX game, which itself relies very little on events and the Windows operating system.

Window Manipulation

There are a number of messages that Windows sends to notify you that the user has manipulated your window. Table 3.4 contains a small list of some of the more interesting manipulation messages that Windows generates.

Table 3.4. Window Manipulation Messages
Value Description
WM_ACTIVATE Sent when a window is being activated or deactivated. This message is sent first to the window procedure of the top-level window being deactivated. It is then sent to the window procedure of the top-level window being activated.
WM_ACTIVATEAPP Sent when a window belonging to an application other than the active window is about to be activated. The message is sent both to the application whose window is being activated and to the application whose window is being deactivated.
WM_CLOSE Sent as a signal that a window or an application should terminate.
WM_MOVE Sent after a window has been moved.
WM_MOVING Sent to a window that the user is moving. By processing this message, an application can monitor the size and position of the drag rectangle and, if needed, change its size or position.
WM_SIZE Sent to a window after its size has changed.
WM_SIZING Sent to a window that the user is resizing. By processing this message, an application can monitor the size and position of the resizing rectangle and, if needed, change its size or position.

Let's take a look at WM_ACTIVATE, WM_CLOSE, WM_SIZE, and WM_MOVE and what they do. For each one of these messages, I'm going to list the message, wparam, lparam, and some comments, along with a short example WinProc() handler for the event.

Message: WM_ACTIVATE

Parameterization:

fActive      = LOWORD(wParam);       // activation flag
fMinimized   = (BOOL)HIWORD(wParam); // minimized flag
hwndPrevious = (HWND)lParam;         // window handle

The fActive parameter basically defines what is happening to the window—that is, is the window being activated or deactivated? This information is stored in the low-order word of wparam and can take on the values shown in Table 3.5.

Table 3.5. The Activation Flags for WM_ACTIVATE
Value Description
WA_CLICKACTIVE Activated by a mouse click.
WA_ACTIVE The window has been activated by some means other than the mouse, such as the keyboard interface.
WA_INACTIVE The window is being deactivated.

The fMinimized variable simply indicates if the window was minimized. This is true if the variable is nonzero. Lastly, the hwndPrevious value identifies the window being activated or deactivated, depending on the value of the fActive parameter. If the value of fActive is WA_INACTIVE, hwndPrevious is the handle of the window being activated. If the value of fActive is WA_ACTIVE or WA_CLICKACTIVE, hwndPrevious is the handle of the window being deactivated. This handle can be NULL. That makes sense, huh?

In essence, you use the WM_ACTIVATE message if you want to know when your application is being activated or deactivated. This might be useful if your application keeps track of every time the user Alt+Tabs away or selects another application with the mouse. On the other hand, when your application is reactivated, maybe you want to play a sound or do something. Whatever, it's up to you.

Here's how you code when your application is being activated in the main WinProc():

case WM_ACTIVATE:
{
// test if window is being activated
if (LOWORD(wparam)!=WA_INACTIVE)
   {
   // application is being activated
   } // end if
else
   {
   // application is being deactivated
   } // end else

} break;

Message: WM_CLOSE

Parameterization: None

The WM_CLOSE message is very cool. It is sent right before a WM_DESTROY and the following WM_QUIT are sent. The WM_CLOSE indicates that the user is trying to close your window. If you simply return(0) in your WinProc(), nothing will happen and the user won't be able to close your window! Take a look at DEMO3_7.CPP and the executable DEMO3_7.EXE to see this in action. Try killing the application—you won't be able to!

WARNING

Don't panic when you can't kill DEMO3_7.EXE. Simply press Ctrl+Alt+Del, and the Task Manager will come up. Then select and terminate the DEMO3_7.EXE application. It will cease to exist—just like service at electronics stores starting with "F" in Silicon Valley.


Here's the coding of the empty WM_CLOSE handler in the WinProc() as coded in DEMO3_7.CPP:

case WM_CLOSE:
    {
    // kill message, so no further WM_DESTROY is sent
    return(0);
} break;

If making the user mad is your goal, the preceding code will do it. However, a better use of trapping the WM_CLOSE message might be to include a message box that confirms that the application is going to close or maybe do some housework. DEMO3_8.CPP and the executable take this route. When you try to close the window, a message box asks if you're certain. The logic flow for this is shown in Figure 3.20.

Figure 3.20. The logic flow for WM_CLOSE.

graphics/03fig20.gif

Here's the code from DEMO3_8.CPP that processes the WM_CLOSE message:

case WM_CLOSE:
{
// display message box
int result =  MessageBox(hwnd,
    "Are you sure you want to close this application?",
              "WM_CLOSE Message Processor",
               MB_YESNO | MB_ICONQUESTION);

// does the user want to close?
if (result == IDYES)
   {
   // call default handler
   return (DefWindowProc(hwnd, msg, wparam, lparam));
   } // end if
else // throw message away
   return(0);

} break;

Cool, huh? Notice the call to the default message handler, DefWindowProc(). This occurs when the user answers Yes and you want the standard shutdown process to continue. If you knew how to, you could have sent a WM_DESTROY message instead, but since you haven't learned how to send messages yet, you just called the default handler. Either way is fine, though.

Next, let's take a look at the WM_SIZE message, which is an important message to process if you've written a windowed game and the user keeps resizing the view window!

Message: WM_SIZE

Parameterization:

fwSizeType = wParam;         // resizing flag
nWidth     = LOWORD(lParam); // width of client area
nHeight    = HIWORD(lParam); // height of client area

The fwSizeType flag indicates what kind of resizing just occurred, as shown in Table 3.6, and the low and high word of lParam indicate the new window client dimensions.

Table 3.6. Resizing Flags for WM_SIZE
Value Description
SIZE_MAXHIDE Message is sent to all pop-up windows when some other window is maximized.
SIZE_MAXIMIZED Window has been maximized.
SIZE_MAXSHOW 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 the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies.

As I said, processing the WM_SIZE message can be very important for windowed games because when the window is resized, the graphics display must be scaled to fit. This will never happen if your game is running in full-screen, but in a windowed game, you can count on the user trying to make the window larger and smaller. When this happens, you must recenter the display and scale the universe or whatever to keep the image looking correct. As an example of tracking the WM_SIZE message, DEMO3_9.CPP prints out the new size of the window as it's resized. The code that tracks the WM_SIZE message in DEMO3_9.CPP is shown here:

case WM_SIZE:
         {
         // extract size info
         int width  = LOWORD(lparam);
         int height = HIWORD(lparam);

         // get a graphics context
         hdc = GetDC(hwnd);

         // set the foreground color to green
         SetTextColor(hdc, RGB(0,255,0));

         // set the background color to black
         SetBkColor(hdc, RGB(0,0,0));

         // set the transparency mode to OPAQUE
         SetBkMode(hdc, OPAQUE);

         // draw the size of the window
         sprintf(buffer,
         "WM_SIZE Called -  New Size = (%d,%d)", width, height);
         TextOut(hdc, 0,0, buffer, strlen(buffer));

         // release the dc back
         ReleaseDC(hwnd, hdc);

         } break;

WARNING

You should know that the code for the WM_SIZE message handler has a potential problem: When a window is resized, not only is a WM_SIZE message sent, but a WM_PAINT message is sent as well! Therefore, if the WM_PAINT message was sent after the WM_SIZE, the code in WM_PAINT could erase the background and thus the information just printed in WM_SIZE. Luckily, this isn't the case, but it's a good example of problems that can occur when messages are out of order or when they aren't sent in the order you think they are.


Last, but not least, let's take a look at the WM_MOVE message. It's almost identical to WM_SIZE, but it is sent when a window is moved rather than resized. Here are the details:

Message: WM_MOVE

Parameterization:

xPos = (int) LOWORD(lParam); // new horizontal position in screen coords
yPos = (int) HIWORD(lParam); // new vertical position in screen coords

WM_MOVE is sent whenever a window is moved to a new position, as shown in Figure 3.21. However, the message is sent after the window has been moved, not during the movement in real time. If you want to track the exact pixel-by-pixel movement of a window, you need to process the WM_MOVING message. However, in most cases, processing stops until the user is done moving your window.

Figure 3.21. Generation of the WM_MOVE message.

graphics/03fig21.gif

As an example of tracking the motion of a window, DEMO3_10.CPP and the associated executable DEMO3_10.EXE print out the new position of a window whenever it's moved. Here's the code that handles the WM_MOVE processing:

case WM_MOVE:
         {
         // extract the position
         int xpos = LOWORD(lparam);
         int ypos = HIWORD(lparam);

         // get a graphics context
         hdc = GetDC(hwnd);

         // set the foreground color to green
         SetTextColor(hdc, RGB(0,255,0));

         // set the background color to black
         SetBkColor(hdc, RGB(0,0,0));

         // set the transparency mode to OPAQUE
         SetBkMode(hdc, OPAQUE);

         // draw the size of the window
         sprintf(buffer,
        "WM_MOVE Called -  New Position = (%d,%d)", xpos, ypos);
         TextOut(hdc, 0,0, buffer, strlen(buffer));
         // release the dc back
         ReleaseDC(hwnd, hdc);

         } break;

Well, that's it for window manipulation messages. There are a lot more, obviously, but you should have the hang of it now. The thing to remember is that there is a message for everything. If you want to track something, just look in the Win32 Help and sure enough, you'll find a message that works for you!

The next sections cover input devices so you can interact with the user (or yourself) and make much more interesting demos and experiments that will help you master Windows programming.

Banging on the Keyboard

Back in the old days, accessing the keyboard required sorcery. You had to write an interrupt handler, create a state table, and perform a number of other interesting feats to make it work. I'm a low-level programmer, but I can say without regret that I don't miss writing keyboard handlers anymore!

Ultimately you're going to use DirectInput to access the keyboard, mouse, joystick, and any other input devices. Nevertheless, you still need to learn how to use the Win32 library to access the keyboard and mouse. If for nothing else, you'll need them to respond to GUI interactions and/or to create more engaging demos throughout the book until we cover DirectInput. So without further ado, let's see how the keyboard works.

The keyboard consists of a number of keys, a microcontroller, and support electronics. When you press a key or keys on the keyboard, a serial stream of packets is sent to Windows describing the key(s) that you pressed. Windows then processes this stream and sends your window keyboard event messages. The beauty is that under Windows, you can access the keyboard messages in a number of ways:

  • With the WM_CHAR message

  • With the WM_KEYDOWN and WM_KEYUP messages

  • With a call to GetAsyncKeyState()

Each one of these methods works in a slightly different manner. The WM_CHAR and WM_KEYDOWN messages are generated by Windows whenever a keyboard keypress or event occurs. However, there is a difference between the types of information encapsulated in the two messages. When you press a key on the keyboard, such as A, two pieces of data are generated:

  • The scan code

  • The ASCII code

The scan code is a unique code that is assigned to each key of the keyboard and has nothing to do with ASCII. In many cases, you just want to know if the A key was pressed; you're not interested in whether or not the Shift key was held down and so on. Basically, you just want to use the keyboard like a set of momentary switches. This is accomplished by using scan codes. The WM_KEYDOWN message is responsible for generating scan codes when keys are pressed.

The ASCII code, on the other hand, is cooked data. This means that if you press the A key on the keyboard but the Shift key is not pressed or the Caps Lock key is not engaged, you see an a character. Similarly, if you press Shift+A, you see an A. The WM_CHAR message sends these kinds of messages.

You can use either technique—it's up to you. For example, if you were writing a word processor, you would probably want to use the WM_CHAR message because the character case matters and you want ASCII codes, not virtual scan codes. On the other hand, if you're making a game and F is fire, S is thrust, and the Shift key is the shields, who cares what the ASCII code is? You just want to know if a particular button on the keyboard is up or down.

The final method of reading the keyboard is to use the Win32 function GetAsyncKeyState(), which tracks the last known keyboard state of the keys in a state table—like an array of Boolean switches. This is the method I prefer because you don't have to write a keyboard handler.

Now that you know a little about each method, let's cover the details of each one in order, starting with the WM_CHAR message.

The WM_CHAR message has the following parameterization:

Wparam— Contains the ASCII code of the key pressed.

LparamContains a bit-encoded state vector that describes other special control keys that may be pressed. The bit encoding is shown in Table 3.7.

Table 3.7. Bit Encoding for the Key State Vector
Bits Description
0–15 Contains the repeat count, which is the number of times the keystroke is repeated as a result of the user holding down the key.
16–23 Contains the scan code. The value depends on the original equipment manufacturer (OEM).
24 Boolean; extended key flag. If it's 1, the key is an extended key, such as the right-hand Alt and Ctrl keys that appear on an enhanced 101- or 102-key keyboard.
29 Boolean; indicates whether the Alt key is down.
30 Boolean; indicates the previous key state. It's useless.
31 Boolean; indicates the key transition state. If the value is 1, the key is being released; otherwise, the key is being pressed.

To process the WM_CHAR message, all you have to do is write a message handle for it, like this:

case WM_CHAR:
{
// extract ascii code and state vector
int ascii_code = wparam;
int key_state  = lparam;

// take whatever action

} break;

And of course, you can test for various state information that might be of interest. For example, here's how you would test for the Alt key being pressed down:

// test the 29th bit of key_state to see if it's true

#define ALT_STATE_BIT 0x20000000
if (key_state & ALT_STATE_BIT)
   {
   // do something
   } // end if

And you can test for the other states with similar bitwise tests and manipulations.

As an example of processing the WM_CHAR message, I have created a demo that prints out the character and the state vector in hexadecimal form as you press keys. The program is called DEMO3_11.CPP, and the executable is of course DEMO3_11.EXE. Try pressing weird key combinations and see what happens. The code that processes and displays the WM_CHAR information is shown here, excerpted from the WinProc():

case WM_CHAR:
         {
         // get the character
         char ascii_code = wparam;
         unsigned int key_state = lparam;

         // get a graphics context
         hdc = GetDC(hwnd);

         // set the foreground color to green
         SetTextColor(hdc, RGB(0,255,0));

         // set the background color to black
         SetBkColor(hdc, RGB(0,0,0));

         // set the transparency mode to OPAQUE
         SetBkMode(hdc, OPAQUE);

         // print the ascii code and key state
         sprintf(buffer,"WM_CHAR: Character = %c   ",ascii_code);
         TextOut(hdc, 0,0, buffer, strlen(buffer));

         sprintf(buffer,"Key State = 0X%X  ",key_state);
         TextOut(hdc, 0,16, buffer, strlen(buffer));

         // release the dc back
         ReleaseDC(hwnd, hdc);

         } break;

The next keyboard event message, WM_KEYDOWN, is similar to WM_CHAR, except that the information is not "cooked." The key data sent during a WM_KEYDOWN message is the virtual scan code of the key rather than the ASCII code. The virtual scan codes are similar to the standard scan codes generated by any keyboard, except that virtual scan codes are guaranteed to be the same for any keyboard. For example, it's possible that the scan code for a particular key on your 101 AT–style keyboard is 67, but on another manufacturer's keyboard, it might be 69. See the problem?

The solution used in Windows was to virtualize the real scan codes to virtual scan code with a lookup table. As programmers, we use the virtual scan codes and let Windows do the translation. Thanks, Windows! With that in mind, here are the details of the WM_KEYDOWN message:

Message: WM_KEYDOWN

Wparam—Contains the virtual key code of the key pressed. Table 3.8 contains a list of the most common keys that you might be interested in.

lpara—Contains a bit-encoded state vector that describes other special control keys that may be pressed. The bit encoding is shown in Table 3.8.

Table 3.8. Virtual Key Codes
Symbol Value (Hexadecimal) Description
VK_BACK 08 Backspace key
VK_TAB 09 Tab key
VK_RETURN 0D Enter key
VK_SHIFT 10 Shift key
VK_CONTROL 11 Ctrl key
VK_PAUSE 13 Pause key
VK_ESCAPE 1B Esc key
VK_SPACE 20 Spacebar
VK_PRIOR 21 Page Up key
VK_NEXT 22 Page Down key
VK_END 23 End key
VK_HOME 24 Home key
VK_LEFT 25 Left-arrow key
VK_UP 26 Up-arrow key
VK_RIGHT 27 Right-arrow key
VK_INSERT 2D Ins key
VK_DELETE 2E Del key
VK_HELP 2F Help key
No VK_Code 30–39 0–9 keys
No VK_Code 41–5A A–Z keys
VK_F1 - VK_F12 70–7B F1–F12 keys
Note: The keys A–Z and 0–9 have no VK_ codes. You must use the numeric constants or define your own.

In addition to the WM_KEYDOWN message, there is WM_KEYUP. It has the same parameterization—that is, wparam contains the virtual key code, and lparam contains the key state vector. The only difference is that WM_KEYUP is sent when a key is released.

For example, if you're using the WM_KEYDOWN message to control something, take a look at the code here:

case WM_KEYDOWN:
     {
     // get virtual key code and data bits
     int virtual_code = (int)wparam;
     int key_state    = (int)lparam;

     // switch on the virtual_key code to be clean
     switch(virtual_code)
            {
            case VK_RIGHT:{} break;
            case VK_LEFT: {} break;
            case VK_UP:   {} break;
            case VK_DOWN: {} break;
            // more cases...

            default: break;
            } // end switch

     // tell windows that you processed the message
     return(0);
     } break;

As an experiment, try modifying the code in DEMO3_11.CPP to support the WM_KEYDOWN message instead of WM_CHAR. When you're done, come back and we'll talk about the last method of reading the keyboard.

The final method of reading the keyboard is to make a call to one of the keyboard state functions: GetKeyboardState(), GetKeyState(), or GetAsyncKeyState(). We'll focus on GetAsyncKeyState() because it works for a single key, which is what you're usually interested in rather than the entire keyboard. If you're interested in the other functions, you can always look them up in the Win32 SDK. Anyway, GetAsyncKeyState() as the following prototype:

SHORT GetAsyncKeyState(int virtual_key);

You simply send the function the virtual key code that you want to test, and if the high bit of the return value is 1, the key is pressed. Otherwise, it's not. I have written some macros to make this easier:

#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEYUP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

The beauty of using GetAsyncKeyState() is that it's not coupled to the event loop. You can test for keypresses anywhere you want. For example, say that you're writing a game and you want to track the arrow keys, spacebar, and maybe the Ctrl key. You don't want to have to deal with the WM_CHAR or WM_KEYDOWN messages; you just want to code something like this:

if (KEYDOWN(VK_DOWN))
   {
   // move ship down, whatever
   } // end if

if (KEYDOWN(VK_SPACE))
   {
   // fire weapons maybe?
   } // end if

// and so on

Similarly, you might want to detect when a key is released to turn something off. Here's an example:

if (KEYUP(VK_ENTER))
   {
   // disengage engines
   } // end if

As an example, I have created a demo that continually prints out the status of the arrow keys in the WinMain(). It's called DEMO3_12.CPP, and the executable is DEMO3_12.EXE. Here's the WinMain() from the program:

int WINAPI WinMain(HINSTANCE hinstance,
            HINSTANCE hprevinstance,
            LPSTR lpcmdline,
               int ncmdshow)
{
WNDCLASSEX winclass; // this will hold the class we create
HWND       hwnd;     // generic window handle
MSG       msg;      // generic message
HDC        hdc;      // graphics device context

// first fill in the window class stucture
winclass.cbSize       = sizeof(WNDCLASSEX);
winclass.style        = CS_DBLCLKS | CS_OWNDC |
                        CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc  = WindowProc;
winclass.cbClsExtra  = 0;
winclass.cbWndExtra  = 0;
winclass.hInstance  = hinstance;
winclass.hIcon      = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor    = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground    = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName    = NULL;
winclass.lpszClassName    = WINDOW_CLASS_NAME;
winclass.hIconSm      = LoadIcon(NULL, IDI_APPLICATION);

// save hinstance in global
hinstance_app = hinstance;

// register the window class
if (!RegisterClassEx(&winclass))
    return(0);

// create the window
if (!(hwnd = CreateWindowEx(NULL,                  // extended style
               WINDOW_CLASS_NAME,     // class
                "GetAsyncKeyState() Demo", // title
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                0,0,      // initial x,y
                400,300,  // initial width, height
                NULL,      // handle to parent
                NULL,      // handle to menu
               hinstance,// instance of this application
                NULL)))    // extra creation parms
return(0);

// save main window handle
main_window_handle = hwnd;

// enter main event loop, but this time we use PeekMessage()
// instead of GetMessage() to retrieve messages
while(TRUE)
    {
    // test if there is a message in queue, if so get it
    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
       {
       // test if this is a quit
       if (msg.message == WM_QUIT)
           break;

       // translate any accelerator keys
       TranslateMessage(&msg);

       // send the message to the window proc
       DispatchMessage(&msg);
       } // end if

       // main game processing goes here

       // get a graphics context
       hdc = GetDC(hwnd);

       // set the foreground color to green
       SetTextColor(hdc, RGB(0,255,0));

       // set the background color to black
       SetBkColor(hdc, RGB(0,0,0));

       // set the transparency mode to OPAQUE
       SetBkMode(hdc, OPAQUE);

       // print out the state of each arrow key
       sprintf(buffer,"Up Arrow: = %d   ",KEYDOWN(VK_UP));
       TextOut(hdc, 0,0, buffer, strlen(buffer));

       sprintf(buffer,"Down Arrow: = %d   ",KEYDOWN(VK_DOWN));
       TextOut(hdc, 0,16, buffer, strlen(buffer));

       sprintf(buffer,"Right Arrow: = %d   ",KEYDOWN(VK_RIGHT));
       TextOut(hdc, 0,32, buffer, strlen(buffer));

       sprintf(buffer,"Left Arrow: = %d   ",KEYDOWN(VK_LEFT));
       TextOut(hdc, 0,48, buffer, strlen(buffer));
       // release the dc back
       ReleaseDC(hwnd, hdc);

    } // end while

// return to Windows like this
return(msg.wParam);

} // end WinMain

Also, if you review the entire source on the CD-ROM, you'll notice that there aren't handlers for WM_CHAR or WM_KEYDOWN in the message handler for the window. The fewer messages that you have to handle in the WinProc(), the better! In addition, this is the first time you have seen action taking place in the WinMain(), which is the section that does all game processing. Notice that there isn't any timing delay or synchronization, so the redrawing of the information is free-running (in other words, working as fast as possible). In Chapter 4, "Windows GDI, Controls, and Last-Minute Gift Ideas," you'll learn about timing issues, how to keep processes locked to a certain frame rate, and so forth. But for now, let's move on to the mouse.

Squeezing the Mouse

The mouse is probably the most innovative computer input device ever created. You point and click, and the mouse pad is physically mapped to the screen surface—that's innovation! Anyway, as you guessed, Windows has a truckload of messages for the mouse, but we're going to look at only two classes of messages: WM_MOUSEMOVE and WM_*BUTTON*.

Let's start with the WM_MOUSEMOVE message. The first thing to remember about the mouse is that its position is relative to the client area of the window that it's in. Referring to Figure 3.22, the mouse sends coordinates relative to the upper-left corner of your window, which is 0,0.

Figure 3.22. The details of mouse movement.

graphics/03fig22.gif

Other than that, the WM_MOUSEMOVE message is fairly straightforward.

Message: WM_MOUSEMOVE

Parameterization:

int mouse_x = (int)LOWORD(lParam);
int mouse_y = (int)HIWORD(lParam);

int buttons = (int)wParam;

Basically, the position is encoded as 16-bit entries in the lparam, and the buttons are encoded in the wparam, as shown in Table 3.9.

Table 3.9. Button Bit Encoding for WM_MOUSEMOVE
Value Description
MK_LBUTTON Set if the left mouse button is down.
MK_MBUTTON Set if the middle mouse button is down.
MK_RBUTTON Set if the right mouse button is down.
MK_CONTROL Set if the Ctrl key is down.
MK_SHIFT Set if the Shift key is down.

So all you have to do is logically AND one of the bit codes with the button state and you can detect which mouse buttons are pressed. Here's an example of tracking the x,y position of the mouse along with the left and right buttons:

case WM_MOUSEMOVE:
{
// get the position of the mouse
int mouse_x = (int)LOWORD(lParam);
int mouse_y = (int)HIWORD(lParam);

// get the button state
int buttons = (int)wParam;

// test if left button is down
if (buttons & MK_LBUTTON)
   {
   // do something
   } // end if
// test if right button is down
if (buttons & MK_RBUTTON)
   {
   // do something
   } // end if

} break;

Trivial, ooh, trivial! For an example of mouse tracking, take a look at DEMO3_13.CPP on the CD-ROM and the associated executable. The program prints out the position of the mouse and the state of the buttons using the preceding code as a starting point. Take note of how the button changes only when the mouse is moving. This is as you would expect because the message is sent when the mouse moves rather than when the buttons are pressed.

Now for some details. The WM_MOUSEMOVE is not guaranteed to be sent all the time. You may move the mouse too quickly for it to track. Therefore, don't assume that you'll be able to track individual mouse movements that well—for the most part, it's not a problem, but keep it in mind. Also, you should be scratching your head right now, wondering how to track if a mouse button was pressed without a mouse move. Of course, there is a whole set of messages just for that. Take a look at Table 3.10.

Table 3.10. Mouse Button Messages
Message Description
WM_LBUTTONDBLCLK The left mouse button was double-clicked.
WM_LBUTTONDOWN The left mouse button was pressed.
WM_LBUTTONUP The left mouse button was released.
WM_MBUTTONDBLCLK The middle mouse button was double-clicked.
WM_MBUTTONDOWN The middle mouse button was pressed.
WM_MBUTTONUP The middle mouse button was released.
WM_RBUTTONDBLCLK The right mouse button was double-clicked.
WM_RBUTTONDOWN The right mouse button was pressed.
WM_RBUTTONUP The right mouse button was released.

The button messages also have the position of the mouse encoded just as they were for the WM_MOUSEMOVE message—in the wparam and lparam. For example, to test for a left button double-click, you would do this:

case WM_LBUTTONDBLCLK:
     {
     // extract x,y and buttons
     int mouse_x = (int)LOWORD(lParam);
     int mouse_y = (int)HIWORD(lParam);
     // do something intelligent

     // tell windows you handled it
     return(0);
     } // break;

Killer! I feel powerful, don't you? Windows is almost at our feet!

      Previous Section Next Section
    



    JavaScript EditorAjax Editor     JavaScript Editor