Teach Yourself Visual C++ 6 in 21 Days

Previous chapterNext chapterContents


- 3 -
Allowing User Interaction--Integrating the Mouse and Keyboard in Your Application



Depending on the type of application you are creating, you might need to notice what the user is doing with the mouse. You need to know when and where the mouse was clicked, which button was clicked, and when the button was released. You also need to know what the user did while the mouse button was being held down.

Another thing that you might need to do is read the keyboard events. As with the mouse, you might need to know when a key was pressed, how long it was held down, and when it was released.

Today you are going to learn

Understanding Mouse Events

As you learned yesterday, when you are working with most controls, you are limited to a select number of events that are available in the Class Wizard. When it comes to mouse events, you are limited for the most part to click and double-click events. Just looking at your mouse tells you that there must be more to capturing mouse events than recognizing these two. What about the right mouse button? How can you tell if it has been pressed? And what about drawing programs? How can they follow where you drag the mouse?

If you open the Class Wizard in one of your projects, select the dialog in the list of object IDs, and then scroll through the list of messages that are available, you will find a number of mouse-related events, which are also listed in Table 3.1. These event messages enable you to perform any task that might be required by your application.

TABLE 3.1. MOUSE EVENT MESSAGES.

Message Description
WM_LBUTTONDOWN The left mouse button has been pressed.
WM_LBUTTONUP The left mouse button has been released.
WM_LBUTTONDBLCLK The left mouse button has been double-clicked.
WM_RBUTTONDOWN The right mouse button has been pressed.
WM_RBUTTONUP The right mouse button has been released.
WM_RBUTTONDBLCLK The right mouse button has been double-clicked.
WM_MOUSEMOVE The mouse is being moved across the application window space.
WM_MOUSEWHEEL The mouse wheel is being moved.

Drawing with the Mouse

Today you are going to build a simple drawing program that uses some of the available mouse events to let the user draw simple figures on a dialog window. This application depends mostly on the WM_MOUSEMOVE event message, which signals that the mouse is being moved. You will look at how you can tell within this event function whether the left mouse button is down or up. You will also learn how you can tell where the mouse is on the window. Sound's fairly straight ahead, so let's get going by following these steps:

1. Create a new MFC AppWizard workspace project, calling the project Mouse.

2. Specify that this project will be a dialog-based application in the first AppWizard step.

3. Use the default settings in the AppWizard. In the second step, specify a suitable dialog title, such as Mouse and Keyboard.

4. After the application shell is created, remove all controls from the dialog window. This provides the entire dialog window surface for drawing. This step is also necessary for your application to capture any keyboard events.


NOTE: If there are any controls on a dialog, all keyboard events are directed to the control that currently has input focus--the control that is highlighted or has the cursor visible in it. To capture any keyboard events in a dialog, you have to remove all controls from the dialog.
5. Open the Class Wizard. Select WM_MOUSEMOVE from the list of messages, and add a function by clicking the Add Function button. Click the OK button to accept the suggested function name.

6. Click the Edit Code button to edit the OnMouseMove function you just created, adding the code in Listing 3.1.

LISTING 3.1. THE OnMouseMove FUNCTION.

 1: void CMouseDlg::OnMouseMove(UINT nFlags, CPoint point)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Check to see if the left mouse button is down
10:     if ((nFlags & MK_LBUTTON) == MK_LBUTTON)
11:     {
12:         // Get the Device Context
13:         CClientDC dc(this);
14: 
15:         // Draw the pixel
16:         dc.SetPixel(point.x, point.y, RGB(0, 0, 0));
17:     }
18: 
19:     ///////////////////////
20:     // MY CODE ENDS HERE
21:     ///////////////////////
22: 
23:     CDialog::OnMouseMove(nFlags, point);
24: }

Look at the function definition at the top of the listing. You will notice that two arguments are passed into this function. The first of these arguments is a set of flags that can be used to determine whether a mouse button is depressed (and which one). This determination is made in the first line of your code with the if statement:

if ((nFlags & MK_LBUTTON) == MK_LBUTTON)

In the first half of the condition being evaluated, the flags are filtered down to the one that indicates that the left mouse button is down. In the second half, the filtered flags are compared to the flag that indicates that the left mouse button is down. If the two match, then the left mouse button is down.

