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

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

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

Working with Menus and Toolbars

Figure 13-7

Message Type

Description

 

 

COMMAND

This type of message is issued when a particular menu item has

 

been selected. The handler should provide the action appropriate

 

to the menu item being selected, for example, setting the current

 

color in the document object or setting the element type.

UPDATE_COMMAND_UI

This is issued when the menu should be updated — checked or

 

unchecked, for example — depending on its status. This message

 

occurs before a pop-up menu is displayed so you can set the

 

appearance of the menu item before the user sees it.

 

 

The way these work is quite simple. When you click a menu item in the Menu bar, an UPDATE_ COMMAND_UI message is sent for each item in that menu before the menu is displayed. This provides the opportunity to do any necessary updating of the menu items’ properties before the user sees it. When these messages are handled and any changes to the items’ properties are completed, the menu is drawn. When you then click one of the items in the menu, a COMMAND message for that menu item is sent. I’ll deal with the COMMAND messages now, and come back to the UPDATE_COMMAND_UI messages a little later in this chapter.

Because events for menu items result in command messages, you can choose to handle them in any of the classes that are currently defined in the Sketcher application. So how do you decide where you should process a message for a menu item?

689

Chapter 13

Choosing a Class to Handle Menu Messages

Before you can decide which class should handle the messages for the menu items you’ve added, you need to decide what you want to do with the messages.

You want the element type and the element color to be modal — that is, whatever is set for the element type and element color should remain in effect until one or the other is changed. This allows you to create as many blue circles as you want, and when you want red circles, you just change the color. You have two basic possibilities for handling the setting of a color and the selection of an element type: setting them by view or by document. You could set them by view, in which case, if there’s more than one view of a document, each view has its own color and element set. This means that you might draw a red circle in one view, switch to another view, and find that you’re drawing a blue rectangle. This would be confusing and in conflict with how you would probably want them to work.

It would be better, therefore, to have the current color and element selection apply to a document. You can then switch from one view to another and continue drawing the same elements in the same color. There might be other differences between the views that you implement, such as the scale at which the document is displayed perhaps, but the drawing operation is consistent across multiple views.

This suggests that you should store the current color and element in the document object. These could then be accessed by any view object associated with the document object. Of course, if you had more than one document active, each document would have its own color and element type settings. It would therefore be sensible to handle the messages for your new menu items in the CSketcherDoc class and to store information about the current selections in an object of this class. I think you’re ready to dive in and create a handler for the Black menu item.

Creating Menu Message Functions

Highlight the CSketchDoc class name in the Event Handler Wizard dialog box by clicking it. You’ll also need to click the COMMAND message type. You can then click the Add and Edit button. This closes the dialog box and the code for the handler you have created in the CSketcherDoc class is displayed in the edit window. The function looks like this:

void CSketcherDoc::OnColorBlack()

{

// TODO: Add your command handler code here

}

The highlighted line is where you’ll put your code that handles the event that results from the user selecting the Black menu item. The wizard also has updated the CSketcherDoc class definition:

class CSketcherDoc : public CDocument

{

protected: // create from serialization only CSketcherDoc(); DECLARE_DYNCREATE(CSketcherDoc)

//Attributes public:

//Operations

690

Working with Menus and Toolbars

public:

//Overrides public:

virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar);

//Implementation

public:

virtual ~CSketcherDoc(); #ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const; #endif

protected:

// Generated message map functions protected:

DECLARE_MESSAGE_MAP() public:

afx_msg void OnColorBlack();

};

The OnColorBlack() method has been added as a public member of the class and the afx_msg prefix marks it as a message handler.

You can now add COMMAND message handlers for the other color menu IDs and all the Element menu IDs in exactly the same way as this one. You can create each of the handler functions for the menu items with just four mouse clicks. Right-click the menu item, click the Add Event Handler menu item, click the CSketcherDoc class name in the dialog box for the Event Handler wizard, and click the Add and Edit button for the dialog box.

The Event Handler wizard should now have added the handlers to the CSketcherDoc class definition, which now looks like this:

class CSketcherDoc: public CDocument

