Chapter 15. Working with Menus and Toolbars

WHAT YOU WILL LEARN IN THIS CHAPTER:

  • How an MFC-based program handles messages

  • Menu resources, and how you can create and modify them

  • Menu properties, and how you can create and modify them

  • How to create a function to service the message generated when a menu item is selected

  • How to add handlers to update menu properties

  • How to add toolbar buttons and associate them with existing menu items

In the last chapter, you saw how a simple framework application generated by the MFC Application Wizard is made up and how the parts interrelate. In this chapter, you'll start customizing a Multiple Document Interface (MDI) framework application called Sketcher with a view to making it into a useful program. The first step in this process is to understand how menus are defined in Visual C++ 2010, and how functions are created to service the application-specific menu items that you add to your program. You'll also see how to add toolbar buttons to the application.

COMMUNICATING WITH WINDOWS

As you saw in Chapter 12, Windows communicates with your program by sending messages to it. In an MFC application, most of the drudgery of message handling is taken care of, so you don't have to worry about providing a WndProc() function at all. MFC enables you to provide functions to handle just the individual messages that you're interested in and to ignore the rest. These functions are referred to as message handlers or just handlers. Because your application is MFC-based, a message handler is always a member function of one of your application's classes.

The association between a particular message and the function in your program that is to service it is established by a message map, and each class in your program that can handle Windows messages will have one. A message map for a class is simply a table of member functions that handle Windows messages bounded by a couple of macros. Each entry in the message map associates a function with a particular message; when a given message occurs, the corresponding function is called. Only the messages relevant to a class appear in the message map for the class.

A message map for a class is created automatically by the MFC Application Wizard when you create a project, or by ClassWizard when you add a class that handles messages to your program. Additions to, and deletions from, a message map are mainly managed by ClassWizard, but there are circumstances in which you need to modify the message map manually. The start of a message map in your code is indicated by a BEGIN_MESSAGE_MAP() macro, and the end is marked by an END_MESSAGE_MAP() macro. Let's look into how a message map operates using our Sketcher example.

Understanding Message Maps

A message map is established by the MFC Application Wizard for each of the main classes in your program. In the instance of an MDI program such as Sketcher, a message map is defined for each of CSketcherApp, CSketcherDoc, CSketcherView, CMainFrame, and CChildFrame. You can see the message map for a class in the .cpp file containing the implementation of the class. Of course, the functions that are included in the message map also need to be declared in the class definition, but they are identified here in a special way. Look at the definition for the CSketcherApp class shown here:

class CSketcherApp : public CWinAppEx
{
public:
      CSketcherApp();


// Overrides
public:
      virtual BOOL InitInstance();

// Implementation
      BOOL  m_bHiColorIcons;

      virtual void PreLoadState();
      virtual void LoadCustomState();
      virtual void SaveCustomState();

      afx_msg void OnAppAbout();
      DECLARE_MESSAGE_MAP()
};

Only one message handler, OnAppAbout(), is declared in the CSketcherApp class. The word afx_msg at the beginning of the line declaring the OnAppAbout() function is there just to distinguish a message handler from other member functions in the class. It is converted to white space by the preprocessor, so it has no effect when the program is compiled.

The DECLARE_MESSAGE_MAP() macro indicates that the class can contain function members that are message handlers. In fact, any class that you derive from the MFC class CCmdTarget can potentially have message handlers, so such classes will have this macro included as part of the class definition by the MFC Application Wizard or by the Add Class Wizard that you'll use to add a new class for a project, depending on which was responsible for creating it. Figure 15-1 shows the MFC classes derived from CCmdTarget that have been used in our examples so far.

FIGURE 15-1

Figure 15.1. FIGURE 15-1

The classes that have been used directly, or as a direct base for our own application classes, are shown shaded. Thus, the CSketcherApp class has CCmdTarget as an indirect base class and, therefore, always includes the DECLARE_MESSAGE_MAP() macro. All the view (and other) classes derived from CWnd also have it.

If you are adding your own members to a class directly, it's best to leave the DECLARE_MESSAGE_MAP() macro as the last line in the class definition. If you do add members after DECLARE_MESSAGE_MAP(), you'll also need to include an access specifier for them: public, protected, or private.

Message Handler Definitions

If a class definition includes the macro DECLARE_MESSAGE_MAP(), the class implementation must include the macros BEGIN_MESSAGE_MAP() and END_MESSAGE_MAP(). If you look in Sketcher.cpp, you'll see the following code as part of the implementation of CSketcherApp:

BEGIN_MESSAGE_MAP(CSketcherApp, CWinAppEx)
      ON_COMMAND(ID_APP_ABOUT, &CSketcherApp::OnAppAbout)
      // Standard file-based document commands
      ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)
      ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen)
      // Standard print setup command
      ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup)
END_MESSAGE_MAP()

This is a message map. The BEGIN_MESSAGE_MAP() and END_MESSAGE_MAP() macros define its boundaries, and each of the message handlers in the class appears between these macros. In the preceding case, the code is handling only one category of message, the type of WM_COMMAND message called a command message, which is generated when the user selects a menu option or enters an accelerator key. (If that seems clumsy, it's because there's another kind of WM_COMMAND message called a control notification message, which provides information about the activity of a control.)

The message map knows which menu or key is pressed by the identifier (ID) that's included in the message. There are four ON_COMMAND macros in the preceding code, one for each of the command messages to be handled. The first argument to this macro is an ID associated with one particular command, and the ON_COMMAND macro ties the function name to the command specified by the ID. Thus, when a message corresponding to the identifier ID_APP_ABOUT is received, the function OnAppAbout() is called. Similarly, for a message corresponding to the ID_FILE_NEW identifier, the function OnFileNew() is called. This handler is actually defined in the base class, CWinApp, as are the two remaining handlers.

The BEGIN_MESSAGE_MAP() macro has two arguments. The first argument identifies the current class name for which the message map is defined, and the second provides a connection to the base class for finding a message handler. If a handler isn't found in the class defining the message map, the message map for the base class is then searched.

Note that command IDs such as ID_APP_ABOUT are standard IDs defined in MFC. These correspond to messages from standard menu items and toolbar buttons. The ID_ prefix is used to identify a command associated with a menu item or a toolbar button, as you'll see when I discuss resources later. For example, ID_FILE_NEW is the ID that corresponds to the selection of the File

Message Handler Definitions

There are symbols besides WM_COMMAND that Windows uses to identify standard messages. Each of them is prefixed with WM_ for "Windows message." These symbols are defined in Winuser.h, which is included in Windows.h. If you want to look at them, you'll find Winuser.h in the include subfolder of the VC folder containing your Visual C++ 2010 system.

Note

There's a nice shortcut for viewing a .h file. If the name of the file appears in the editor window, you can just right-click it and select the menu item Open Document "Filename.h" from the pop-up menu. This works with standard library headers, too.

Windows messages often have additional data values that are used to refine the identification of a particular message specified by a given ID. The WM_COMMAND message, for instance, is sent for a whole range of commands, including those originating from the selection of a menu item or a click on a toolbar button.

Note that when you are adding message handlers manually, you should not map a message (or, in the case of command messages, a command ID) to more than one message handler in a class. If you do, it won't break anything, but the second message handler is never called. Normally, you add message handlers through the Properties window, and, in this case, you will not be able to map a message to more than one message handler. If you want to see the Properties window for a class, right-click a class name in Class View and select Properties from the pop-up menu. You add a message handler by selecting the Messages button at the top of the Properties window that is displayed (see Figure 15-2). To determine which button is the Messages button, simply hover the cursor over each button until the tooltip displays.