The second argument to this function is the location of the mouse. This argument gives you the coordinates on the screen where the mouse currently is. You can use this information to draw a spot on the dialog window.

Before you can draw any spots on the dialog window, you need to get the device context for the dialog window. This is done by declaring a new instance of the CClientDC class. This class encapsulates the device context and most of the operations that can be performed on it, including all the screen drawing operations. In a sense, the device context is the canvas upon which you can draw with your application. Until you have a canvas, you cannot do any drawing or painting. After the device context object is created, you can call its SetPixel function, which colors the pixel at the location specified in the first two arguments with the color specified in the third argument. If you compile and run your program, you can see how it allows you to draw on the window surface with the mouse, as shown in Figure 3.1.

FIGURE 3.1. Drawing on the window with the mouse.


NOTE: In Windows, colors are specified as a single number that is a combination of three numbers. The three numbers are the brightness levels for the red, green, and blue pixels in your computer display. The RGB function in your code is a macro that combines these three separate values into the single number that must be passed to the SetPixel function or to any other function that requires a color value. These three numbers can be any value between and including 0 and 255.

Using the AND and OR Binaries

If you are new to C++, you need to understand how the different types of AND and OR work. The two categories of ANDs and ORs are logical and binary. The logical ANDs and ORs are used in logical or conditional statements, such as an if or while statement that is controlling the logic flow. The binary ANDs and ORs are used to combine two values on a binary level.

The ampersand character (&) is used to denote AND. A single ampersand (&) is a binary AND, and a double ampersand (&&) is a logical AND. A logical AND works much like the word AND in Visual Basic or PowerBuilder. It can be used in an if statement to say "if this condition AND this other condition..." where both conditions must be true before the entire statement is true. A binary AND is used to set or unset bits. When two values are binary ANDed, only the bits that are set to 1 in both values remain as 1; all the rest of the bits are set to 0. To understand how this works, start with two 8-bit values such as the following:

Value 1 01011001
Value 2 00101001

If you binary AND these two values together, you wind up with the following value:

ANDed Value 00001001

All the bits that had 1 in one of the values, but not in the other value, were set to 0. All the bits that were 1 in both values remained set to 1. All the bits that were 0 in both values remained 0.

OR is represented by the pipe character (|), and as with AND, a single pipe (|) is a binary OR, whereas a double pipe (||) is a logical OR. As with AND, a logical OR can be used in conditional statements such as if or while statements to control the logical flow, much like the word OR in Visual Basic and PowerBuilder. It can be used in an if statement to say "if this condition OR this other condition..." and if either condition is true, the entire statement is true. You can use a binary OR to combine values on a binary level. With OR, if a bit is set to 1 in either value, the resulting bit is set to 1. With a binary OR, the only way that a bit is set to 0 in the resulting value is if the bit was already 0 in both values. Take the same two values that were used to illustrate the binary AND:

Value 1 01011001
Value 2 00101001

If you binary OR these two values together, you get the following value:

ORed Value 01111001

In this case, every bit that was set to 1 in either value was set to 1 in the resulting value. Only those bits that were 0 in both values were 0 in the resulting value.

Binary Attribute Flags

Binary ANDs and ORs are used in C++ for setting and reading attribute flags. Attribute flags are values where each bit in the value specifies whether a specific option is turned on or off. This enables programmers to use defined flags. A defined flag is a value with only one bit set to 1 or a combination of other values in which a specific combination of bits is set to 1 so that multiple options are set with a single value. The flags controlling various options are ORed together, making a composite flag specifying which options should be on and which should be off.

If two flags that specify certain conditions are specified as two different bits in a byte, those two flags can often be ORed together as follows:

Flag 1 00001000
Flag 2 00100000
Combination 00101000

This is how flags are combined to specify a number of settings in a limited amount of memory space. In fact, this is what is done with most of the check box settings on the window and control properties dialogs. These on/off settings are ORed together to form one or two sets of flags that are examined by the Windows operating system to determine how to display the window or control and how it should behave.

On the flip side of this process, when you need to determine if a specific flag is included in the combination, you can AND the combination flag with the specific flag that you are looking for as follows:

