Você está na página 1de 55

Visual C++ Version 6 Practice Assignments CS193W

Dr. Patrick Young, Stanford University


This version of Practice Assignments is for students using Visual C++ Version 6 only. I will be revising each assignment for Visual Studio .NET and handing the revised assignments out throughout the quarter. The enclosed assignments are designed to give you practical experience with the concepts covered in CS193W lecture. Each assignment should take one hour or less and ideally should be completed immediately after the corresponding lecture. Practice assignments will not be collected, however, you will be required to keep a log of which practice assignments you have completed (see last page of handout). This log will be turned in and the end of the quarter. Students taking CS193W for a grade should complete a minimum of twelve practice assignments, while students taking the class CR/NC should complete at least ten assignments. Practice assignment sections marked For Further Study or Learning More do not need to be completed to receive credit on any given assignment. Many of the assignments build on each other. If you skip an assignment, feel free to modify subsequent assignments as necessary to take in to account steps you have skipped.

Practice1:OverviewofMFC
In our first practice assignment we will study the process of creating new applications using the Visual C++ AppWizard. We will also get an opportunity to study the default MFC applications created by AppWizard.

Creating New Applications with AppWizard


To create a new application, select the New option on the File menu. You should be presented with the dialog box shown below:

Note that there are four tabs on this dialog, as you can use it to create new files and new workspaces as well as new projects.1 We want to create a new project. Visual C++ can be used to create a variety of MFC-based projects including MFC applications, MFC-based libraries, and MFC-based ActiveX controls. In addition, it can create standard Win32 applications and Win32 libraries, COM libraries, and quite a few other types of projects. The exact project types available will depend on your version of Visual C++ (e.g., the Enterprise Edition has more available project types than the Standard Edition) and is expandable, as you can create your own project types.

The Workspace in Visual C++ constitutes the current set of open files. It is distinguished from a project because a workspace can contain more than one project. In addition, a workspace can include files completely unrelated to its constituent projects. Think of the workspace as being a snapshot of Visual C++, including currently active projects, open files, and even window positions.

For CS193W we will be primarily concerned with the three MFC types, and for now were only going to work on MFC Applications. Select the MFC AppWizard (exe) choice, type in a project name (and optionally choose a parent directory to place it in). Then hit the OK button. MFC will now take you through a series of steps which will allow you to customize the application it creates.

Step OneChoosing MDI or SDI


After clicking on the OK button, you should be presented with the dialog box belowif youre not, it probably means you didnt select MFC AppWizard (exe) as the type of application you were creating. This dialog allows us to decide whether were going to create an MDI (Multiple Document Interface) application or an SDI (Single Document Interface) application. You may also choose to create Dialog-based application. This is a simple application that consists of a single dialog boxuseful for very simple applications, but not for anything complicated. For now, lets start off by choosing SDI application.

Before you click on Next lets take a moment to study one of Visual C++s nicer features its help system. If youre not sure what one of the options presented actually does, right mouse click on it and select Whats this? from the popup menu. In most cases, this will give you a brief description of the meaning of the given option. For example, right clicking on the What language would you like your resources in? brings up the following description popup:

Once youve had a chance to experiment with the right-mouse help system, go ahead and click on Next and lets move on to Step 2.

Step TwoDatabase Support


MFC can include support for interacting with a database. We probably wont get a chance to explore this option this quarter. Lets leave the database support set to None.

Step ThreeOLE Support


This step allows you to provide support for OLE Object Linking and Embedding. You can ask the AppWizard to provide code which allows your application to act as a container. This means the user can insert things like Excel spreadsheets into your document. You can also ask the AppWizard to make your application a Server. This means that your documents can be inserted into and viewed within an OLE container application such as Microsoft Word.2

A full-server program is a normal MFC application which also supports embedding its documents into other applications which are OLE containers. An OLE mini-server is a program which cannot run stand alone and only starts up and runs when its corresponding documents are embedded into another programs documents.

For now, go ahead and leave compound document support set to None. Well talk about OLE support much later in the quarter. OLE Automation is a special kind of OLE technique which allows other programs to take control of your program. Lets leave it unchecked. ActiveX Controls are special DLLs (dynamic load libraries) which act similar to the original built-in Windows controls. They allow programmers to extend the available number of controls by buying (or building) control types not included with Windows. You may leave the ActiveX Controls checkbox in its default checked state if you like, but we wont be using them for much of the quarter either. Click on Next and well move to step 4.

Step FourMiscellaneous Support


In step 4, you get a chance to determine if your new application is going to support a variety of behaviors. For now the most important ones are whether or not we want toolbars, whether or not we want status bars (thats the bar that runs along the bottom of the application window and is used to display status messages), and whether or not we want print previewing. To find more about the other options, dont forget your handy right-mouse menu!

In addition to these options, the options provided by the Advanced button are also important. Go ahead and click on it. You should see something like this:

Most Windows applications tack a three-character file extension on the end of a file name. This extension is used to identify which application created the file. Word documents, for example use the doc extension, while Excel documents use xls. This Advanced dialog is where you set the three-character extension youll be using for your documents. The other elements of this Advanced dialog box include the names which will be used for your documents in a variety of different dialogs and IDs used in the Windows Registry and for OLE Automation support. The Window Styles tab allows us to select various Window styles and initial settings for our main frame and, if we have an MDI application, child frame. Were going to leave these alone for now. Go ahead and create a three-character extension for your documents and close the Advanced dialog.3 Leave the other Step 4 settings at their defaults. In general for the class, well go ahead and keep the Toolbars, Status Bars, and Printing Support in (along with the 3D controls). Lets move on to Step 5.

Step 5Comments and Libraries


The fifth step allows us to tell the AppWizard if we want it to add comments telling you where to make modifications in the AppWizard generated code (I always recommend you leave this on) and what type of linking we want to take place. If you choose static linking, your executable will include the MFC libraries in it. This will make your application larger, but will guarantee that your application always runs (as the libraries will always be present). If you choose dynamic linking, your executable will be smaller because the MFC binaries will not be included as part of the executable. Instead your application will try to dynamically link with existing copies of the MFC DLLs (dynamic load libraries) when it runs. The downside of this is that it may load a bit slower (as it has to link dynamically) and, more importantly, there is no guarantee that the system youre application is running on actually has the versions of the MFC DLLs you are expecting. You can take

You can actually give extensions longer than three characters now. The three-character limit is a vestige from the infamous old 8.3 DOS file naming scheme. 8.3 limited file names to eight uppercase characters followed by a period followed by a three character extension.

care of this during your programs installation process, but that is a bit more work (and for simple projects, you may not have an installation process at all). The MFC Standard and Windows Explorer option at the top of the page is new to Visual C++ 6.0. Selecting Windows Explorer will create a splitter window with a tree view on the left and the main view on the right. Im not sure why this option was added to this particular stage (probably just because there was extra space available in the dialog box ). In addition, it seems to be a bit specialized to put in the AppWizard. Go ahead and leave all the options on this page to their default values and lets move on to the final step.

Step 6Class and File Names


Our last step just gives us a chance to rename the automatically generated classes, if desired, and to change the normal file names for them. For now, lets leave them at their defaults.

Experimenting with the Applications


Were half done with the assignment for this lecture! Go ahead and finish the creation of our new SDI application. Compile it and run it. Play with the various menus and the toolbar. Generate a new MDI application and compile it and run it. Get familiar with the classes of both applications and get comfortable using the ClassView of the Workspace window4 to move between the various classes and member variables and functions of the pre-defined applications.

As youll recall from lecture, the Workspace window is the window on the left-side of Visual C++ which is used to display classes, files, or resources. These three tabs are referred to as ClassView, ResourceView, and FileView.

Learning More
One natural question to ask is, what does changing the various AppWizard options do to the actual code? You can easily determine this on your own by creating multiple projects of the same name (in different folders) and then comparing them using WinDiff. If you arent already familiar with WinDiff, you should get to know this handy tool as soon as possible. Its the Windows version of the UNIX diff tool which is used to compare to files. In the case of WinDiff, you can compare files or folders. WinDiff is located in the Visual Studio Tools folder in your Start Menu. Lets say, for example, you want to know what checking the toolbars in Step 4 really does? Create one project with the toolbars checked. Create a second project with the same name, but in a different folder, which is exactly the same, except that the toolbars arent checked. Run WinDiff on both folders. What you should discover is that the CMainFrames are different. The CMainFrame on one has a CToolbar as a member variable and initializes the toolbar in its OnCreate function. The other CMainFrame doesnt deal with toolbars at all. Voil, youve isolated the difference between checking and unchecking the toolbar checkbox and you now have a better understanding of how toolbars and the main frame work.

10

Practice2:IntrotoGraphics
Over the next few weeks we will be developing a simple TicTacToe game. This game will be used to gain experience with the following concepts: documents and views, basic drawing, inputs from the mouse, serialization, mapping modes, printing.