FIGURE 15-2

Figure 15.2. FIGURE 15-2

Clicking the Messages button brings up a list of message IDs; however, before I go into what you do next, I need to explain a little more about the types of messages you may be handling.

Message Categories

There are three categories of messages that your program may be dealing with, and the category to which a message belongs determines how it is handled. The message categories are:

MESSAGE CATEGORY

DESCRIPTION

Windows messages

These are standard Windows messages that begin with the WM_ prefix, with the exception of WM_COMMAND messages, which we shall come to in a moment. Examples of Windows messages are WM_PAINT, which indicates that you need to redraw the client area of a window, and WM_LBUTTONUP, which signals that the left mouse button has been released.

Control notification messages

These are WM_COMMAND messages sent from controls (such as a list box) to the window that created the control, or from a child window to a parent window. Parameters associated with a WM_COMMAND message enable messages from the controls in your application to be differentiated.

Command messages

These are also WM_COMMAND messages that originate from the user interface elements, such as menu items and toolbar buttons. MFC defines unique identifiers for standard menu and toolbar command messages.

The standard Windows messages in the first category are identified by the WM_ - prefixed IDs that Windows defines. You'll be writing handlers for some of these messages in the next chapter. The messages in the second category are a particular group of WM_COMMAND messages that you'll see in Chapter 18 when you work with dialog boxes. You'll deal with the last category, messages originating from menus and toolbars, in this chapter. In addition to the message IDs defined by MFC for the standard menus and toolbars, you can define your own message IDs for the menus and toolbar buttons that you add to your program. If you don't supply IDs for these items, MFC automatically generates IDs for you, based on the menu text.

Handling Messages in Your Program

You can't put a handler for a message anywhere you like. The permitted sites for a handler depend on what kind of message is to be processed. The first two categories of message that you saw earlier — that is, standard Windows messages and control notification messages — are always handled by objects of classes that are ultimately derived from CWnd. Frame window classes and view classes, for example, have CWnd as an indirect base class, so they can have member functions to handle Windows messages and control notification messages. Application classes, document classes, and document template classes are not derived from CWnd, so they can't handle these messages.

Using the Properties window for a class to add a handler solves the headache of remembering where to place handlers, as it offers you only the IDs allowed for the class. For example, if you select CSketcherDoc as the class, you won't be offered any of the WM_ messages in the Properties window for the class.

For standard Windows messages, the CWnd class provides default message handling. Thus, if your derived class doesn't include a handler for a standard Windows message, it is processed by the default handler defined in the base class. If you do provide a handler in your class, you'll sometimes still need to call the base class handler as well, so that the message is processed properly. When you're creating your own handler, a skeleton implementation of it is provided when you select the handler in the Properties window for a class, and this includes a call to the base handler where necessary.

Handling command messages is much more flexible. You can put handlers for these in the application class, the document and document template classes, and, of course, in the window and view classes in your program. So what happens when a command message is sent to your application, bearing in mind that there are a lot of options as to where it is handled?

How Command Messages Are Processed

All command messages are sent to the main frame window for the application. The main frame window then tries to get the message handled by routing it in a specific sequence to the classes in your program. If one class can't process the message, the main frame window passes it on to the next.

For an SDI program, the sequence in which classes are offered an opportunity to handle a command message is:

  1. The view object

  2. The document object

  3. The document template object

  4. The main frame window object

  5. The application object

The view object is given the opportunity to handle a command message first, and, if no handler has been defined, the next class object has a chance to process it. If none of the classes has a handler defined, default Windows processing takes care of it, essentially throwing the message away.

For an MDI program, things are only a little more complicated. Although you have the possibility of multiple documents, each with multiple views, only the active view and its associated document are involved in the routing of a command message. The sequence for routing a command message in an MDI program is:

  1. The active view object

  2. The document object associated with the active view

  3. The document template object for the active document

  4. The frame window object for the active view

  5. The main frame window object

  6. The application object

It's possible to alter the sequence for routing messages, but this is so rarely necessary that I won't go into it in this book.

EXTENDING THE SKETCHER PROGRAM

You're going to add code to the Sketcher program you created in the previous chapter to implement the functionality you need to create sketches. You'll provide code for drawing lines, circles, rectangles, and curves with various colors and line thicknesses, and for adding annotations to a sketch. The data for a sketch is stored in a document, and you'll also allow multiple views of the same document at different scales.

It will take several chapters to learn how to add everything that you need, but a good starting point would be to add menu items to deal with the types of elements that you want to be able to draw, and to select a color for drawing. You'll make both the element type and color selection persistent in the program, which means that once you've selected a color and an element type, both of these will remain in effect until you change one or the other.

To add menus to Sketcher, you'll work through the following steps:

  1. Define the menu items to appear on the main menu bar and in each of the menus.

  2. Decide which of the classes in our application should handle the message for each menu item.

  3. Add message handling functions to the classes for the menu messages.

  4. Add functions to the classes to update the appearance of the menus to show the current selection in effect.

  5. Add a toolbar button complete with tooltips for each of the menu items.

ELEMENTS OF A MENU

You'll be looking at two aspects of dealing with menus with the MFC: the creation and modification of the menu as it appears in your application, and the processing necessary when a particular menu item is selected — the definition of a message handler for it. Let's look first at how you create new menu items.

Creating and Editing Menu Resources

Menus are defined external to the program code in a resource file, and the specification of the menu is referred to as a resource. You can include several other kinds of resources in your application: typical examples include dialogs, toolbars, and toolbar buttons. You'll be seeing more on these as you extend the Sketcher application.

Having a menu defined in a resource allows the physical appearance of the menu to be changed without affecting the code that processes menu events. For example, you could change your menu items from English to French or Norwegian or whatever without having to modify or recompile the program code. The code to handle the message created when the user selects a menu item doesn't need to be concerned with how the menu looks, only with the fact that it was selected. Of course, if you do add items to the menu, you'll need to add some code for each of them to ensure that they actually do something!

The Sketcher program already has a menu, which means that it already has a resource file. You can access the resource file contents for the Sketcher program by selecting the Resource View pane, or, if you have the Solution Explorer pane displayed, you can double-click Sketcher.rc. This switches you to the Resource View, which displays the resources. If you expand the menu resource by clicking on the [unfilled] symbol, you'll see that it includes two menus, indicated by the identifiers IDR_MAINFRAME and IDR_SketchTYPE. The first of these applies when there are no documents open in the application, and the second when you have one or more documents open. MFC uses the IDR_ prefix to identify a resource that defines a complete menu for a window.

You're going to be modifying only the menu that has the identifier IDR_SketchTYPE. You don't need to look at IDR_MAINFRAME, as your new menu items will be relevant only when a document is open. You can invoke a resource editor for the menu by double-clicking its menu ID in Resource View. If you do this for IDR_SketchTYPE, the Editor pane appears, as shown in Figure 15-3.

FIGURE 15-3

Figure 15.3. FIGURE 15-3

Adding a Menu Item to the Menu Bar

To add a new menu item, you can just click the menu box on the menu bar with the text "Type Here" to select it, and then type in your menu name. If you insert an ampersand (&) in front of a letter in the menu item, the letter is identified as a shortcut key to invoke the menu from the keyboard. Type the first menu item as E&lement. This selects l as the shortcut letter, so you will be able to invoke the menu item and display its pop-up by typing Alt+L. You can't use E because it's already used by Edit. When you finish typing the name, you can right-click the new menu item and select Properties from the pop-up to display its properties, as shown in Figure 15-4.

FIGURE 15-4

