Document/View Example

In this example, I will write an application to manage a relatively simple address book. This application will demonstrate the underlying principles of document/view architecture.

Use the Appwizard to write a MDI skeletal application. Name the application AddressBook. The appwizard will write the following classes:


The application class (CAddressBookApp) contains the application’s main message loop (WinMain.) There is exactly one instance of this class (theApp defined in AddressBook.cpp.) theApp accepts messages from the Operating System and either handles them or forwards the messages that it does not want to handle to the MainFrame.

The MainFrame class (CMainFrame) is responsible for maintaining the application’s main frame, menus, tool bar, status bar, etc. and the window operations such as Minimize, Maximize, Move and Resize. The MainFrame class forwards the messages that it does not want to handle to either Document or View.

The ChildFrame class (CChildFrame) is responsible for maintaining the application’s child frames that are contained within the client area of its main frame, and the window operations such as Minimize, Maximize, Move and Resize.

The Document class (CAddressBookDoc) is responsible for maintaining consistent data structures by encapsulating the applications data within the class and providing member functions that perform operations to manipulate, save and retrieve the data.

The view class (CAddressBookView) is responsible for maintaining a consistent view of the underlying document, interacting with the user and communicating relevant user changes to the Document.

I begin by designing the application’s document class. The document class will encapsulate the address book’s data and will provide member functions to access and manipulate that data. Each entry of the address book consists of 3 fields: name, address and phone number. The structure shown below is sufficient to maintain the address book records.

struct AddressRec {

CString name;

CString address;

CString phone;

};

Let’s assume that the number of phone book entries is fixed and that the constant MAX represents this number. Then, the storage required to store our address records can be defined as

AddressRec storage[MAX];

I also need to know how much of the storage is occupied at any given time. An integer variable, size, will be used for this purpose:

int size;

The variable size must be initialized to –1 in the CAdressBookDoc’s constructor.

CAdressBookDoc must provide at least two functions allowing addition and deletion. The following function will perform addition

BOOL CAddressBookDoc::Add(CString name, CString address, CString phone) {

if (size >= MAX-1)

return FALSE;

else {

++size;

storage[size].name=name;

storage[size].address=address;

storage[size].phone=phone;

return TRUE;

}

}

And, the following function will perform deletion:

BOOL CAddressBookDoc::Delete(CString name) {

int i=0;

while (i <= size)

if (storage[i].name == name) {

storage[i].name=storage[size].name;

storage[i].phone=storage[size].name;

storage[i].address=storage[size].address;

--size;

return TRUE;

}

else ++i;

return FALSE;

}

Step 1. Preparing the Document class.

1.1.Define an integer constant MAX;

1.2.Add the structure “addressRec” as a private structure to CAddressBookDoc.

1.3.Add private variables storage and size to CAddressBookDoc.

1.4.Add public member functions Add and Delete to CAddressBookDoc.

With Add and Delete functions in place, we must decide how to invoke them. I will add a menu for this. The menu will be named Address Book and will have two entries: Add and Delete

Step 2. Adding Address Book Menu.

2.1.Choose the Resource View.

2.2.
Expand the Menu Resource and double click on IDR_ADDRESSTYPE.

2.3.In the empty box next to “Help” type Address Book.

2.4.In the empty box under “Address Book” type Add.

2.5.In the empty box under “Add” type Delete.

2.6.Right click on the Add Menu item and choose properties.

2.7.In the Menu Item Properties Dialog, change the ID to ID_ADDRESSBOOK_ADD.

2.8.Similarly, change the ID of the Delete menu item to ID_ADDRESSBOOK_DELETE.

2.9.Build and execute AddressBook.exe

You should notice that while the Address Book menu contains the items Add and Delete, both items are grayed out and can not be selected. The reason for this is that no handler will respond to the events of choosing these menu items. The next task is to write a handler for Add menu item. The question here is where shall we place the handler for Add. It can be placed within the Document class, View Class, MainFrame and so on. Because adding to and deleting from the address book requires manipulating the address book’s private members, I will place the handlers in the document class.

Step 3. Adding handlers for the Add and Delete Menu Items.

3.1.Choose the Resource View.

3.2.Expand the Menu Resouce and double click on IDR_ADDRESSTYPE.

