In each of these articles, and included with this one, is an AppWizard created MFC application, Shapes.exe, illustrating the Framework integration. For its Model, Shapes reads an XML file that contains elements that define a series of graphical Rectangle and Ellipse objects. As you can see from the graphics at the beginning of the article, the Shapes application presents three different Views of the Model.

Design View - the main CView of the application which displays the shapes in their graphical form

Explorer View - a CTreeCtrl contained in an CDockingPane listing the shapes by name

Properties View - a CMFCPropertyGridCtrl contained in an CDockingPane listing the current shape's properties

I originally planned to discuss all three views in this article; however, when the article grew to over 50 printed pages and even I couldn't bear to read through it, I decided to split it into at least two, if not three separate articles.

In this article, I plan to go into detail describing the first of the views, the Design View, and how it is driven by Model change and how the Framework generalizes most of its functionality so that it can be leveraged with a minimum of additional coding at the application level.

The source code provided with this article is contained in a VS2008 solution, SbjDev, with three projects. The three projects are the same as were presented in the second article, with only a few minor bug fixes.

There is one more piece of the Model architecture that I waited until now to present because the Design and Explorer Views provide examples of its use. In the namespace SbjCore::Mvc::Model, you'll find two classes, Model::Action and Model::ActionHandler.

The Model::Action class provides a generic recursive traversal of the Model. During the traversal, if a ModelItem has an associated Model::ActionHandler class mapped to its Type, an instance of the handler will be created and called to apply whatever action it implements.

Because of the recursive nature of the traversal, the handler implements two methods which handle the ModelItem. The first, Model::ActionHandler::BeginHandling, is called when the ModelItem is first encountered. The second, Model::ActionHandler::EndHandling, is called after the ModelItem's children are processed. In this way, the Model::ActionHandler can maintain state while processing the child ModelItems. Although the Design View discussed in this article doesn't need to take advantage of this feature, the value will become clear in the next article when you see how the Explorer View handles insertions into its CTreeCtrl.

The typedef enum Results declares values which are returns from the Model::ActionHandler handling methods. The intent of kStop and kContinue should be self-evident. kReject can be returned to short circuit the traversal of a branch of the Model.

The Model::Action class is constructed for a given Model::Controller. Two optional parameters may be provided as well; the first, a HandleMap, and the second, a default CRuntimeClass pointer to be used for any ModelItemTypes that are not specifically mapped.

As stated earlier, the Model::Action class creates Model::ActionHandler objects as necessary during the traversal. The HandlerMap is the map of ModelItemTypes to the CRuntimeClass object of Model::ActionHandler derivatives. If a HandlerMap is provided without a default, ModelItemTypes without a mapped handler will be assigned a base Model::ActionHandler object which does nothing but return kContinue. If no HandleMap is provided, obviously a default must be provided, since the base Model::ActionHandler object does nothing. The SetDefaultHandler method can be used to switch defaults at any time during the traversal.

The Apply method is the actual traversal, and may be passed any ModelItemHANDLE as a starting point.

The Model::ActionHandler should be fairly self-explanatory. The Model::Action::Apply method calls the handler's Initialize method with itself as the Action parameter and the current ModelItemHANDLE. By providing access to the Action, the ActionHandler can retrieve any state information maintained by the Action while processing the ModelItemHANDLE. Derivatives override the two virtual methods, OnBeginHandling and OnEndHandling, to implement the desired action.

First of all, there is no actual DesignView class in the Framework; rather, there is a DesignView namespace. The application specific CView or derivative gains its functionality through the DesignView::Controller class and the associated components. The Shapes application implementation of the Design View is its ShapesView class which is derived from ControlledView.

Before showing you the ShapesView class and how it utilizes the Framework, I'll explain the workings of the DesignView components.

Class DesignView::Item

Through the components in the DesignView namespace, the view draws a graphical representation of application specific ModelItems. The graphical representation for a ModelItem is defined in the Framework by the DesignView::Item class. An application will define an Item derivative for each ModelItemType that is to be displayed. The only thing the application specific Item class is required to provide is an implementation for its OnDraw method (the default does nothing).

