In the September 1994 issue of Borland C++ Developer's Journal, we introduced you to the Model-View-Controller (MVC) design strategy ("Implementing a Model-View-Design in C++"). In that article, we demonstrated the benefits of isolating your user-interface code from the code that performs the core functions of your application. Then, in the October 1994 issue, we showed how you could transfer an MVC design from a DOS or TurboVision application to an ObjectWindows Library (OWL) 2.0 application ("Moving a Simple MVC Design to OWL 2.0").
However, OWL 2.0 includes several other classes that directly
support an MVC design style for Windows applications. Collectively,
Borland refers to these classes as the Document/Viewor
Doc/Viewarchitecture. In this article, we'll
show how you can use the Doc/View architecture to implement sophisticated
MVC designs.
To gain a better understanding of how you can begin using the OWL Doc/View architecture, let's review the steps you'll take whenever you create a Doc/View application. Then we'll put the technique into practice by rewriting the MVC application from the October issue as a Doc/View application.
To create a simple Doc/View application, you'll need to
do the following:
1) Derive a new document class from TDocument or TFileDocument and override the appropriate virtual functions.
2) Derive one or more new view classes from TView or another TView-derived class. (Make sure you provide new versions of the StaticName() and GetViewName() functions.)
3) Define a new Doc/View template class for your document class and view class by using the macro DEFINE_DOC_TEMPLATE_CLASS.
4) Create an instance of the new Doc/View template class. (This is very important.)
5) Initialize the document manager.
6) Define response functions and response
table entries for your application class to respond to the document/view
creation and destruction event.
Obviously, a complex application will require more than this.
However, these are the core activities you'll perform for
most Doc/View applications.
As we did in the October article, let's use the Automobile model class. For your convenience, we've reproduced the file that declares this classAUTOMOBI.Hin Listing A. (Here, we've eliminated the CarViewController class that appeared in the previous articles. If you've already entered this code, there's no need to delete this class now. It won't affect the performance of the present application.)
Listing A: AUTOMOBI.H
class Automobile { char make[20]; char model[20]; int year; public: Automobile(char* ma, char* mo, int ye) { strcpy(make, ma); strcpy(model, mo); year = ye; } char* getMake() { return make; } char* getModel() { return model; } int getYear() { return year; } };
To begin, launch the Borland C++ 4.0 Integrated Development Environment (IDE). Choose New Project... from the Project menu. In the New Project dialog box, enter \CARS4\CARS4.IDE in the Project Path And Name entry field, select Application [.exe] from the Target Type list box, select Windows 3.x (16) from the Platform box, and then select the OWL check box in the Standard Libraries section. Click OK to create the new project.
If necessary, choose New from the File menu and enter the code from Listing A. (If you still have the file from the previous articles, you can just copy the file to the \CARS4 directory instead.) When you finish, choose Save from the File menu, enter \CARS\AUTOMOBI.H in the Save File As dialog box, and then click OK.
Next, double-click on the name CARS4 [.CPP]. When the editing window for this file appears, enter the code from Listing B.
When you finish entering this code, choose Save from the File menu. Then, choose New from the File menu and enter the resource code from Listing C in the editing window that appears.
Listing C: CARS4.RC
#include <\bc4\source\owl\owl.rc> 1 MENU { POPUP "Cars" { MENUITEM "New Doc", CM_FILENEW MENUITEM SEPARATOR MENUITEM "New View", CM_VIEWCREATE MENUITEM SEPARATOR MENUITEM "Close Doc", CM_FILECLOSE } }
When you finish entering the resource file code, choose Save from the File menu and then enter CARS4.RC in the Save File As dialog box. Click OK to save the file.
You'll notice that we added a #include directive for the OWL.RC file to this resource file. We did this in order to allow a TDocManager object to handle these menu commands. (We'll provide more information on this later.)
To allow the compiler to use the default module definition file
that Borland supplies, right-click on the name CARS4 [.DEF]
in the Project window and choose Edit Node Attributes...
from the pop-up menu that appears. In the Node Attributes dialog
box, enter \BC4\LIB\DEFAULT in the Name entry field and
click OK.
Before we compile the application, let's take a closer look at the code in the main source file, CARS4.CPP. In the earlier versions of this application (the DOS, TurboVision, and simple OWL 2.0 versions), we placed the control over the model-view interaction in the controller class called CarViewController. However, we placed much of the user-interface code (displaying and responding to menus, for example) in the application class.
In CARS4.CPP, you'll notice that we don't include any message response functions for menu commands in the TCarIVApp class (or in any of the other classes, for that matter). For this simple application, we're taking advantage of the TDocManager class's ability to respond to some of the common document-oriented menu commands.
Since the TDocManager object will provide the menu portion
of the user interface, you may be wondering how the application
will execute any of the code we've created. In fact, when
the TDocManager object responds to a given menu command,
it begins sending messages to your application object and calling
various member functions in your TDocument-derived or
TView-derived classes. (To see how this works for the
CM_FILENEW command, which the TDocManager object
uses to create a new document, see " Understanding How Doc/View
Applications Create Views, ".)
In the CARS4.CPP file, we begin by declaring a new document class TGarageDoc, which we derive publicly from the TDocument class. The TGarageDoc class includes a constructor, a destructor, six utility functions (GetCarModel(), GetCarMake(), GetDocName(), DisplayCarInfo(), GetCurrent(), and SetCurrent()), and an overridden version of the IsOpen() virtual function from the TDocument class.
We'll occasionally call the six TGarageDoc utility
functions from the view classes to directly modify or retrieve
document data. The TDocManager object will call the IsOpen()
function to determine whether to enable the CM_FILECLOSE
command. (We return TRUE in this application to enable
the command whenever a TGarageDoc object exists.)
Next, we declare a new view class called TModelView, which we derive publicly from both the TView class and the TListBox class. (For more information on why we chose to create the TModelView class this way, see "Designing View Classes,".) The TModelView class includes a constructor, a destructor, a private initialization function (GetCarData()), overridden versions of several functions from the TView and TListBox parent classes, four message response functions, and the appropriate response table macros.
As we mentioned earlier, you must provide a new version of the StaticName() function and override the GetViewName() function in the TModelView class. In addition, we define an overridden version of the SetDocTitle() virtual function to customize the window titles and an overridden version of the Create() virtual function to initialize the items in the list box. Finally, the TModelView class defines two message response functions for responding to mouse events in the view itselfCmSelChange() and CmDoubleClick()and two response functions to respond to view notifications from the TDocManager objectVnIsWindow() and VnCommit().
To provide a different view of the TGarageDoc data, we
then declare another view classTBrandView, a
simple derivative of the TModelView class. The TBrandView
class provides its own versions of the functions StaticName()
and GetViewName() and a new version of the private initialization
function GetCarData(). This private function will initialize
the list box items with custom data.
At the end of CARS4.CPP, we declare a new application class TCarsIVApp. This class sets up the TDocManager object, performs a simple style of Multiple-Document Interface (MDI) initialization, and provides two functions for responding to view messages from the TDocManager objectEvViewCars() and EvClose()and the appropriate response table macros.
Since this is an MDI application, the EvClose() function does nothing except prevent the TApplication class from responding to this message. The EvViewCars() function creates a new TMDIChild object to contain a new view object (via the view.GetWindow() function call). See Understanding How Doc/View Applications Create Views " for a more thorough explanation.
Before we build the application, consider how the Doc/View classes and your classes fit into the MVC design style we described previously. Table A illustrates how the various classes from our previous articles correspond to the classes we've defined in CARS4.CPP.
Table A - The Doc/View classes and the classes we've defined in CARS4.CPP perform the functions of other classes from previous versions.
App | Model | Controller | View |
CARS2 | Automobile | CarViewController | Tdialog (TV) TListBox (OWL) |
CARS3 | Automobile | CarViewController | TDialog (OWL) TListBox (OWL) |
CARS4 | Automobile | TGarageDoc TDocManager TDocTemplate |
TView (OWL) TListBox (OWL) TModelView TBrandView |
Looking at Table A, you can see that the model class, Automobile,
hasn't changed for any version of the application. Also
notice that when you move your applications to the Doc/View architecture,
the TDocManager and TDocTemplate<> classes
will perform many of the duties you'd otherwise include
in a controller class.
Now, to build and run CARS4.EXE, choose Run from the Debug menu. When the compiler finishes building the application, its main window will appear.
Open a new document by choosing New Doc from the Cars menu. When you choose this item, the application will display a pop-up menu that lists the different views you can use to edit the new document, as shown in Figure A.
Figure A - You'll use the Document Type pop-up menu to select the initial view of a new document.
From the pop-up menu, choose Model View Of Doc. Now, you'll see an MDI child window appear with a list of the model names of the three cars in the document. To open an additional view of this document, choose New View from the Cars menu.
This time, the application will display a pop-up menu that lists the new views you can create for the current document, as shown in Figure B. (The TDocManager object uses the text from the StaticName() functions you've defined for your view classes when it displays the View Type pop-up menu. The TView::GetProperty() function calls the GetViewName() function to retrieve the name of a view, so you can return different information than the StaticName() function returns.)
Figure B - You'll use the View Type pop-up menu to create new views of an existing document.
From the pop-up menu, choose Brand View. Now, you'll see a new MDI child window appear with a list of the brand names of the three cars in the document. You can also create multiple views of the same type for a given document.
To create another view, choose New View from the Cars menu again,
then choose Model View from the View Type pop-up menu. When the
new view window appears, you'll see three views of the
current document, as shown in Figure C.
Figure C - You can use the CARS4.EXE application to display multiple views for a given TGarageDoc object.
To see how the Doc/View architecture allows you to coordinate activity between multiple open views and their associated document, press the [up arrow] or [down arrow] key to change the current item in the top view. If you watch the other two views, you'll notice that the current item in those windows changes as you change the top window. Let's take a closer look at how this happens.
Each time that you change the current item in the top view, Windows sends a notification messageLBN_SELCHANGEto the application. In the TModelView and TBrandView classes, we defined the message response function CmSelChange() to respond to this message by calling the SetCurrent() function of its associated TGarageDoc object.
When the document object executes this command, it changes the item index. Then, the document object tells all the open views about the change by sending a vnCommit view notification message using the NotifyViews() function. In the view classes, we defined a message response functionVnCommit()to allow each view to respond to the notification by updating the view's selection.
Now, double-click on one of the items in the top view. The application will display detailed information about that item, as shown in Figure D.
Figure D - When you double-click on an item in one of the views, you'll see detailed information about that car.
When you double-click in the window, Windows sends an LBN_DBLCLK
message to the application. In the view classes, we defined the
message response function CmDoubleClick() to respond
to this message by calling the DisplayCarInfo() function
of its associated TGarageDoc object.
Now, open a second document by choosing New Doc from the Cars menu and then choosing Brand View Of Doc from the Document Type pop-up menu. When the new document's window appears, choose New View from the Cars menu, then choose Model View from the View Type pop-up menu.
At this point, the CARS4.EXE application displays two documents via five MDI windows. There are three windows for the first document and two views for the second document, as illustrated in Table B. (Fortunately, the TDocManager object maintains all the necessary associations between the different documents.)
Table B - The TDocManager object maintains the necessary associations between documents, templates, and views.
Documents | Templates | Views |
Car Document #1 | modelTemp brandTemp |
Car Document #1:1 Car Document #1:3 Car Document #1:2 |
Car Document #2 | modelTemp brandTemp |
Car Document #2:1 Car Document #2:2 |
To close a specific document, you can click on one of its view windows and then choose Close Doc from the Cars menu, or you can close each of the view windows individually. When you close the last open view for a document, the TDocManager object closes the document as well.
To close the CARS4.EXE application, double-click on its System
menu icon. When the IDE reappears, choose Exit from the File menu
to quit.
Borland's Doc/View architecture is a complex but powerful
system used for creating MVC-style applications that isolate their
core code from the user interface code. (You'll notice
that the view classes don't directly call any of the member
functions of the Automobile class.) By learning to use
the Doc/View architecture that's part of OWL 2.0, you'll
be able to take advantage of Borland's MVC design expertise
in your own applications. In future issues, we'll show
how you can take advantage of other elements of the Doc/View architecture.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.