5.7 Document-View


The Document-View pattern is also known as the Document-View architecture . In this section we'll discuss a refined version of the Document-View pattern that allows for an event notification mechanism. This refined version is sometimes called the Observable-Observer pattern, or the Publisher-Subscriber pattern.

The problem arises when you have a number of different representations of the same data. How do we keep the representations in synch with each other?

The solution , as shown in Figure 5.9, is to have one Document (or Publisher ) class that holds the core data, and to have a range of View (or Subscriber ) classes that display the data. To smooth out our exposition, we'll write 'document' or 'view' to mean, respectively, a Document object or a View object.

Figure 5.9. The Document-View pattern

graphics/05fig09.gif

The document needs to be able to add and remove views from its active list. And it needs to have a UpdateAllViews method that tells all of the views that some of the data may have changed.

A view needs to have a getDoc() method that returns a pointer to the owner document. When the document calls UpdateAllViews , each view executes an OnUpdate method which checks the data in the document and updates the state of the view accordingly .

Another aspect of the Document-View architecture is that the view not only needs to be able to access the data in the document, it should also be able to mutate the data in the document. Part of the implementation of the document mutators must be that when document data is changed, the document calls UpdateAllViews so that all of the active views will show the new data. This acts as a roundabout way for the views to communicate with each other. We can represent this by a UML sequence diagram as shown in Figure 5.10.

Figure 5.10. Sequence diagram of a view editing a document

graphics/05fig10.gif

As this is our first sequence diagram, we need to mention that UML sequence diagrams are used to show how the objects in a program interact over time. The diagram is set up as a series of columns, with one column for each object. Each column has a vertical lifeline showing the lifetime of the object. Arrows are drawn from lifeline to lifeline to symbolize the passing of messages via method calls. We normally label a message with the name of a method being called, and this method is expected to be a member method of the class column that the message points to. Use a dotted arrow line when the caller object is not shown. A call that an object makes to itself is drawn as an arrow that starts and ends on the object's own lifeline. The labels at the heads of the columns can either be class names or, if you want to distinguish among multiple instances of a class, you can use object names which are written in the format Class:Object1.

In this diagram, we see how the View1 object can edit the Doc data and mutate it. The document mutator uses an UpdateAllViews call to 'publish' the changed data out to both View1 and View2 . Presumably there has been a call to Document::updateAllViews before the start of this sequence diagram, so that View1 is in fact looking at a current view of the Doc before beginning to edit it.

Documents and views in Windows programs

Most Windows programs use variations of the Document-View pattern to simultaneously show multiple views of multiple documents. Putting this a bit differently, the Document-View architecture allows you to have different views of the same set of data, and it also allows you to display different views of different sets of data.

In a Windows Document-View architecture program you can get new views in two ways. (a) You can use a command with a name like File New or File Open to open an additional document, or (b) you can use a command with a name like Window New to open an additional view of the currently active document. In case (a) what you have is a completely new set of data inside the new window, while in case (b) you get a different view of the data of one of the windows you already had open (the window which currently had the focus, i.e. the window that had the highlighted caption bar).

Each onscreen window view shows the data in an associated document. It is possible to have several view windows showing the same document, but it is not possible to have one view window showing more than one document.

One can imagine, say, a financial analysis program in which you might want to view the same numerical data both as a table and as a bar graph at the same time. These would be two different views of the same document, with the document being the set of numerical data. Or consider a computer game in which you want to show both a 3D rendered view of what virtual player sees, and a 2D overview map of the landscape. Here again, we'd have two views of the same document, where the document might have information like the game level, the positions of the players in the landscape, and the players' scores and strengths. In a word-processing program, the document is the text you're working on. If you use a splitter window, then the two subwindows represent two different views of the same document. In a paint or photo-retouching program, the document is the image you're working on, and you have a variety of possible views showing, for instance, different zoom levels, different layers of the image, and so on.

Having the different views show the same data is not something that you get for free. You have to write code to make it happen. It used to be fairly hard to write a Document-View architecture program, but now, thanks to MFC and the 'AppWizard' (the Visual Studio tool for creating projects), it's pretty easy. When you use the AppWizard to generate some starting code for a new project, its default choice is to use the Document-View architecture. The default program architecture chosen by the AppWizard is called MDI. This uses the Document-View architecture.

In Windows, Document-View architecture programs used to keep all of their views of all their documents inside a single-frame window. But now it's becoming the fashion to have a different frame window for each document. In the versions of Word starting with Word 2000, for instance, you'll find that the program pops up completely different frame windows when you open different documents. Some programs, such as Macromedia Dreamweaver, even pop up separate frame windows for each view, visually shattering the program into something like a sea of dialog boxes. But under the hood and on their menus , these are still Document-View architecture programs in which one executable manages a set of documents, each with its own set of views.

The app, the doc, and the view in MFC

Table 5.1 summarizes how MFC sets up the Document-View architecture for the Pop Framework.

As we discuss below, not all of your application's data is supposed to go into the CDocument . A flag saying whether or not you want to mute the speaker might go into your CWinApp . And a flag saying whether you want to show you graphical objects as solids or as wire- frames might go into your CView . It's only the data that relates to the description of what you're looking at that goes into the CDocument .