Each DesignView::Item has an associated RectTracker object (derived from the MFC CRectTracker class of which I assume you are familiar) which defines the rectangle in the ControlledViewDevice Context where Item drawing should be restricted. The RectTracker object itself is only displayed when its associated Item is selected. This is illustrated in the graphics of the Shapes application at the beginning of this article where the shape named "Second Rectangle" is selected.

Like the CRectTracker, it interacts with the user to provide Item selection, movement, and sizing capabilities. Currently, the RectTracker is tied to its Item object's associated ModelItemHANDLE, and it updates the Model directly with the user's changes. The ability to select, move, and resize the associated Item can be turned off in a derived Item by overriding the OnIsTrackable virtual method and returning false. If an application specific Item requires a different implementation of the RectTracker class, this can be provided by overriding the OnGetRectTracker virtual method. An example of this might be if an application specific Item was allowed to move but not resize.

Note: It occurs to me while writing this article that it might be better to decouple the RectTracker class from the MVC Framework altogether and have it communicate the user's changes through Event firings. You may see this change in a future revision of the Framework.

Class DesignView::Action and Class DesignView::DrawAction

In addition to the DesignView::Controller responsibility of maintaining the DesignView::Item state in response to Model changes, its main functions are to create the Item objects in response to the EVID_FILE_OPEN event, and to ask them to draw themselves in response to the WM_PAINT message sent to its ControlledView. Although the Controller supplies the routine that creates the Item objects, and Item objects are responsible for drawing themselves, it is two Controller created Model::Action derivatives that provide the transversal of the Model, and their associated ModelItem specific Model:ActionHandler classes that apply the actions to each Item.

The first class, DesignView::Action, is used as the action for the insertion process, and only adds an accessor to the assigned DesignView::Controller to the base Model::Action class. DesignView::Action also provides the base for DesignView::DrawAction, which additionally provides an accessor to the ControlledViewDevice Context.

As you can see in the InsertActionHandler::OnBeginHandling method, the handler retrieves the DesignView::Controller from the Action along with the Model::Controller and the HANDLE to the current ModelItem. Using these, it calls the DesignView::Controller method CreateItem.

In the case of the DesignView::DrawActionHandler, the handler retrieves the DesignView::Controller from the Action along with the HANDLE to the current ModelItem and the Device Context for the ControlledView. Using these, it calls the DesignView::Controller method GetItem and then calls DesignView::Item::Draw.

The DesignView::Controller maintains a map to associate the DesignView::Item class CRuntimeClass pointers to ModelItemType names that the application maps through a call to MapItemToModelTypeName. As explained in the previous section, the Controller uses this map to create the appropriate Item object for the ModelItems while traversing the Model during the insertion action. These Item objects are then kept in a map of ModelItemHANDLE to DesignView::Item pointer. The Controller uses this map to access the associated Item objects while traversing the Model during the draw action.

InsertDesign is the actual method that creates the Model::Action responsible for the insertion. LoadDesign adds the extra step of deleting all the previous DesignView::Item objects before calling InsertDesign. This way, the same InsertDesign method can be used whether inserting the initial Model or when adding new ModelItems to an existing one.

Other than the methods you would expect for the maintenance of the DesignView::Item objects, there is also an instance of MultiRectTracker. If you've ever created a Dialog with the AppStudio, I'm sure you're familiar with how multiselection works. The MultiRectTracker handles the coordination of selected RectTracker objects, and provides notification of changes in selection through its EVID_SELCOUNT_CHANGED event. Through the handling of this event, the Controller can update the Model's set of currently selected ModelItems.

Struct ControllerImpl

As with most classes in the Framework, DesignView::Controller delegates most of its implementation to its private member struct ControllerImpl* const m_pImpl.

In addition to containing the MultiRectTracker, the map collections, and the Item maintenance methods, ControllerImpl also contains the EventHandler and MsgHandler classes necessary to communicate respectively with the Model and the User. Rather than go into detail and show the source for each of these, I'll give a brief explanation of what each does and leave it to you to examine the source in detail if you so desire.

Event Handlers

SbjCore::Mvc::Doc::Events

localNS::FileOpenEventHandler - calls DesignView::Controller::LoadDesign to load the design indicated by the root HANDLE passed by the FileOpen event

localNS::DocModifiedEventHandler - calls CView::Invalidate in response to the DocModified event