Figure 15.4. FIGURE 15-4

Properties are simply parameters that determine how the menu item will appear and behave. Figure 15-4 displays the properties for the menu item grouped by category. If you would rather have them displayed in alphabetical sequence, just click the second button from the left. Note that the Popup property is set to True by default; this is because the new menu item is at the top level on the menu bar, so it would normally present a pop-up menu when it is selected. Clicking any property in the left column enables you to modify it in the right column. In this case, you want to leave everything as it is, so you can just close the Properties window. No ID is necessary for a pop-up menu item, because selecting it just displays the menu beneath and there's no event for your code to handle. Note that you get a new blank menu box for the first item in the pop-up menu, as well as one on the main menu bar.

It would be better if the Element menu appeared between the View and Window menus, so place the mouse cursor on the Element menu item and, keeping the left mouse button pressed, drag it to a position between the View and Window menu items, and release the left mouse button. After positioning the new Element menu item, the next step is to add items on the pop-up menu that corresponds to it.

Adding Items to the Element Menu

Select the first item (currently labeled "Type Here") in the Element pop-up menu by clicking it; then, type Line as the caption and press the Enter key. You can see the properties for this menu item by right-clicking it and selecting Properties; the properties for this first item in the pop-up menu are shown in Figure 15-5.

FIGURE 15-5

Figure 15.5. FIGURE 15-5

The properties modify the appearance of the menu item and also specify the ID of the message passed to your program when the menu item is selected. Here, we have the ID already specified as ID_ELEMENT_LINE, but you can change it to something else if you want. Sometimes it's convenient to specify the ID yourself, such as when the generated ID is too long or its meaning is unclear. If you choose to define your own ID, you should use the MFC convention of prefixing it with ID_ to indicate that it's a command ID for a menu item.

Because this item is part of a pop-up menu, the Popup property is False by default. You could make it another pop-up menu with a further list of items by setting the Popup property to True. As you see in Figure 15-5, you can display the possible values for the Popup property by selecting the down arrow. Don't you love the way pop-ups pop up all over the place?

You can enter a text string for the value of the Prompt property that appears in the status bar of your application when the menu item is highlighted. If you leave it blank, nothing is displayed in the status bar. I suggest you enter Draw lines as the value for the Prompt property. Note how you get a brief indication of the purpose of the selected property at the bottom of the Properties window. The Break property can alter the appearance of the pop-up by shifting the item into a new column. You don't need that here, so leave it as it is. Close the Properties window and click the Save icon in the toolbar to save the values that you have set.

Modifying Existing Menu Items

If you think you may have made a mistake and want to change an existing menu item, or even if you just want to verify that you set the properties correctly, it's very easy to go back to an item. Just double-click the item you're interested in, and the Properties window for that item is displayed. You can then change the properties in any way you want and close the window when you're done. If the item you want to access is in a pop-up menu that isn't displayed, just click the item on the menu bar to display the pop-up.

Completing the Menu

Now, you can go through and create the remaining Element pop-up menu items that you need: Rectangle, Circle, and Curve. You can accept the default IDs ID_ELEMENT_RECTANGLE, ID_ELEMENT_CIRCLE, and ID_ELEMENT_CURVE, respectively, for these. You could also set the values for the Prompt property value to Rectangle, Circle, and Curve, respectively.

You also need a Color menu on the menu bar, with pop-up menu items for Black, Red, Green, and Blue. You can create these, starting at the empty menu entry on the menu bar, using the same procedure that you just went through. You can use the default IDs (ID_COLOR_BLACK, etc.) as the IDs for the menu items. You can also add the status bar prompt for each as the value of the Prompt property. After you've finished, if you drag Color so that it's just to the right of Element, the menu should appear as shown in Figure 15-6.

FIGURE 15-6

Figure 15.6. FIGURE 15-6

Note that you need to take care not to use the same letter more than once as a shortcut in the main menu. There's no check made as you create new menu items, but if you right-click with the cursor on the menu bar when you've finished editing it, you'll get a pop-up that contains the item Check Mnemonics. Selecting this checks your menu for duplicate shortcut keys. It's a good idea to do this every time you edit a menu, because it's easy to create duplicates by accident.

That completes extending the menu for elements and colors. Don't forget to save the file to make sure that the additions are safely stored away. Next, you need to decide in which classes you want to deal with messages from your menu items, and add member functions to handle each of the messages. For that, you'll be using the Event Handler Wizard.

ADDING HANDLERS FOR MENU MESSAGES

To create an event handler for a menu item, right-click the item and select Add Event Handler from the pop-up displayed. If you try this with the Black menu item in the Color menu pop-up, you'll see the dialog shown in Figure 15-7.

FIGURE 15-7

Figure 15.7. FIGURE 15-7

As you can see, the wizard has already chosen a name for the handler function. You could change it, but OnColorBlack seems like a good name to me.

You obviously need to specify the message type as one of the choices shown in the dialog box. The "Message type:" box in the window in Figure 15-7 shows the two kinds of messages that can arise for a particular menu ID. Each type of message serves a distinct purpose in dealing with a menu item.

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, such as by 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 messages 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?

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 it is changed. This enables 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 will have 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 your tools 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 element 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 will be 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 CSketcherDoc 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's selecting the Black menu item. The wizard also has updated the CSketcherDoc class definition:

class CSketcherDoc : public CDocument
{
  ...

// 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
{
  ...

// 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();
 };

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, &CSketcherDoc::OnColorBlack)
ON_COMMAND(ID_COLOR_RED, &CSketcherDoc::OnColorRed)
ON_COMMAND(ID_COLOR_GREEN, &CSketcherDoc::OnColorGreen)
ON_COMMAND(ID_COLOR_BLUE, &CSketcherDoc::OnColorBlue)
ON_COMMAND(ID_ELEMENT_LINE, &CSketcherDoc::OnElementLine)
ON_COMMAND(ID_ELEMENT_RECTANGLE, &CSketcherDoc::OnElementRectangle)
ON_COMMAND(ID_ELEMENT_CIRCLE, &CSketcherDoc::OnElementCircle)
ON_COMMAND(ID_ELEMENT_CURVE, &CSketcherDoc::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, let's 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

Adding Members to Store Color and Element Mode
FIGURE 15-8

Figure 15.8. FIGURE 15-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 entered the type as ElementType. You will define this type in the next section. 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:
  // Current element type
  ElementType 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. Of course, you could have used the Add Member Variable Wizard dialog as you did for m_Element; the difference is that you would type COLORREF in the "Variable type:" box, rather than selecting it from the drop-down list.

Initializing the New Class Data Members

You can define ElementType as an enum type, as follows:

// Element type definitions
enum ElementType{LINE, RECTANGLE, CIRCLE, CURVE};

Because it's an enum type, each of the possible values is automatically defined as a unique value. If you need to add further types in the future, it will obviously be very easy to add them here.

For the color values, it would be good to use constant variables that are initialized with the values Windows uses to define the colors 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, as you learned in Chapter 13. It is defined in the Wingdi.h header file that is included as part of Windows.h. Just to remind you, 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, 0 being no color component and 255 being 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 the ElementType definition and the color constants, so create a new header file and call it SketcherConstants.h. You can create a new file by right-clicking the Header Files folder in the Solution Explorer tab and selecting the Add

Initializing the New Class Data Members
//Definitions of constants

#pragma once

   // Element type definitions
enum ElementType{LINE, RECTANGLE, CIRCLE, CURVE};


   // 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.

After saving the header file, you can add the following #include statement to the beginning of the file Sketcher.h, after the #pragma once directive:

#include "SketcherConstants.h"

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

You can verify that the new color constants are now part of the project by expanding Global Functions and Variables in the Class View. You'll see that the names of the color constants that have been added now appear, along with the other global variables. The new element type now appears as a type in the Class View upper pane.

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
}

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:

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 interest 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

