This lab introduces programming using C++, Microsoft Foundation Classes, and Visual Studio. The goal of the step is to introduce features of this environment that you may not have had experience with in the past. It is not a large-scale tutorial. I hope everyone will pick up much of the rest of the environment features you will need as you do other steps and the projects.
This step assignment is a tutorial you are to do on your own time. If you are already experienced with Windows programming and Visual Studio 2008, you may be able to skip this, but be sure you know how to create menus, dialog boxes, and that you understand event driven programming.
|
This class will
be using Visual Studio 2008. In addition, future assignments
(after this one) will require the Microsoft Platform Software
Development Kit (SDK) and the Microsoft DirectX SDK.
Visual Studio 2008 is available to CSE students in the public labs and
from http://msdn.cse.msu.edu.
The two required SDK's are available from msdn.microsoft.com. You
will not need the SDK's for this assignment.
These products can take a considerable time to download and install, so be sure to get started early. |
The specific features that will be introduced are:
The features we are introducing in this step are mostly basic features you need to know to use Visual C++. Very little of this step is specific to CSE 471 material. Next week we'll spend a lot of time working with images and beginning to work with time.
You should be aware that programming in any GUI environment is VERY different from the programming you've done at the command line. The main difference is that the environment is Event Driven. This means that everything you will do (well, for now) will be done in response to a message from Windows. This may be indicated by function calls or by a message handler you will supply. The major ramification of this is that you will not write loops that get input and then process it. It's a very different world, so keep an open mind. (we'll get back to loops later, but special loops that are designed for this environment).
| Remember: You will not write loops that get input and then process it ! |
Notice: This is the way real interactive programming is done, be it Windows, X-Windows, or the Macintosh. Command-line programs are for utilities and novice programmers!
First, you need to start Visual Studio 2008. You'll find it in the Start menu under Visual Studio. When you have the program started, select File/New/Project. In the New Project dialog box, select the Project Type as Visual C++. Then choose the Template named "MFC Application". You need to give your project a name and a location. You may name it what you wish, but for now I would suggest naming it "Step". Enter the project name and location into the dialog box. Press Ok.
You can create your project in your normal account, but you should be aware that the size of a MFC project starts at about 20MB and grows from there, so it's a bit rough on quotas.
When the MFC Application Wizard comes up, select Application Type of the left side of the page. Unclick "Tabbed documents" and select "MFC standard" as the project style. When everything is selected, hit the Finish button and your project is created.
You'll see several things on the screen. To the right is the Solution Explorer. On the left is a large multiple document interface (MDI) area for editing. On the bottom are information windows. Press the ClassView tab at the bottom of Solution Explorer and expand "Step". This is a list of all C++ classes for your application (plus any macros and global functions and variables). The classes are:
If you see more classes than this, you didn't select MFC standard. I would exit Visual Studio, delete the directory it created, and start again.
The Document/View (also called the Model/View) architecture is a very common one. The idea is that you have something you are modeling. It might be an image you are editing, a Word document, a spreadsheet, or a graphical object. This is the "Document". If you wish to see your document in any way on the screen, you attach one or more views to it. The advantage of this method is that multiple views can exist at the same time for the same document, allowing multiple windows into the document or split windows.
You'll have to make decisions as to where data is stored and where commands will be processed in your application. Suppose I have an image and a program that allows me to edit that image. The image clearly goes into the Document class. But, what might we have in the View class? Well, we might be able to create selection rectangles in the image (selecting a region to cut or paste, for example). The location and dimensions of this rectangle would be in the View class if it is only to appear on one window on the screen.
It's common that multiple windows will provide multiple views of the same object. You might have one window at full scale and another where you have blown the image up much larger so you can edit the pixels. This "scale factor" would be part of the view.
As created by Visual Studio, this application is ready to compile and run (but it won't do much, yet). Select Build/Build Solution. This will compile your program. Then you can select Debug/Start Without Debugging. Note in the executing program that you have menus and can create windows, etc. Exit the program.
The function that is called to draw is CStepView::OnDraw. In the ClassView window, click on the CStepView class. The class members will appear in a window a bit lower on the screen. In that window, double-click on the OnDraw member function. It will pop up in the editor ready for your brilliant additions. Note that VS has created a template function that does next to nothing other than getting a pointer to the attached document object (remember that every view is a view of a document). The document doesn't do anything at this time, either, so we'll not worry about it, yet.
The OnDraw function has a single parameter passed to it: pDC of type CDC. By default the parameter is commented out, so remove the comments. A CDC object is the "Display Context". A display context records the state of the mechanism for drawing on the screen. It knows what the current font is, what the current line drawing width and color is, and other characteristics of drawing. Because of this, most functions for drawing are members of this class as you will see.
Add the following code in OnDraw, replacing where it says "TODO":
pDC->MoveTo(0, 0);
pDC->LineTo(100, 150);
Compile and run your program. The window will display a diagonal line.
The display context knows how to draw a line. This is referred to as the "Pen" characteristics of the context. Let's change how this gets drawn by changing the pen. Replace the code you just put in with this code:
CPen pen;
pen.CreatePen(PS_SOLID, 2, RGB(0, 255, 0));
CPen *oldpen = pDC->SelectObject(&pen);
pDC->MoveTo(0, 0);
pDC->LineTo(100, 150);
pDC->SelectObject(oldpen);
Note that the line draw code did not change. Here's what this code does:
After the line draw, add the following code:
CFont font;
font.CreateFont(14, 0, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, TEXT("Arial"));
CFont *oldfont = pDC->SelectObject(&font);
pDC->TextOut(40, 50, TEXT("TextOut"));
pDC->SelectObject(oldfont);
Run this and see what it does. You may be wondering what all of the parameters of CreateFont mean. Select Help/Index and enter CFont to find information on this class. Double-click on the CFont main entry and select the version for Microsoft Foundation Classes (watch out for various versions in documentation including Windows CE). You can then select documentation for CreateFont. The 14 value is for the height of the font in pixels. The 200 means the font is at a 20 degree angle.
Why are the text literals surrounded with TEXT()? Windows uses Unicode internally for all operations. Older version allowed regular C strings as parameters, but as of 2005, all of that is history and you'll have to be sure to support Unicode. When you put TEXT() around a literal string, Windows will make the literal Unicode for you. Note that this is NOT a function you can use, it's only a way to indicate that a string you put into your code should be interpreted as Unicode.
Note that I've again changed an element of the display context: the current font. Again I saved off the old version and restored it when I was done.
At this point you might want to figure out what all of the parameters to CreateFont are. Use Help in Visual Studio and search for CFont, then find the member function CreateFont. You'll need to be able to find things if you are going to use MFC.
We're going to add some things to our document, now. I want to create a document that knows where to draw the line to, where to draw the text, and what text to draw. We want this to be an element of the document so it appears in all views. We'll add ways to change these values momentarily.
To add a member variable to a class, right-click on the class CStepDoc in ClassView and select Add/Add Variable from the menu. We want to add the following private member variables to CStepDoc:
| Why the m_? It is a common convention in Microsoft code that member variables are prefixed with m_. This is a great habit to get into and will be required in this class. Then it's clear in your code when you're working with a member variable as opposed to a local variable. |
| CString or string? I've use the CString class in this example. That class is the MFC string class. The standard template library equivalent is string. You can use STL classes in your MFC programs (and I common do), but when you are dealing with the user interface functions, the MFC classes are directly supported and are somewhat easier to use and deal with Unicode properly. Also, "string" is not for Unicode characters, so you'll need to use wstring. I'll include examples of using STL classes later. |
To add member variables to an existing class in VC++, right click on the class in the class browser. Select Add Member Variable. We'll enter these variables, all of them private. Do that now. You can also double-click on the class and jump right to the header and enter them manually. Ensure that your program compiles before proceeding.
Now, we need to go to the constructor for CStepDoc and set default values for these variables. In the class browser, expand CStepDoc and doubleclick on CStepDoc(), the default constructor. Enter the following code, replacing the TODO:
m_linetox = 100;
m_linetoy = 100;
m_textx = 50;
m_texty = 50;
m_text = TEXT("Default Text");
If you used Add Variable to add the member variables to the class, Visual Studio will have created initializers in the constructor, but not to the values we a want to use. Ensure that this compiles.
| Aside: Because the x and y values are always used together, I would probably use a CPoint object rather than separate member variables for x and y. You can pass CPoint to the MoveTo and LineTo member functions. See the online documentation for CPoint. |
In order to be displayed, this data must be accessed by the View. We'll add some access functions to CStepDoc. I'll put these right into the class as public member functions:
void GetLineTo(int &x, int &y) const {x = m_linetox; y = m_linetoy;}
void GetTextLoc(int &x, int &y) const {x = m_textx; y = m_texty;}
const CString &GetText() const {return m_text;}
Because these are in the class declaration, they will be inlined and incur no function overhead. Note the extensive use of const. You should be using const as often as you can in code for both performance and type safety reasons.
Now, let's access these in the view. Change the TextOut line in OnDraw to this:
int x, y;
pDoc->GetTextLoc(x, y);
pDC->TextOut(x, y, pDoc->GetText());
We are asking the document class for the location of the text and the actual text. Run this and it should work just fine.
Add the code necessary to draw the line to the correct end point as well.
Supposed you want to know what the value of x and y are when pDC->TextOut is called the first time? How would you determine that? You can't use printf or cout in an MFC application, so just printing the values won't work. There is a function called TRACE which works like a printf statement, but the easiest way is to set a breakpoint.
Click to the right of the pDC->TextOut statement and a red dot should appear as illustrated below.

Now select Debug/Start Debugging to run the program in the debugger. It should stop at the breakpoint and display the values of all variables at the bottom of the screen. You can disable the breakpoint by clicking on it and use the Continue button to continue running the program. Here's what the debugging buttons look like in Visual Studio:

Now, we're going to create a menu option to move the text location to a fixed position of 40, 20. This will entail several steps.
Select the menu option View/Other Windows/Resource View. This will bring up the ResourceView tab in the ClassView area. Resources are additional elements of your program not expressed as actual code. They include the menu, icons, etc. We're going to edit a menu, so expand "Step", expand "Step.rc", then expand the Menu folder.
You'll find several menu resources here. IDR_MAINFRAME is the menu used when no windows are open. If no windows are open, we have no views, so we don't add functions to that menu. Instead, double-click on IDR_STEPTYPE. You'll see a window with a menu. To the right of "help" in the menu is a box that says "Type Here".. Click on that box and type "&Step stuff" and press return. You've just created a new menu. (The & underlines the S and makes it accessible via the keyboard).
Now, click on Step Stuff and enter: &Text to 40, 20 in the "Type Here" submenu box. Compile and run your program. Note that the new menu exists and the new menu option is included, but disabled. That's because we're not processing the option.
| Aside: The menu editor is very easy to use. Feel free to move items around to the order you like by just dragging them. |
When you select a menu option in Windows, a message is sent to your program. You have to decide where to handle that message. In this example, the menu option changes the values in the document, so I'll handle it in the document. Go the your newly added menu option in the resource editor and right-click. In the new menu that pops up, select "Add Event Handler...". This brings up the event handler wizard dialog box. Scroll in Class list until you get to CStepDoc. Select that and press "Add and Edit". This is creating a member function in CStepDoc that will be called when the menu option is selected and jumping it to it so you can edit it..
You are jumped right to the function and can fill in the functionality. Enter the following code:
m_textx = 40;
m_texty = 20;
UpdateAllViews(NULL);
So, what does this do? Well, it's obvious that it changes the values of the variables. Then, it calls UpdateAllViews to tell all of the views that the underlying document has changed. This forces them to redraw themselves.
Compile and run this. Type various options of multiple windows open to see what happens. You can easily add another menu option to move the text elsewhere and see what that does.
| Notice: UpdateAllViews is only valid within
the document class. If you handle a menu option in a view, you
instead call the function Invalidate(). This invalidates only the
local view. Try commenting out the UpdateAllViews and see what happens. The display won't change when you change the variables. But, try resizing the window and see what happens. |
The most common way to modify parameters is through dialog boxes. We'll now create a simple dialog box we can use to set the text position and the text we'll print. There are four steps in creating a dialog box:
In resource view, right-click on step.rc and select "Add Resource...". In the Add Resource dialog box, select "Dialog" and press New. You now have a new, empty dialog box. First, we need to give this dialog box an identifier value. Right-click in the window anywhere and select properties. To the lower right on the screen, you'll see the Properties pane. Scroll until you see a property named "ID". It will likely have the default value of IDD_DIALOG1. Change that to IDD_TEXTDLG. Then enter for the caption (another property): "Where the text?"
Now, we need to add controls to the dialog box. On the screen you'll see a palette of available controls on the left side of the screen in the Toolbox. Select "edit control" and draw an edit box in your dialog box. Do this twice for the X and Y values and then a third time (larger, preferably) for the text value. Finally, select "static text" and enter captions for each of these controls. Here's what this should look like:

| Aside: There is lots of material on-line about using the dialog box editor. We'll only concern ourselves here with getting a dialog box on the screen. Consult the on-line help for more information on making your dialog box look nice. |
The last thing we need to do is associate an ID with each control in the dialog box. The default ID is something like IDC_EDIT3, which is not descriptive. To set the ID, right click each control and select properties. In the properties pane, set the ID to IDC_X, IDC_Y, and IDC_TEXT. When you are done, be sure everything compiles, the you can close the dialog box editor if you wish.
Now we'll create a C++ class that is associated with this dialog box. Choose the Project/Add Class menu option. Select MFC" as the category. To the right, double-click on MFC Class (not any of the "from" variations). The MFC Class Wizard should appear. Enter CTextDlg as the name. For the base class, choose CDialog. The Dialog ID should already be set to IDD_TEXTDLG. Click Finish.
If you go to ClassView now, you'll see your new class now exists. VC++ created two files: TextDlg.h and TextDlg.cpp for you automatically.
| By the way: You'll notice that I used a leading capital C for my class name. This is a convention Microsoft uses throughout MFC and I recommend that you consider it for your projects as well. If you will use the leading C, MFC will automatically figure out the file name to use for the class header and implementation modules as the class name without the C and with the appropriate suffix. If you don't do this, it will just use the class name. This is why I called the class "CTextDlg" rather than just TextDlg. |
Open the dialog box back up in the dialog editor. Right-click on the X edit control and select Add Variable. Leave the access as "public". Change the "Category" to Value. Set the variable type to int. Name the variable m_x. When set, press Finish. So the same for IDC_Y.
For IDC_TEXT, set the variable type to CString.
You can add some validation here if you want such as min and max values or the maximum string length. Just ignore that for now.
Press Ok.
| Aside: What this has done is create public variables in the CTextDlg class. When the dialog box is displayed the first time, the boxes are filled in from these variables. When the Ok button is pressed, these variables are set to the (potentially edited) values from the dialog box controls. |
Now we want to create a menu option that brings up the dialog box. First, create a new menu option in the Step Stuff menu called "Text Dialog..." and associate a handler in CStepDoc with the menu option. Get that working first. Scroll up if you need to review how to do this step.
Now, add this code to the menu handler you just created:
CTextDlg dlg;
dlg.m_x = m_textx;
dlg.m_y = m_texty;
dlg.m_text = m_text;
if(dlg.DoModal() == IDOK)
{
m_textx = dlg.m_x;
m_texty = dlg.m_y;
m_text = dlg.m_text;
UpdateAllViews(NULL);
}
Then, go to the head of the file and add this #include line:
#include "TextDlg.h"
| Notice: Any #include statements you add MUST be after #include "stdafx.h". Anything you add before the #include "stdafx.h" will be ignored. |
Compile this and run it. You should now be able to use the dialog box to set parameters of the text on the screen.
Microsoft Foundation classes have a very easy to use method for serializing documents to files. This method is very powerful. But, I've NEVER seen a program actually use it. It defines a file format very specifically. We will often be working with specific files of other formats, so what you'll see here is how to create a file yourself.
Right-click on CStepDoc in ClassView and select properties. In the properties window (usually the lower right corner of the screen), you'll find a series of buttons. Look for the one that looks like a very small book and for which the balloon help says is "Overrides". Here's what the button looks like:

You'll find OnSaveDocument() in it. Use the menu (down arrow) to the right of OnSaveDocument to create the virtual function. Replace the dummy code in the function with this:
ofstream ostr(lpszPathName);
ostr << m_linetox << " " << m_linetoy << " " <<
m_textx << " " << m_texty << endl;
char s[100];
size_t cnt;
wcstombs_s(&cnt, s, 100, m_text, _TRUNCATE);
ostr << s << endl;
ostr.close();
return true;
The bit of yuckiness starting at the char s[100] line converts the Unicode string to an 8 bit string (multiple byte symbols in fact) suitable for output to a file. You'll probably find the wcstombs_s function rather useful in Windows programming.
Add the following to the beginning of this file:
#include <fstream>
using namespace std;
Notice: never use the older Standard Template Library headers that end with ".h". They are included only for compatibility and are obsolete. Note the use of "using namespace std;".
Compile and run this. Then take the generated file and drag it onto Visual C++. You should see the 4 numbers and the string.
Right-click on CStepDoc in the class browser and select Add Virtual Function... Click on OnOpenDocument and press Add and Edit. Delete the default code that was created for you (do not call the default OnOpenDocument). Enter the following:
ifstream istr(lpszPathName);
if(!istr)
{
AfxMessageBox(TEXT("Unable to open input file"));
return FALSE;
}
istr >> m_linetox >> m_linetoy >> m_textx >> m_texty;
istr.get();
char buffer[200];
istr.getline(buffer, sizeof(buffer));
m_text = buffer;
return TRUE;
Note the use of AfxMessageBox to display a message if the file cannot be opened. The rest is simply code to read what we output before. Note that the CString type is smart enough to convert the 8 bit string into Unicode for us when reading back in.