Combination 00101000
Flag 1 00001000
Result 00001000

The result of this operation can be compared to the flag that you used to filter the combined flag. If the result is the same, the flag was included. Another common approach is to check whether the filtered combination flag is nonzero. If the flag being used for filtering the combination had not been included, the resulting flag would be zero. As a result, you could have left the comparison out of the if statement in the preceding code, leaving you with an if statement that looks like the following:

if (nFlags & MK_LBUTTON)

You can modify this approach to check whether a flag is not in the combination as follows:

if (!(nFlags & MK_LBUTTON))

You might find one of these ways of checking for a flag easier to understand than the others. You'll probably find all of them in use.

Improving the Drawing Program

If you ran your program, you probably noticed a small problem. To draw a solid line, you need to move the mouse very slowly. How do other painting programs solve this problem? Simple, they draw a line between two points drawn by the mouse. Although this seems a little like cheating, it's the way that computer drawing programs work.

As you move the mouse across the screen, your computer is checking the location of the mouse every few clock ticks. Because your computer doesn't have a constant trail of where your mouse has gone, it has to make some assumptions. The way your computer makes these assumptions is by taking the points that the computer does know about and drawing lines between them. When you draw lines with the freehand tool in Paint, your computer is playing connect the dots.

Because all the major drawing programs draw lines between each pair of points, what do you need to do to adapt your application so that it also uses this technique? First, you need to keep track of the previous position of the mouse. This means you need to add two variables to the dialog window to maintain the previous X and Y coordinates. You can do this by following these steps:

1. In the workspace pane, select the Class View tab.

2. Select the dialog class--in this case, the CMouseDlg class.

3. Right-click the mouse and select Add Member Variable from the pop-up menu.

4. Enter int as the Variable Type and m_iPrevY as the Variable Name and specify Private for the access in the Add Member Variable dialog, as shown in Figure 3.2.

FIGURE 3.2. The Add Member Variable dialog.

5. Click OK to add the variable.

6. Repeat steps 3 through 5, specifying the Variable Name as m_iPrevX to add the second variable.

After you add the variables needed to keep track of the previous mouse position, you can make the necessary modifications to the OnMouseMove function, as shown in Listing 3.2.

LISTING 3.2. THE REVISED OnMouseMove FUNCTION.

 1: void CMouseDlg::OnMouseMove(UINT nFlags, CPoint point)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Check to see if the left mouse button is down
10:     if ((nFlags & MK_LBUTTON) == MK_LBUTTON)
11:     {
12:         // Get the Device Context
13:         CClientDC dc(this);
14: 
15:         // Draw a line from the previous point to the current point
16:         dc.MoveTo(m_iPrevX, m_iPrevY);
17:         dc.LineTo(point.x, point.y);
18: 
19:         // Save the current point as the previous point
20:         m_iPrevX = point.x;
21:         m_iPrevY = point.y;
22:     }
23: 
24:     ///////////////////////
25:     // MY CODE ENDS HERE
26:     ///////////////////////
27: 
28:     CDialog::OnMouseMove(nFlags, point);
29: }

Look at the code that draws the line from the previous point to the current point:

dc.MoveTo(m_iPrevX, m_iPrevY);
dc.LineTo(point.x, point.y);

You see that you need to move to the first position and then draw a line to the second point. The first step is important because without it, there is no telling where Windows might think the starting position is. If you compile and run your application, it draws a bit better. However, it now has a peculiar behavior. Every time you press the left mouse button to begin drawing some more, your application draws a line from where you ended the last line you drew, as shown in Figure 3.3.

FIGURE 3.3. The drawing program with a peculiar behavior.

Adding the Finishing Touches

Your application is doing all its drawing on the mouse move event when the left button is held down. Initializing the previous position variables with the position of the mouse when the left button is pressed should correct this application behavior. Let's try this approach by following these steps:

1. Using the Class Wizard, add a function for the WM_LBUTTONDOWN message on the dialog object.

2. Edit the OnLButtonDown function that you just created, adding the code in Listing 3.3.

LISTING 3.3. THE OnLButtonDown FUNCTION.

 1: void CMouseDlg::OnLButtonDown(UINT nFlags, CPoint point)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Set the current point as the starting point
