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

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

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

Drawing in a Window

 

R

G

B

 

 

 

 

Background — red

1

0

0

Pen — red

1

0

0

XORed

0

0

0

NOT XOR — produces white

1

1

1

 

 

 

 

As the last line indicates the line comes out as white and because the rest of the background is white, the line disappears.

You need to take care to use the right background color here. You should be able to see that drawing with a white pen on a red background is not going to work too well, as the first time you draw something it is red, and therefore invisible. The second time it appears as white. If you draw on a black background, things appear and disappear, as on a white background, but they are not drawn in the pen color you choose.

Coding the OnMouseMove() Handler

Start by adding the code that creates the element after a mouse move message. Because you are going to draw the element from the handler function, you need to create an object for the device context. The most convenient class to use for this is CClientDC, which is derived from CDC. As I said earlier, the advantage of using this class rather than CDC is that it automatically takes care of creating the device context for you and destroying it when you are done. The device context that it creates corresponds to the client area of a window, which is exactly what you want. Add the following code to the outline handler that you defined earlier:

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

{

// Define a Device Context object for the view

CClientDC aDC(this);

// DC is for this view

aDC.SetROP2(R2_NOTXORPEN);

// Set the drawing mode

if(nFlags & MK_LBUTTON)

 

{

 

m_SecondPoint = point;

// Save the current cursor position

// Test for a previous temporary element

{

//We get to here if there was a previous mouse move

//so add code to delete the old element

}

//Create a temporary element of the type and color that

//is recorded in the document object, and draw it

m_pTempElement = CreateElement();//

Create a

new element

m_pTempElement->Draw(&aDC);

//

Draw the

element

}

}

749

Chapter 14

The first new line of code creates a local CClientDC object. The this pointer that you pass to the constructor identifies the current view object, so the CClientDC object has a device context that corresponds to the client area of the current view. As well as the characteristics I mentioned, this object has all the drawing functions you need because they are inherited from the CDC class. The first member function you use is SetROP2(), which sets the drawing mode to R2_NOTXORPEN.

To create a new element, you save the current cursor position in the data member m_SecondPoint, and then call a view member function CreateElement(). (You’ll define the CreateElement() function as soon as you have finished this handler.) This function should create an element using the two points stored in the current view object, with the color and type specification stored in the document object, and return the address of the element. Save this in m_pTempElement.

Using the pointer to the new element, you call its Draw() member to get the object to draw itself. The address of the CClientDC object is passed as an argument. Because you defined the Draw() function as virtual in the base class, CElement, the function for whatever type of element m_pTempElement is pointing to is automatically selected. The new element is drawn normally with the R2_NOTXORPEN because you are drawing it for the first time on a white background.

You can use the pointer m_pTempElement as an indicator of whether a previous temporary element exists. The code for this part of the handler is:

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

{

// Define a Device Context object for the view

CClientDC aDC(this);

// DC is for this view

aDC.SetROP2(R2_NOTXORPEN);

// Set the drawing mode

if(nFlags&MK_LBUTTON)

 

{

 

m_SecondPoint = point;

// Save the current cursor position

if(m_pTempElement)

{

// Redraw the old element so it disappears from the view m_pTempElement->Draw(&aDC);

delete m_pTempElement;

//

Delete the old element

m_pTempElement = 0;

//

Reset the pointer to 0

}

 

 

//Create a temporary element of the type and color that

//is recorded in the document object, and draw it

m_pTempElement = CreateElement();//

Create a

new element

m_pTempElement->Draw(&aDC);

//

Draw the

element

}

}

A previous temporary element exists if the pointer m_pTempElement is not zero. You need to redraw the element to which it points to remove it from the client area of the view. You then delete the element and reset the pointer to zero. The new element is then created and drawn by the code that you added previously. This combination automatically rubberbands the shape being created, so it appears to be attached

750

Drawing in a Window