SbjCore::Mvc::Model::Events

localNS::ItemInsertEventHandler - inserts the ModelItem indicated by the HANDLE passed by the ItemInsert event

localNS::ItemChangeEventHandler - updates the ModelItemRectTracker indicated by the HANDLE passed by the ItemChange event

localNS::ItemRemoveEventHandler - removes the ModelItem indicated by the HANDLE passed by the ItemRemove event

Only three changes have been made; ShapesView is now derived from ControlledView; the OnRButtonUp and the OnContextMenu methods have been removed, and the class has been outfitted with a Pimplstruct ShapesViewImpl* const m_pImpl member.

Next, I'll show the changes to ShapesView.cpp. Since even the AppWizard generated file is rather large, I'll limit the display to those areas that have been modified.

In the local namespace, you see four DesignView::Item derivatives. First, a DrawingItem class which acts as the required root of the design, and does nothing but override OnIsTrackable to prevent it from being selectable. Since it has no OnDraw override, it is never displayed either. The other three consist of a base ShapeItem class and two specific shapes, RectangleItem and EllipseItem. As was stated in the discussion on DesignView::Item, the only thing necessary of these derivatives is to provide an implementation of OnDraw.

Next is the ShapesViewImpl implementation of the DesignView::Controller. It has two functions: first it instantiates the DesignView::Item derivatives and maps their CRuntimeClass pointers to the appropriate ModelItem names, and second, it provides the OnPrepareCtxMenu override to add commands for creating new shapes.

Other than creating its Pimpl, the ShapesView class itself does nothing except call the ShapesViewImpl controller's base class method Draw from its OnDraw method.

This article presented details regarding the MVC Framework Design View implementation and how an application can leverage its functionality by providing classes that fulfill only Model specific requirements. The Shapes application is simple by design; however, I think you can see that the Design View could support extremely complex Models through the techniques illustrated here. Future articles will cover the Explorer and Properties View where you will see that the MVC Framework again provides the same level of functional support, limiting the application to supplying only those details relevant to its specific Model domain. And, as I've said in the last two articles, all the code is included in the attachments accompanying this article, so run the Shapes application, explore the code, and please offer any feedback you'd like to contribute.

General Notes on the Source

All components of the MVC Framework belong to the namespace SbjCore::Mvc.

I have tried to follow the rule of either making virtual methods private, or in the case where derivatives must call base implementations, protected, that are only accessed through a call to a public method declared in the base class.

In the case of the ControlledCmdTargetT and ControlledWndT classes, the Pimpl often doubles as the Controller of the class.

Most .cpp files contain a local namespace called localNS; however, localNS is actually a macro defined in Platform.h, and resolves to an empty string. It is used purely as a documentation tool somewhat like MFC's afxmsg macro for indicating methods that act as message handlers.

Share

About the Author

Real name is Steve Johnson. Programming since 1979. Started on a Heathkit Micro with a DEC LSI-11 and UCSD Pascal. Moved to PCs & DOS as soon as Turbo Pascal became available. Did some Assembly, ISR, TSR etc. All this while working for a Manufacturing Co. for 8 years. Had my own solo Co. doing barcode labeling software for 4 years (terrible business man, all I wanted to do was code). Since then working for various software companies. Moved to Windows around the time of 3.1 with Borland C then C++. Then on to VC++ and MFC, and just about anything I could get my hands on or had to learn for my job, and been at it ever since. Of course recently I've been playing with .NET, ASP, C#, WPF etc.

Comments and Discussions

But these series of articles is a little obscure to understand. After finished reading all of them, i can't understand all your intention of wrapping Document/View architecture to MVC by this way. I think this is one of the reasons having less reply and argument. Sorry for my directness.

You may be right about that. I have had friends comment privately along the same lines. When I take it up again in the near future I may shift gears and talk more about the Shapes application. I think if you reviewed the Shapes application code directly instead of trying to understand the Framework you'd more readily see the benefits. This was my fault in thinking that the interesting aspects were the architecture of the Framework rather than the benefits of using the Framework to minimize the code necessary to build applications.

As far as your question as to why I would wrap the Doc/View architecture, the answer lies in why anyone would embrace MVC or its derivatives; separation of comcerns. Here is a link which you might find helpful...