Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
108
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Drawing in a Window

with a pointer to the pen object as an argument. The function returns a pointer to the previous pen object being used, so that you can save it and restore the old pen when you have finished drawing. A typical statement selecting a pen is:

CPen* pOldPen = pDC->SelectObject(&aPen);

// Select aPen as the pen

To restore the old pen when you’re done, you simply call the function again, passing the pointer returned from the original call:

pDC->SelectObject(pOldPen);

// Restore the old pen

You can see this in action if you amend the previous version of the OnDraw() function in the

CSketcherView class to:

void CSketcherView::OnDraw(CDC* pDC)

{

CSketcherDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

if(!pDoc)

return;

//Declare a pen object and initialize it as

//a red solid pen drawing a line 2 pixels wide CPen aPen;

aPen.CreatePen(PS_SOLID, 2, RGB(255, 0, 0));

CPen* pOldPen = pDC->SelectObject(&aPen); // Select aPen as the pen

pDC->Arc(50,50,150,150,100,50,150,100);

// Draw the 1st circle

// Define the bounding rectangle for the 2nd circle

CRect* pRect = new CRect(250,50,300,100);

 

CPoint Start(275,100);

// Arc start point

CPoint End(250,75);

// Arc end point

pDC->Arc(pRect,Start, End);

// Draw the second circle

delete pRect;

 

 

 

pDC->SelectObject(pOldPen);

// Restore the old pen

 

 

}

If you build and execute the Sketcher application with this version of the OnDraw() function, you get the same arcs drawn as before, but this time the lines will be thicker and they’ll be red. You could usefully experiment with this example by trying different combinations of arguments to the CreatePen() function and seeing their effects. Note that we have ignored the value returned from the CreatePen() function, so you run the risk of the function failing and not detecting it in the program. It doesn’t matter here, as the program is still trivial, but as you develop the program it becomes important to check for failures of

this kind.

Creating a Brush

An object of the CBrush class encapsulates a Windows brush. You can define a brush to be solid, hatched, or patterned. A brush is actually an 8x8 block of pixels that’s repeated over the region to be filled.

719

Chapter 14

To define a brush with a solid color, you can specify the color when you create the brush object. For example,

CBrush aBrush(RGB(255,0,0));

// Define a red brush

This statement defines a red brush. The value passed to the constructor must be of type COLORREF, which is the type returned by the RGB() macro, so this is a good way to specify the color.

Another constructor is available to define a hatched brush. It requires two arguments to be specified, the first defining the type of hatching, and the second specifying the color, as before. The hatching argument can be any of the following symbolic constants:

Hatching Style

Description

 

 

HS_HORIZONTAL

Horizontal hatching

HS_VERTICAL

Vertical hatching

HS_BDIAGONAL

Downward hatching from left to right at 45 degrees

HS_FDIAGONAL

Upward hatching from left to right at 45 degrees

HS_CROSS

Horizontal and vertical crosshatching

HS_DIAGCROSS

Crosshatching at 45 degrees

 

 

So, to obtain a red, 45-degree crosshatched brush, you could define the CBrush object with the statement:

CBrush aBrush(HS_DIAGCROSS, RGB(255,0,0));

You can also initialize a CBrush object in a similar manner to that for a CPen object, by using the CreateSolidBrush() member function of the class for a solid brush, and the CreateHatchBrush() member for a hatched brush. They require the same arguments as the equivalent constructors. For example, you could create the same hatched brush as before, with the statements:

CBrush aBrush;

// Define a brush object

aBrush.CreateHatchBrush(HS_DIAGCROSS, RGB(255,0,0));

Using a Brush

To use a brush, you select the brush into the device context by calling the SelectObject() member of the CDC class in a parallel fashion to that used for a pen. This member function is overloaded to support selecting brush objects into a device context. To select the brush defined previously, you would simply write:

pDC->SelectObject(aBrush);

// Select the brush into the device context

There are a number of standard brushes available. Each of the standard brushes is identified by a predefined symbolic constant, and there are seven that you can use. They are the following:

720

Drawing in a Window

GRAY_BRUSH LTGRAY_BRUSH DKGRAY_BRUSH

BLACK_BRUSH WHITE_BRUSH

HOLLOW_BRUSH NULL_BRUSH

The names of these brushes are quite self-explanatory. To use one, you call the SelectStockObject() member of the CDC class, passing the symbolic name for the brush that you want to use as an argument. To select the null brush, which leaves the interior of a closed shape unfilled, you could write:

pDC->SelectStockObject(NULL_BRUSH);

Here, pDC is a pointer to a CDC object, as before. You can also use one of a range of standard pens through this function. The symbols for standard pens are BLACK_PEN, NULL_PEN (which doesn’t draw anything), and WHITE_PEN. The SelectStockObject() function returns a pointer to the object being replaced in the device context. This enables you to save it for restoring later when you have finished drawing.