to the cursor position as it moves. You must not forget to reset the pointer m_pTempElement back to 0 in the WM_LBUTTONUP message handler after you create the final version of the element.

Creating an Element

You should add the CreateElement() function as a protected member to the ‘Operations’ section of the CSketcherView class:

class CSketcherView: public CView

{

//Rest of the class definition as before...

//Operations

public:

protected:

CElement* CreateElement(void); // Create a new element on the heap

// Rest of the class definition as before...

};

To do this you can either amend the class definition directly by adding the shaded line, or you can rightclick on the class name, CSketcherView, in Class View, and select Add > Add Function from the context menu. This opens the dialog shown in Figure 14-19.

Figure 14-19

751

Chapter 14

Add the specifications of the function, as shown, and click the Finish button. A declaration for the function member is added to the class definition and you are taken directly to a skeleton for the function in SketcherView.cpp. If you added the declaration to the class definition manually, you’ll need to add the complete definition for the function to the .cpp file. This is:

// Create an element of the current type CElement* CSketcherView::CreateElement(void)

{

// Get a pointer to the document for this view

CSketcherDoc* pDoc = GetDocument();

 

ASSERT_VALID(pDoc);

// Verify the pointer is good

// Now select the element using the type stored in the document switch(pDoc->GetElementType())

{

case RECTANGLE:

return new CRectangle(m_FirstPoint, m_SecondPoint, pDoc->GetElementColor());

case CIRCLE:

return new CCircle(m_FirstPoint, m_SecondPoint,

pDoc->GetElementColor());

case CURVE:

return new CCurve(pDoc->GetElementColor());

case LINE:

return new CLine(m_FirstPoint, m_SecondPoint, pDoc->GetElementColor());

default:

// Something’s gone wrong AfxMessageBox(_T(“Bad Element code”), MB_OK); AfxAbort();

return NULL;

}

}

The lines that aren’t shaded are those that will have been supplied automatically if you added the function to the class using the Add > Add Function dialog. The first thing you do here is get a pointer to the document by calling GetDocument(), as you’ve seen before. For safety, you use the ASSERT_VALID() macro to ensure that a good pointer is returned. In the debug version of MFC that’s used in the debug version of your application, this macro calls the AssertValid() member of the object, which is specified as the argument to the macro. This checks the validity of the current object, and if the pointer is NULL or the object is defective in some way, an error message is displayed. In the release version of MFC, the ASSERT_VALID() macro does nothing.

The switch statement selects the element to be created based on the type returned by a function in the document class, GetElementType(). Another function in the document class is used to obtain the current element color. You can add the definitions for both these functions directly to the CSketcherDoc class definition because they are very simple:

752

Drawing in a Window

class CSketcherDoc: public CDocument

{

//Rest of the class definition as before...

//Operations

public:

 

unsigned int GetElementType()

// Get the element type

{ return m_Element; }

 

COLORREF GetElementColor()

// Get the element color

{return m_Color; }

//Rest of the class definition as before...

};

Each of the functions returns the value stored in the corresponding data member. Remember that putting a member function definition in the class definition is equivalent to a request to make the function inline, so as well as being simple, these should be fast.

Dealing with WM_LBUTTONUP Messages

The WM_LBUTTONUP message completes the process of creating an element. The job of the handler for this message is to pass the final version of the element that was created to the document object, and then clean up the view object data members. You can access and edit the code for this handler in the same way as you did for the previous one. Add the following lines to the function:

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

{

// Make sure there is an element if(m_pTempElement)

{

//Call a document class function to store the element

//pointed to by m_pTempElement in the document object

delete m_pTempElement;

//

This code

is temporary

m_pTempElement = 0;

//

Reset the

element pointer

}

}

The if statement verifies that m_pTempElement is not zero before processing it. It’s always possible that the user could press and release the left mouse button without moving the mouse, in which case no element would have been created. As long as there is an element, the pointer to the element is passed to the document object; you’ll add the code for this in the next chapter. In the meantime, you just delete the element here so as not to pollute the heap. Finally, the m_pTempElement pointer is reset to 0, ready for the next time the user draws an element.