For this lecture, lets get some practice with both MFCs documents and views and with MFCs drawing capabilities. This initial version of TicTacToe, we will be performing two steps: (1) we will be creating the TicTacToe document and (2) we will be drawing the TicTacToe board. Heres a screen shot showing what the application should look like at the end of this practice assignment. Note that the board has been partially filled for debugging purposes.

Go ahead and create a new SDI project with the name TicTacToe. Use all the default behaviors, except give your documents the file extension toe. Thus when we learn how to store our TicTacToe games next week theyll have file names such as GameOne.toe. Remember you set the file extensions in Step 4 of the AppWizard using the Advanced dialog. Your application should have support for printing by default (also in Step 4). Make sure that printing is set, as well need that later.

11

Working with the Document


Okay, lets go ahead and modify our TicTacToe document. Remember, our document class stores all non-view specific information about the document.5 In this case, CTicTacToeDoc should store the state of the TicTacToe board. I recommend creating a new enum to store the state of any given square in the board (blank, filled with an X, or filled with an O) and then creating a 3 by 3 array based on your new enum to store the actual board state. This 3 by 3 array should be a member of the CTicTacToe document. Dont forget by convention, the names of all member variables in an MFC application are preceeded by an m_ (e.g., m_boardState). Once youre done adding the board in to your document, the next step is to modify CTicTacToeDoc to properly handle the new state information weve just added to it. There are a number of places in the document which may require modification. We must initialize the document when a new document is created, this is handled in the DeleteContents function (see below). We should also modify the Serialize function so that opening and saving documents works properly. Note that we do not have to modify OnNewDocument, OnOpenDocument, or OnSaveDocument. The default behavior of these functions provided by the framework will typically be correct for our purposes. When a document is opened or created in an SDI application, a new CDocument object is not created, instead the existing document object is reused. Therefore, you cannot carryout document initialization in the documents constructor. Instead initialize document contents in the documents DeleteContents function. DeleteContents will be called by both SDI and MDI applications when a document is created or loaded. Okay, lets go ahead and write our own version of DeleteContents. The easiest way to do this is via the Class Wizard. To open the Class Wizard, go to the View menu or just hit Ctrl+W. The Class Wizard allows us to easily create commonly used member functions and message handlers for Win32 messages. Go to the Class name: pulldown and select

View-specific information (e.g., which page of a document a specific view is showing) is naturally stored in the view. Everything else should be stored in the document.

12

CTicTacToeDoc. Then double click below in the Messages: on DeleteContents. This will create a new DeleteContents member function for our CTicTacToeDoc. To easily move to the new function, double click on the newly added DeleteContents in the MemberFunctions list at the bottom of the window. Go ahead and write DeleteContents. All we want to do here is initialize our 3 by 3 array. Were done with the document for now. In the future, well take care of serializing our TicTacToe board so that we can save and open a TicTacToe game. Well also set things up so that the dirty bit gets set when the board state is modified. You may want to compile your project at this point and make sure everything is working so far.

Working with the View


Our second task for today is to actually get the board to draw properly. Remember, all drawing in an MFC application is done in the views OnDraw function. A stub for this function is already written for you. Go ahead and open TicTacToeView.cpp and start modify OnDraw.6

Using GDI Objects


If we use the default state of our DC (or device context), the board will be drawn using a very thin pen. Thats not going to look very impressive. Instead we want to use a nice thick pen. In order to do this, we need to create a new CPen and load it in to our DC. Go ahead and create a CPen. I recommend storing it on the stack as a local variable and not on the heap using the new operator, since well only be using it briefly. Dont remember the proper parameters for creating a CPen? Not a problem, type CPen and then select your newly typed text CPen by double clicking on it. Then hit the F1 key. This will get you the on-line documentation on CPen. You can create a color to use for the CPen using the RGB macro (you can read about the parameters to RGB using the on-line documentation as well). Once weve created our CPen we need to select it in to our DC. CPen, like CBrush, CFont, and a few other classes, is a GDI object. Remember the following rules when using GDI objects. Always restore the DC to its previous state when youre done with it and always remember to delete your GDI objects when youre done with them. Okay, we can load our CPen in to the DC using the DCs SelectObject function. However, we need to save the object returned by SelectObject for later use. Heres the basic structure anytime we use a GDI object: CPen newPen(PS_SOLID,3,RGB(255,255,255)); CPen *pOldPen = pDC->SelectObject(&newPen); // load in new pen, but keep pointer to old pen ... // use new pen here pDC->SelectObject(pOldPen); // restore old pen As you can see when we select the new pen in to the DC, we get a pointer to the old pen. When were done, we use the pointer to the old pen to restore the DC to its original state.

You can easily move to CTicTacToeView::OnDraw using the Class Wizard (Ctrl+W remember) or using the ClassView in Visual C++s left-hand Workspace window.

13

Drawing
Okay, now were ready to draw. The easiest way to draw the board would be to create a fixed size board, regardless of the size of the window. However, our application will look much nicer if we size the board based on the size of the view window. In order to do this, we need to get the current size of the view window. We can do this with GetClientRect. I recommend reading about both GetClientRect and CRect the every-handy F1 key. Once youve figured out where you want to draw, you can draw the actual board grid using CDCs LineTo and MoveTo. I leave it to you to peruse the CDC class members in order to draw your TicTacToe board. Since we want our CView to properly draw a filled board as well as the empty board weve got now, you may want to modify your DeleteContents temporarily to fill the board with some Xs and Os, just for testing purposes. Thats it for today. Next lecture well learn how to get mouse click information to actually change the state of the board.

Learning More
SelectObject, GetClientRect, LineTo how do you find these functions? While we will be learning many of these functions in class, there are far too many to cover in a one quarter class. The only way to learn MFC (or any other large framework, for that matter) is a combination of practice and exploration. Explore the documentation for CDocument, CView, and CDC. Look over the capabilities of these classes. Learn what they can do. You may not remember, the exact function names you see in your explorations, but youll probably have at least some memory of what the capabilities are. Then, when you need them, youll know they exist, and you just need to look them up and find their names and signatures. The only way you will master the material is through repeated practice.

14

Practice 3: Inputs
Our practice assignment for today is to add mouse inputs to our TicTacToe game. You will get a chance to get inputs from the keyboard in the SuperPad project which will be handed out next lecture.

Responding to a Mouse Button


As discussed in lecture, when the left mouse button is pressed, the underlying window receives a WM_LBUTTONDOWN message. When the button is released, the underlying window receives a WM_LBUTTONUP message. For our practice assignment, well keep things simple and capture the WM_LBUTTONDOWN messages only. Open your TicTacToe workspace. Open the Class Wizard (using ctrl+W) and select CTicTacToeView as the active class. Scroll down the list of messages until you see WM_LBUTTONDOWN. Double clicking on it, will create a new message handler OnLButtonDown. Double click on the newly created OnLButtonDown in the lower Member Functions: panel to edit the new function.

Your OnLButtonDown function should exhibit the following behavior. If the button is pressed inside a legal, empty board square, the square should be filled with either an X or an O. The first square clicked on will be filled with an X. After that, Os and Xs will alternate. If the button is pressed, but either there isnt a square under it, or if the square is filled, ignore it. OnLButtonDowns point parameter provides the position of the mouse down event relative to the current window. This is probably what you want.
15

For any given function, always check the documentation to determine if a point is relative to the current window or the entire screen. If you need to convert between the two, check CWnds class members for conversion functions. Remember, we need to separate what happens in the document from what happens in the view. Properly following the document / view model, we should have the following sequence of events when the left mouse button is pressed in a square: Check to see if there is a legal, empty board square under the mouse. If there is, change the board representation in the document to reflect the newly filled square. Call CDocuments UpdateAllViews with pSender = NULL. This will force a redraw of the view, which will in turn display the newly filled square with either an X or and O as appropriate.

Generally speaking, your best response to user interaction is to modify the document, followed by an UpdateAllViews call. This allows us to keep all the drawing code in a single placethe CView's OnDraw function. This in turn keeps your code simple and improves maintainability.7 In order to modify the document, youll need to access it. The recommended way of getting the document from within the view is: CTicTacToeDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); Notice this is the same code used at the beginning of the OnDraw method.

Further Study
The real work of messaging is done automatically for you when you use the Class Wizard. Open up your TicTacToeView.h file and scroll down near the bottom. You should see: // Generated message map functions protected: //{{AFX_MSG(CTicTacToeView) afx_msg void OnLButtonDown(UINT nFlags, CPoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() The OnLButtonDown declaration was created automatically for you by the Class Wizard. In your TicTacToeView.cpp file, there is a matching entry in the message map at the top of the file: In some cases, you may want the view to respond directly, without waiting for the document to force the view to redraw. In this case, you will still need to call UpdateAllViews, in case there are any other views displaying the same document. You can also provide additional information in the UpdateAllViews identifying which parts of the document have changed. This will further improve efficiency. We will study these techniques later in the quarter.
7

16

BEGIN_MESSAGE_MAP(CTicTacToeView, CView) //{{AFX_MSG_MAP(CTicTacToeView) ON_WM_LBUTTONDOWN() //}}AFX_MSG_MAP ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP() The ON_WM_LBUTTONDOWN creates an entry in the message map, linking the WM_LBUTTONDOWN message with the new OnLButtonDown function.