3.3.Choose the ADD Menu item and invoke the class wizard.

3.4.
Choose CAddressBookDoc for class name, ID_ADDRESSBOOK_ADD for Object ID; Command for Message, and click Add function.

3.5.The class wizard responds by informing you that it will name the new function OnAddressbookAdd, accept that name and click edit code.

3.6.You can now enter the body of the function OnAddressBookAdd. For now just type

AfxMessageBox(“Add is chosen”);

3.7.Repeat the above steps for the Delete Menu item and for the body of the function OnAddressbookDelete, type:

AfxMessageBox(“Delete is chosen”);

3.8.Build and execute AddressBook.exe

At this time our Add and Delete menus are working correctly. However, their handlers only display messages indicating that they are selected. What we need is that when either of Add or Delete menus is selected, A dialog to be displayed to allow the user to enter the needed information.

In case of Add, the user must enter a name, an address and a phone number. The following dialog is sufficient for this purpose:


Step 4. Preparing the Add Dialog.

4.1.Insert a new dialog.

4.2.Change the ID of the new dialog to IDD_Add and change its caption to Add

4.3.Insert three Edit Boxes for Name, Address and Phone.

4.4.Change the IDs of the above Edit Boxes to IDC_Add, IDC_Address and IDC_Name.

4.5.Add three static text controls as labels for Name, Address and Phone Edit Boxes

4.6.Rearrange the controls as above

Now that we have our Add dialog what do we do with it? Because the Add dialog is only a resource, it can not be directly displayed and interact with the user. For this we need a Dialog object. This dialog object must be an instance of a class that will facilitate all required interactions with the Add Dialog. We will create this class using the class wizard.

Step 5. Writing a class for the Add Dialog.

5.1.Select IDD_ADD in the resource view and choose class wizard (from View menu)

5.2.Class wizard will note that no class for IDD_ADD exists and will display:

5.3.

Click OK. The class wizard will ask you for the name of the new class, its base class, Dialog ID and whether or not it should contain automation. Enter CAddDlg for the name of the class and accept the default values for the others. Click OK.

5.4.Choose member variables tab and add:

  • a member variable, m_Address, for IDC_Address
  • a member variable, m_Name, for IDC_Name
  • a member variable, m_Phone, for IDC_Phone


5.5.Click OK

We are now ready to modify the OnAdd function that we previously wrote. OnAdd function must:

declare an instance of the CAddDlg,

  • call the DoModal function to display the dialog and allow it to interact with the user,
  • verify whether or not the OK button of Add Dialog was pressed (the DoModal function returns the Id of the button that was pressed), and
  • if the OK button was pressed, call the Document’s Add function to add a new entry to the Address Book.

Step 6. Invoking the Add Dialog when Add menu item is chosen

6.1.Replace the OnAdd function with the following function:

void CAddressBookDoc::OnAddressbookAdd()

{

// TODO: Add your command handler code here

CAddDlg dlg;

if (dlg.DoModal() == IDOK)

Add(dlg.m_Name, dlg.m_Address, dlg.m_Phone);

}

6.2.Include AddDlg.h in AddressBookDoc.cpp

6.3.Build and execute AddressBook.exe

The next enhancement to the program is to display the Address Book On the screen. When a view needs to be painted, A WM_PAINT message is generated. WM_PAINT is handled by the view’s OnPaint function. OnPaint declares an instance of CPaintDC, performs all necessary initialization and then calls the OnDraw function of CaddressBookView, passing a pointer to the CPaintDC object.

We must modify the skeleton OnDraw function shown below:

void CAddressBookView::OnDraw(CDC* pDC)