Exercising Sketcher

Before you can run the example with the mouse message handlers, you must update the OnDraw() function in the CSketcherView class implementation to get rid of any old code that you added earlier.

753

Chapter 14

To make sure that the OnDraw() function is clean, go to Class View and double-click the function name to take you to its implementation in SketcherView.cpp. Delete any old code that you added, but leave in the first four lines that the wizard provided to get a pointer to the document object. You’ll need this later to get to the elements when they’re stored in the document. The code for the function should now be:

void CSketcherView::OnDraw(CDC* pDC)

{

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

if(!pDoc)

return;

}

Because you have no elements in the document as yet, you don’t need to add anything to this function at this point. When you start storing data in the document in the next chapter, you’ll add code to draw the elements in response to a WM_PAINT message. Without it, the elements just disappear whenever you resize the view, as you’ll see.

Running the Example

After making sure that you have saved all the source files, build the program. If you haven’t made any mistakes entering the code, you’ll get a clean compile and link, so you can execute the program. You can draw lines, circles and rectangles in any of the four colors the program supports. A typical window is shown in Figure 14-20.

Figure 14-20

Try experimenting with the user interface. Note that you can move the window around and that the shapes stay in the window as long as you don’t move it so far that they’re outside the borders of the

754

Drawing in a Window

application window. If you do, the elements do not reappear after you move it back. This is because the existing elements are never redrawn. When the client area is covered and uncovered, Windows sends a WM_PAINT message to the application that causes the OnDraw() member of the view object to be called. As you know, the OnDraw() function for the view doesn’t do anything at present. This gets fixed when you use the document to store the elements.

When you resize the view window, the shapes disappear immediately, but when you move the whole view around, they remain (as long as they don’t slide beyond the application window border). How come? Well, when you resize the window, Windows invalidates the whole client area and expects your application to redraw it in response to the WM_PAINT message. If you move the view around, Windows takes care of relocating the client area as it is. You can demonstrate this by moving the view so that a shape is partially obscured. When you slide it back, you still have a partial shape, with the bit that was obscured erased.

If you try drawing a shape while dragging the cursor outside the client view area, you’ll notice some peculiar effects. Outside the view window, you lose track of the mouse, which tends to mess up the rubber-banding mechanism. What’s going on?

Capturing Mouse Messages

The problem is caused by the fact that Windows is sending the mouse messages to the window under the cursor. As soon as the cursor leaves the client area of your application view window, the WM_MOUSEMOVE messages are being sent elsewhere. You can fix this by using some inherited members of the

CSketcherView class.

The view class inherits a function, SetCapture(), which you can call to tell Windows that you want your view window to get all the mouse messages until such time as you say otherwise, by calling another inherited function in the view class, ReleaseCapture(). You can capture the mouse as soon as the left button is pressed by modifying the handler for the WM_LBUTTONDOWN message:

// Handler for left mouse button down message

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

{

m_FirstPoint = point;

//

Record the cursor position

SetCapture();

//

Capture subsequent mouse messages

}

Now you must call the ReleaseCapture() function in the WM_LBUTTONUP handler. If you don’t do this, other programs cannot receive any mouse messages as long as your program continues to run. Of course, you should only release the mouse if you’ve captured it earlier. The GetCapture() function that the view class inherits returns a pointer to the window that has captured the mouse, and this gives you a way of telling whether or not you have captured mouse messages. You just need to add the following to the handler for WM_LBUTTONUP:

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

{

if(this == GetCapture())

ReleaseCapture(); // Stop capturing mouse messages

// Make sure there is an element

755

Chapter 14

if(m_pTempElement)

{

//Call a document class function to store the element

//pointed to by m_pTempElement in the document object

delete m_pTempElement;

// This code is temporary

m_pTempElement = 0;

// Reset the element pointer

}

 

}