17

18

Practice4: Controls
This time well take a break from our TicTacToe game and develop an application which allows us to keep track of a list of tasks. Heres a screen shot showing the actual application up and running:

The user enters a new task description in the text edit box and selects a priority from the pulldown menu. Clicking on the Add button adds the new task to the list of tasks in the list control below. This application will give us practice working with four different types of Windows controls: the CEdit, CComboBox, CButton, and CListCtrl. In addition it will give us another chance to work with our message handling skills. Create a new MFC application named ToDo. The ToDo application should be an SDI (single document interface application). Follow the default settings, except remove the docking toolbar in Step 4. Give the ToDo documents the file extension tdo (Step 4 Advanced dialog box).

Create Controls
Our CToDoView includes four windows controls. We need to create member variables in our view for each of our controls. Create member variables for our CEdit, CComboBox, CButton, and CListCtrl. Each of our controls will need a unique identification number. When our CView receives a message from one of the controls, it will use this identification number to determine where the message came from. You may #define or use const int to define four ID numbers, one for each control. I use the following convention for naming these constant ID numbers: IDC_, followed by a unique identifying name, followed by _, followed by the type of control. For example, my CComboBox has the following identifer:

19

#define

IDC_ITEMPRIORITY_COMBO

Start your IDC numbers with 1 (not 0). These identifiers should go somewhere near the top of your views *.cpp file. As is typical for MFC classes, we not only create the various controls using the C++ constructor, we must also explicitly call a Create function on each of them. Since we want each of our controls created when the view window is created, the best place to create them is on a message handler for the views WM_CREATE message. Go ahead and use the Class Wizard to create a new message handler for WM_CREATE. The Object ID: should be CToDoView (or whatever your view class is called). If you scroll down the list of Messages: you should see WM_CREATE, go ahead and create a handler for it. In the new OnCreate message handler, call Create for each of your four controls. Typically the Create function will take a number of parameters including: a set of window styles, the rectangle where the control will be placed, the parent window, and the ID number that the parent window will use to identify the control. Some Create functions may take additional parameters (the CButton control, for example, takes the text which will be displayed in the buttonin our case Add). Always check the on-line documentation to determine what the Create function is expecting. Lets take a look at each of the basic parameters in more detail: StylesEach controls Create function will take a style parameter. The controls style is defined by combining a number of different styles. Possible styles include the basic windows styles and control specific styles. Almost every window you create will use the WS_VISIBLE and WS_CHILD window styles. Many of them will use the WS_BORDER window style. Any time you use a control, you should check the control specific styles. In our case, we will use the CBS_DROPDOWN style for our combo box control and the LVS_REPORT style for our list control. Styles are combined using the C++ binary or operator. For example, our list control will be visible, a child window, and will use the list control report style, so our style parameter will be: WS_VISIBLE | WS_CHILD | LVS_REPORT Note that the prefix WS stands for Window Style, CBS stands for ComboBox Style, and LVS stands for List View Style. RectangleThe Create function will also take the rectangle where the control is to be placed. At the time the views WM_CREATE message is received, we dont quite know where the controls will be placed (as we need to know the size of the view window). We will be accurately placing the controls when the view receives a WM_SIZE message. For now, go ahead and create a dummy CRect rectangle and pass it in to each of the controls Create functions. CRect tempRect(0,0,0,0); m_itemPriority.Create(,createRect,,); // pass in the tempRect along with some other stuff Parent WindowSince the view is acting as the parent window for each of the controls, pass in the views this pointer as the parent window.
20

IDThe ID numbers are the constants we defined earlier. Go ahead and pass them in directly. Again, any time you are working with a control, check the Create function to see what parameters it needs. Also check the various control styles to see which styles are appropriate for your needs.

Placing Data in Controls


The CComboBox and CListCtrl will both need to be setup to display initial data. The CComboBox is used to select the priority of a given task. You can insert items into the CComboBox using the AddString function. CListCtrls can be used to display information in a variety of forms. The Windows Shell Explorer, for example, uses a CListCtrl to display the contents of each folder on your computer. In the shell, you may display folder contents using large icons, small icons, lists, or details. Each of these displays corresponds to a different CListCtrl style. In our case, we will be using the LVS_REPORT style. This style is the same one that is used by the Windows Shell Explorer when it is set to detailed. Items inserted in to a CListCtrl may include subitems. A CListCtrl in LVS_REPORT style can display both regular items and their subitems. Our CListCtrl should have two columns, Task and Priority. You can insert columns in to a CListCtrl using the InsertColumn function. The Task Column will display the main text string associated with each item. Setup the Priority Column to display the 1st subitem of each main item using the long version of InsertColumn: m_listOfItems.InsertColumn(1,"Priority", LVCFMT_LEFT,-1,1);

Laying Out the Controls


Now that weve created the controls, we need to lay them out. We should lay them out in response to any WM_SIZE message received by the view. That means we will lay them out initially when the view is originally sized and that we will be able to resize them correctly anytime the view is resized. Go ahead and create an event handler in your view for WM_SIZE. In your OnSize event handler, pace your controls such that the CEdit, CComboBox, and the CButton are all at the top of your window. The CListCtrl should fill up the rest of the window. Have the CComboBox and CButton maintain a fixed width as the window is resized. See the screenshot on the first page for placement guidance. Windows can be positioned and sized using CWnds SetWindowPos function. The controls inherit from CWnd, so they have access to this function as well. Be sure to set SetWindowPos nFlags parameter correctly. Check the online documentation for details. Warning: The combo box control must be large enough to display itself not only in its normal state, but with the drop down menu deployed as well. If the size of the combo box is set so that its only large enough to show the chosen item, the menu will not be shown when you click on the arrow to the right of the combo box.
21

In addition to setting the size of the controls themselves, you should also set the column sizes in the CListCtrl when the view is resized. You can control the column sizes using SetColumnWidth. Columns are numbered starting with 0.

Adding Items
When the user presses the Add button, we need to respond by adding a new task to our list. As youll recall from lecture, when the user interacts with a control, it sends a WM_COMMAND or WM_NOTIFY message to its parent window (the original Windows controls use WM_COMMAND while the newer Windows 95 Common Controls use WM_NOTIFY). We need to create a message handler and respond to this message. We used the Class Wizard to add message handlers in response to mouse and keyboard inputs. Similarly, as we will see, we can also use the Class Wizard to create message handlers respond to control messages if the parent window is a dialog box. Unfortunately, there is currently no way to use the Class Wizard to create message handlers in response to control messages that or in non-dialog windows. Instead well have to add our message handlers manually. Most Windows controls generate one or more responses to various user actions. You can usually read about them on the class overview page of a given control class. For example, go to the CButton overview page in the online documentation. If you scroll to the bottom of the page, youll see that CButtons can generate messages in response to either single or double clicks. In this case, the buttons have specific macros defined for capturing single and double clicks. In order to write a message handler for single clicks you will need to carry out the following steps: 1. 2. 3. Declare a message handler in the *.h file. Fill in the body of the message handler in the *.cpp file. Connect the message to the message handler by modifying the class message map.

Lets take a look at each of these steps in more detail: Declaring Message HandlerMessage handlers have a variety of function signatures. If we check the online documentation for CButton (on CButtons overview page), we see that the function prototype for our message handler should be: afx_msg void memberFxn( ); Where memberFxn is, of course, the actual name of our function. This prototype or signature is nice and simple. However, do make sure to check, as some message handlers will have parameters. Go ahead and add a new member function to your ToDo view class using the function prototype given. I recommend giving it a name like: OnAddItemBtnClicked. Generally, MFC will prefix message handler names with On and I suggest you follow the same

22

convention. Make sure you include the afx_msg.8 Message handlers are typically declared as protected member functions. Defining the Message HandlerOf course youll need a corresponding function definition in your *.cpp file in order for anything to actually happen! In this case, when the Add button is clicked you need to add a new item to the CListCtrl at the bottom of the view window. Lets go ahead and hold off on the details of this function until were done talking about message handlers. Define the function in your *.cpp file, but leave the body empty for now. Modifying the Message MapThe views message map is where MFC determines which message handlers get called in response to each message. Scroll to the top of you Views *.cpp file and you should see something like this: BEGIN_MESSAGE_MAP(CToDoView, CView) //{{AFX_MSG_MAP(CToDoView) ON_WM_CREATE() ON_WM_SIZE() //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP() This is the actual map. Youll need to add an entry to this map. Before you do, though, notice that the lines between //{{AFX_MSG_MAP(CToDoView) and //}}AFX_MSG_MAP are greyed out. These lines were generated by the ClassWizard when we added message handlers for WM_CREATE and WM_SIZE. No lines should be manually entered between the two ClassWizard generated comments. Instead, well add our new entry below those lines, but before the END_MESSAGE_MAP line. Go ahead and take another look at the CButton documentation. as we can see, the message map entry takes the form ON_Notification( id, memberFxn ) where in our case, the notification is ON_BN_CLICKED. The id is whatever ID number you used when creating the actual CButton in the OnCreate handler and memberFxn is whatever name you gave your message handler in the last step. If youve been following the naming conventions recommended here, your message entry should look something like this: ON_BN_CLICKED(IDC_ADDITEM_BTN,OnAddBtnClicked) Go ahead and place your message entry just above the END_MESSAGE_MAP line. Notice that there is no semi-colon (;) used to terminate a message map entry. Okay, were done with dealing with the message map. Now we need to deal with the actual body of the message handler. What are we actually going to do when the button is clicked? We need to get the information out of our CEdit and CComboBox and put them in the CListCtrl.