{

...

protected:

// Generated message map functions protected:

DECLARE_MESSAGE_MAP() public:

afx_msg void OnColorBlack(); afx_msg void OnColorRed();

afx_msg void OnColorGreen(); afx_msg void OnColorBlue(); afx_msg void OnElementLine(); afx_msg void OnElementRectangle(); afx_msg void OnElementCircle(); afx_msg void OnElementCurve();

};

691

Chapter 13

A declaration has been added for each of the handlers that you’ve specified in the Event Handler wizard dialog box. Each of the function declarations has been prefixed with afx_msg to indicate that it is a message handler.

The Event Handler wizard also automatically updates the message map in your CSketcherDoc class implementation with the new message handlers. If you take a look in the file SketcherDoc.cpp, you’ll see the message map as shown here:

BEGIN_MESSAGE_MAP(CSketcherDoc, CDocument)

ON_COMMAND(ID_COLOR_BLACK, OnColorBlack)

ON_COMMAND(ID_COLOR_RED, OnColorRed)

ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)

ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)

ON_COMMAND(ID_ELEMENT_LINE, OnElementLine)

ON_COMMAND(ID_ELEMENT_RECTANGLE, OnElementRectangle)

ON_COMMAND(ID_ELEMENT_CIRCLE, OnElementCircle)

ON_COMMAND(ID_ELEMENT_CURVE, OnElementCurve)

END_MESSAGE_MAP()

The Event Handler wizard has added an ON_COMMAND() macro for each of the handlers that you have identified. This associates the handler name with the message ID, so, for example, the member function OnColorBlack() is called to service a COMMAND message for the menu item with the ID

ID_COLOR_BLACK.

Each of the handlers generated by the Event Handler wizard is just a skeleton. For example, take a look at the code provided for OnColorBlue(). This is also defined in the file SketcherDoc.cpp, so you can scroll down to find it, or go directly to it by switching to the Class View and double-clicking the function name after expanding the tree for the class CSketcherDoc (make sure that the file is saved first):

void CSketcherDoc::OnColorBlue()

{

// TODO: Add your command handler code here

}

As you can see, the handler takes no arguments and returns nothing. It also does nothing at the moment, but this is hardly surprising, because the Event Handler wizard has no way of knowing what you want to do with these messages!

Coding Menu Message Functions

Now consider what you should do with the COMMAND messages for our new menu items. I said earlier that you want to record the current element and color in the document, so you need to add a data member to the CSketcherDoc class for each of these.

Adding Members to Store Color and Element Mode

You could add the data members that you need to the CSketcherDoc class definition just by editing the class definition directly, but let’s use the Add Member Variable wizard to do it. Display the dialog box for the wizard by right-clicking the CSketcherDoc class name in the Class View and then selecting Add > Add Variable from the pop-up menu that appears. You then see the dialog box for the wizard as shown in Figure 13-8.

692

Working with Menus and Toolbars

Figure 13-8

I’ve already entered the information in the dialog box for the m_Element variable that stores the current element type to be drawn. I have selected protected as the access because it should not be accessible directly from outside the class. I have also selected the type as unsigned int because you use a positive integer to identify each type of element. When you click the Finish button, the variable is added to the class definition in the CSketcherDoc.h file.

Add the CSketcherDoc class member to store the element color manually just to show that you can. Its name is m_Color and its type is COLORREF, which is a type defined by the Windows API for representing a color as a 32-bit integer. You can add the declaration for the m_Color member to the CSketcherDoc class like this:

class CSketcherDoc : public CDocument

{

...

// Generated message map functions protected:

DECLARE_MESSAGE_MAP() public:

afx_msg void OnColorBlack(); afx_msg void OnColorRed(); afx_msg void OnColorGreen(); afx_msg void OnColorBlue(); afx_msg void OnElementLine(); afx_msg void OnElementRectangle(); afx_msg void OnElementCircle(); afx_msg void OnElementCurve();

protected:

693

Chapter 13

// Current element type

unsigned

int m_Element;

 

COLORREF

m_Color;

// Current drawing color

};

The m_Color member is also protected, as there’s no reason to allow public access. You can always add functions to access or change the values of protected or private class members with the advantage that you then have complete control over what values can be set.

Initializing the New Class Data Members