If the pointer returned by the GetCapture() function is equal to the pointer this, your view has captured the mouse, so you release it.

The final alteration you should make is to modify the WM_MOUSEMOVE handler so that it only deals with messages that have been captured by the view. You can do this with one small change:

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

{

// Define a Device Context object for the view

CClientDC aDC(this);

// DC is for this view

aDC.SetROP2(R2_NOTXORPEN);

// Set the drawing mode

if((nFlags & MK_LBUTTON) && (this == GetCapture()))

{

 

m_SecondPoint = point;

// Save the current cursor position

if(m_pTempElement)

{

// Redraw the old element so it disappears from the view

m_pTempElement->Draw(&aDC);

 

delete m_pTempElement;

// Delete the old element

m_pTempElement = 0;

// Reset the pointer to 0

}

 

//Create a temporary element of the type and color that

//is recorded in the document object, and draw it

m_pTempElement = CreateElement();//

Create a

new element

m_pTempElement->Draw(&aDC);

//

Draw the

element

}

}

The handler now processes only the message if the left button is down and the left button down handler for your view has been called, so that the mouse has been captured by your view window.

If you rebuild Sketcher with these additions, you’ll find that the problems that arose earlier when the cursor was dragged off the client area no longer occur.

Summar y

After completing this chapter, you should have a good grasp of how to write message handlers for the mouse, and how to organize drawing operations in your Windows programs. The important points covered in this chapter are:

756

Drawing in a Window

By default, Windows addresses the client area of a window using a client coordinate system with the origin in the upper-left corner of the client area. The positive x direction is from left to right, and the positive y direction is from top to bottom.

You can only draw in the client area of a window by using a device context.

A device context provides a range of logical coordinate systems called mapping modes for addressing the client area of a window.

The default origin position for a mapping mode is the upper-left corner of the client area. The default mapping mode is MM_TEXT, which provides coordinates measured in pixels. The positive x axis runs from left to right in this mode, and the positive y axis from top to bottom.

Your program should always draw the permanent contents of the client area of a window in response to a WM_PAINT message, although temporary entities can be drawn at other times. All the drawing for your application document should be controlled from the OnDraw() member function of a view class. This function is called when a WM_PAINT message is received by your application.

You can identify the part of the client area you want to have redrawn by calling the InvalidateRect() function member of your view class. The area passed as an argument is added by Windows to the total area to be redrawn when the next WM_PAINT message is sent to your application.

Windows sends standard messages to your application for mouse events. You can create handlers to deal with these messages by using ClassWizard.

You can cause all mouse messages to be routed to your application by calling the SetCapture() function in your view class. You must release the mouse when you’re finished with it by calling the ReleaseCapture() function. If you fail to do this, other applications are unable to receive mouse messages.

You can implement rubberbanding when creating geometric entities by drawing them in the message handler for mouse movements.

The SetROP2() member of the CDC class enables you to set drawing modes. Selecting the right drawing mode greatly simplifies rubber-banding operations.

Exercises

You can download the source code for the examples in the book and the solutions to the following exercises from http://www.wrox.com.

1.Add the menu item and Toolbar button for an element of type ellipse, as in the exercises from Chapter 13, and define a class to support drawing ellipses defined by two points on opposite corners of their enclosing rectangle.

2.Which functions now need to be modified to support drawing an ellipse? Modify the program to draw an ellipse.

3.Which functions must you modify in the example from the previous exercise so that the first point defines the center of the ellipse, and the current cursor position defines a corner of the

757

Chapter 14

enclosing rectangle? Modify the example to work this way. (Hint — look up the CPoint class members in Help.)

4.Add a new menu pop-up to the IDR(SketcherTYPE menu for Pen Style, to allow solid, dashed, dotted, dash-dotted, and dash-dot-dotted lines to be specified.

5.Which parts of the program need to be modified to support the operation of the menu, and the drawing of elements in these line types?

6.Implement support for the new menu pop-up and drawing elements in any of the line types.

758