If you check the MFC source code, afx_msg doesnt actually do anything! Nevertheless, you should prefix all message handlers declarations with afx_msg. This will let the reader know that they are message functions.

23

Using the LVS_REPORT list control style, a CListCtrl can display information about items in columns. Each item will have a main item associated with it and one or more subitems. For our purposes, the main items will be the agenda descriptions entered from our CEdit, while the subitem will be the priority chosen in the CComboBox. Lets take a look at how were going to get information in to the list control: Setting the Main ItemYou can insert items in to the list control using CListCtrls InsertItem member function. If you look over the documentation for this function, youll discover there are a number of different versions of it. I recommend using the LVITEM version. This version requires you to setup an LVITEM structure before calling InsertItem. Its a bit annoying, but it will give you the greatest amount of control. This use of structs to provide information is very common in MFC and is an example of Win32s non-C++ orientation coming through to MFC. Click on one of the links to the LVITEM help page and look over the LVITEM structure. In order to add our main item in, well need to get the text out of our CEdit first. If you look over the CEdit class members, youll discover that there doesnt appear to be anything useful for getting text out! Thats because the function you need is actually part of CWnds class members (CEdits base class). Youll need to use the GetWindowText function. I leave getting the main item in to your control to you. You will need to carefully read the documentation on all functions involved. Setting the Priority ItemAdding in the priority item works similarly. In this case, well want to add priority as a subitem. You can determine which priority is selected using CComboBoxs GetCurrSel and then converting the integer returned to a string. However, there is one tricky part involved in setting the priority item. Warning: you may not add subitems using CListCtrls InsertItem function. You must use the SetItem member function in order to add subitems. Okay were almost done with our application. The last thing you should do is clear the CEdit and return the CComboBox to normal priority so theyre ready for the next agenda item entered.

For Further Study


Notice that we didnt modify our views OnDraw function at all for this assignment! In this case, all the controls did our work for us. While you probably wont have all the details of each control memorized, you should get familiar with the controls that are available. This will save you lots of time in the future. Instead of trying to implement a particular behavior, youll be able to take advantage of a control which supplies the behaviors you need for free. Theres a lot more to learn about working with controls. One thing youll need to know is how to find out which messages a control can generate. In many cases, this information can be found on the controls overview page, just as it was for our CButton. If you look at the CComboBox overview page, for example, youll discover that CComboBox can generate messages in response to all kinds of events including when the current selection has changed and when the combo box loses the keyboard focus. In some cases, unfortunately, the class overview page does not list any events. The CListCtrl overview page, for example, doesnt tell us what messages, if any, the CListCtrl generates. In this case youll have to follow links to Using CListCtrl and then follow the link to Processing Notification Messages in List Controls. Unfortunately in this case, MFC doesnt do a very good job of hiding the raw details of the OS from you.
24

Practice5: Menus
For this assignment well be modifying TicTacToe to (1) add a Clear Board menu item, (2) add a keyboard accelerator, and (3) enable and disable Clear Board based on the current state of the board (an empty board cant be cleared or saved).

Adding a Menu Item


Lets start by adding the Clear Board menu item. Menu items are resources, so you need to change the workspace window (on the left-hand side of the Visual Studio window) to display resources instead of classes. Click on the resource tab (displaying a very small bitmap of a sun and a cactus). Open the Menu item and double click on the IDR_MAINFRAME item. This item represents the menus associated with the main frame. As well discover when working with an MDI application, MDI applications have one menu used when no child windows are open, and a separate menu for each document type. Double clicking on IDR_MAINFRAME should open up a menu editor. We want to add the Clear Board menu item to the Edit menu, so click on the Edit menu. The blank menu item surrounded by a dotted outline at the bottom is used to add new menu items. Double click on the blank outlined menu item. This should bring up a properties dialog box for the item. Fill in the caption and prompt. The caption is what actually shows up in the menu. You can give it a mnemonic9 by preceding one of the letters with an &. To have an accelerator key displayed, follow the name of the of the menu item with a \t tab followed by the accelerator. For example, for our current item, we probably want the caption to look something like this: C&lear Board\tCtrl+K As well discover, while the \tCtrl+K makes the accelerator show up correctly in the menu, it doesnt actually link the Ctrl+K key to any particular action. Thats a separate step which well have to take care of below. Fill in a prompt string. If the window displaying the menu has a status bar10 the prompt string associated with each menu item will be displayed in the status bar as the item is selected. If you choose not to provide a prompt string, MFC will give you a warning message in the debug output window every time the menu is selected when you are running the program in debug mode. To see an example of prompt strings in action, select one of the Visual C++ menus and move the mouse up and down over the various menu items while looking at the lower left hand corner of the Visual C++ application. You should see a descriptive string associated with each menu item. These are the prompt strings.
In Windows parlance a mnemonic is a key that can be used in conjunction with the ALT key to select menus and menu items. The mnemonic is displayed as an underscore under one of the letters in the menu items name. For example the Edit menu can be deployed by holding down ALT and E. 10 The status bar is the bar running along the bottom of many Windows applications. In Microsoft Word, for example, the status bar displays the current page number, current column number, and other information.
9

25

By convention, the ID is named ID, followed by an underscore (_) followed by the name of the menu, followed by an underscore, followed by the name of the menu item. In this case, for example, your ID should be something like this ID_EDIT_CLEARBOARD.11 If you leave the ID empty, Visual C++ will automatically fill in the ID using this convention once you close the window. Since the Clear Board menu item is distinct from the standard Cut, Copy, and Paste menu items, we should probably separate it from the others using a separator. To create a seperator, click and drag the empty outlined menu item between the Paste item and your new Clear Board item. Double click on it, and instead of providing a caption, click on the seperator checkbox.

Adding Command Message Handler


Okay now we need to actually write the event handler to handle clearing the table. We can do this in the Class Wizard. On the Message Maps tab if you look in the Object IDs window you should now see an ID_EDIT_CLEARBOARD. This is from our new menu item! We can respond to menu selections in any of a number of places. Remember, when a menu item is selected, the following classes are checked in order for message handlers: View Document Document Template MDI Child Frames (for MDI applications) Frame Application

In our case, the clear board message should probably be handled by the document. After all, that is whats getting modified here. Select the CTicTacToeDoc class as your active class in the class wizard and add a new COMMAND (well discuss the meaning of the UPDATE_COMMAND_UI option in a minute). Go ahead and edit the code for your new message handler. Dont forget that in addition to clearing the actual board data structure, youll also need to call UpdateAllViews to force views on the document to redraw. When youre done, compile and run your application and make sure that selecting Clear Board does indeed clear the board.

The ID is actually an integer. However, the Visual C++ software development environment keeps track of its value for you and allows you to refer to it using ID_EDIT_CLEARBOARD. The actual relationship between IDs and integers can be found in the applications string tablewhich is another application resource.

11

26

Adding an Accelerator
Lets add in the keyboard accelerator for Ctrl+K now. Go to the Resources tab of the Workspace window. Open up the IDR_MAINFRAME resource under Accelerators. You should now see a list of various accelerators. Double click on the blank accelerator at the bottom and create a new accelerator for you ID_EDIT_CLEARBOARD (which should be in the list of IDs displayed in the accelerator property window).

Disabling Menu Items


Clear Board shouldnt always be available. When the board is empty, for example, it cant be cleared. Our last task will be to write an update handler for Clear Board so that this menu item is disabled when the board is empty. Go back to the Class Wizard. Select ID_EDIT_CLEARBOARD and CTicTacToeDoc again. This time, however, instead of selecting COMMAND, select UPDATE_COMMAND_UI. The OnUpdate function is called whenever the menu item is shown. The menu item is enabled or disabled based on the state of the CCmdUI passed to your OnUpdate function. Go ahead and read up on the CCmdUI class in Visual C++s on-line documentation and then write your update handler to enable the menu item only when appropriate.

27

28

Practice6: DialogBoxes
In this assignment well get practice creating a modal dialog box. Youll get a chance to work with non-modal dialogs in the SuperPad project. Our main program for this assignment will draw a color filled rectangle. A menu item will bring up our dialog box which will allow the user to select a new color. When the dialog box is closed, our rectangle should be filled with the new color. Heres a screen shot of the main application:

The Edit menu includes a Change Color item which brings up the dialog box below:

