Document-View Architecture

The “document” holds the data. The “view” presents the data to the user. There may be more than one way to view the same data.

The document-view architecture refers to separating the document and each (kind of ) view into different parts of the program.

Examples: In Word your document contains the text, and the formatting information. You have normal view, outline view, page layout view, online layout view, and print-preview view.

In a spreadsheet, you can view your data as a matrix of numbers and words, or as a pie chart, or as a bar chart, etc.

Some programs can only open one document at a time. These are called “SDI” (single document interface). For example, Notepad. You can open two copies of Notepad, but you cannot open two documents in one copy of Notepad.

Other programs can open several documents at once. These are called “MDI” (multiple document interface). For example, Word.

If the program offers different views, you can open several views of each document. Thus you might have two views each of two documents, using four windows.

Let's look at a simple example. Remember the Weather program? It had one string for data which was either Spring, Summer, Fall, or Winter. All it did was display that string. There was a Weather menu that allowed the user to change the data.

Now, we remake that program, but this time as MDI, rather than SDI.

Here it is with one document open:


Now, we can open a new document by choosing File | New,

or a new view of the same document by choosing Window | New.

Here it is with a new view of the first document open:


The title bars of the document windows number the views after the colon and the documents before the colon.

After opening two views each of two documents, and choosing Window | Tile, we see this:


Now there are two CDocument objects and four CView objects.

Now click in one of the windows, say the upper right one. That's view 2 of document 1. Use the menu to change the weather to Winter.

The menu item handler is this:

void CDocViewView::OnWeatherWinter()

{

CDocViewDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->m_weather = "Winter";

Invalidate();

}


Now, why didn't the display change in the lower right window too?

The document data should have changed to Winter.

You can check that it did change. Just resize the lower right window a little:


The problem is that the View object corresponding to the lower right window never got a WM_PAINT message until the window was resized, so it did not display the changed document data.

Next we will learn what should be done so that the views get updated properly when the data changes.

Updating all the views when the data changes

When the document data is changed, you should call UpdateAllViews, which is a member function of the document class. This will result in OnUpdate being called in each view class (but see below for an exception).

void CDocViewView::OnWeatherWinter()

{

CDocViewDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->m_weather = "Winter";

pDoc->UpdateAllViews(this);

Invalidate();

}

When the data is changed in a view class, normally that view will update itself as well as update the document data. It will then not be necessary for OnUpdate to be called as a result of UpdateAllViews. To allow for this, UpdateAllViews takes a parameter, which is set to either NULL or to a pointer to a view class. If it is NULL, OnUpdate will be called for all views. If it is a parameter to a view class object, OnUpdate will be called for all other views except that one. An example call would be GetDocument()->UpdateAllViews(this)

as shown in the code sample above.

Writing OnUpdate

Your OnUpdate function should make sure that the current data is correctly presented. In the present case, it's enough to have OnUpdate call Invalidate(). Actually, OnUpdate is a virtual function in the CView class, and it has a default implementation which just calls Invalidate, so you don't even have to write one for this example. You will find that the Weather example program runs as desired as soon as you call UpdateAllViews as shown above.

But still, in more complex examples you will have to write OnUpdate, so learn how: Use Class Wizard:


void CDocViewView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)

{

Invalidate();

}

Fine-tuning the update

The UpdateAllViews function can take additional parameters. These are LPARAM lHint, CObject *pHint. Note that LPARAM just means "long" . You can use these parameters to indicate that only part of the view needs updating, and tell which part. These parameters are passed through to each OnUpdate function.

CDocument::UpdateAllViews

voidUpdateAllViews(CView*pSender,LPARAMlHint=0L,CObject*pHint=NULL);

Parameters

pSender

Points to the view that modified the document, or NULL if all views are to be updated.

lHint

Contains information about the modification.

pHint

Points to an object storing information about the modification.

Remarks

Call this function after the document has been modified. You should call this function after you call the SetModifiedFlag member function. This function informs each view attached to the document, except for the view specified by pSender, that the document has been modified. You typically call this function from your view class after the user has changed the document through a view.