Table 5.1. The application, the document, and the view in MFC.

Colloquial name

Pop class

Inherits from

Code is in

Application

CPopApp

CWinApp

Pop.*

Document

CPopDoc

CDocument

PopDoc.*

View

CPopView

CView

PopView.*

This raises the point that you may want to be able to refer to one of the classes from inside one of the others. Suppose, for instance, that one of our CPopView methods wants to look at a _soundflag that lives inside CPopApp . And surely, our CPopView is going to need to find the data that's in the CPopDoc . If we're writing code inside a CPopApp , CPopDoc , or CPopView method, is there a way to talk about the other classes?

It turns out that in terms of navigating among the app, the docs, and the views there are four tasks that we normally care about.

  • First, we need (but not very often) a way for any doc or view to get a pointer to the app that owns them.

  • Second, we need a way for any view to get a pointer to its 'owner' doc.

  • Third, we need a way for a doc to tell all of its views to update themselves , and we prefer to do this without having to individually list the views.

  • Fourth, we need a way for a document to know which of its views, if any, is the active focus window of the user interface.

The first task is done by the global method ::AfxGetApp() , as in:

 CPopApp* papp = (CPopApp*)(::AfxGetApp());//cast CWinApp* 

The second task is accomplished by the CView::GetDocument() method, as in:

 CPopDoc* pdoc = GetDocument(); 

As it happens, you don't need the cast on GetDocument ; because GetDocument is redefined for each child of CView to include the cast. (To make this clear, you can look at the definition of CPopView::GetDocument() in popview.h .)

The third task is accomplished by the CDocument::UpdateAllViews method, which cascades a call to CView::OnUpdate down to each of the doc's views.

The full prototype of this third method is void UpdateAllViews( CView* pSender, LPARAM lHint = 0, CObject* pHint = NULL ) . The first argument isn't used very often. The second argument is used for a document to signal to its views if it is in some different-looking state, for instance if a game is over, the doc might put a number into lHint to tell the views to change color . The third argument is a catch-all where a doc can put pretty much anything it likes to pass to its views. That is, a doc can wrap some information up inside a class object and then pass the view a pointer to the class with the information.

As we mention in Chapter 23: Programming Windows with MFC in Part II, there actually is a way to individually step through a doc's views, but this is a technique that you should use only rarely. Using UpdateAllViews is a higher-level and cleaner way for your doc to pass information to its views.

The fourth task can be accomplished by a special CPopDoc::getActiveView() method that we wrote for our CPopDoc class; see the Levels of Windows section in Chapter 23 for details.

Documents and views in the Pop Framework

Using the Document-View architecture forces a programmer to think about where to declare his or her variables . In this subsection we talk about this issue in the Pop Framework.

Your program is normally going to have a number of variables that describe the data being displayed by the program as well as the current state of the program. These are the kinds of variables that you might once have made into global variables or into static variables living inside your main function. In MFC programming you will usually put these variables either into your CDocument class or into your CView class “ well, actually they go into your app's specific children of these classes (which are called CPopDoc and CPopView in our example program). Once in a while you might store a particular variable in the CWinApp class or in your CMainFrame class instead; an example of this kind of variable might be a global switch that specifies whether or not to pause your program when another program is in the foreground. But the overwhelming majority of your variables will live in your CDocument and your CView . But in which one?

The CDocument variables tend to be either the kinds of file variables that you might want to save, or the kinds of temporary helper objects that you might want to share among several views. And variables whose values are specific to an individual view go into the CView .

Let's say more about the Document-View distinction in terms of a computer game program such as we'll be writing in this book. In a computer game you'll often have an array of 'critters' moving around on top of a bitmap background. Each of your critters will have a position, a velocity, a health-index, and so on. (Normally one of the critters will represent you, the player, and its actions will be controlled by your input rather than by the program code. But it's still just a critter.) As the user plays the game, he or she will see the critters moving inside a window. It may be that if the user wants the game to run faster, he or she will have the choice of showing the critters in simple outline instead of in colorful detail. The variable controlling this choice would probably be stored at view level rather than at document level. Another example of a situation where there might be a view-specific variable might be the user's 'point of view'. This would be a factor if the critters' world is larger than the window, and the user can scroll the view this way and that. Or perhaps the world has a lot of detail and the user can zoom in or out. Or perhaps the game is three-dimensional, and the user will have the option of changing the angle of view.

Two views of a Pop Dambuilder game document

graphics/05icon01.gif

In our computer games , we'd expect the critters and the background bitmap information to live in the CDocument . But, as mentioned, the switch that determines whether or not to show the critters in detail would live in the CView , as would the variable (perhaps a real-valued rectangle or a matrix) that specifies the user's point of view. If you were to 'save' your game, you'd normally only want to save the states of the critters, that is the document data. But it could happen that you might want to save some information about the view as well, e.g. the current location of the player's point of view.