10:     m_iPrevX = point.x;
11:     m_iPrevY = point.y;
12: 
13:     ///////////////////////
14:     // MY CODE ENDS HERE
15:     ///////////////////////
16: 
17:     CDialog::OnLButtonDown(nFlags, point);
18: }

When you compile and run your application, you should find that you can draw much like you would expect with a drawing program, as shown in Figure 3.4.

FIGURE 3.4. The finished drawing program.

Capturing Keyboard Events

Reading keyboard events is similar to reading mouse events. As with the mouse, there are event messages for when a key is pressed and when it is released. These events are listed in Table 3.2.

TABLE 3.2. KEYBOARD EVENT MESSAGES.

Message Description
WM_KEYDOWN A key has been pressed down.
WM_KEYUP A key has been released.

The keyboard obviously has fewer messages than the mouse does. Then again, there are only so many things that you can do with the keyboard. These event messages are available on the dialog window object and are triggered only if there are no enabled controls on the window. Any enabled controls on the window have input focus, so all keyboard events go to them. That's why you remove all controls from the main dialog for your drawing application.

Changing the Drawing Cursor

To get a good idea of how you can use keyboard-related event messages, why don't you use certain keys to change the mouse cursor in your drawing application? Make the A key change the cursor to the default arrow cursor, which your application starts with. Then you can make B change the cursor to the I-beam and C change the cursor to the hourglass. To get started adding this functionality, follow these steps:

1. Using the Class Wizard, add a function for the WM_KEYDOWN message on the dialog object.

2. Edit the OnKeyDown function that you just created, adding the code in Listing 3.4.

LISTING 3.4. THE OnKeyDown FUNCTION.

 1: void CMouseDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     char lsChar;        // The current character being pressed