Because the function works with a variety of objects — you’ve seen pens and brushes in this chapter, but it also works with fonts — the type of the pointer returned is CGdiObject*. The CGdiObject class is a base class for all the graphic device interface object classes and thus a pointer to this class can be used to store a pointer to any object of these types. However, you need to cast the pointer value returned to the appropriate type so that you can select the old object back to restore it. This is because the SelectObject() function you use to do this is overloaded for each of the kinds of object that can be selected. There’s no version of SelectObject() that accepts a pointer to a CGdiObject as an argument, but there are versions that accept an argument of type CBrush*, CPen*, and pointers to other GDI objects.

The typical pattern of coding for using a stock brush and later restoring the old brush when you’re done is:

CBrush* pOldBrush = (CBrush*)pDC->SelectStockObject(NULL_BRUSH);

// draw something...

pDC->SelectObject(pOldBrush);

// Restore the old brush

You’ll be using this in your example later in the chapter.

Drawing Graphics in Practice

You now know how to draw lines and arcs, so it’s about time to consider how the user is going to define what they want drawn in Sketcher. In other words, you need to decide how the user interface is going to work.

Because the Sketcher program is to be a sketching tool, you don’t want the user to worry about coordinates. The easiest mechanism for drawing is using just the mouse. To draw a line, for instance, the user could position the cursor and press the left mouse button where they wanted the line to start, and then define the end of the line by moving the cursor with the left button held down. It would be ideal if you could arrange that the line was continuously drawn as the cursor was moved with the left button down (this is known as “rubber-banding” to graphic designers). The line would be fixed when the left mouse button was released. This process is illustrated in Figure 14-7.

721

Chapter 14

Left mouse button down

Line is fixed when the mouse button is released

Left mouse button up

Cursor movement

Line is continuously updated as the cursor moves

Figure 14-7

You could allow circles to be drawn in a similar fashion. The first press of the left mouse button would define the center and, as the cursor was moved with the button down, the program would track it. The circle would be continuously redrawn, with the current cursor position defining a point on the circumference of the circle. As with drawing a line, the circle would be fixed when the left mouse button was released. You can see this process illustrated in Figure 14-8.

You can draw a rectangle as easily as you draw a line, as illustrated in Figure 14-9.

The first point is defined by the position of the cursor when the left mouse button is pressed. This is one corner of the rectangle. The position of the cursor when the mouse is moved with the left button held down defines the diagonal opposite corner of the rectangle. The rectangle actually stored is the last one defined when the left mouse button is released.

722

Drawing in a Window

Circle is fixed when the

mouse button is released

Left mouse button down

Left mouse button up

Cursor movement

Circle is continuously updated as the cursor moves

Figure 14-8

A curve is somewhat different. An arbitrary number of points may define a curve. The mechanism you’ll use is illustrated in Figure 14-10.

As with the other shapes, the first point is defined by the cursor position when the left mouse button is pressed. Successive positions recorded when the mouse is moved are connected by straight line segments to form the curve, so the mouse track defines the curve to be drawn.

Now you know how the user is going to define an element, clearly the next step in understanding how to implement this is to get a grip on how the mouse is programmed.

723

Chapter 14

Left mouse button down

Rectangle is fixed when the mouse button is released

Left mouse button up

Cursor movement

Rectangle is continuously updated as the cursor moves

Figure 14-9

 

Left mouse button up

 

stops tracking of the

Left mouse

cursor and ends

button

curve.

down

 

 

Cursor path

Curve is defined by straight line segments joining successive cursor positions

Figure 14-10

724

Drawing in a Window

Programming the Mouse

To be able to program the drawing of shapes in the way I have discussed, you need to know various things about the mouse:

Pressing a mouse button signals the start of a drawing operation

The location of the cursor when the mouse button is pressed defines a reference point for the shape

A mouse movement after detecting that a mouse button has been pressed is a cue to draw a shape, and the cursor position provides a defining point for the shape

The cursor position at the time the mouse button is released signals that the final version of the shape should be drawn

As you may have guessed, all this information is provided by Windows in the form of messages sent to your program. The implementation of the process for drawing lines and circles consists almost entirely of writing message handlers.

Messages from the Mouse

When the user of our program is drawing a shape, they will interact with a particular document view. The view class is, therefore, the obvious place to put the message handlers for the mouse. Right-click the CSketcherView class name in Class View and then display its properties window by selecting Properties from the context menu. If you then click the messages button (wait for the button tool tips to display if you don’t know which it is), you’ll see the list of message IDs. You will then see the list of message IDs for the standard Windows messages sent to the class, which have IDs prefixed with WM_.

You need to know about three mouse messages at the moment, so I scrolled down to bring them into view in Figure 14-11.

They are the following:

Message

Description

 

 

WM_LBUTTONDOWN