If we change the RGB values listed and select OK the color of our filled rectangle will change. Of course, if we change the RGB values in the dialog box and choose Cancel no changes should occur.

Preliminaries
Lets start out by writing the main application. Then well create the dialog box and add a menu item to display the dialog. Go ahead and create an SDI application. Your document should contain one data item, a COLORREF used to store the color of the rectangle. The view should simply fill the rectangle (dont worry about the specific size of the rectangle, anything that clearly shows the fill color will be fine for this assignment). You may have to peruse the device context (CDC class) member function documentation in order to find a suitable graphics call.
29

Designing the Dialog Box


Okay, now that youve gotten the basic application up and running, now we need to design a new dialog box. Go to the Workspace window and switch it to the ResourceView. Open the resources folder (if it isnt already) and right click on the dialog folder. Select the Insert Dialog item. You should be presented with a new dialog window as shown below. In addition, the Controls bar (shown below at right) may or may not be visible. If its not visible, were going to need it. To bring up a closed toolbar in a Visual C++, right mouse click either on top of an existing toolbar or in an empty area of Visual C++s background. You should get a menu listing all of Visual C++s toolbars, both open and closed. Scroll down to the one you want to open and select it. The Controls bar should now be displayed.

For our dialog box, well want to place three static text controls and three edit controls. If youre not sure what a particular control in the control bar is, move your mouse cursor on top of it, and leave it for a moment. A tool tip describing the control should appear. Go ahead and drag out three static text controls and three edit controls. Somewhere on your screen (typically at the bottom of Visual C++s main frame) should be another toolbar called the Dialog toolbar. Using this toolbar, you can align or distribute the various controls and make their widths and heights the same. If the toolbar isnt visible, well, you know what to do (use the right-mouse toolbar menu as described above for the Control toolbar).

Once youve got the controls laid out to your satisfaction, our next step is to set the properties of each control. To get to the properties dialog box, for a particular control, right-mouse click on a control (dont double click, that does something else ) and select the Properties menu item from the context menu displayed. If you dont want to have to bring up the context menu for each control individually, you can pin the properties dialog by pressing on the push pin-shaped icon in the top-left corner of the properties dialog box. This will keep the properties dialog open until you explicitly close it. We need to give each control an ID. I recommend using something like IDC_REDSTATIC, IDC_GREENSTATIC, IDC_BLUESTATIC for the statics and IDC_REDEDIT, etc. for the edits. Youll note that IDC is MFCs standard prefix for the ID of a control. In addition, you should give each of the statics a caption. If you look at the screenshot of my dialog box (see previous page), youll notice that each of the static text labels includes a mnemonic (remember, thats the underscored letter that can be used in conjunction with the ALT key to
30

select an item). Because of my mnemonics, if you were to type an ALT-G the Green text field would become active. You can make the mnemonics show up in a given static text label by using the ampersand (&) just as you did for menu items. Depending on the order in which you laid out the various controls, the tabbing order may or may not be correct. When a Windows dialog box is active, you may use the tab key to shift the keyboard focus from one element to another. The order in which the elements are selected is called the tabbing order. The tabbing order can be set by selecting the Tabbing Order menu item from the Layout menu. Note that the Layout menu is only visible when the user is editing a dialog box. In order for our mnemonics to work correctly, each static text label should precede its corresponding text edit in the tabbing order. Thats how Windows knows how to handle the user of the ALT- mnemonic key sequence. You can set a new tabbing order by clicking on each control in the order you want used. Note that OK and Cancel should normally be numbers one and two. To set the title of the dialog and the ID number for the dialog, bring up the property window for the dialog itself (right mouse click on the dialog window). Rename the dialog Choose Color and give it the ID number IDD_CHOOSECOLORDIALOG. Finally, you can test your dialog box out and make sure that it works to your satisfaction by using the test switch. This is the button that looks like a light switch and it is located on the far-left of the Dialog toolbar.

Creating a Dialog Class


Okay, so far so good, weve got our dialog box all laid out. What we need to do now is to create an actual MFC class to go along with it. Go ahead and double click on the dialog box and Visual C++ should offer to create a new class for you. Agree and give it a name (I recommend something like CChooseColorDialogtypically all dialog class names end with the Dialog). Once the class has been created, MFC will place you in the Class Wizard. Well need to get and set the values of our three text edit controls. In order to do this, were going to create member variables for them. Switch the current Class Wizard tab to the Member Variables tab. Create a new member variable for each of your three edits by: 1. 2. 3. Selecting the ID of the Edit. Double-clicking or pressing the Add Variable button. Giving the new member variable a name (I recommend something like m_redValue, m_blueValue, etc.). Leave the category at Value. Were going to take advantage of MFCs ability create a member variable to access the value displayed in the CEdit rather than manipulating it as a CEdit ourself. Finally set the variable type to int. Go ahead and close the Add Member Variable window. When you close the window, you should now have an option to set minimum and maximum values for the newly created member variable. Set them to 0 and 255 respectively.

4.

31

Displaying the Dialog Box