Modifying the Class Constructor
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 15-9.

FIGURE 15-9

Figure 15.9. FIGURE 15-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 can also verify that Alt+C and Alt+L work. Ideally, the currently selected element of the color menu item should be checked as a cue to the current state. Let's look at how you can fix that.

Adding Message Handlers to Update the User Interface

To set the checkmark 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 IDR_SketchTYPE menu 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 CSketcherDoc as the class and UPDATE_COMMAND_UI as the message type, as shown in Figure 15-10.

FIGURE 15-10

Figure 15.10. FIGURE 15-10

The name for the update function has been generated as 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 the skeleton function definition in SketcherDoc.cpp being generated, its declaration is added to the class definition. An entry for it is also made in the message map in the SketcherDoc.cpp that looks like this:

ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, &CSketcherDoc::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:

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 used only 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 are as follows:

FUNCTION

DESCRIPTION

ContinueRouting()

Passes the message on to the next priority handler.

Enable()

Enables or disables the relevant interface item.

SetCheck()

Sets a checkmark 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 sets it as unchecked if you pass 0 as the argument. The parameter has a default value of 1, so if you just want to set a checkmark 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

Coding a Command Update Handler

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.

A typical Element menu item update handler is coded as follows:

void CSketcherDoc::OnUpdateElementLine(CCmdUI* pCmdUI)
{
   // Set Checked if the current element is a line
   pCmdUI->SetCheck(m_Element==LINE);
}

You can now code all the other update handlers for items in the Element pop-up in a similar manner.

Exercising the Update Handlers

When you've added the code for all the update handlers, you can build and execute the Sketcher application again. Now, when you change a color or an element type selection, this is reflected in the menu, as shown in Figure 15-11.

FIGURE 15-11

Figure 15.11. FIGURE 15-11

When you click on a menu item, the default way the menu works is to display the most recently used menu item first without displaying the complete pop-up. You can change this by right-clicking the menu bar in the Sketcher application window and selecting Customize. . . from the pop-up. If you select the Options from the dialog that displays, you can uncheck the option "Menus show recently used commands first." Now, the full menu pop-up will display when you select a menu item.

You have completed all the code that you need for the menu items. Make sure that you have saved everything before embarking on the next stage. These days, toolbars are a must in any Windows program of consequence, so the next step is to take a look at how you can add toolbar buttons to support our new menus.

ADDING TOOLBAR BUTTONS

Select the Resource View and extend the toolbar resource. You'll see that there are two toolbars, one that has the same ID as the main menu, IDR_MAINFRAME, and another that has the ID IDR_MAINFRAME_256. The former provides you with toolbar icons that have four-bit color (16 colors) that will be displayed when small icons are selected the toolbar, whereas the latter provides icons with 24-bit color (256 colors) that correspond to the large icon toolbar. To implement Sketcher properly, you must add buttons to both toolbars.

If you right-click IDR_MAINFRAME_256 and select Open from the pop-up, the editor window appears, as shown in Figure 15-12.

FIGURE 15-12

Figure 15.12. FIGURE 15-12

If the toolbar displays as a contiguous image, rather than as shown in Figure 15-12, right-click in the editor window and select "Toolbar editor . . ." from the pop-up.

A toolbar button is a 16-by-15 array of pixels that contains a pictorial representation of the function it initiates. You can see in Figure 15-12 that the resource editor provides an enlarged view of a toolbar button so that you can see and manipulate individual pixels. It also provides you with a color palette to the right, from which you can choose the current working color. If you click the new button icon at the right end of the row as indicated, you'll be able to draw this icon. Before starting the editing, drag the new icon about half a button-width to the right. It separates from its neighbor on the left to start a new block.

You should keep the toolbar button icons in the same sequence as the items on the menu bar, so you'll create the element type selection buttons first. You'll probably want to use the following editing buttons provided by the resource editor, which appear in the toolbar for the Visual C++ 2010 application window:

  • Pencil for drawing individual pixels

  • Eraser for erasing individual pixels

  • Fill an area with the current color

  • Zoom the view of the button

  • Draw a line

  • Draw a rectangle

  • Draw an ellipse

  • Draw a curve

If it is not already visible, you can display the window that displays the palette, from which you can choose a color by right-clicking a toolbar button icon and selecting Show Colors Window from the pop-up. You select a foreground color by left-clicking a color from the palette, and a background color by right-clicking.

The first icon will be for the toolbar button corresponding to the Element

FIGURE 15-12

If you make a mistake, you can select the Undo button or you can change to the Erase Tool editing button and use that, but in the latter case, you need to make sure that the color selected corresponds to the background color for the button you are editing. You can also erase individual pixels by clicking them using the right mouse button, but again, you need to be sure that the background color is set correctly when you do this. After you're happy with what you've drawn, the next step is to edit the toolbar button properties.

Editing Toolbar Button Properties

Right-click your new button icon in the toolbar and select Properties from the pop-up to bring up its Properties window, as shown in Figure 15-13.

FIGURE 15-13

Figure 15.13. FIGURE 15-13

The Properties window shows a default ID for the button, but you want to associate the button with the menu item Element

FIGURE 15-13

You can now move on to designing the other three element buttons. You can use the rectangle editing button to draw a rectangle and the ellipse button to draw a circle. You can draw a curve using the pencil to set individual pixels, or use the curve button. You need to associate each button with the ID corresponding to the equivalent menu item that you defined earlier.

Now add the buttons for the colors. You should also drag the first button for selecting a color to the right, so that it starts a new group of buttons. You could keep the color buttons very simple and just color the whole button with the color it selects. You can do this by selecting the appropriate foreground color, then selecting the Fill editing button and clicking on the enlarged button image. Again, you need to use ID_COLOR_BLACK, ID_COLOR_RED, and so on as IDs for the buttons. The toolbar editing window should look like the one shown in Figure 15-14.

FIGURE 15-14

Figure 15.14. FIGURE 15-14

You now need to repeat the whole process for the IDR_MAINFRAME toolbar. Create equivalent buttons for the element types and colors, and assign the same IDs to the buttons that you assigned to the corresponding IDR_MAINFRAME_256 toolbar buttons.

That's all you need for the moment, so save the resource file and give Sketcher another spin.

Exercising the Toolbar Buttons

Build the application once again and execute it. You should see the application window shown in Figure 15-15.

FIGURE 15-15

Figure 15.15. FIGURE 15-15

There are some amazing things happening here. The toolbar buttons that you added already reflect the default settings that you defined for the new menu items, and the selected button is shown depressed. If you let the cursor linger over one of the new buttons, the prompt for the button appears in the status bar. The new buttons work as a complete substitute for the menu items, and any new selection made, with either the menu or the toolbar, is reflected by the toolbar button being shown depressed. The menu items now have the button icons in front of the text, with the selected menu item having the icon depressed. You can also enable large icons for the toolbar by right-clicking in the toolbar area or selecting Toolbars and Docking Windows from the View menu to display the Customize dialog.

If you close the document view window, Sketch1, you'll see that our toolbar buttons are automatically grayed and disabled. If you open a new document window, they are automatically enabled once again. You can also try dragging the toolbar with the cursor. You can move it to either side of the application window, or have it free-floating. You can also enable or disable it through the Customize dialog. You got all this without writing a single additional line of code!

Adding Tooltips