You need to decide how to represent an element type. You could just set m_Element to a unique numeric value, but this would introduce “magic numbers” into the program, the significance of which would be less than obvious to anyone else looking at the code. A better way would be to define a set of constants that you can use to set values for the member variable, m_Element. In this way, you can use a standard mnemonic to refer to a given type of element. You could define the element types with the following statements:

//Element type definitions

//Each type value must be unique const unsigned int LINE = 101U; const unsigned int RECTANGLE = 102U; const unsigned int CIRCLE = 103U; const unsigned int CURVE = 104U;

The constants initializing the element types are arbitrary unsigned integers. You can choose different values, if you like, as long as they are all distinct. If you want to add further types in the future, it will obviously be very easy to add definitions here.

For the color values, it would be a good idea if we used constant variables that are initialized with the values that Windows uses to define the color in question. You could do this with the following lines of code:

// Color values for drawing

const COLORREF BLACK = RGB(0,0,0); const COLORREF RED = RGB(255,0,0); const COLORREF GREEN = RGB(0,255,0); const COLORREF BLUE = RGB(0,0,255);

Each constant is initialized by RGB(), which is a standard macro defined in the Wingdi.h, header file that is included as part of Windows.h. The three arguments to the macro define the red, green, and blue components of the color value respectively. Each argument must be an integer between 0 and 255, where these limits correspond to no color component and the maximum color component. RGB(0,0,0) corresponds to black because there are no components of red, green, or blue. RGB(255,0,0) creates a color value with a maximum red component, and no green or blue contribution. You can create other colors by combining red, green, and blue components.

You need somewhere to put these constants, so let’s create a new header file and call it OurConstants.h. You can create a new file by right-clicking the Header Files folder in the Solution Explorer tab and selecting the Add > Add New Item menu option from the pop-up. Enter the header file name OurConstants in the dialog box that displays and then click the Open button. You’ll then be able to enter the constant definitions in the Editor window as shown here.

694

Working with Menus and Toolbars

//Definitions of constants

#pragma once

//Element type definitions

//Each type value must be unique const unsigned int LINE = 101U; const unsigned int RECTANGLE = 102U; const unsigned int CIRCLE = 103U; const unsigned int CURVE = 104U;

///////////////////////////////////

//Color values for drawing

const COLORREF BLACK = RGB(0,0,0); const COLORREF RED = RGB(255,0,0); const COLORREF GREEN = RGB(0,255,0); const COLORREF BLUE = RGB(0,0,255);

///////////////////////////////////

As you’ll recall, the pre-processor directive #pragma once is there to ensure that the definitions cannot be included more than once in a file. The statements in the header file are included into a source file only by an #include directive if it has hasn’t been included previously. After the header has been included in a file, the statements will not be included again.

After saving the header file, you can add the following #include statement to the beginning of the file

Sketcher.h:

#include “OurConstants.h”

Any .cpp file that has an #include directive for Sketcher.h has the constants available.

You can verify that the new constants are now part of the project by expanding Global Functions and Variables in the Class View. You’ll see the names of the color and element types that have been added now appear along with the global variable theApp.

Modifying the Class Constructor

It’s important to make sure that the data members you have added to the CSketcherDoc class are initialized appropriately when a document is created. You can add the code to do this to the class constructor as shown here:

CSketcherDoc::CSketcherDoc() : m_Element(LINE), m_Color(BLACK)

{

// TODO: add one-time construction code here

}

The wizard already has arranged that the m_Element member will be initialized to 0 so change the initial value to LINE. You then need to add the initializer for the m_Color member with BLACK as the value so that everything is consistent with the initial check marks that you specified for the menus.

Now you’re ready to add the code for the handler functions that you created for the Element and Color menu items. You can do this from the Class View. Click the name of the first handler function, OnColorBlack(). You just need to add one line to the function, so the code for it becomes:

695

Chapter 13

void CSketcherDoc::OnColorBlack()

{

m_Color = BLACK;

// Set the drawing color to black

}

The only job that the handler has to do is to set the appropriate color. In the interests of conciseness, the new line replaces the comment provided originally. You can go through and add one line to each of the Color menu handlers setting the appropriate color value.