10:     HCURSOR lhCursor;    // The handle to the cursor to be displayed
11: 
12:     // Convert the key pressed to a character
13:     lsChar = char(nChar);
14: 
15:     // Is the character "A"
16:     if (lsChar == `A')
17:     {
18:         // Load the arrow cursor
19:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
20:         // Set the screen cursor
21:         SetCursor(lhCursor);
22:     }
23: 
24:     // Is the character "B"
25:     if (lsChar == `B')
26:     {
27:         // Load the I beam cursor
28:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
29:         // Set the screen cursor
30:         SetCursor(lhCursor);
31:     }
32: 
33:     // Is the character "C"
34:     if (lsChar == `C')
35:     {
36:         // Load the hourglass cursor
37:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_WAIT);
38:         // Set the screen cursor
39:         SetCursor(lhCursor);
40:     }
41: 
42:     // Is the character "X"
43:     if (lsChar == `X')
44:     {
45:         // Load the arrow cursor
46:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
47:         // Set the screen cursor
48:         SetCursor(lhCursor);
49:         // Exit the application
50:         OnOK();
51:     }
52: 
53:     ///////////////////////
54:     // MY CODE ENDS HERE
55:     ///////////////////////
56: 
57:     CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
58: }

In the function definition, you see three arguments to the OnKeyDown function. The first is the key that was pressed. This argument is the character code of the character, which needs to be converted into a character in the first line of your code. After you convert the character, you can perform straight-ahead comparisons to determine which key was pressed:

void CMouseDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

The second argument to the OnKeyDown function is the number of times that the key is pressed. Normally, if the key is pressed and then released, this value is 1. If the key is pressed and held down, however, the repeat count rises for this key. In the end, this value tells you how many times that Windows thinks the key has been pressed.

The third argument to the OnKeyDown function is a combination flag that can be examined to determine whether the Alt key was pressed at the same time as the key or whether the key being pressed is an extended key. This argument does not tell you whether the shift or control keys were pressed.

When you determine that a specific key was pressed, then it's time to change the cursor to whichever cursor is associated with that key. There are two steps to this process. The first step is to load the cursor into memory. You accomplish this step with the LoadStandardCursor function, which loads one of the standard Windows cursors and returns a handle to the cursor.


NOTE: A sister function, LoadCursor, can be passed the file or resource name of a custom cursor so that you can create and load your own cursors. If you design your own cursor in the resource editor in Visual C++, you can pass the cursor name as the only argument to the LoadCursor function. For example, if you create your own cursor and name it IDC_MYCURSOR, you can load it with the following line of code:
lhCursor = AfxGetApp()->LoadCursor(IDC_MYCURSOR);



After you load your own cursor, you can set the mouse pointer to your cursor using the SetCursor function, as with a standard cursor.


After the cursor is loaded into memory, the handle to that cursor is passed to the SetCursor function, which switches the cursor to the one the handle points to. If you compile and run your application, you should be able to press one of these keys and get the cursor to change, as in Figure 3.5. However, the moment you move the mouse to do any drawing, the cursor switches back to the default arrow cursor. The following section describes how to make your change stick.

FIGURE 3.5. Changing the cursor with specific keys.

Making the Change Stick

The problem with your drawing program is that the cursor is redrawn every time you move the mouse. There must be some way of turning off this behavior.

Each time the cursor needs to be redrawn--because the mouse has moved, because another window that was in front of your application has gone away, or because of whatever other reason--a WM_SETCURSOR event message is sent to your application. If you override the native behavior of your application on this event, the cursor you set remains unchanged until you change it again. To do this, follow these steps:

1. Add a new variable to the CMouseDlg class, as you did for the previous position variables. This time, declare the type as BOOL and name the variable m_bCursor, as shown in Figure 3.6.

FIGURE 3.6. Defining a class member variable.

2. Initialize the m_bCursor variable in the OnInitDialog with the code in Listing 3.5.

LISTING 3.5. THE OnInitDialog FUNCTION.

 1: BOOL CMouseDlg::OnInitDialog()
 2: {
 3:     CDialog::OnInitDialog();
 4: 
 5: .
 6: .
 7: .
 8:     // Set the icon for this dialog.  The framework does this 			      Âautomatically
 9:     //  when the application's main window is not a dialog
10:     SetIcon(m_hIcon, TRUE);            // Set big icon
11:     SetIcon(m_hIcon, FALSE);        // Set small icon
12: 
13:     // TODO: Add extra initialization here
14: 
15:     ///////////////////////
16:     // MY CODE STARTS HERE
17:     ///////////////////////
18: 
19:     // Initialize the cursor to the arrow
20:     m_bCursor = FALSE;
21: 
22:     ///////////////////////
23:     // MY CODE ENDS HERE
24:     ///////////////////////
25: 
26:     return TRUE;  // return TRUE  unless you set the focus to a 			      Âcontrol
27: }
3. Alter the OnKeyDown function to set the m_bCursor flag to TRUE when you change the cursor, as in Listing 3.6.

LISTING 3.6. THE OnKeyDown FUNCTION.

 1: void CMouseDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     char lsChar;        // The current character being pressed
10:     HCURSOR lhCursor;    // The handle to the cursor to be displayed
11: 
12:     // Convert the key pressed to a character
13:     lsChar = char(nChar);
14: 
15:     // Is the character "A"
16:     if (lsChar == `A')
17:         // Load the arrow cursor
18:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
19: 
20:     // Is the character "B"
21:     if (lsChar == `B')
22:         // Load the I beam cursor
23:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
24: 
25:     // Is the character "C"
26:     if (lsChar == `C')
27:         // Load the hourglass cursor
28:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_WAIT);
29: 
30:     // Is the character "X"
31:     if (lsChar == `X')
32:     {
33:         // Load the arrow cursor
34:         lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
35:         // Set the cursor flag
36:         m_bCursor = TRUE;
37:         // Set the screen cursor
38:         SetCursor(lhCursor);
39:         // Exit the application
40:         OnOK();
41:     }
42:     else
43:     {
44:         // Set the cursor flag
45:         m_bCursor = TRUE;
46:         // Set the screen cursor
47:         SetCursor(lhCursor);
48:     }
49: 
50:     ///////////////////////
51:     // MY CODE ENDS HERE
52:     ///////////////////////
53: 
54:     CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
55: }
4. Using the Class Wizard, add a function for the WM_SETCURSOR message on the dialog object.

5. Edit the OnSetCursor function that you just created, adding the code in Listing 3.7.

LISTING 3.7. THE OnSetCursor FUNCTION.

 1: BOOL CMouseDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // If the cursor has been set, then return TRUE
10:     if (m_bCursor)
11:         return TRUE;
12:     else
13: 
14:     ///////////////////////
15:     // MY CODE ENDS HERE
16:     ///////////////////////
17: 
18:     return CDialog::OnSetCursor(pWnd, nHitTest, message);
19: }

The OnSetCursor function needs to always return TRUE or else call the ancestor function. The ancestor function resets the cursor and does need to be called when the application first starts. Because of this, you need to initialize your variable to FALSE so that until the user presses a key to change the cursor, the default OnSetCursor processing is executed. When the user changes the cursor, you want to bypass the default processing and return TRUE instead. This allows the user to draw with whichever cursor has been selected, including the hourglass, as shown in Figure 3.7.

FIGURE 3.7. Drawing with the hourglass cursor.


NOTE: The most common cursor change that you are likely to use in your programs is setting the cursor to the hourglass while your program is working on something that might take a while. There are actually two functions available in MFC that you can use to handle this task. The first is BeginWaitCursor, which displays the hourglass cursor for the user. The second function is EndWaitCursor, which restores the cursor to the default cursor. Both of these functions are members of the CCmdTarget class, from which all of the MFC window and control classes are derived.
If you have a single function controlling all the processing during which you need to display the hourglass and you don't need to display the hourglass after the function has finished, an easier way to show the hourglass cursor is to declare a variable of the CWaitCursor class at the beginning of the function. This automatically displays the hourglass cursor for the user. As soon as the program exits the function, the cursor will be restored to the previous cursor.

Summary

In this chapter, you learned about how you can capture mouse event messages and perform some simple processing based upon these events. You used the mouse events to build a simple drawing program that you could use to draw freehand figures on a dialog window.

You also learned how to grab keyboard events and determine which key is being pressed. You used this information to determine which cursor to display for drawing. For this to work, you had to learn about the default cursor drawing in MFC applications and how you could integrate your code with this behavior to make your application behave the way you want it to.

From here, you will learn how to use the Windows timer to trigger events at regular intervals. You will also learn how to use additional dialog windows to get feedback from the user so that you can integrate that feedback into how your application behaves. After that, you will learn how to create menus for your applications.

Q&A

Q How can I change the type of line that I am drawing? I would like to draw a larger line with a different color.

A When you use any of the standard device context commands to draw on the screen, you are drawing with what is known as a pen, much like the pen you use to draw on a piece of paper. To draw bigger lines, or different color lines, you need to select a new pen. You can do this by adapting the code in the OnMouseMove function, starting where you get the device context. The following code enables you to draw with a big red pen:

// Get the Device Context
CClientDC dc(this);
// Create a new pen
CPen lpen(PS_SOLID, 16, RGB(255, 0, 0));
// Use the new pen
dc.SelectObject(&lpen);
// Draw a line from the previous point to the current point
dc.MoveTo(m_iPrevX, m_iPrevY);
dc.LineTo(point.x, point.y);
Q How can you tell whether the Shift or Ctrl keys are being held down when you receive the WM_KEYDOWN message?

A You can call another function, ::GetKeyState, with a specific key code to determine whether that key is being held down. If the return value of the ::GetKeyState function is negative, the key is being held down. If the return value is nonnegative, the key is not being held down. For instance, if you want to determine whether the Shift key is being held down, you can use this code:

if (::GetKeyState(VK_SHIFT) < 0)
   MessageBox("Shift key is down!");
A number of virtual key codes are defined in Windows for all the special keys. These codes let you look for special keys without worrying about OEM scan codes or other key sequences. You can use these virtual key codes in the ::GetKeyState function and pass them to the OnKeyDown function as the nChar argument. Refer to the Visual C++ documentation for a list of the virtual key codes.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you've learned. The answers to the quiz questions and exercises are provided in Appendix B, "Answers."

Quiz

1. What are the possible mouse messages that you can add functions for?

2. How can you tell if the left mouse button is down on the WM_MOUSEMOVE event message?

3. How can you prevent the cursor from changing back to the default cursor after you set it to a different one?

Exercises

1. Modify your drawing program so that the left mouse button can draw in red and the right mouse button can draw in blue.

2. Extend the OnKeyDown function to add some of the following standard cursors:


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.

Hosted by uCoz