There's one further tweak that you can add to your toolbar buttons that is remarkably easy: tooltips. A tooltip is a small box that appears adjacent to the toolbar button when you let the cursor linger on the button. The tooltip contains a text string that is an additional clue to the purpose of the toolbar button.

To add a tooltip for a toolbar button, select the button in the toolbar editor tab. Select the Prompt property for the button in the Properties pane and enter followed by the text for the tooltip. For example, for the toolbar button with the ID ID_ELEMENT_LINE, you could enter the Prompt property value as Draw lines. The tooltip text will be displayed when you hover the mouse cursor over the toolbar button. If you put the after the text in the Prompt property value — for example, Draw lines — the text will be displayed in the status bar rather than as a tooltip when the mouse cursor is over the button.

You can have a different text in the status bar and in the tooltip. Set the value for the Prompt property for the Line menu item as Draw Lines Line.

This will display "Draw Lines" in the status bar when you hover the mouse cursor over the toolbar button. The tooltip will display the button icon image and the word "Line" with the status bar prompt text below it.

Change the Prompt property text for each of the toolbar buttons corresponding to the Element and Color menus in a similar way. That's all you have to do. After saving the resource file, you can now rebuild the application and execute it. Placing the cursor over one of the new toolbar buttons causes the prompt text to appear in the status bar and the tooltip to be displayed after a second or two.

MENUS AND TOOLBARS IN A C++/CLI PROGRAM

Of course, your C++/CLI programs can also have menus and toolbars. A good starting point for a Windows-based C++/CLI program is to create a Windows Forms application. The Windows Forms technology is oriented toward the development of applications that use the vast array of standard controls that are provided, but there's no reason you can't draw with a form-based application. There is much less programming for you to do with a Windows Forms application, because you add the standard components for the GUI using the Windows Forms Designer capability, and the code to generate and service the GUI components will be added automatically. Your programming activity will involve adding application-specific classes and customizing the behavior of the application by implementing event handlers.

Understanding Windows Forms

Windows Forms is a facility for creating Windows applications that execute with the CLR. A form is a window that is the basis for an application window or a dialog window to which you can add other controls that the user can interact with. Visual C++ 2010 comes with a standard set of more than 60 controls that you can use with a form. Because there is a very large number of controls, you'll get to grips only with a representative sample in this book, but that should give you enough of an idea of how they are used so you can explore the others for yourself. Many of the standard controls provide a straightforward interactive function, such as Button controls that represent buttons to be clicked, or TextBox controls that allow text to be entered. Some of the standard controls are containers, which means that they are controls that can contain other controls. For example, a GroupBox control can contain other controls such as Button controls or TextBox controls, and the function of a GroupBox control is simply to group the controls together for some purpose and optionally provide a label for the group in the GUI. There are also a lot of third-party controls available. If you can't find the control you want in Visual C++, it is very likely that you can find a third party that produces it.

A form and the controls that you use with a form are represented by a C++/CLI class. Each class has a set of properties that determines the behavior and appearance of the control or form. For example, whether or not a control is visible in the application window and whether or not a control is enabled to allow user interaction are determined by the property values that are set. You can set a control's properties interactively when you assemble the GUI using the IDE. You can also set property values at run time using functions that you add to the program, or via code that you add to existing functions through the code editor pane. The classes also define functions that you call to perform operations on the control.

When you create a project for a Windows Forms application, an application window based on the Form class is created along with all the code to display the application window. After you create a Windows Forms project, there are four distinct operations involved in developing a Windows Forms application:

  • You create the GUI interactively in the Form Design tab that is displayed in the Editor pane by selecting controls in the Toolbox window and placing them on the form. You can also create additional form windows.

  • You modify the properties for the controls and the forms to suit your application needs in the Properties window.

  • You create click event handlers for a control by double-clicking the control on the Form Design tab. You can also set an existing function as the handler for an event for a control from its Properties window.

  • You modify and extend the classes that are created automatically from your interaction with the Form Design tab to meet the needs of your application.

You'll get a chance to see how it works in practice.

Understanding Windows Forms Applications

You first need to get an idea of how the default code for a Windows Forms application works. Create a new CLR project using the Windows Forms Application template and assign the name CLR Sketcher to the project. The procedure is exactly the same as the one you used back in Chapter 12 when you created Ex12_03, so I won't repeat it here. The Design window for CLR Sketcher will show the form as it is defined initially, and you'll customize it to suit the development of a version of Sketcher for the CLR. It won't have the full capability of the MFC version, but it will demonstrate how you implement the major features in a C++/CLI context. You'll continue to add functionality to this application over the next four chapters.

The Editor pane displays a graphical representation of the application window because with a Windows Forms application, you build the GUI graphically. Even double-clicking Form1.h in the Solution Explorer pane does not display the code, but you can see it by right-clicking in the editor window and selecting View Code from the context menu.

The code defines the Form1 class that represents the application window, and the first thing to note is that the code is defined in its own namespace:

namespace CLRSketcher
{
 using namespace System;
 using namespace System::ComponentModel;
using namespace System::Collections;
 using namespace System::Windows::Forms;
 using namespace System::Data;
 using namespace System::Drawing;

  // rest of the code
}

When you compile the project, it creates a new assembly, and the code for this assembly is within the namespace CLRSketcher, which is the same as the project name. The namespace ensures that a type in another assembly that has the same name as a type in this assembly is differentiated, because each type name is qualified by its own namespace name.

There are also six directives for .NET library namespaces, and these cover the library functionalities you are most likely to need in your application. These namespaces are:

NAMESPACE

CONTENTS

System

This namespace contains classes that define data types that are used in all CLR applications. It also contains classes for events and event handling, exceptions, and classes that support commonly used functions.

System::ComponentModel

This namespace contains classes that support the operation of GUI components in a CLR application.

System::Collections

This namespace contains collection classes for organizing data in various ways, and includes classes to define lists, queues, dictionaries (maps), and stacks.

System::Windows::Forms

This namespace contains the classes that support the use of Windows Forms in an application.

System::Data

This namespace contains classes that support ADO.NET, which is used for accessing and updating data sources.

System::Drawing

This namespace defines classes that support basic graphical operations such as drawing on a form or a component.

The Form1 class is derived from the Form class that is defined in the System::Windows::Forms namespace. The Form class represents either an application window or a dialog window, and the Form1 class that defines the window for CLR Sketcher inherits all the members of the Form class.

The section at the end of the Form1 class contains the definition of the InitializeComponent() function. This function is called by the constructor to set up the application window and any components that you add to the form. The comments indicate that you must not modify this section of code using the code editor, and this code is updated automatically as you alter the application window interactively. It's important when you do use Form Design that you do not ignore these comments and modify the automatically generated code yourself; if you do, things are certain to go wrong at some point. Of course, you can write all the code for a Windows Forms application from the ground up, but it is much quicker and less error-prone to use the Form Design capability to set up the GUI for your application interactively. This doesn't mean you shouldn't know how it works.

The code for the InitializeComponent() function initially looks like this:

void InitializeComponent(void)
{
  this->components = gcnew System::ComponentModel::Container();
  this->Size = System::Drawing::Size(300,300);
  this->Text = L"Form1";
  this->Padding = System::Windows::Forms::Padding(0);
  this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
}

The components member of the Form1 class is inherited from the base class, and its role is to keep track of components that you subsequently add to the form. The first statement stores a handle to a Container object in components, and this object represents a collection that stores GUI components in a list. Each new component that you add to the form using the Form Design capability is added to this Container object.

Modifying the Properties of a Form