We want the dialog box displayed when the user selects the Choose Color menu item. Go ahead and add a Choose Color menu item with a corresponding Ctrl+K accelerator to the Edit menu. If you want it to look nice, place a separator between the Choose Color item and the pre-existing Paste item. Create a new message handler for the ID_EDIT_CHANGECOLOR (or whatever the ID number of your new menu item is). I would recommend placing the message handler in the view, but you can do it in the document as well. In the message handler well need to carry out a number of tasks. 1. 2. Create a CChooseColorDialog object. Generally the dialog object will be a local variable of the message handler (I recommend naming the variable dlg). Once weve created the CChooseColorDialog object, we can set values for its various member variables. Set the m_redValue, m_blueValue, and m_greenValue member variables. These variables should be set to your documents current color setting. You can get from a COLORREF back to the constituent RGB components using the macros GetRValue, GetBValue, and GetGValue. Now that the member variables are set correctly its time to display the actual dialog, we can do this using the dialogs DoModal member function. This function returns a value indicating whether the user clicked OK or Cancel. Therefore, the call to DoModal is usually placed in the context of an if statement, as follows: if (dlg.DoModal() == IDOK) { // handle dialog actions ... } where actions in response to the dialog are only carried out if the dlg.DoModal call returns IDOK. 4. The last thing we need to do is fill in the actual dialog actions. If the user selects okay, we need to get the new red, green, and blue values back out of the dialog. This is easy, as we can just retrieve them using the dialogs m_redValue, m_blueValue, and m_greenValue member variables. Change the document to reflect these new color values and then call the documents UpdateAllViews. Finally, in order to get your view to compile, youll have to tell the compiler about the new dialog class. Add a #include "ChooseColorDialog.h" (or whatever the name of the appropriate file for your application is) to your views cpp file.

3.

5.

32

Practice7: Serialization
In this assignment we serialize our TicTacToe game. As you will recall from lecture, the MFC framework provides most of the work needed for opening and saving documents. The only work you need to do is to write the documents Serialize function and, depending on what your document looks like, write a DeleteContents function for the document. Weve already written the TicTacToe games DeleteContents function, so all thats left is to write Serialize.

Writing the Serialize Function


This is really very easy. Just go in and modify your CDocument-based class Serialize function. In the Serialize function, if the archive is storing, write out each element in the TicTacToe board and whose turn is next. If the archive is reading instead of storing, read in each element of the TicTacToe board and whose turn was next. You can use CArchives overloaded >> and << operators in order to carry this out. If youve created an enumerated type to store board states (which is good software engineering), youll need to convert from your enumerated type to a type supported by CArchives >> and << operators (ints should work). Make sure that you read in elements in the exact same order that you used when writing them out.

For Further Study


Serializing TicTacToe was fairly trivial. There are a number of important serialization issues that youll want to keep in mind when writing your own programs. Remember that classes must be declared serializable using the DECLARE_SERIAL and IMPLEMENT_SERIAL macros if they are to support serialization. In addition, your class must includes a default constructor (i.e., a constructor which takes no parameters) if it is to be serialized. If youre interested in getting more practice serializing, you might want to try reimplementing TicTacToe using MFCs array collections classes and then try serializing with these. MFCs serialization support for collections is quite good and very handy if you choose the right class. You might also consider trying to serialize the ToDo list.

33

34

Practice8: Collections
In this assignment well build a new practice applicationa bar graph drawing program. Here is a screenshot of the running application:

Creating the Application


The bar graph application is an MDI application. Go ahead and use the default AppWizard settings. In the advanced settings dialog box, set the file extension to bgr.

Defining the CDataItem Class


Define a new class CDataItem based on CObject. One easy way of creating a new class is to right-mouse click over the root BarGraph classes node in the ClassView window. You should see a menu item named New Class Go ahead and select it, and you will be presented with the New Class dialog (shown at right). When this dialog starts up the class type is set to MFC Class. Youll notice that CObject is not on the list of MFC base classes. This is because classes based on CObject-only are not considered MFC classes, they are considered Generic Classes. Change the tab at the top to Generic Class and tell MFC that your new CDataItem is derived from CObject. When your done, click on the OK button. MFC will complain that it cant find appropriate
35

headers for CObject. Ignore the warning messagethe header files for CObject are already included (via StdAfx.h) and your project will compile without any problems. Each CDataItem should have two member variablesa label and a value. The label is a CString and the value is an int. Since were trying to get the application up with a minimum amount of work, you may either make the members publicly accessible or, following good software engineering techniques, make them private with public accessor functions. Well want to save our CDataItems to disk, so we need to make CDataItem serializable. This involves several steps: 1. 2. 3. 4. Add the DECLARE_SERIAL(CDataItem) macro to your DataItem.h file. Dont forget DECLARE_SERIAL macros are not followed by semicolons. Add a void Serialize(CArchive &ar); member function declaration to your DataItem.h file. Add an IMPLEMENT_SERIAL(CDataItem, CObject, 1) macro to your DataItem.cpp file. Again, as with DECLARE, there is no semicolon at the end of this macro. Write your CDataItem::Serialize function.

Modify the Document Class


Including Template Header File
Well be modifying the document class to contain a list of CDataItems. Since the list will be based on an MFC template class, we need to include the template header files into the project. Add the following line to your StdAfx.h file: #include <afxtempl.h>

Adding Data Member Variable


Add a new member variable to your document class. The variable type should be CTypedPtrList. As discussed in lecture, you may base your CTypedPtrLists on CObList or on CPtrList. In this case, since we want to serialize our list, we must base it on CObList. Since our CDataItems are based on CObject, this will work just fine. Heres the declaration: CTypedPtrList<CObList,CDataItem*> You may leave this member variable public as well. m_data;

Cleaning Up
Since your program is responsible for any data maintained in the CTypedPtrList, you need to handle cleanup of the data. Add a DeleteContents function to your CDocument-based class. In this function, remove and delete each member of the list. I recommend using a while loop, which tests the lists IsEmpty function. While the list isnt empty, remove the head element using RemoveHead and delete it.

36

Serialization
Our last change to the document is to write the serialization function. This is very simple, as CtypedPtrLists which are based on CObList support the Serialize function. All you need to do is call the CTypedPtrLists Serialize from inside your documents Serialize.

Add Item Dialog Box


Well need to create a dialog box which allows us to add new data items to our bar graph. It should look something like this:

Go ahead and create a corresponding CDialog-based class (remember you can do this by double-clicking on the dialog while in the dialog editor). In the ClassWizard, add value member variables for both the CEdits.

Changes to the View Class


Add Data Item Menu Handler
Create a new menu item named Add Data Item. Notice that because BarGraph is an MDI application there are two menu resources already defined. The IDR_BARGPHTYPE is the menu displayed when the active child frame window is of type BarGraph. The IDR_MAINFRAME represents the menus displayed when no child frame window is open. Since we want to add data items when a bar graph window is active, add your new menu item to the IDR_BARGPHTYPE menu. If you have the time and want the practice, go ahead and add a mnemonic and an accelerator for it (dont forget to modify the accelerator table). Using ClassWizard, add a message handler for your new menu item. In the handler, create a CAddDataItemDialog object and display the dialog using DoModal. Dont forget to check the return value of DoModal to see if its IDOK. If its not, the user has clicked on the Cancel button and you should ignore everything. Otherwise, create a new CDataItem based on whatever data has been entered into the dialog box. Add the CDataItem to the documents CTypedPtrList of CDataItem*s. Finally call the documents UpdateAllViews.

Drawing
Our last step is to actually draw the bar graphs. You can decide how fancy you want your bar graph drawing, depending on how much time youve already spent on this assignment so far. If you look at my screenshot on the front page, youll notice Ive right-aligned the labels on my bar graphs. You dont need to do this unless you have the time or inclination. At minimum you should draw the labels (left or right-aligned, your choice) and draw the bars. If you want to assume a maximum label size thats fine (in fact, you could set a maximum character length using ClassWizard on the dialog-class). Its also up to you to decide how you actually want to draw the bars. You can draw them proportionally to fit within the width of the window based on the largest value. Alternatively, you can draw them with fixed
37

widths in relationship to values (i.e., value of 20 = 200 pixels, value of 50 = 500 pixels). If you used fixed widths, you may assume a maximum value as well. In any case, dont worry about the case where there are too many data items to fit within the view window. Well fix this later. If you want to follow my lead, youll get a bit of practice iterating over MFC lists. Heres my algorithm: 1. Go through the CDataItems calculating the maximum label size and determining the maximum value. You can determine the maximum label size given the current font using CDCs GetTextExtent function. Go back through the items again and draw them. Draw the labels right-aligned based on the maximum label size found in Step 1. Draw the bar sizes based on the maximum value determined by Step 1.

2.

38

Practice9: MappingModes
As weve discussed in lecture, the Microsoft Windows platform provides a number of mapping modes. These modes, when used correctly, can make drawing much easier. In this assignment, well experiment with using different mapping modes for the TicTacToe application. For this assignment, youll give the user the option of dynamically choosing which one of four mapping modes the CTicTacToeView should use. Please note that this is for educational purposes only. In real applications, you would never allow the user to dynamically change the mapping mode. Instead you would choose whichever mapping mode worked best for your application domain and then you would hard code that mode in to your application.

Preliminaries
In order to study the various mapping modes, were going to provide a new set of menu items for TicTacToe allowing the user to pick a mapping mode. Add four new menu items to the View menuText Mode, LoEnglish Mode, Anisotropic Mode, and Isotropic Mode. Since each of the measurement oriented modes (LoEnglish, HiEnglish, LoMetric, HiMetric, and Twips) work the same, were only going to work with LoEnglish. Add a member variable on to your CView-based class to keep track of the current mapping mode. Since Windows represents mapping modes as ints, I recommend making this variable an int. Dont forget to initialize it somewhere. Use MM_TEXT as the initial mapping mode. Write menu handlers for each of the new mapping mode menu items. In each case, you should set your views mapping mode member variable and call Invalidate (to force the view to redraw). In addition, if youd like to get fancy, since only one of mapping mode can be active at any one time, you might consider putting a check mark next to the current modes menu item. You can do this by creating UPDATE_COMMAND_UI handlers for each of the mapping mode menu items. In the handler, you can call the CCmdUIs SetCheck function. Since the mapping mode is normally set in the OnPrepareDC function, youll need to create a OnPrepareDC function for your view. If you go to the ClassWizard with your view as the active class, youll find OnPrepareDC listed in the messages list. Double click on it to create an OnPrepareDC member function.

Mapping Modes
Okay, well need to make two different sets of changes. First well need to set the mapping mode in the OnPrepareDC (and for some of the modes, well also need to do a bit of additional pre-drawing work) and second, well need to do the actual drawing. You can setup your actual drawing code anyway you want as long as your application does the work described below. Personally, I created a separate drawing function for each of the mapping modes, and used a big switch statement in the original OnDraw to determine which function to call.
39

As far as the actual drawing goes, if youre feeling pressed for time, you may ignore drawing the Xs and Os. Just draw the board for each mapping mode. That should be sufficient to give you a feel for how each mode works. Lets look at each of the modes in turn.

MM_TEXT
In OnPrepareDC, set the mapping mode to MM_TEXT using CDCs SetMapMode member function. Depending on how fancy your original TicTacToe was, your application might already act as if its in isotropic mode. Since were trying to understand the differences Lets rewrite our actual drawing code so its simpler. Rewrite the OnDraw function so, when Text Mode is selected, your application draws a 300 pixel x 300 pixel tic-tac-toe board.

MM_LOENGLISH
Working with MM_LOENGLISH is fairly straightforward as well. In the OnPrepareDC all you need to do is explicitly set the mapping mode to MM_LOENGLISH (using CDCs SetMapMode). For drawing, draw the tic-tac-toe board with each square exactly 1 across. Remember, in MM_LOENGLISH, each unit is 0.01. Dont forget that the Y coordinate increases in the opposite direction from what your used to with MM_TEXT. Youll have to use negative ycoordinates to actually draw the board.

MM_ANISOTROPIC
When working with MM_ANISOTROPIC, well need to do a bit more than just set the mapping mode in the OnPrepareDC. Well also need to set the window and viewport extents. Lets setup the window so that it is 300 logical units by 300 logical units. In OnPrepareDC, first set the mapping mode. Next, set the window extent to 300 x 300 using SetWindowExt. Finally, call SetViewportExt. We want to use the entire window as the viewport, so well need to get the size of the views client rectangle using GetClientRect first. Then well pass in the width and height of the client rectangle in to SetViewportRect. Just as a reminder, you must always call SetWindowExt before calling SetViewportExt. For drawing, go ahead and draw the tic-tac-toe board to fill the window. Since our new window extent is 300 x 300, each of our tic-tac-toe squares should be 100 logical units by 100 logical units.

MM_ISOTROPIC
Repeat the procedure youve just completed for MM_ISOTROPIC. Except for the call to SetMapMode, this should be exactly the same as for MM_ANISOTROPIC.

40

Practice10: UsingCScrollView
As we learned in lecture, if you need to support scrolling within a view, using CScrollView can make your life much easier. In this assignment, we extend the bar graph program which we originally created for practice with dialog boxes. Our original version of the bar graph program didnt handle cases where there were too many data items to fit within the current window. In this practice assignment, we modify our bar graph program to use CScrollView. The program will dynamically change the scroll area so the user can enter as many data items as desired.

Change Base Class of CBarGraphView


Our original CBarGraphView was based on CView, not CScrollView. If we were creating our application from scratch, and knew we wanted to use CScrollView, we could change the class CBarGraphView was going to be based on in Step 6 of the AppWizard when originally creating the application. Since were working with an existing application, we need to change the base class manually. We need to make three changes: 1. 2. 3. In BarGraphView.h change the actual C++ base class of CBarGraphView from CView to CScrollView. In BarGraphView.cpp, change the IMPLEMENT_DYNCREATE macro to reflect that CBarGraphView is based on CScrollView now, not CView. In BarGraphView.cpp, change the BEGIN_MESSAGE_MAP macro to reflect that CBarGraphView is based on CScrollView now, not CView.

Modify OnUpdate
If were creating a fixed size scrolling view, we can call SetScrollSizes once in the views OnInitialUpdate function. In this case, however, we want to change the size of the scrolling area as the user adds additional data items. In order to do this, we need to call SetScrollSizes each time the document is changed. We can do this by calling it in the views OnUpdate function. Youll need to calculate the appropriate size of the scroll view based on how much space you think it will take to draw all the data items. The exact calculation will depend on how fancy you got in the original version of bar graph. Here are a few tips which may or may not be useful: If you dont want to hassle with the line and page sizes, you can just skip those parameters. If you need to estimate text sizes, dont forget, you can create a CClientDC right in your OnUpdate function.

One final note, make sure you call Invalidate from within your OnUpdate function.
41

For Further Study


While CScrollView provides automatic support for proportional scrollbars, it provides no support for the keyboard. Users will expect the Home/End, Page Up/Page Down, and arrow keys to scroll the view window. You can add keyboard support to your application by adding keyboard message handlers. In your message handlers, you can call OnHScroll and OnVScroll and the view will react, just as if the user had manipulated the scroll bars directly.

42

Practice11: MultipleViewClasses
Some types of documents can be viewed in a variety of different ways. MFC can provide support for these documents by allowing programmers to define more than one view class for a given document type. In this practice assignment, we extend our bar graph program to support two different views of the documentour original graphical view and a new tabular view. Here is a screenshot showing both types of views of the same document:

Creating a New View Class


Our first step is to create a new view class. The easiest way to do this is to right-mouse click on the root BarGraph Classes node in the ClassView. Select New Class and create a new class named CTableView based on CView. The original AppWizard generated CBarGraphView class has a number of handy features that we dont get with this new view. In particular CBarGraphView includes a GetDocument function which both retrieves the document and correctly coerces the document to a CBarGraphDoc. Copy the declaration of GetDocument from BarGraphView.h and the definition of GetDocument from BarGraphView.h and BarGraphView.cpp into the new TableView .h and .cpp files. Note that there are actually two definitions for CBarGraphViews GetDocumenta non-debug version is found at the bottom of the BarGraphView.h file and a debug version is in the .cpp file. Our last step with CTableView is to write the OnDraw function. Unfortunately, as youll discover, we wont actually be able to test it until we make some additional changes to the program. You can either write OnDraw now and debug it later, or hold of on it until youre able to test it. In either case, dont worry too much about getting everything to look nice, as the main focus of this assignment is creating multiple views, not on drawing.

Telling MFC about the New View Class


Our next step is to tell MFC that weve defined a new view class. We do this by creating a new CDocTemplate. CDocTemplates are created in the CApplication-based class InitInstance function. If you scroll to the middle of the definition of CBarGraphApp::InitInstance you should see the following code: CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_BARGPHTYPE, RUNTIME_CLASS(CBarGraphDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CBarGraphView)); AddDocTemplate(pDocTemplate); We need to make a number of changes to keep track of the various DocTemplate, as each DocTemplate. The easiest way to do to keep track of our DocTemplates. CBarGraphApp. The default application doesnt theres only one. Well need to explicitly access this is to define two variables on CBarGraphApp Add two variables m_pGraphTemplate and
43

