Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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).
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.
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).
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
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.
4.
31
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.
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:
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.
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.
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.
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.
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
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:
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.
44
45
46
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.
49
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.
SetWindowLong(hWnd,8,RGB(255,0,0));
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
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