The remaining statements in the InitializeComponent() function set properties for Form1. You must not modify any of these directly in the code, but you can choose your own values for these through the Properties window for the form, so return to the Form1.h (Design) tab for the form in the editor window and right-click it to display the Properties window shown in Figure 15-16.

Figure 15-16 shows the properties for the form categorized by function. You can also display the properties in alphabetical sequence by clicking the second button at the top of the window. This is helpful when you know the name of the property you want to change.

FIGURE 15-16

Figure 15.16. FIGURE 15-16

It's worth browsing through the list of properties for the form to get an idea of the possibilities. Clicking any of the properties displays a description at the bottom of the window. Figure 15-16 shows the Properties window with the width increased to make the descriptions visible. You can select the cell in the right column to modify the property value. The properties that have an [unfilled] symbol to the left have multiple values, and clicking the symbol displays the values so you can change them individually. You can make the form a little larger by changing the value for the Size property in the Layout group to 500,350. You don't want to make it too large at this point because that will make it difficult to work with when you have several windows open in the IDE, so just size the form so it's easy to work with on your display. You could also change the Text property (in the Appearance category) to CLRSketcher. This changes the text in the title bar for the application window. If you go back to the Form1.h tab in the editor window, you'll see that the code in the InitializeComponent() function has been altered to reflect the property changes you have made. As you'll see, the Properties window is a fundamental tool for implementing support for GUI components in a Forms-based application.

Note that you can also arrange for the application window to be maximized when the program starts by setting the value for the WindowState property. With the default Normal setting, the window is the size you have specified, but you can set the property to Maximized or Minimized by selecting from the list of values for this property.

How the Application Starts

Execution of the application begins, as always, in the main() function, and this is defined in the CLRSketcher.cpp file as follows:

int main(array<System::String ^> ^args)
{
       // Enabling Windows XP visual effects before any controls are created
       Application::EnableVisualStyles();
       Application::SetCompatibleTextRenderingDefault(false);

       // Create the main window and run it
       Application::Run(gcnew Form1());
       return 0;
}

The main() function calls three static functions that are defined in the Application class that is defined in the System::Windows::Forms namespace. The static functions in the Application class are at the heart of every Windows Forms application. The EnableVisualStyles() function that is called first in main() enables visual styles for the application. The SetCompatibleTextRenderingDefault() function determines how text is to be rendered in new controls based on the argument value. A value of true for the argument specifies that the Graphics class is to be used, and a false value specifies that the TextRenderer class is to be used. The Run() function starts a Windows message loop for the application and makes the Form object that is passed as the argument visible. An application running with the CLR is still ultimately a Windows application, so it works with a message loop in the same way as all other Windows applications.

Adding a Menu to CLR Sketcher

The IDE provides you with a standard set of controls that you can add to your application interactively through the Design window. Press Ctrl+Alt+X or select from the View menu to display the Toolbox window. The window containing the list of available groups of controls is displayed, as shown in Figure 15-17.

FIGURE 15-17

Figure 15.17. FIGURE 15-17

The block in the Toolbox window, labeled All Windows Forms, lists all the controls available for use with a form. You can click the [unfilled] symbol for the All Windows Forms tab (if it is not already expanded) to see what is in it. This shows all the controls that you can use with Windows Forms. You can collapse any expanded group by clicking the [filled] symbol to the left of the block heading. The controls are also grouped by type in the list, starting with the Common Controls block. You may find it most convenient, initially, to use the group containing all the controls at the beginning of the list, but after you are familiar with what controls are available, it will be easier to collapse the groups and just have one group expanded at one time.

A menu strip is a container for menu items, so add one to the form by dragging a MenuStrip from the Menus & Toolbars group in the Toolbox window to the form; the menu strip attaches itself to the top of the form, below the title bar. You'll see a small arrow at the top right of the control. If you click this, a pop-up window appears, as shown in Figure 15-18.

FIGURE 15-18

Figure 15.18. FIGURE 15-18

The first item in the MenuStrip Tasks pop-up embeds the menu strip in a ToolStripContainer control that provides panels on all four sides of the form plus a central area in which you can place another control. The second item generates four standard menu items: File, Edit, Tools, and Help, complete with menu item lists. As you'll see in a moment, you can also do this without displaying this pop-up. The RenderMode menu enables you to choose the painting style for the menu strip, and you can leave this at the default selection. The Dock option enables you to choose on which side of the form the menu strip is to be docked, or you can elect to have it undocked. The GripStyle option determines whether the grip for dragging the menu around is visible or not. The Visual C++ 2010 menu strips have visible grips that enable you to move them around. Selecting the final Edit Items. . . option displays a dialog box in which you can edit the properties for the menu strip or for any of its menu items.

If you right-click the menu strip and select Insert Standard Items from the pop-up, the standard menu items will be added; you'll be implementing only the File menu item, but you can leave the others there if you want. Alternatively, you can delete any of them by right-clicking the item and selecting Delete from the pop-up. I'll leave them in so they will appear in the subsequent figures.

To add the Element menu, click the box displaying "Type Here" and type E&lement. You can then click the box that displays below Element and type Line for the first submenu item. Continue to add the remaining submenu items by entering Rectangle, Circle, and Curve in successive boxes in the drop-down menu. To shift the Element menu to the left of the Help menu, select Element on the menu strip and drag it to the left of Help. Next, you can add the Color menu by typing &Color in the "Type Here" field on the menu strip. You can then enter Black, Red, Green, and Blue, successively, in the menu slots below Color to create the drop-down menu. After dragging Color to the left of Help, your application window should look like Figure 15-19.

FIGURE 15-19

Figure 15.19. FIGURE 15-19

To check the default Color

FIGURE 15-19

You can add shortcut key combinations for menu items by setting the value of the ShortcutKeys property. Figure 15-20 shows this property for the Line menu item.

FIGURE 15-20

Figure 15.20. FIGURE 15-20

Select the modifier or modifiers by clicking the checkboxes, and select the key from the drop-down list. Figure 15-20 shows Ctrl+Shift+L specified as the shortcut key combination. You can control whether or not the shortcut key combination is displayed in the menu by setting the value of the ShowShortcutKeys property appropriately. Take care not to specify duplicate key combinations for shortcuts.

The next step is to add event handlers for the menu items.

Adding Event Handlers for Menu Items

Event handlers are delegates in a C++/CLI program, but you hardly need to be aware of this in practice because all the functions that are delegates will be created automatically. Return to the Design tab by clicking on it. You can start by adding handlers for the items in the Element menu.

If it is not already visible, extend the Element drop-down menu on the form by clicking it, then right-click the Line menu item and select Properties from the pop-up. Click the Events button in the Properties window (the one that looks like a lightning flash) and double-click the Click event at the top of the list. The IDE will switch to the code tab showing the delegate function for the menu item, which looks like this:

private: System::Void lineToolStripMenuItem_Click(System::Object^  sender,
                                                  System::EventArgs^  e)
{

}

I have rearranged the code slightly to make it easier to read. This function will be called automatically when you click the Line menu item in the application. The first parameter identifies the source of the event, the menu item object in this case, and the second parameter provides information relating to the event. You'll learn more about EventArgs objects a little later in this chapter. This function has already been identified as the event handler for the menu item with the following code:

this->lineToolStripMenuItem->Click += gcnew System::EventHandler(
                                this, &Form1::lineToolStripMenuItem_Click);

This is in the InitializeComponent() function, located at about line 364 in the Form1.h listing on my system, but if you deleted the surplus standard menu items, it will be a different line; you'll find it in the section labeled in comments as // lineToolStripMenuItem.

