The MVC classes are designed to be general-purpose and can be incorporated into your MFC application in any number of ways. You could, for example, use MVC alone, as an alternative to the document/view architecture. You could also use parts of MVC to take advantage of its reuse potential. You can use as much or as little MVC in your application as you find appropriate and even mix it with document/view based code.
In an MVC triad, you have three primary parts that you need to integrate into your new or existing application. The model is usually integrated via containment into the document class. The viewport is usually integrated via containment into the window or CView-derived class. Lastly, the controller is normally instantiated by the viewport it controls. The remainder of this section describes the steps required to incorporate an MVC triad into your application.
To define a model class, complete the following steps:
Create your CMvcModel derived class. At this point, you can either derive your model from CMvcModel or MvcPresentationModel_T. For the purposes of this tutorial, we'll use the presentation model as a base. If you require serialization support in your model, you need to multiply inherit your model from CObject and MvcPresentationModel_T.
class CloudDiagram : public CObject, public MvcPresentationModel_T<CMvcVisualComponent> { public: ~CloudDiagram(); |
Add your model class as a member variable inside your document.
class CMyDoc : public CDocument { // Attributes protected: CloudDiagram m_CloudDiagram; |
Create an accessor member inside your document that simply returns the model.
CloudDiagram* GetCloudDiagram() { return &m_CloudDiagram; }; |
Override CDocument::IsModified() so that it tests the modified flag of the contained model also.
BOOL CMyDoc::IsModified() { return CDocument::IsModified() || GetCloudDiagram()->IsModified(); } |
Override your document's serialize member so that the contained model is serialized.
void CMyDoc::Serialize(CArchive& ar) { GetCloudDiagram()->Serialize(ar); } |
If your model creates and destroys objects for which you want to support undoable deletion, multiply derive those objects from IRefCount.
class CloudComponent : public CObject, public CMvcVisualComponent, public IRefCount { . . . |
Override the cloud diagram's Draw() member and implement its data presentation.
void CloudDiagram::Draw(CDC* pDC) { Iterator_T<CloudComponentPtr> i(GetClouds()); for (CloudComponent* pCloud = i.GetFirst(); pCloud; pCloud = i.GetNext()) pCloud->Draw(pDC); } |
To define a controller class, complete the following steps:
Create a controller class that understands how to translate events into actions on the model and viewport classes. The MvcController class is used instead of the more generic CMvcController, because it contains an MFC message map and can have message handlers added to it using the MFC Class Wizard. When deriving from the MvcController class, it is convenient to define a type-safe access function for the model.
class CloudController : public MvcController { // Constructors public: CloudController(); virtual ~CloudController(); // Overrides public: CloudDiagram* GetDiagram () { return (CloudDiagram*)m_pModel; }; }; |
Add a message map to your controller class so that Class Wizard can be used to manage your message handlers.
// Generated message map functions protected: //{{AFX_MSG(CloudController) afx_msg void OnLButtonDown(UINT nFlags, CPoint point); //}}AFX_MSG FOUNDATION_DECLARE_MESSAGE_MAP() |
Generate a new Class Wizard file, which incorporates your new controller class. You can do this by deleting the *.clw files and then running the Class Wizard. Class Wizard prompts you to rebuild the .clw file.
To incorporate your controller into your application, the last step is to include it in the standard message routing. This step allows your controller to listen and handle the messages being sent to the containing window. You must override two functions - OnWndMsg() and OnCmdMsg() as follows:
BOOL CMvcCloudView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // First pump through normal channels. // This allows you to override the components // default handling inside the view class. if (m_component.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; else return CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } BOOL CMvcCloudView::OnWndMsg( UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult ) { // First pump through normal channels. // This allows you to override the components // default handling inside the view class. if (m_component1.OnWndMsg(message, wParam, lParam, pResult)) return TRUE; else return CView::OnWndMsg(message, wParam, lParam, pResult); } |
To define a viewport class, complete the following steps:
Create your CMvcViewport derived class. Pass a visual component class, such as CMvcLogicalPartImpl, in as the first template parameter. Pass in the type of model and type of controller as the other two template parameters. Override the Draw(), CreateController(), OnInitalUpdate(), SetVirtualSize(), and GetVirtualSize() members.
class CloudViewport : public CMvcViewport<CMvcLogicalPartImpl, CloudDiagram, CloudController> { public: virtual void Draw(CDC* pDC); virtual BOOL CreateController(); virtual void OnInitialUpdate(); void SetVirtualSize(int cx, int cy); CSize GetVirtualSize() const; |
Add your viewport as a member of your CWnd or CView derived class.
class CMyView : public CView { protected: // create from serialization only CMyView (); FOUNDATION_DECLARE_DYNCREATE(CMvcCloudView) // Attributes protected: MyViewport m_component; |
Create your viewport and attach it to the model that is contained in the document. This is accomplished via a call to the viewport's Create() and SetModel() members respectively. This initialization is typically done from the OnCreate() member.
int CMyView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; CMvcCloudDoc* pDoc = GetDocument(); CloudDiagram* pModel = pDoc->GetCloudDiagram(); m_component.Create(this->m_hWnd, NULL); m_component.SetModel(pModel); return 0; } |
Delegate all calls to OnInitialUpdate() and OnDraw() to your viewport from the CView or CWnd-derived class that contains it. This gives your viewport the opportunity to initialize and render itself on the drawing surface of its container.
void CMyView::OnInitialUpdate() { m_component.OnInitialUpdate(); } void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); m_component.Draw(pDC); } |
Next, size and position your viewport to occupy the entire client area of its container.
void MyView::OnSize(UINT nType, int cx, int cy) { m_component.SetOrigin(0, 0); // Position the viewport m_component.SetSize(cx, cy); // Size the viewport CView::OnSize(nType, cx, cy); } |
Override the CreateController() method in your viewport and create and initialize the controller.
BOOL CloudViewport::CreateController() { m_pCtlr = new CloudController; m_bAutoDelCtlr = true; return TRUE; } |
Setting the m_bAutoDelCtlr flag instructs the viewport to destroy the controller in its destructor. In other words, it ties the lifetime of the controller to the viewport. If you don't want to tie the lifetime of the controller to the viewport, then set the bAutoDelCtlr flag to FALSE, but make sure you take care of deleting the controller at the appropriate time.
Having the viewport create the controller is optional. It is only provided as a convenient mechanism for creating a default controller for the viewport. In many cases, it is not desirable to have the viewport have knowledge of the type of controller. In fact, you may want to use several types of controllers with your viewport class. If that is the case, then do not override CreateController(). Instead, create the controller outside of the scope of your viewport class and assign it to the viewport using the SetController() method.
Next, override the Draw() method and supply code to render the model onto viewport. Since the model in this sample is a presentation model, the viewport can simply instruct the model to draw itself.
void CloudViewport::Draw(CDC* pDC) { OnPrepareDC(pDC); GetCloudDiagram()->Draw(pDC); } |
Override OnInitialUpdate in your viewport class. This is a good place to initialize the logical and container extents of the viewport's client area. Basically, all this statement indicates is that for every 1000 units along the X-axis in the container's client area, there are 4000 logical units in this viewport. We didn't use 1 and 4 because small values like these don't leave much room for zooming in and out. Extents can never go below 1 because CDC::SetWindowExt() expects an integer.
void CloudViewport::OnInitialUpdate() { SetAxisExtents(X, 4000, 1000); SetAxisExtents(Y, 4000, 1000); } |
Define the Get() and Set() functions for the virtual size of the viewport. The virtual size of the viewport is equated to the size of the diagram because the diagram is rendered through the viewport and may be larger than the viewport. Consequently, the size of the diagram is the virtual size of the viewport.
void CloudViewport::SetVirtualSize(int cx, int cy) { GetDiagram()->SetSize(cx, cy); } CSize CloudViewport::GetVirtualSize() const { return GetDiagram()->GetSize(); } |
You are done. At this point, you have a completely reusable component integrated into your document/view application that defines its control, its data, and its rendering. It can be moved to any other MFC application using the same steps outlined above.
Copyright © Rogue Wave Software, Inc. All Rights Reserved.
The Rogue Wave name and logo, and Stingray, are registered trademarks of Rogue Wave Software. All other trademarks are the property of their respective owners.
Provide feedback to Rogue Wave about its documentation.