For Those Of You Who Are Interested...
I have posted the following articles in the series describing the MVC Framework.
Table of Contents
In the Model-View-Controller architectural pattern, the Model represents the application data, the View, the visual components that present the data to the user, and the Controller manages the user's interactions with the various input devices and interprets how they should affect the Model. Once affected, the Model informs the View of the need to update its presentation to the user. That's a rather simplistic explanation; however, if you want a more complete explanation, there are many good articles and books explaining the MVC pattern and the motivations behind its design. One very good source can be found in the Wikipedia [^].
As anyone with MFC experience can tell you, its Document/View architecture is a variation of the MVC pattern. In MFC, the CDocument
class represents the Model, and the CView
class represents both the View and the Controller. Obviously, the Model-View-Controller Framework presented in this article will bring back the separation of View and Controller to the MFC application.
I have used aspects of the MVC Framework in existing MFC applications with years of legacy code as well as with new MFC Feature Pack AppWizard created applications. The point I make here is that the Framework does not disrupt the existing Doc/View architecture, and can be implemented unobtrusively over time. Old code can be incrementally refactored to use the new MVC architecture while still using the existing Doc/View architecture.
I don't consider the Framework to be complete by any means, as you will see in the TODO section at the bottom of the article; however, I have completed enough to illustrate the underlying concepts. This article introduces the Framework by presenting the classes that form the Framework base that integrates with the MFC Doc/View architecture.
The source code provided with this article is contained in a VS2008 solution, SbjDev, with three projects:
- SbjCore - The foundation DLL
- XmlMvc - A DLL that contains MVC Framework extensions that support an XML Model
- Shapes - The sample EXE
The approach I've taken is that any code that could be used in any application should go into SbjCore. Any code that is specific to an XML implementation of the MVC Framework should go into XmlMvc, and only code which is specific to the application should go into the application. Granted, I'm not always successful, and sometimes, I don't see the generalities in the code that can be factored down to the lower levels, but that has been my intent.
SbjCore.dll
The MVC Framework is part of the foundation DLL, SbjCore.dll. The code is all namespaced, and to some degree, the structure of the namespaces is mimicked in the structure of the Visual Studio Project where it resides. Not surprisingly, the root is the namespace SbjCore
, and all of the MVC code is in SbjCore::Mvc
. Along with the MVC Framework, SbjCore
also contains a large number of utility components, many of which were influenced by CodeProject articles, for helping deal with a wide variety of common programming tasks such as Memory, Strings, Images, the Clipboard, etc. There are also components that implement an Event architecture, DragNDrop, UndoRedo, and more. As I said, anything that can be used in any application goes here.
XmlMvc.dll
The XmlMvc.dll uses the MSXML6 implementation of the Document Object Model (DOM), which provides an ideal base of generic routines for handling an XML Model. All XML extensions to the MVC Framework are contained in this separate DLL. In general, the XmlMvc Model makes the assumption that XML elements are like records in a database and XML attributes are like the fields of the record. In this way, it's able to generalize a lot of code that might have ended up application specific.
Other technologies that one would use to implement a Model such as ADO, SQL, or proprietary DBs would be treated in the same way.
Shapes.exe
Shapes.exe is a rudimentary drawing program created using the MFC Feature Pack AppWizard that integrates the MVC Framework and XmlMvc Model extensions with its base MFC Doc/View architecture.
An existing Shapes.xml file is provided as an illustration of what XML file format the Shapes application expects. As you can see from the listing below, under the Shapes
document element is an element called Drawing
, which contains elements of type Rectangle
and Ellipse
. Each of these has a number of attributes which describe its appearance when displayed in the main View of the application.
Shapes.xml
="1.0"="utf-8"
<Shapes>
<Drawing name="Test Drawing">
<Rectangle label="The First Rectangle" left="250" top="250" right="750" bottom="750"
borderRGB="255" borderWidth="9" fillRGB="128"/>
<Rectangle label="Blue Rectangle" left="100" top="90" right="200" bottom="200"
borderRGB="16711680" borderWidth="1" fillRGB="16711680"/>
<Ellipse label="The First Ellipse" left="300" top="200" right="800" bottom="400"
borderRGB="65280" borderWidth="1" fillRGB="65280"/>
</Drawing>
</Shapes>
As you would assume, the Shapes application provides the user with the ability to add, delete, modify, and move individual and multiply selected Rectangles and Ellipses. As illustrated in the screenshot at the beginning of the article, the application contains the Main Design View and two Docking Panes: an Explorer Tree View of the Drawing and a Property Grid showing the attributes of the selected shape.
If you've used MFC at all, you should be familiar with the way the CDocument
class informs its list of CView
classes of the need to update through calls to each CView::OnUpdate
method. You should also be familiar with how the CCmdTarget
and CWnd
classes handle messages through their message maps and handler methods. So, rather than going into detail describing the MFC implementation, I'll provide an overview of the Framework and its base components, with the focus on how they integrate with the Doc/View architecture.
The key to the integration is the ability to selectively hijack the Windows messages sent to the CCmdTarget
and CWnd
classes before they are processed by the default MFC message map and routing implementation. This is accomplished by two classes.
template<class _baseClass>
class ControlledCmdTargetT : public _baseClass
Project location: SbjCore/Mvc/ControlledCmdTargetT.h
template<class _baseClass>
class ControlledWndT : public ControlledCmdTargetT<_baseClass>
Project location: SbjCore/Mvc/ControlledWndT.h
The ControlledCmdTargetT
class overrides the OnCmdMsg
method of its CCmdTarget
derived _baseClass
, and the ControlledWndT
class additionally overrides the OnWndMsg
method of its CWnd
derived _baseClass
. These overrides direct the incoming messages first to an assigned Controller
class for handling before letting the standard MFC message map processing to occur.
By using a template base class, these classes can derive respectively from any CCmdTarget
or CWnd
derived class. For instance...
class ControlledDocument : public ControlledCmdTargetT<CDocument>
class ControlledView : public ControlledWndT<CView>
How Windows messages are handled is a major difference between the MFC Doc/View architecture and the MVC Framework. In the Framework, message handlers are not methods of a CCmdTarget
or CWnd
class as they are in the Doc/View, but are classes in their own right. There are three basic variations, all deriving from a base MessageHandler
class, each handling a different type of Windows message.
MessageHandler
CmdMsgHandler
- WM_COMMAND
NotifyMsgHandler
- WM_NOTIFY
WndMsgHandler
- other WM_XXX
CmdMsgHandler
, NotifyMsgHandler
, and WndMsgHandler
are all pure virtual, and it is always one of these that provide the base for the actual concrete handlers. The MessageHandler
base class is never directly derived from. Also, the Message ID is not directly assigned to the handler, rather it is mapped to the handler when it is added to a Controller
.
Class MessageHandler
MessageHandler
provides the base for the CmdMsgHandler
, NotifyMsgHandler
, and WndMsgHandler
classes. In addition to providing common base for the derived classes, MessageHandler
also provides a chaining mechanism so derived Controller
s can provide MessageHandler
classes for the same message ID, allowing them to access previously assigned MessageHandler
classes. This is similar to the way MFC method based message handlers access base class method handlers. When assigned to a Controller
, the Controller
calls MessageHandler::Initialize
with itself as the Controller
, and if there is one, the last previously assigned MessageHandler
. CmdMsgHandler
, NotifyMsgHandler
, and WndMsgHandler
provide methods for calling previous handlers they have been assigned.
class AFX_EXT_CLASS MessageHandler
{
public:
MessageHandler();
virtual ~MessageHandler();
public:
void Initialize(Controller* pCtrlr, MessageHandler* pPrevHandler);
Controller* GetController() const;
protected:
MessageHandler* GetPrevHandler() const;
private:
struct MessageHandlerImpl* const m_pImpl;
};
Project location: SbjCore/Mvc/MessageHandler.h
Class CmdMsgHandler
CmdMsgHandler
is an abstract
class for handling WM_COMMAND
messages. The CmdTargetController
calls the public Handle
methods of the CmdMsgHandler
for handling not only the CmdMsg
variation, but also the CmdUI
variation of messages. The private OnHandleCmd
method is a pure virtual, and must be implemented in the derived class. The OnHandleCmdUI
method has a default implementation that returns true
for the CCmdUI::Enable
method of the passed CCmdUI
. To support the functionality derived from its base MessageHandler
class for the chaining of handlers, derived OnHandleCmd
and OnHandleCmdUI
methods should call the public
methods HandleCmdPrev
and HandleCmdUIPrev
, as appropriate.
class AFX_EXT_CLASS CmdMsgHandler : public MessageHandler
{
public:
virtual ~CmdMsgHandler();
CmdTargetController* GetController() const;
public:
bool HandleCmd(EventID nID);
bool HandleCmdUI(CCmdUI* pCmdUI);
bool HandleCmdPrev(EventID nID);
bool HandleCmdUIPrev(CCmdUI* pCmdUI);
private:
virtual bool OnHandleCmd(EventID nID) = 0;
virtual bool OnHandleCmdUI(CCmdUI* pCmdUI);
};
Project location: SbjCore/Mvc/CmdMsgHandler.h
Class NotifyMsgHandler
NotifyMsgHandler
is an abstract
class for handling WM_NOTIFY
messages. The private OnHandleNotify
method is a pure virtual, and must be implemented in the derived class. To support the functionality derived from its base MessageHandler
class for the chaining of handlers, derived OnHandleNotify
methods should call the public
method HandleNotifyPrev
, as appropriate.
class AFX_EXT_CLASS NotifyMsgHandler : public MessageHandler
{
public:
virtual ~NotifyMsgHandler();
CmdTargetController* GetController() const;
public:
bool HandleNotify(NMHDR* pNMHDR, LRESULT* pResult);
bool HandleNotifyPrev(NMHDR* pNMHDR, LRESULT* pResult);
private:
virtual bool OnHandleNotify(NMHDR* pNMHDR, LRESULT* pResult) = 0;
};
Project location: SbjCore/Mvc/NotifyHandler.h
Class WndMsgHandler
WndMsgHandler
is an abstract
class for handling all the rest of the Windows messages. The private OnHandleWndMsg
method is a pure virtual, and must be implemented in the derived class. To support the functionality derived from its base MessageHandler
class for the chaining of handlers, derived OnHandleWndMsg
methods should call the public
method HandleWndMsgPrev
, as appropriate. Provision is made for calling the MFC default message handling first. The OnCallDefaultFirst
method, by default, returns false
; however, derived classes may override this to return true
where appropriate.
class AFX_EXT_CLASS WndMsgHandler : public MessageHandler
{
public:
virtual ~WndMsgHandler();
WndController* GetController() const;
public:
LRESULT HandleWndMsg(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
bool HandleWndMsgPrev(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
bool CallDefaultFirst();
private:
virtual LRESULT OnHandleWndMsg(WPARAM wParam,
LPARAM lParam, LRESULT* pResult) = 0;
virtual bool OnCallDefaultFirst();
};
Project location: SbjCore/Mvc/WndMsgHandler.h
In the MVC Framework, one of the roles the Controller
plays is that of a message manager for the ControlledCmdTargetT
or ControlledWndT
to which it is assigned by maintaining a map of MessageHandler
objects. When a ControlledCmdTargetT
or ControlledWndT
class passes a message to its Controller
class, the Controller
uses the ID of the message to look for a handler in its map, and if found, it gives the handler a chance to respond. If no handler is found, or the handler wishes for the message to continue to be handled, the base class of the Controlled class is given a chance. In most cases, this will be a CmdTarget
or CWnd
derived class with a standard MFC message map implementation. It is in this way that the Framework integrates seamlessly with the existing MFC implementation.
As with the MessageHandler
classes, there is a base Controller
class, and in this case, just two base derivatives: CmdTargetController
and WndController
.
Controller
CmdTargetController
WndController
Class Controller
Controller
provides a base for the CmdTargetController
and WndController
classes. It is responsible for maintaining the map of MessageHandler
classes for its Controlled class. It has methods for adding and removing handlers from its map, and an accessor to get the current handler for a given ID. This is used internally to chain handlers for the same ID.
You'll notice a method PrepareCtxMenu
. Derived Controller
classes are queried through this method in response to the WM_CONTEXTMENU
message as to what menu items should be added to the current invocation of the Context Menu.
class AFX_EXT_CLASS Controller
{
public:
Controller();
virtual ~Controller();
public:
void Initialize();
public:
void AddHandler(EventID nID, MessageHandler* p);
void AddHandler(EventID nFirstID, EventID nLastID, MessageHandler* p);
void RemoveHandler(EventID nID);
void RemoveHandler(EventID nFirstID, EventID nLastID);
MessageHandler* GetHandler(EventID nID) const;
SbjCore::Utils::Menu::ItemRange PrepareCtxMenu(CMenu& ctxMenu) const;
private:
virtual void OnInitialize();
virtual SbjCore::Utils::Menu::ItemRange OnPrepareCtxMenu(CMenu& ctxMenu) const;
private:
struct ControllerImpl* const m_pImpl;
};
Project location: SbjCore/Mvc/Controller.h
Class CmdTargetController
The CmdTargetController
class controls ControlledCmdTargetT
classes. It handles the mapping of the CmdMsgHandler
and NoftifyMsgHandler
classes. When the ControlledCmdTargetT
receives a message, its OnCmdMsg
method determines, in the same way that MFC does, which flavor of message it is. It then calls the appropriate CmdTargetController::HandleCmdMsg
, CmdTargetController::HandleCmdUIMsg
, or CmdTargetController::HandleNotifyMsg
method. If the message is not handled, it then calls CmdController::RoutCmdMsg
, giving the controller a chance to override the default MFC routing. If the message still isn't handled, or warrants further processing, the base class of the ControlledCmdTargetT
is given a chance.
In this class, you'll see the GetUndoRedoMgr
method. Explanation of this is really beyond the scope of this article; however, the SbjCore::Mvc::DocController
derived from CmdTargetController
assigns the SbjCore::UndoRedo::Manager
to the task, and it is implemented in full in the Shapes.exe application.
class AFX_EXT_CLASS CmdTargetController : public Controller
{
public:
CmdTargetController();
virtual ~CmdTargetController();
public:
void SetCmdTarget(CCmdTarget* p);
CCmdTarget* GetCmdTarget() const;
public:
bool RoutCmdMsg(EventID nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo);
UndoRedo::Manager* GetUndoRedoMgr() const;
public:
bool HandleCmdMsg(EventID nID);
bool HandleCmdUIMsg(EventID nID, CCmdUI* pCmdUI);
bool HandleNotifyMsg(NMHDR* pNMHdr, LRESULT* pResult);
private:
virtual bool OnRoutCmdMsg(EventID nID,
int nCode,
void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo);
virtual UndoRedo::Manager* OnGetUndoRedoMgr() const;
private:
struct CmdTargetControllerImpl* const m_pImpl;
};
Project location: SbjCore/Mvc/CmdTargetController.h
Class WndController
The WndController
class controls the ControlledWndT
classes. It handles the mapping of the WmdMsgHandler
classes. Of course, since it is derived from CmdTargetController
, it can also handle CmdMsgHandler
and NotifyHandler
classes.
As you know from handling Windows messages in MFC, it is sometimes appropriate to call the base class handler before processing the Windows message in your handler. To provide for this, when the ControlledWndT
receives a message, its OnWndMsg
method calls the WndMsgController::CallDefaultFirst
method, which in turn queries the handler map for an entry, and if found, asks the handler if default processing should precede the call to it. Of course, if the message is not handled, or warrants further processing, the base class of the ControlledWndT
is given a chance.
class AFX_EXT_CLASS WndController : public CmdTargetController
{
public:
WndController();
virtual ~WndController();
public:
void SetWnd(CWnd* p);
CWnd* GetWnd() const;
public:
BOOL HandleWndMsg(EventID message, WPARAM wParam,
LPARAM lParam, LRESULT* pResult);
bool CallDefaultFirst(EventID message);
private:
struct WndControllerImpl* const m_pImpl;
};
Rather than using the CView::OnUpdate
method, the MVC Framework instead uses an Event architecture. Each time the Model changes, an Event is fired, indicating the type of change that has occurred. Event handlers are registered for a given type of change, and respond when the Event is fired. Unlike the MFC Doc/View architecture, the Event architecture is not limited to CView
and derivative classes, and can be utilized by any object interested in Model change.
Events are identified by unique ID values which are created using Joseph M. Newcomer's RegisterWindowMessage
technique [^] that I've been using forever.
DECLARE_EVENT_ID macro
#define DECLARE_EVENT_ID(name, guid) DECLARE_USER_MESSAGE(name, guid)
Project location: SbjCore/EventMgr/EventMgr.h
Namespace EventMgr
The Event
architecture is not actually part of the MVC Framework as it can be used independently. Rather, the namespace SbjCore::EventMgr
contains the functions and classes that make up the Event
architecture.
EventMgr
maintains a private map of registered EventHandler
classes which are keyed by unique ID.
typedef UINT EventID;
EventHandler
classes register themselves with the private EventMgr
handler map during construction, and unregister when destructed. When an Event
class with a matching ID key is fired, its NotifyHandlers
method will call each registered EventHandler::Handle
method, passing a pointer to itself. Listed below are the various components of the Event
architecture.
Class Event
The Event
class is pretty straightforward. When fired, it notifies its handlers.
class AFX_EXT_CLASS Event
{
public:
Event();
Event(EventID nEventID, bool bAutoFire = true);
virtual ~Event();
public:
void Init(EventID nEventID, bool bAutoFire = true);
void NotifyHandlers();
private:
struct EventImpl* const m_pImpl;
};
Project location: SbjCore/EventMgr/EventMgr.h
Class EventT
The EventT
class provides a generic derivation of Event
through which handlers can receive any specific data necessary for processing the event.
template <class T>
class EventT : public Event
{
T theData;
public:
EventT(EventID nEventID, T data, bool bAutoFire = true) :
Event(nEventID, false), theData(data)
{
if (bAutoFire) {
NotifyHandlers();
}
}
virtual ~EventT()
{
}
T GetData() const
{
return theData;
}
};
Project location: SbjCore/EventMgr/EventT.h
Fire Function
The Fire
function provides a convenient way to fire an event when the only information the handler needs is the fact that the event happened.
AFX_EXT_API void Fire(EventID nEventID);
Project location: SbjCore/EventMgr/EventMgr.h
Class EventHandler
EventHandler
is an abstract
base class. Derived classes must implement the private
virtual method OnHandle
which is called from the public
method Handle
. EventHandler
classes register themselves with the EventMgr
handler map on construction, and unregister on destruction.
class AFX_EXT_CLASS EventHandler
{
public:
EventHandler(EventID nEventID);
virtual ~EventHandler();
public:
void Handle(Event* pEvent);
private:
virtual void OnHandle(Event* pEvent) = 0;
private:
struct EventHandlerImpl* const m_pImpl;
};
Project location: SbjCore/EventMgr/EventMgr.h
AbortException
AbortException
provides a way for handlers to short circuit the fire mechanism. When thrown by a handler, the Event::NotifyHandlers
method will stop firing the event to subsequent handlers.
typedef CUserException AbortException;
Project location: SbjCore/EventMgr/EventMgr.h
In the preceding sections, I've introduced the classes that provide the base functionality of the MVC Framework, and shown how they integrate with the MFC Doc/View architecture. From these classes, the Framework is extended by providing ControlledCmdTargetT
and ControlledCWndT
derivatives and associated Controller
classes for the main components of an MFC application. From these, the XmlMvc.dll provides extensions that support an XML Model implementation. If there is interest, future articles will explore the specific application of the Framework to CDockablePane
types and the controls they contain, the sample DesignView and XML Model used in the Shapes.exe, and the reusable aspects of these components. All of this is contained in the source code provided with this article, so, run Shapes.exe and explore the code in the application and DLLs. I think you'll be surprised at how little code is actually at the application level.
- Support for all common controls
- Support for MFC
CView
derivatives - Implement
DocTemplate
derivatives for CDockablePane
- Complete generalization based on
ModelItemHandle
- 2008 Oct. 20 - Original article submitted
- 2008 Nov. 24 - Added link to follow up article
- 2009 Mar. 19 - Added link to second follow up article