You can add event handlers for all the menu items in the Element and Color menus in the same way. If you switch back to the Design tab and you have not closed the Properties window, clicking a new menu item will display the properties for that item in the window. You can then double-click the Click property to generate the handler function. Note that the names of the handler functions are created by default, but if you want to change the name, you can edit it in the value field to the right of the event name in the Properties window. The default names are quite satisfactory in this instance, though.

Implementing Event Handlers

Following the model of the MFC version of Sketcher, you need a way to identify different element types in the CLR version. An enum class will do nicely for this. Add the following line of code to the Form1.h file following the using directives at the beginning of the file, and immediately before the comments preceding the Form1 class definition:

enum class ElementType {LINE, RECTANGLE, CIRCLE, CURVE};

This defines the four constants you need to specify the four types of elements in Sketcher. As soon as you have entered this code, you will see the new class appear in the Class View window as part of CLR Sketcher.

To identify the colors, you can use objects of type System::Drawing::Color that represent colors. The System::Drawing namespace defines a wide range of types for use in drawing operations, and you'll be using quite a number of them before CLR Sketcher is finished. The Color structure defines a lot of standard colors, including Color::Black, Color::Red, Color::Green, and Color::Blue, so you have everything you need.

The element type and the drawing color are modal, so you should add variables to the Form1 class to store the current element type and color. You can do this manually or use a code wizard to do it. To use a code wizard, right-click Form1 in Class View and select Add

Implementing Event Handlers

The new variables need to be initialized when the Form1 constructor is called, so double-click the constructor name, Form1(void), in the lower part of the Class View window. (For the members of Form1 to be displayed, Form1 must be selected in Class View.) Modify the code for the constructor to the following:

Form1(void) : elementType(ElementType::LINE), color(Color::Black)
      {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
      }

The new variables are initialized in the first line. You should see IntelliSense prompts for possible values for the ElementType and Color types as you type the initialization list. You now have enough in place to implement the menu event handlers. You can find the Line menu item handler toward the end of the code in Form1.h, or double-click the Line menu item in the Design window to go directly to it. Modify the handler code to the following:

private: System::Void lineToolStripMenuItem_Click(
                                    System::Object^  sender, System::EventArgs^  e)
{
  elementType = ElementType::LINE;
}

This just sets the current element type appropriately. You can do the same for the other three Element menu handlers. Simple, isn't it?

The first Color menu handler should be modified as follows:

private: System::Void blackToolStripMenuItem_Click(
System::Object^  sender, System::EventArgs^  e)
{
           color = Color::Black;
}

The other Color menu handlers should set the value of color appropriately to Color::Red, Color::Green, or Color::Blue.

Setting Menu Item Checks

At present, selecting element and color menu items sets the mode in the program, but the checks don't get set on the menus. You don't have the COMMAND and COMMAND_UI message types with a CLR program that you have with the MFC, so you need a different approach to set the checkmark against the element and color menu items that are in effect. You can handle messages for the Element and Color menu items on the menu strip when one or the other is selected, and, conveniently, the handler for one of the messages is called before the corresponding drop-down menu is displayed. Right-click the Element menu item in the Design window and select Properties from the pop-up. When you select the Events button in the Properties window, the window should look like Figure 15-21.

FIGURE 15-21

Figure 15.21. FIGURE 15-21

The Action group shows the events relating to when the menu item is clicked. Double-click the DropDownOpening event in the Action group to create a handler for it. This handler will be executed before the drop-down Element menu is displayed, so you can set the checkmark in the function by modifying it like this:

private: System::Void elementToolStripMenuItem_DropDownOpening(
                                    System::Object^  sender, System::EventArgs^  e)
{
  lineToolStripMenuItem->Checked = elementType == ElementType::LINE;
  rectangleToolStripMenuItem->Checked = elementType == ElementType::RECTANGLE;
  circleToolStripMenuItem->Checked = elementType == ElementType::CIRCLE;
  curveToolStripMenuItem->Checked = elementType == ElementType::CURVE;
}

The four lines of code you add set the Checked property for each of the menu items in the drop-down list. The Checked property will be set to true for the case in which the value stored in elementType matches the element type set by the menu item, and all the others will be false. The drop-down will then be displayed with the appropriate menu item checked.

You can create a DropDownOpening event handler for the Color menu item and implement it like this:

private: System::Void colorToolStripMenuItem_DropDownOpening(
                                    System::Object^  sender, System::EventArgs^  e)
{
  blackToolStripMenuItem->Checked = color == Color::Black;
  redToolStripMenuItem->Checked = color == Color::Red;
  greenToolStripMenuItem->Checked = color == Color::Green;
  blueToolStripMenuItem->Checked = color == Color::Blue;
}

If you recompile CLR Sketcher and execute it, you should see that the menu item checks are working as they should.

Adding a Toolbar

You can add a toolbar with buttons corresponding to the Element and Color menu items. To add a toolbar to the application, it's back to the Design window and the toolbox. Toolbar buttons sit on a tool strip, so drag a ToolStrip control from the Menus & Toolbars group in the Toolbox window onto the form; the tool strip will attach itself to the top of the form, below the menu strip. You can add a set of standard toolbar buttons by right-clicking the tool strip and selecting Insert Standard Items from the pop-up. You get more than you need, and, because you will be adding eight more buttons, remove all but the four on the left that control creating a new document, opening a file, saving a file, and printing. To remove a button, just right-click it and select Delete from the pop-up.

You will need bitmap images to display on the new toolbar buttons, so it's a good idea to create these first. Select the Resource View tab and expand the tree in the window so the app.rc file name is visible. The app.rc file contains resources such as icons and bitmaps that are used by the application, so you will add the bitmaps to this file. Right-click app.rc in the Resource View window, and then select Add Resource. . . from the pop-up. Select Bitmap in the dialog that displays and click the New button. You will then see a window with a default 48 × 48 pixel image that you can edit, and the Image Editor toolbar will be displayed for editing bitmap and icon images.

The bitmap will be identified in the Resource View window with a default ID, IDB_BITMAP1. It will be convenient to change the ID and file name for the bitmap to something more meaningful, so extend app.rc in the Resource pane, right-click the IDB_BITMAP1 resource ID, and select Properties from the pop-up. In the Properties window, you can change the Filename property value to line.bmp and the ID property value to IDB_LINE. The Filename property here contains the full path to the bitmap resource file, so change only the file name part of the property value to what you entered before, line.bmp. You can then click the pane displaying the bitmap itself and change the Height and Width property values to 16 instead of 48, respectively. You can also change the Colors property value to 24-bit, but I will stick with four-bit color values. The new ID will enable you to identify the bitmap in Resource View in the event that you want to edit it. The new file name will make it easy to choose the correct bitmap for a given toolbar button.

If the Colors window that displays the color palette is not visible, right-click in the window displaying the bitmap and select Show Colors Window from the pop-up. As with the icon editor you used in the native version of Sketcher, right-clicking a color in the Colors window sets the background color, and left-clicking a color sets the foreground color. When using the Image Editor tools, holding the left mouse button down when drawing in the bitmap uses the foreground color, and holding the right button down uses the background color. Use the Image Editor toolbar buttons to draw your own image to represent the Line menu item action. You can then save the file before adding the next bitmap. You can add three more bitmaps corresponding to the Rectangle, Circle, and Curve menu item actions to complete the element set. To add each of these, you can right-click the Bitmap folder name in the Resource View window and select Insert Bitmap from the pop-up. You should end up with four items in the Bitmap folder in Resource View, as shown in Figure 15-22.

FIGURE 15-22