m_pTableTemplate of type CMultiDocTemplate* to the application class. Now modify the code currently used to create the bar graph template as follows: m_pGraphTemplate = new CMultiDocTemplate( IDR_BARGPHTYPE, RUNTIME_CLASS(CBarGraphDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CBarGraphView)); AddDocTemplate(m_pGraphTemplate); Now we can access the bar graph template at a later point using the m_pGraphTemplate variable. We now need to create a second DocTemplate for our tables view. If we were adding a second document type, instead of simply a second view type we would need to create a different set of IDR strings to complement the ones defined by IDR_BARGPHTYPE. However, since both the graph view and table view are used on the same type of document, the only change we need to make when creating our second doctemplate is to change the runtime class of the associated view. Add the following lines to InitInstance: m_pTableTemplate = new CMultiDocTemplate( IDR_BARGPHTYPE, RUNTIME_CLASS(CBarGraphDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CTableView)); AddDocTemplate(m_pTableTemplate); At this point, you should be able to compile and run your program. When the program runs, youll be confronted by a choice of creating either a BarGraph or a BarGraph. Hmmm, whats going on here? When multiple DocTemplates are registered MFC gives the user the option of which one to use when a new document is created. If you select the top BarGraph youll get the original graphic view, if you select the bottom BarGraph youll get the new table view. Everything should work fine, except if you defined the message handler for the Add Data Item menu handler to CBarGraphView, you wont be able to add data items if the active view is a table view. If you want, you can simply add a second menu handler to CTableView and copy the code to respond to the Add Data Item menu item from the CBarGraphView to CTableView.

Setting a Default View Type


In many cases, you wont want the user to have to choose from the different views available when creating a new document. Lets modify the code so our BarGraph or BarGraph dialog box doesnt show up anymore. Add a message handler for the New Document menu item in your application. Define it as follows: void CBarGraphApp::OnFileNew() { m_pGraphTemplate->OpenDocumentFile(NULL); } Compile and run your application. Everything should work fine except how will we ever create a Table view now?

44

Adding New Graph and Table Windows


The New Window menu item in the Window menu allows us to create new views on the currently active document. Since we now have two different types of view, we should replace the New Window menu item with two new menu items New Graph Window and New Table Window. Create message handlers for the new menu items in the CBarGraphDoc. These message handlers can create new windows by accessing the DocTemplate from the CBarGraphApp. Heres the code for the New Table Window handler: void CBarGraphDoc::OnWindowNewTableWindow() { CDocTemplate* pTemplate = ((CBarGraphApp*) AfxGetApp())->m_pTableTemplate; CFrameWnd* pFrame = pTemplate->CreateNewFrame(this,NULL); pFrame->InitialUpdateFrame(this,TRUE); } Go ahead and run your application. You should now be able to create new graph and table views of the currently active document via the Window menu.

45

46

Practice 12: T oolbarsand StatusBars


In order to get some practice making toobars and status bars, were going to make some minor modifications to your SuperPad project.

Toolbars
Add a new toolbar to SuperPad. Your new toolbar should contain buttons to bring up the find/replace dialog and bring up the goto line dialog. Again, the steps for creating a toolbar are (1) create a new toolbar resource, (2) modify your main frame code to actually create and dock the toolbar, and (3) respond to user interactions with the toolbar. Go ahead and create a new toolbar resource in the ResourceView. Draw buttons for each of our toolbar items. Dont worry too much about the appearance of the toolbar buttons (we arent testing your graphics abilities). Youll need to give each of your toolbar buttons an ID. Remember, you get the properties window for a toolbar button by double-clicking on the button. Give the find dialog toolbar button the same ID as your find dialog menu item (probably ID_EDIT_FIND). Similarly, give the goto line dialog toolbar button the same ID number as used by your goto line dialog menu item (probably ID_EDIT_GOTOLINE). Once youve gotten the toolbar resource created, add a member variable for the toolbar to your main frame. Then create the toolbar and dock it. You can follow the code examples found already in the main frame code for the original/default toolbar created by MFC. In this case, you wont actually need to write any code to respond to user interactions. If your ID numbers for the toolbar buttons are exactly the same as that for the menu items, your existing message handlers should get called automatically.

Status Bar
Add two panes to the status bar to provide information on the carets current location. One pane should show the line number the caret is on. The second should show the character position of the caret within the current line. First, create two new strings in the string table, one for your line pane and one for your character pane. The string values are the default values written in to the status panes. Dont worry too much about the actual values, as youll be overwriting them, but remember, you must provide default values. Modify the static indicators array in your MainFrame.cpp file. Add entries for your two new panes using the ID numbers youve just created in the string table. If you use the default pane sizes, the panes wont be big enough to display the information you want, so in the main frames OnCreate, after the status bar is created, resize both panes by calling SetPaneInfo. Manually declare and define on command UI handlers for both panes. Add entries for the handlers in the frames message map. Remember the signature for an update command UI handler is:
47