It isn't always easy to decide whether to put a given variable into the document or into the view, but as we go along in our example programs the process should become a little clearer. Very often there is no one ' absolutely right' way to program something. (But there are plenty of things that are absolutely wrong!) For a beginning programmer, the number of choices is daunting, and it's easy to feel paralyzed with indecision. Well, the only real way to learn is by doing, so go ahead and program, but keep an open mind and be willing to go back and rewrite what you did before. Another way of putting this is that learning programming is a matter of first making every possible mistake, so the faster you make your mistakes the faster you're learning!

Here's how the CPopDoc updates the game. First, the stepDoc method calls _pgame->step , where the cGame *_pgame reference member object holds the data of the game document. And second, the stepDoc uses UpdateAllViews to update the views to display the newly updated _pgame data.

 void CPopDoc::stepDoc(Real dt)  {      CPopView *pview = getActiveView();      _pgame->step(dt, pview); /* Move the critters for timestep dt.          Maybe add or delete some critters. Critters might use the          pview to sniff out the pixel colors near their current image          locations (rarely used). */      cTimeHint timehint(dt); //Wrap dt up so we can pass it to the views.      UpdateAllViews(NULL, 0, &timehint); /* Redraw all the views and          possibly animate their viewpoints with the dt inside          timehint. */  } 

As a sequence diagram, this looks like Figure 5.11. Since we only have one object of each class type in this picture, we just label the columns with the class names.

Figure 5.11. Sequence diagram of the CPopDocument::stepDoc cascade

graphics/05fig11.gif

Controlling multiple documents and views

How do you fit the Document-View pattern into your application if you want more than document? And how do you send messages to the various pieces of the program?

One solution is to have an App class that holds an array of Document objects, and to have App , Document , and View inherit from a base class Target . This is the architecture used by the MFC framework when you go for a MDI.

Pop showing two game documents

graphics/05icon02.gif

The idea is to use an App class in addition to a Document and a View class. In the MFC Application Framework, these classes are called CWinApp , CDocument , and CView .

What we have here is close to a pattern called 'model-view-controller.' In the model-view-controller pattern the 'model' plays the role of the Document , the 'view' is a View in the same sense we've already talked about, and the 'controller' is an abstraction of the user interface controls. Stretching things a bit, you might think of the controller as being the App , so that Document-View-App becomes a close analog of model-view-controller.

This analogy is, however, imperfect, because in the specific example of MFC we actually process user commands with any of the three Document-View-App components , which in MFC are the CDocument objects, the CView objects, and a CWinApp object. In an MFC program, all three of these classes can process messages and act like a 'controller'.

In any case, this is a good place to say a bit about how MFC processes user commands. In MFC there is a general base class called CCmdTarget . A CCmdTarget object is characterized as being an object that you can send Windows messages to. Put differently, a CCmdTarget is something that can process, say, menu item selection messages such as OnCommandGamePlaySounds . (As it so happens, in the Pop program this message was originally processed by the CPopDoc , and later we changed it so that the message is processed by the CPopApp .) Now we can send a message either to a document, to the application, or to a window, and Figure 5.12 expresses that notion. Just to make the diagram a little bigger, we've put in the three child classes CPopDoc , CPopApp , and CPopView as well.

Figure 5.12. Inheritance diagram in a multiple document pattern

graphics/05fig12.gif

This diagram shows inheritance; now let's talk about composition and navigation. As we've already said, the CWinApp class corresponds to the program as a whole, and the CDocument class corresponds to the files that the program currently has open. Although a CWinApp does not have a set of CDocument objects as explicit members , we certainly think of it as associating with the CDocument class. As for navigation, MFC happens to have a global ::AfxGetApp method that returns the current CWinApp , so we can navigate from CDocument to CWinApp . And, although it's not simple to describe, there is also an accessor-like process by which a CWinApp can walk through a list of its CDocument objects, so we can say that we can navigate from CWinApp to CDocument . (If you're curious about the details of Windows programs, see Chapter 23: Programming Windows with MFC). So we draw a composition line in one direction with a navigation line coming back the other way. A CWinApp can open as many views as we like (by using Window New ), but a CDocument has only one associated CWinApp. So we put a * by CDocument at the end of the composition line. This is shown in Figure 5.13.

Figure 5.13. App and documents

graphics/05fig13.gif

This is entirely analogous to how a Document relates to a View (Figure 5.14).

Figure 5.14. Document and views

graphics/05fig14.gif

For the sake of completeness, let's draw one more UML diagram (Figure 5.15) showing some of the relationships among our Pop Framework MFC classes.

Figure 5.15. Class diagram of the Pop Framework MFC classes

graphics/05fig15.gif

You might say that this UML diagram is primarily about the Windows operating system, and the UML diagram of the Pop Framework classes in the last chapter was primarily about the Pop program. It make sense that CPopDoc and CPopView appear in both diagrams, because these classes are designed by MFC to contain, respectively, the application's data and the application's appearance. The CPopView , your app's onscreen image, is where it makes contact with the Windows operating system.



Software Engineering and Computer Games
Software Engineering and Computer Games
ISBN: B00406LVDU
EAN: N/A
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net