The element menu handlers are much the same. The handler for the Element > Line menu item is:

void CSketcherDoc::OnElementLine()

{

m_Element = LINE;

// Set element type as a line

}

With this model, it’s not too difficult to write the other handlers for the Element menu. That’s eight message handlers completed. You can now rebuild the example and see how it works.

Running the Extended Example

Assuming that there are no typos, the compiled and linked program should run without error. When you run the program, you should see the window shown in Figure 13-9.

Figure 13-9

The new menus are in place on the menu bar, and you can see that the items you have added to the menu are all there, and you should see the Prompt message in the status bar that you provided in the properties box when the mouse cursor is over a menu item. You could also verify that Alt+C and Alt+l work as well. The things that don’t work are the check marks for the currently selected color and element, which remain firmly stuck to their initial defaults. Let’s look at how you can fix that.

696

Working with Menus and Toolbars

Adding Message Handlers to Update the User Interface

To set the check mark correctly for the new menus, you need to add the second kind of message handler, UPDATE_COMMAND_UI (signifying update command user interface), for each of the new menu items. This sort of message handler is specifically aimed at updating the menu item properties before the item is displayed.

Go back to viewing the Sketcher.rc file in the Editor window. Right-click the Black item in the Color menu and select Add Event Handler from the pop-up menu. You can then select UPDATE_COMMAND_UI as the message type and CSketcherDoc as the class as shown in Figure 13-10.

Figure 13-10

The name for an update function has been generated[md]OnUpdateColorBlack(). Because this seems a reasonable name for the function you want, click the Add and Edit button and have the Event Handler wizard generate it. As well as generating the skeleton function definition in SketcherDoc.cpp, its declaration is added to the class definition. An entry for it is also made in the message map that looks like this:

ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColorBlack)

This uses the ON_UPDATE_COMMAND_UI() macro that identifies the function you have just generated as the handler to deal with update messages corresponding to the ID shown. You could now enter the code for the new handler but I’ll let you add command update handlers for each of the menu items for both the Color and Element menus first.

Coding a Command Update Handler

You can access the code for the OnUpdateColorBlack() handler in the CSketcherDoc class by selecting the function in Class View. This is the skeleton code for the function:

697

Chapter 13

void CSketcherDoc::OnUpdateColorBlack(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

}

The argument passed to the handler is a pointer to an object of the CCmdUI class type. This is an MFC class that is only used with update handlers, but it applies to toolbar buttons as well as menu items. The pointer points to an object that identifies the item that originated the update message so you use this to operate on the item to update how it appears before it is displayed. The CCmdUI class has five member functions that act on user interface items. The operations that each of these provides is as follows:

Method

Description

 

 

ContinueRouting()

Passes the message on to the next priority handler.

Enable()

Enables or disables the relevant interface item.

SetCheck()

Sets a check mark for the relevant interface item.

SetRadio()

Sets a button in a radio group on or off.

SetText()

Sets the text for the relevant interface item.

 

 

We’ll use the third function, SetCheck(), as that seems to do what we want. The function is declared in the CCmdUI class as:

virtual void SetCheck(int nCheck = 1);

This function sets a menu item as checked if you pass 1 as the argument and set it unchecked if you pass 0 as the argument. The parameter has a default value of 1, so if you just want to set a check mark for a menu item regardless, you can call this function without specifying an argument.

In our case, you want to set a menu item as checked if it corresponds with the current color. You can, therefore, write the update handler for OnUpdateColorBlack() as:

void CSketcherDoc::OnUpdateColorBlack(CCmdUI* pCmdUI)

{

// Set menu item Checked if the current color is black

pCmdUI->SetCheck(m_Color==BLACK);

}

The statement you have added calls the SetCheck() function for the Color > the argument expression m_Color==BLACK results in 1 if m_Color is BLACK, or therefore, is to check the menu item only if the current color stored in m_Color cisely what you want.

Black menu item, and 0 otherwise. The effect, is BLACK, which is pre-

The update handlers for all the menu items in a menu are always called before the menu is displayed so you can code the other handlers in the same way to ensure that only the item corresponding to the current color (or the current element) is checked:

698