Version 2.0 of our application framework replaces the Publisher-Subscriber pattern with the View Handler pattern. Models don't need to be publishers anymore. Instead, each model maintains a reference to the view handler. When the model changes state, it calls the view handler's notifyAllViews() function, which calls the draw() function of each open view.
Both the active controller and the model need pointers to the view handler. The model needs to know where the view handler is so that it can notify open views of changes in the application data. The active controller needs to know where the view handler is so that it can delegate view commands:
One of the features of AFW 2.0 is that views are created dynamically. The user types the "view" command followed by the type of view to be created:
-> view ViewTYPE
OID = 506
done
The controller forwards the command to the view handler, which creates the view, then returns the assigned OID to the controller. The controller displays the OID to the user for future reference. Of course dynamic instantiation requires the prototype pattern (see Chapter 5). In AFW 2.0 the responsibilities of the prototype pattern's Product base class are divided between views (the products) and the view handler (the factory).
View constructors are protected. This prevents unauthorized creation of views (only the view handler can create a view). Views are also equipped with a unique object identifier (OID), which will subsequently be used by clients to refer to them. As required by the prototype pattern, prototypes of View-derived class must know how to clone themselves, so we add a pure virtual clone() function to the View base class. We must also be able to query a prototype about its type, so we add a getType() function that uses RTTI in its implementation (see Chapter 5). Notice that in AFW 2.0 the View class no longer derives from the Subscriber class:
class View: public UIComponent
{
public:
virtual ~View() {}
virtual View* clone() const = 0;
void setModel(Model* m) { theModel = m;
}
string getType() const;
ViewOID getOID() const { return oid; }
void setOID(ViewOID h) {oid = h; }
protected:
Model* theModel;
ViewOID oid;
View(Model* m = 0, ViewOID h = -1);
};
View object identifiers (View OID's) are integers:
typedef int ViewOID;
The view handler is a singleton (see Chapter 3) that maintains a table (i.e., map) of all open views. A factory method called openView() is used to create new views, closeView() destroys an existing view, and notifyAllViews() forces each open view to draw itself:
class ViewHandler
{
public:
static ViewHandler* makeViewHandler()
{
if (!theViewHandler)
theViewHandler = new
ViewHandler();
return theViewHandler;
}
typedef map<string, View*>
ProtoTable;
static View* addPrototype(View* p);
void notifyAllViews(GC& gc =
theGC);
ViewOID openView(string type, Model* m
= 0);
void closeView(ViewOID h);
// etc.
private:
map<ViewOID, View*> openViews;
ViewHandler() {}
ViewHandler(const ViewHandler& vh)
{}
~ViewHandler() {}
static ProtoTable protoTable;
static ViewOID nextOID; // OID
generator
static ViewHandler* theViewHandler; //
the singleton
};
We can't forget to define the view handler's static members (this would normally be done in afw2.cpp):
ViewHandler* ViewHandler::theViewHandler = 0;
ViewHandler::ProtoTable ViewHandler::protoTable;
ViewOID ViewHandler::nextOID = 500;
The view notifier uses an iterator to traverse the table of open views, passing a graphical context to each view's draw() function:
void ViewHandler::notifyAllViews(GC& gc)
{
map<ViewOID, View*>::iterator p;
for(p = openViews.begin(); p !=
openViews.end(); p++)
((*p).second)->draw(gc);
}
The openView() function is the view handler's factory method. Given the type of view to be opened (a string), it searches the prototype table, generates a new OID, clones the prototype, adds the clone to the table of open views, then sets the view's model and OID:
ViewOID ViewHandler::openView(string type, Model* m)
{
View *proto, *v = 0;
if (!find(type, proto, protoTable))
throw AFWError("View type
unknown");
ViewOID oid = nextOID++;
v = proto->clone();
openViews[oid] = v;
v->setModel(m);
v->setOID(oid);
cout << "OID = "
<< oid << endl;
return oid;
}
The Model class in AFW 2.0 is almost identical to the Model class in AFW 1.0. We drop the Publisher base class and we add a pointer to the view handler:
class Model: public Persistent
{
public:
void setViewHandler(ViewHandler* vh) {
theViewHandler = vh; }
void notify()
{
if (theViewHandler)
theViewHandler->notifyAllViews();
}
// etc.
protected:
ViewHandler* theViewHandler;
// etc.
};
The ActiveController class declaration in AFW 2.0 is virtually identical to the corresponding declaration in AFW 1.0. We only need to add a pointer to the view handler:
class ActiveController: public Controller
{
ViewHandler* theViewHandler;
// etc.
};
The ActiveController constructor creates the singleton view handler:
ActiveController::ActiveController(Model* m)
: Controller(m)
{
theViewHandler =
ViewHandler::makeViewHandler();
if (theModel)
theModel->setViewHandler(theViewHandler);
}
We can now handle additional application-independent view commands in the active controller's control loop:
// view commands:
else if (msg == "tile") theViewHandler->tileViews();
else if (msg == "cascade") theViewHandler->cascadeViews();
else if (msg == "view")
{
cin >> arg; // read view type
theViewHandler->openView(arg,
theModel);
cout << "done\n";
}
else if (msg == "closeView")
{
cin >> oid; // read view OID
theViewHandler->closeView(oid);
cout << "done\n";
}
// etc.
Version 2.0 of Brick CAD, our CAD/CAM system for designing bricks, is almost identical to version 1.0, except it customizes AFW 2.0 instead of AFW 1.0.
Our Brick CAD test harness no longer needs to statically create views:
BCController* bc = new BCController(new Brick());
bc->controlLoop();
We start Brick CAD and modify the model. Notice the lack of feedback. This is because no views have been created:
-> setHeight 10
done
Next, we use the "view" command to create a couple of views:
-> view TopView
OID = 500
done
-> view SideView
OID = 501
done
Now, modifying the model causes the newly created views to draw themselves:
-> setHeight 12
*** TOP VIEW ***
width = 5 inches
length = 5 inches
*** SIDE VIEW ***
height = 12 inches
length = 5 inches
done
We can use the "closeView" command to delete a view. We need to supply the view's OID as an argument:
-> closeView 500
deleting view #500
done
We can prove the view is gone by modifying the model once again:
-> setHeight 15
*** SIDE VIEW ***
height = 15 inches
length = 5 inches
done
No changes need to be made from version 1.0 in the definitions of Brick and BCController:
class Brick: public Model { /* as before */ };
class BCController: public ActiveController { /* as before */ };
Only slight changes need to be made to the View-derived classes. In particular, we need to add the machinery required by the Prototype pattern: a clone() function and a prototype holder:
class FrontView: public View
{
public:
FrontView(Brick* b = 0): View(b) {}
void draw(GC& gc);
View* clone() const { return new
FrontView(*this); }
private:
static View* myPrototype;
};
Of course we must also create prototype views:
View* FrontView::myPrototype =
ViewHandler::addPrototype(new
FrontView());