Figure 15.22. FIGURE 15-22

Create four more bitmap resources in app.rc for the color toolbar buttons. You can use the same principle for file names and IDs that you used for the element type bitmaps, so the files will be black.bmp, red.bmp, green.bmp, and blue.bmp, and the IDs will be IDB_BLACK, IDB_RED, IDB_GREEN, and IDB_BLUE.

You can now return to the Design window to add the toolbar buttons. Click the down arrow adjacent to the Add ToolStripButton icon on the tool strip to display the list shown in Figure 15-23.

FIGURE 15-23

Figure 15.23. FIGURE 15-23

Select the Button item at the top of the list to add a button to the tool strip. You can then right-click the new button, select Set Image from the pop-up, and select line.bmp from the dialog that displays. Select the Open button in the dialog to set this image for the toolbar button.

Right-click the new line button and display its properties. In the Properties window, change the value of the (Name) property in the Design group to toolStripLineButton. You can also change the value of the ToolTipText property to something helpful, such as Draw lines.

The DisplayStyle property determines how the button is displayed on the toolbar. The default value is Image, which causes just the bitmap image to be displayed. You could change this to Text, which causes the value of the Text property to be displayed with no image. We need something short and meaningful for the value of the Text property, so I chose just the name of the shape, such as Circles for the button to draw circles. Setting the value of DisplayStyle to ImageAndText results in the image and the value of the Text property being displayed.

To be consistent with the standard buttons, you really want the new button to be transparent. To arrange this, set the ImageTransparentColor property value for each of the buttons to White.

Select the Events button to display the events for the button. You don't want to create a new handler for this button as the handler for the Line menu item will work perfectly well. Click the down arrow in the value column for the Click event for the button to display the available handler functions, as shown in Figure 15-24.

FIGURE 15-24

Figure 15.24. FIGURE 15-24

Select the lineToolStripMenuItem_Click handler from the list to register this function as the delegate for the Click event for the button. Repeat this process to create buttons to create the element toolbar buttons for drawing rectangles, circles, and curves, and, in each case, set the Click event handler to be the corresponding menu item Click event handler.

Before you add the buttons for element colors, click the down arrow at the side of the Add ToolStripButton icon on the toolbar and select Separator from the list; this inserts a separator to divide the element type button group from the colors group. You can now add the four buttons for colors, with the Click event handler being the same as the handler for the corresponding menu item. Don't forget to set the button properties in the same way as for the element type buttons.

The toolbar buttons don't get updated when you change the element type or drawing color, so you need to add some code for this. You can add two private functions to the Form1 class, one to update the state of the element buttons and the other to update the state of the color buttons. You can add these by right-clicking Form1 in Class View and selecting Add

FIGURE 15-24
// Set the state of the element type toolbar buttons
void SetElementTypeButtonsState(void)
{
  toolStripLineButton->Checked = elementType == ElementType::LINE;
  toolStripRectangleButton->Checked = elementType == ElementType::RECTANGLE;
toolStripCircleButton->Checked = elementType == ElementType::CIRCLE;
  toolStripCurveButton->Checked = elementType == ElementType::CURVE;
}

Calling this function will set the state of all four element type buttons based on the value of elementType. Thus the button matching elementType will be set as checked.

The function to set checks for the color buttons works in exactly the same way:

// Set the state of the color toolbar buttons
void SetColorButtonsState(void)
{
  toolStripBlackButton->Checked = color == Color::Black;
  toolStripRedButton->Checked = color == Color::Red;
  toolStripGreenButton->Checked = color == Color::Green;
  toolStripBlueButton->Checked = color == Color::Blue;
}

To set the initial state of the buttons correctly, you must call both functions in the Form1 class constructor:

Form1(void) : elementType(ElementType::LINE), color(Color::Black)
{
  InitializeComponent();
  //
  SetElementTypeButtonsState();
  SetColorButtonsState();
  //
}

You must also update the state of the buttons whenever a change occurs to either the element type or the current drawing color. Each of the Click event handlers for the element type menu items must call the SetElementTypeButtonsState() function, so modify all four handler functions like this:

System::Void lineToolStripMenuItem_Click(
                                    System::Object^  sender, System::EventArgs^  e)
{
  elementType = ElementType::LINE;
  SetElementTypeButtonsState();
}

You can add a statement to each of the functions that deal with Click events for the color menu items in a similar way:

System::Void blackToolStripMenuItem_Click(
                                  System::Object^  sender, System::EventArgs^  e)
{
  color = Color::Black;
  SetColorButtonsState();
}

If you now recompile CLR Sketcher and execute it, you should see your version of the application as something like the window shown in Figure 15-25.

FIGURE 15-25

Figure 15.25. FIGURE 15-25

Figure 15-25 shows the tooltip text for the red button, and you should find that the menu items checks are all working regardless of whether you use the toolbar or the menus to select the element type and drawing color. You now have a CLR version of Sketcher that is essentially the same as the MFC version you developed earlier in this chapter. The two versions of Sketcher won't correspond exactly in subsequent chapters, but the main features will be in both.

SUMMARY

In this chapter, you learned how MFC connects a message with a class member function to process it, and you wrote your first message handlers. Much of the work in writing a Windows program is writing message handlers, so it's important to have a good grasp of what happens in the process. When we get to consider other message handlers, you'll see that the process for adding them is just the same.

You have also extended the standard menu and the toolbar in the MFC Application Wizard–generated program, which provides a good base for the application code that you add in the next chapter. Although there's no functionality under the covers yet, the menu and toolbar operation looks very professional, courtesy of the Application Wizard–generated framework and the Event Handler Wizard.

You learned about creating menus and toolbars in a CLR version of the Sketcher program and how you handle events to provide equivalent function to the MFC version.

In the next chapter, you'll add the code necessary to both versions of Sketcher to draw elements in a view, and use the menus and toolbar buttons that you created here to select what to draw and in which color. This is where the Sketcher program begins to live up to its name.

WHAT YOU LEARNED IN THIS CHAPTER

TOPIC

CONCEPT

Message maps

MFC defines the message handlers for a class in a message map that appears in the .cpp file for the class.

Handling command messages

Command messages that arise from menus and toolbars can be handled in any class that's derived from CCmdTarget. These include the application class, the frame and child frame window classes, the document class, and the view class.

Handling non-command messages

Messages other than command messages can be handled only in a class derived from CWnd. This includes frame window and view classes, but not application or document classes.

Identifying a message handler for command messages

MFC has a predefined sequence for searching the classes in your program to find a message handler for a command message.

Adding message handlers

You should always use the Event Handler Wizard to add message handlers to your program.

Resource files

The physical appearances of menus and toolbars are defined in resource files, which are edited by the built-in resource editor.

Menu IDs

Items in a menu that can result in command messages are identified by a symbolic constant with the prefix ID. These IDs are used to associate a handler with the message from the menu item.

Relating toolbar buttons to menu items

To associate a toolbar button with a particular menu item, give it the same ID as the menu item.

Adding tooltips

To add a tooltip to a toolbar button corresponding to a menu item in your MFC application, select the Prompt property for the button in the Properties pane and enter , followed by the text for the tooltip as the property value.

Creating menus and toolbars in CLR programs

You create menus and toolbars interactively in a CLR program through the Design window. Code is generated automatically to create menu items and toolbar buttons and display them on a form.

Handling events in CLR programs

You can create a delegate to handle a specific event for a control by double-clicking the event type in the Property window for the control. You can optionally select an existing delegate to handle the event. This is particularly relevant to handling toolbar button events that are equivalent to existing menu items events.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.233.44