{

CAddressBookDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

The class CDC provides functions to write text, draw geometric objects etc. on the device context.

  • The function TextOut displays a string at a location specified by its x and y coordinates, where the coordinate system of the view are as shown below:

For example pDC->TextOut(10,15, “This is fun!”); will display the string: This is fun! Starting at the point (10, 15).

  • The function SetTextColor will set the color of the text output to the color specified by its parameter. The color is sepcified using an RGB triplet. For example,

pDC->SetTextColor(RGB(255,0,0) will set the color to red.

Lets start working on the OnDraw function. First, we must declare two integer variables x and y; These would hold the x and y coordiantes on the next output line. We must have a loop that will iterate once for each entry in the Address Book. In each iteration, we will compute the (x,y) coordinates of the next logical line of the device context and display the next address book entry starting at the coordinates (x,y.) To do this we must answer the following questions:

  1. What is the “average height” of the current font? Without knowing this value, we won’t be able to calculate the coordinates of the next logical line. Of course, dc has all the necessary font information (metrics.) The font information is stored in the structure TEXTMETRIC defined below:

typedef struct tagTEXTMETRIC { /* tm */

int tmHeight;

int tmAscent;

int tmDescent;

int tmInternalLeading;

int tmExternalLeading;

int tmAveCharWidth;

int tmMaxCharWidth;

int tmWeight;

BYTE tmItalic;

BYTE tmUnderlined;

BYTE tmStruckOut;

BYTE tmFirstChar;

BYTE tmLastChar;

BYTE tmDefaultChar;

BYTE tmBreakChar;

BYTE tmPitchAndFamily;

BYTE tmCharSet;

int tmOverhang;

int tmDigitizedAspectX;

int tmDigitizedAspectY;

} TEXTMETRIC;

Look at the manuals for an explanation of this structure. For now, we would only need “tmHeight” that holds the height of the current font. The function GetTextMetrics returns, via its parameter, the current font’s metrics. We therefore need the following code to access the current font’s metric information:

TEXTMETRIC tm;

dc->GetTextMetrics(&tm);

Now, tm.tmHeight is the average height of the current font and for writing on every other line, the y of the ith line is:

2 * i * tm.tmHeight.

The x position of all lines are the same and can be fixed at 10.

  1. How do we access the internal data of the AddressBookDoc? The internal data of AddressBookDoc is encapsulated within the Document class. The function GetDocument() returns a pointer to the document object. The pointer is assigned to pDoc and can be used to access the document’s public members. But, the internal data of CAddressBookDoc is declared to be private. We therefore need to write public member functions to return the information that OnDraw Needs. I will add the following functions to CAddressBookDoc

int GetSize(); / Returns the size of the AddressBook
CString GetNameAt(int i); / Returns the name for the iths entry
CString GetAddressFor(CString name); / Returns the address for name
CString GetPhoneFor(CSring name) / Returns the phone for name

.

Step 6. Updating the View

6.1.Add the following functions to CaddressBookDoc

CString CAddressBookDoc::getNameAt(int indx)

{

if ((indx >=0) & (indx <= size))

return storage[indx].name;

else

return "";

}

int CAddressBookDoc::getSize()

{

return size;

}

CString CAddressBookDoc::getAddressFor(CString name)

{

for (int i=0; i <=size; ++i)

if (storage[i].name == name)

return storage[i].address;

return "";

}

CString CAddressBookDoc::getPhoneFor(CString name)

{

for (int i=0; i <=size; ++i)

if (storage[i].name == name)

return storage[i].phone;

return "";

}

6.2.Replace OnDraw function with:

void CAddressBookView::OnDraw(CDC* pDC)

{

CAddressBookDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

TEXTMETRIC tm;

CString name;

pDC->GetTextMetrics(&tm);

for (int i=0; i<=pDoc->getSize(); i++){

name=pDoc->getNameAt(i);

pDC->SetTextColor(RGB(255,0,0));

pDC->TextOut(10, i*tm.tmHeight, name);

pDC->SetTextColor(RGB(0,255,0));

pDC->TextOut(200, i*tm.tmHeight, pDoc->getAddressFor(name));

pDC->SetTextColor(RGB(0,0,255));

pDC->TextOut(400, i*tm.tmHeight, pDoc->getPhoneFor(name));

}

}

6.3.Build and execute AddressBook.exe. You will observe that the view is not updated after add. The reason is that the view is never informed that it needs to be redrawn.

6.4.Add the line

UpdateAllViews(NULL);

6.5.to the functionOnAddressbookAdd(). UpdateAllViews must execute if the AddressBook is changed.

6.6.Build and execute AddressBook.exe.

Step 7. Add Tool Bars

Step 8. Add Prompts and Tool Tips

Step 9. Add Accelerators

Step 10. Serialization

Step 11. Preparing the Delete Dialog.

Step 12. Invoking the Delete Dialog when Delete menu item is chosen


1