afx_msg void OnUpdateHandler(CCmdUI *pCmdUI); The message map entry format is: ON_UPDATE_COMMAND_UI(ID_NUM, OnUpdateHandler) In your handlers, get access to your view and your actual CSuperEditCtrl using CFrameWnds GetActiveView function. Get the current selection and convert it to the correct line number or the correct character position within the line. Place the number in a string, and call CCmdUIs SetText. If the user currently has multiple characters selected, base your line and character indicators on the first character of the current selection.

48

Practice13: StoringPreferences
In this practice assignment we extend our SuperPad program to allow users to change the font used to display text. The last font used when the program is exited will be stored as a preference and when SuperPad is run again, it will use the last font chosen. To keep things simple, well assume that our font can be described simply by the font name and the point size. Well use the built in CFontDialog class, but well ignore the users font style choices. See the For Further Study section for how to correct these limitations.

Adding Preference Information to the App


Well store two pieces of information to handle our font preferencesthe name of the font and the point size of the font. We will store this information as part of the application. In SuperPad we could store this information as part of the view, since theres only one view per application, however as a general rule, user preferences hold across views and across documents, so the best place to put them is on the application. Lets start by adding member variables to your application for font name and font size. Our next step is to load values for these variables when the application starts up and to save the values when the application shuts down. To do this, need to modify the applications InitInstance. In InitInstance, we add calls to GetProfileString to retrieve the last used font name from the registry and GetProfileInt to get the last used font size from the registry. Your code should look something like this: m_fontname = GetProfileString("UserPreferences","FontName", "Arial"); m_fontsize = GetProfileInt("UserPreferences","FontSize",10); Next, we add calls to WriteProfileString and WriteProfileInt to the apps ExitInstance. These calls write the latest settings of font name and font size back out to the registry. Youll probably need to create ExitInstance from the ClassWizard (its one of the standard functions ClassWizard can create for you on your app class).

Using the Preferences


Our next step is to get our editor to actually use the font and font size settings. If youve already got your SuperPad to support fonts, go ahead and modify the initial settings to use the font name and font size defined at the application. If youre version of SuperPad doesnt yet support fonts, add a pointer to a CFont as a member variable to your view. In the views OnCreate function, create the new CFont object and then call CreatePointFont on it, using the font name and font size from your app class. Please note that CreatePointFont takes the font size in 1/10ths of a point. Then set the font of the edit control to your newly created CFont using SetFont.

49

Allowing the User to Change the Font


Our next step is to provide a means for the user to change the font. We can do this by adding a Set Font menu item. Create the new menu item and add a handler for it on your CView. In the handler, bring up a CFontDialog. Let the user select a font. Retrieve the name and point size of the font from the dialog and (1) create a new CFont and set your control to use the new CFont and (2) change the font name and font size member variables of your app class to reflect the new font. Dont forget to Invalidate the window so your view gets redrawn after the user changes the font. If youve done everything correctly, you should be able to change the font displayed while the application is running. When you quit the application, and restart it, it should be using the last font displayed.

For Further Study


Weve simplified this practice assignment by assuming a font is defined simply by the font name and the font size. To correctly handle all aspects of the font chosen by the user via the CFontDialog, youll need to add member variables for each of the parts of the LOGFONT structure found as part of the CHOOSEFONT structure in the m_cf member variable of the CFontDialog. Youll need to store and retrieve each of these when your application initializes and exits.

50

Practice14: WorkingwithWin32
In this practice assignment well study straight Win32 programming. Instead of building a Win32 program from scratch, well modify the Win32 squares example program from lecture. As originally written in lecture our Win32 squares program simply draws a 20x20 square wherever the left mouse button is clicked. In this assignment, well extend the original to color in the square. Clicking the right mouse button will change the color of the square and move the square. Clicking the left mouse button will continue to simply move the square. Well need to make a number of changes to the Win32 squares program. First, well need to store color information somewhere in the program. Well need to use the color information to fill in the square. Finally well need to respond to right mouse button clicks.

Color Information Storage


Were are we going to store information on the color used to fill in the square? If were sure theres only going to be one instance of our window, we can simply store the information as global variables. However, if we think our program might need to create multiple instances of the same window class, well need to store the information as part of the window. Lets assume we want to keep our program as versatile as possible, so we dont want to create a set of global variables. Instead, well store red, green, and blue values as part of the window. As youll recall from lecture, we tell the Windows system how much storage we want for a window as part of the WNDCLASSEX structure used to register the window class. The MyRegisterClass function created by the AppWizard is used to register the window class. The lines: wcex.cbClsExtra = 0; wcex.cbWndExtra = 8; tell the system that we want no bytes for storage shared by all windows in the same window class, but that each individual window needs 8 bytes of storage. Currently we are using these 8 bytes of storage to store the x and y location of the square. Change the cbWndExtra to 12 bytes8 bytes for the x and y location, as before, and an additional 4 bytes for our COLORREF information. User-defined storage space for each window can be initialized when the windows WndProc responds to the WM_CREATE message. Currently our window class WndProc executes the following on WM_CREATE: case WM_CREATE: SetWindowLong(hWnd,0,40); SetWindowLong(hWnd,4,40); break; This sets both the longs at 0 bytes and 4 bytes offset to 40. Our program interprets these two longs as the x and y coordinates of our square. Lets use the long starting at 8 bytes offset to represent our COLORREF. Add the following line of code to the WM_CREATE before the break statement:
51

SetWindowLong(hWnd,8,RGB(255,0,0));

Filling the Square


Now that weve created and initialized space to store our color, its time to actually draw using the color. The actual drawing code in a WndProc is the code executed in response to a WM_PAINT message. Here is the current drawing code: case WM_PAINT: hdc = BeginPaint(hWnd, &ps); xPos = GetWindowLong(hWnd,0); yPos = GetWindowLong(hWnd,4); Rectangle(hdc,xPos,yPos,xPos+20,yPos+20); EndPaint(hWnd, &ps); break; All drawing calls must be enclosed between BeginPaint and EndPaint function calls. In MFC, these calls are taken care of by the framework when it converts WM_PAINT messages in to OnDraw function calls. As we can see, after setting up the handle to a device context (DC), we retrieve the value of the x and y position from the window storage space. Windows has no idea what were storing in the window storage space, however, as long as we use the same index numbers to store and retrieve the same information, everything will work correctly. Lets add a few lines to retrieve our COLORREF value. First, declare a COLORREF variable up at the top of the function, then add a call to GetWindowLong with offset set to 8. Your lines should look something like this: COLORREF color; color = GetWindowLong(hWnd,8); Now we need to take care of the actual painting. If we were programming in MFC, we would call the FillRect method on the CDC. As with most MFC calls, the CDC class FillRect has a Win32 equivalent. The only difference between the two is that the Win32 version is a function, not a class method and therefore requires that we pass in the handle to the DC were drawing in as a parameter. Well need to create a RECT and an HBRUSH for our call to FillRect. The CBrush member function CreateSolidBrush has a corresponding Win32 function which takes the same parameters and creates and returns an HBRUSH. Here are the lines we need to add: COLORREF color; color = GetWindowLong(hWnd,8); rect.left = xPos; rect.top = yPos; rect.right = xPos+20; rect.bottom = yPos+20; FillRect(hdc,&rect,CreateSolidBrush(color));

Responding to the Right Mouse Button


One last modification, we need to get our squares program to respond to right mouse button clicks by both moving the square and changing the color of the square. Add a WM_RBUTTONDOWN case to the switch statement in the WndProc. We can copy most of the code directly from the WM_LBUTTONDOWN handler. As with the left mouse button we need
52

to decode the contents of the message by getting the x position and y position of the cursor from the hi and lo words of the lParam. We will then store the new values in to the window storage using SetWindowLong calls. Before the InvalidateRect call, however, lets also change the value of the 8-11 bytes representing the color of the square with the following lines: color = GetWindowLong(hWnd,8); if (color == RGB(255,0,0)) SetWindowLong(hWnd,8,RGB(0,0,255)); else SetWindowLong(hWnd,8,RGB(255,0,0)); Now our fill color should toggle between red and blue.

53

54

Name: ___________________ Leland ID: ________________ Student ID: _______________

CS193W Practice Log


Complete the practice assignments after the corresponding lectures. When you complete an assignment, initial the practice log and enter the date you completed the assignment. Turn in the practice log on the last day of class. SITN students only may FAX a copy of this log to (650) 723-6092 instead of turning it in. Date Completed Initials

Practice 1: Overview of MFC Practice 2: Intro to Graphics Practice 3: Inputs Practice 4: Controls Practice 5: Menus Practice 6: Dialog Boxes Practice 7: Serialization Practice 8: Collections Practice 9: Mapping Modes Practice 10: Using CScrollView Practice 11: Multiple View Classes Practice 12: Toolbars and Status Bars Practice 13: Storing Preferences Practice 14: Working with Win32

_____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____

___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___

55

Você também pode gostar