Message occurs when the left mouse button is pressed.

WM_LBUTTONUP

Message occurs when the left mouse button is released.

WM_MOUSEMOVE

Message occurs when the button is moved.

 

 

These messages are quite independent of one another and are being sent to the document views in your program even if you haven’t supplied handlers for them. It’s quite possible for a window to receive a WM_LBUTTONUP message without having previously received a WM_LBUTTONDOWN message. This can happen if the button is pressed with the cursor over another window and then moved to your view window before being released.

725

Chapter 14

Figure 14-11

If you look at the list in the properties window you’ll see there are other mouse messages that can occur. You can choose to process any or all of the messages, depending on your application requirements. Define in general terms what you want to do with the three messages that you’re currently interested in, based on the process for drawing shapes that you saw earlier:

WM_LBUTTONDOWN

This starts the process of drawing an element. So you will:

1.Note that the element drawing process has started.

2.Record the current cursor position as the first point for defining an element.

WM_MOUSEMOVE

This is an intermediate stage where we want to create and draw a temporary version of the current element but only if the left mouse button is down, so:

1.

2.

Check that the left button is down.

If it is, delete any previous version of the current element that was drawn.

726

Drawing in a Window

3.If it isn’t, then exit.

4.Record the current cursor position as the second defining point for the current element.

5.Cause the current element to be drawn using the two defining points.

WM_LBUTTONUP

This indicates that the process for drawing an element is finished, so all you need to do is:

1.Store the final version of the element defined by the first point recorded, together with the position of the cursor when the button is released for the second point.

2.Record the end of the process of drawing an element.

Now generate handlers for these three mouse messages.

Mouse Message Handlers

You can create a handler for one of the mouse messages by clicking on the ID to select it and then selecting the down arrow in the adjacent column position; try selecting <add> OnLButtonUp for the ID_LBUTTONUP message, for example. Repeat the process for each of the messages WM_LBUTTONDOWN and WM_MOUSEMOVE. The functions generated in the CSketcherView class are OnLButtonDown(),

OnLButtonUp() and OnMouseMove(). You don’t get the option of changing the names of these functions because you’re adding overrides for versions that are already defined in the base class for the CSketcherView class. Take a look at how you implement these handlers.

You can start by looking at the WM_LBUTTONDOWN message handler. This is the skeleton code that’s generated:

void CSketcherView::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

CView::OnLButtonDown(nFlags, point);

}

You can see that there is a call to the base class handler in the skeleton version. This ensures that the base handler is called if you don’t add any code here. In this case you don’t need to call the base class handler when you handle the message yourself, although you can if you want to. Whether you need to call the base class handler for a message depends on the circumstances.

Generally, the comment indicating where you should add your own code is a good guide. Where it suggests, as in the present instance, that calling the base class handler is optional, you can omit it when you add your own message handling code. Note that the position of the comment in relation to the call of the base class handler is also important, as sometimes you must call the base class message handler before your code, and other times afterwards. The comment indicates where your code should appear in relation to the base class message handler call.

The handler in your class is passed two arguments: nFlags, which is of type UINT and contains a number of status flags indicating whether various keys are down, and the CPoint object point, which defines

727

Chapter 14

the cursor position when the left mouse button was pressed. The UINT type is defined in the Windows API and corresponds to a 32-bit unsigned integer.

The value of nFlags that is passed to the function can be any combination of the following symbolic values:

Flag

Description

 

 

MK_CONTROL

Corresponds to the Ctrl key being pressed.

MK_LBUTTON

Corresponds to the left mouse button being down.

MK_MBUTTON

Corresponds to the middle mouse button being down.

MK_RBUTTON

Corresponds to the right mouse button being down.

MK_SHIFT

Corresponds to the Shift key being pressed.

 

 

Being able to detect if a key is down in the message handler enables you to support different actions for the message depending on what else you find. The value of nFlags may contain more than one of these indicators, each of which corresponds to a particular bit in the word, so you can test for a particular key using the bitwise AND operator. For example, to test for the Ctrl key being pressed, you could write:

if(nFlags & MK_CONTROL)

// Do something...

The expression nFlags & MK_CONTROL will only have the value TRUE if the nFlags variable has the bit defined by MK_CONTROL set. In this way, you can have different actions when the left mouse button is pressed, depending on whether or not the Ctrl key is also pressed. You use the bitwise AND operator here, so corresponding bits are ANDed together. Don’t confuse this with the logical AND, &&, which would not do what you want here.

The arguments passed to the other two message handlers are the same as those for the OnLButtonDown() function; the code generated for them is:

void CSketcherView::OnLButtonUp(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

CView::OnLButtonUp(nFlags, point);

}

void CSketcherView::OnMouseMove(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

CView::OnMouseMove(nFlags, point);

}

Apart from the function names, the skeleton code is the same for each.

728