This function calls the CView::OnUpdate member function for each of the document’s views except the sending view, passing pHint and lHint. Use these parameters to pass information to the views about the modifications made to the document. You can encode information using lHint and/or you can define a CObject-derived class to store information about the modifications and pass an object of that class using pHint. Override the CView::OnUpdate member function in your CView-derived class to optimize the updating of the view’s display based on the information passed.

Did you notice that part above about SetModifiedFlag? Here's the manual entry for this function:

CDocument::SetModifiedFlag

voidSetModifiedFlag(BOOLbModified=TRUE);

Parameters

bModified

Flag indicating whether the document has been modified.

Remarks

Call this function after you have made any modifications to the document. By calling this function consistently, you ensure that the framework prompts the user to save changes before closing a document. Typically you should use the default value of TRUE for the bModified parameter. To mark a document as clean (unmodified), call this function with a value of FALSE.

void CDocViewView::OnWeatherWinter()

{

CDocViewDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->m_weather = "Winter";

pDoc->SetModifiedFlag(); // we should put this in!

pDoc->UpdateAllViews(this);

Invalidate();

}

You can checkthat this works: Open a document, change the weather, and close the document. It will prompt you whether you want to save your changes. Save the document in a file and try again to close it. This time you are not prompted. (Of course, the document wasn't really saved as we put in no code to do so.)

There is also IsModified() which you can use to check this flag. You could, for example, gray out the Save button on the toolbar when the document is "clean" (unmodified).

CView::OnUpdate

virtualvoidOnUpdate(CView*pSender,LPARAMlHint,CObject*pHint);

Parameters

pSenderPoints to the view that modified the document, or NULL if all views are to be updated.

lHintContains information about the modifications.

pHintPoints to an object storing information about the modifications.

Remarks

Called by the framework after the view’s document has been modified; this function is called by CDocument::UpdateAllViews and allows the view to update its display to reflect those modifications. It is also called by the default implementation of OnInitialUpdate. The default implementation invalidates the entire client area, marking it for painting when the next WM_PAINT message is received. Override this function if you want to update only those regions that map to the modified portions of the document. To do this you must pass information about the modifications using the hint parameters.

To use lHint, define special hint values, typically a bitmask or an enumerated type, and have the document pass one of these values. To use pHint, derive a hint class from CObject and have the document pass a pointer to a hint object; when overriding OnUpdate, use the CObject::IsKindOf member function to determine the run-time type of the hint object.

Typically you should not perform any drawing directly from OnUpdate. Instead, determine the rectangle describing, in device coordinates, the area that requires updating; pass this rectangle to CWnd::InvalidateRect. This causes painting to occur the next time a WM_PAINT message is received.

If lHint is 0 and pHint is NULL, the document has sent a generic update notification. If a view receives a generic updatenotification, or if it cannot decode the hints, it should invalidate its entire client area.

Creating and Closing Documents

  1. OnNewDocument, a member function of the document class, is called when, intuitively, a new document is created. But in SDI applications, the document object is re-used when a document is closed and a "new" one opened. That is, OnCreate is called only ONCE, when the application is opened. For example, when you choose File|New in Notepad, your old document is not really destroyed, and a new one is not really created. Instead, OnNewDocument is called, and Notepad initializes the document data to that required for a "fresh" document, e.g. an empty string for the text to be edited.
  2. Specifically, OnNewDocument is called when the application starts, and when File|New is chosen.
  3. In the view class, OnInitialUpdate is called when the application starts, when File|New is chosen, and when File|Open is chosen. That is, just after OnNewDocument is called (in the first two cases), and when File|Open is called. Note that in case of File|Open there is no "new" document data, but the view still needs updating to reflect the data of the just-opened document.
  4. DeleteContents is a member function of the document class. Your overridden DeleteContents is called when the document is closed. That would, in an SDI application, automatically happen when File|New is selected, as well as when the document is explicitly closed.