Draft for a WTL Document/View approach

 

Author:             Gabriel Kniznik

 

 

Introduction

 

                        This is an attempt to port the Doc/View model from MFC to WTL. The basic functionality provided by WTL so far focused mainly in windowing issues, letting more specific aspects of programming uncovered. On this particular issue, WTL designers provided a fairly complete MDI and SDI framework, though no mention is made to document management.

 

                        We will try to ‘adapt’ the MFC Doc/View model to the template paradigm while maintaining the transparency and adaptability achieved by means of polymorphism and inheritance. Unlike the MFC model, we won’t build different models for SDI and MDI: we will consider SDI as a special case with only one document.

 

                        Four main classes reflect the behaviour of this model’s components: the document, the view, the document template and the document manager. A fifth element, the frame, has no class associated in this model.

 

 

1 – The Document

 

 

                        The simplest link of this chain is the document itself. It is supposed to host and/or provide data for its associated view(s). It has no knowledge of the internals of the view(s), but it can signal them that they must update themselves on data changes, so it must maintain a list of references to the view(s).

 

                        The view(s) must have access to the document through a stored pointer, so they can manipulate its data objects, signal other view(s) to update and check for ‘dirty’ data. The document is also responsible for data persistence, which we won’t cover here.

 

                        In MFC, documents inherit from a base class CDocument, which manages

all aspects of the model. In WTL, we must reverse this mechanism, so our template class CDocument inherits from the implementation class CMyDocument.

 

As we will see shortly, some kind of polymorphism must be implemented

in order to access documents from outside its template. In our model, this is achieved through an empty class CDocumentBase. So far, our classes should look like this:

 

class CDocumentBase;

{

};

 

template <class T, class TDoc = CDocumentBase>

class CDocument : public TDoc

{

      ------

};

 

 

2 – The View

 

 

                        This is the model’s element responsible for user interface. It must display a visual representation of the document’s data and retrieve user actions on it. It depends on the document in such way that a particular view can only ‘belong’ to a particular document. The opposite is not true, since a document can have many views associated.

 

                        Once again, the inheritance mechanism for the view must follow the general WTL rules, but since it must be associated with a particular document, one of the template arguments should be a document class. Views must also inherit from a polymorphic class, in order to allow for array storage in the document:

 

template <class TDoc>

class CView 

{

      -----

};

 

template <class T, class TView>

class CViewImpl : public TView

{

      ----

};

 

 

3 – The Frame

 

 

                        A third element must be presented at this time: the Frame. It represents either the Main Frame for SDI architectures or the MDI Child Frame. Its responsibility ends with the creation of its embedded view(s), but it’s a key element in the chain of document instantiation.

 

 

4 – The Document Template

 

 

                        This is probably the heart of the whole model. In addition, it is the most suitable for template implementation: the MFC solution is a little clumsy, since they had to use a runtime class identification mechanism encapsulated in their CRuntimeClass type.

 

                        Basically, the Document Template must create and initialize the Document, instantiate the Frame and ask it to create its View(s). It must carry class information on these three object types, and must provide polymorphism in order to be stored in an array by the document manager. According to the MFC model, it must carry an ID number which identifies menus, toolbars and strings associated with the document:

 

 

class CDocTemplateBase

{

      -----

};

 

 

template <class TDoc, class TView, class TFrame, int nID>

class CDocTemplate : public CDocTemplateBase

{

      -----

};

 

 

5 – The Doc Manager

 

 

                        In MFC, the Doc Manager is stored in the Application object. Since WTL does not provide such an object, an alternative must be chosen. The Main Frame is our candidate, since it represents the highest element in the windowing hierarchy, and there is only one per application. In the case of a SDI application, no Doc Manager is needed, and the Document Template can be hosted directly by the frame itself.

 

                        This object must maintain a list of Document Templates, and since it must manipulate the main frame, it should carry it as a template argument:

 

 

template <class TMainFrame>

class CDocManager

{

      -----

};

 

 

6 – The Creation Context

 

 

                        The CCreateContext template structure carries information about the document, the document template and the view. This helper object follows the MFC model with minor changes.

 

template <class TDoc, class TView>

struct CCreateContext

{

      TDoc* m_pCurrentDoc;

      TView* m_pCurrentView;

      CDocTemplateBase* m_pNewDocTemplate;

     

      -----

};

 

 

7 – Choreography

 

 

                        The function call that triggers the whole creation process is made inside the handler of the corresponding menu command (usually in the main frame).

 

            GetDocTemplate(0)->OpenDocumentFile(NULL);

 

            This line retrieves the document template stored at index position 0 from the document manager, and calls OpenDocumentFile on this object. Next, let us take a look at the internals of this method:

 

CDocumentBase* OpenDocumentFile(LPCTSTR lpszPathName,

BOOL bMakeVisible = TRUE)

{

      TDoc* pDoc = CreateNewDocument();

      TFrame* pFrame = CreateNewFrame(pDoc, 0);

           

      if (lpszPathName == NULL)

      {

      if(!pDoc->OnNewDocument())

                  pFrame->DestroyWindow();

}

      else

{    

if (!pDoc->OnOpenDocument(lpszPathName))

                  pFrame->DestroyWindow();

}

           

      return pDoc;

}

 

                        The first line creates the document, as expected. Then the frame is created. The frame creation method follows:

 

TFrame* CreateNewFrame(TDoc* pDoc, TFrame* pOther)

{

      CCreateContext <TDoc, TView> context;

      context.m_pCurrentDoc = pDoc;

      context.m_pNewDocTemplate = this;

           

      TFrame::GetWndClassInfo().m_uCommonResourceID = nID;

      TFrame* pFrame = new TFrame;

      pFrame->CreateEx(m_hWndClient, NULL, NULL, 0, 0, &context);

 

      return pFrame;

}

 

                        First, a creation context structure is instantiated and filled. The resource ID is assigned to the frame (this is equivalent to the WTL macro DECLARE_FRAME_WND_CLASS). Then the frame itself is created, passing the creation context as parameter. The frame is responsible of creating the view(s) from within its OnCreate message handler.

 

                        Once the frame and its view(s) are fully created, a virtual function in the document is called, depending on wether it’s a new document (OnNewDocument) or an existing one (OnOpenDocument). If either of these calls return FALSE, the frame and the view(s) are destroyed.

 

 

8 – Implementation notes

 

 

-         Document:

 

A document class must inherit from CDocument, and implement its own functionality as desired:

 

class CMyDoc : public CDocument<CMyDoc> 

{

public:

      CMyDoc();

      virtual ~CMyDoc();

 

   protected:

      CMyData     m_Data;     // Application-specific data

 

   public:

      CMyData*    GetData();

      BOOL        OnNewDocument();  // virtual functions

      BOOL        OnSaveDocument(); //

   };

 

   typedef CView<CMyDoc> CMyDocView;

 

 

The last two methods are polymorphic and will be called from inside our framework. The typedef represents any view belonging to this document.

 

-         View:

 

We may deal with several kinds of views: normal UI views, scroll views, form views, control views, and so on. For further information, see the author’s article Views in WTL . For our example, we will focus on a form view, but the implementation is very similar for either kind.

 

class CMyFormView : public CDialogImpl<CMyFormView>,

      public CWinDataExchange<CMyFormView>,

      public CScrollImpl<CMyFormView>,

   public CViewImpl<CMyFormView, CMyDocView>

   {

      ------

   virtual void OnFinalMessage(HWND hWnd)

   {

         m_pDocument->RemoveView(this);

         delete this;

   }

  

   void OnUpdate(CMyDocView* pSender, LPARAM Hint, LPVOID pHint)

{

      -----

}

 

   };

 

 

View objects that participate in this model must remove themselves from the document when destroyed. In addition, since they are created in the heap (through the operator new), they must auto-delete.

The OnUpdate method is called by the document’s UpdateAllViews function, the same as in MFC.

 

-         MDI Frame:

 

Following the example, a typical MDI Child Frame is shown:

 

class CMyChildFrame : public CMDIChildWindowImpl<CMyChildFrame>,

                         public CUpdateUI<CMyChildFrame>

   {

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam,

BOOL& bHandled)

{

   LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;

      LPMDICREATESTRUCT lpms =

         (LPMDICREATESTRUCT)lpcs->lpCreateParams;

      CreateContext<CMyDoc, CMyFormView> * pContext =

         (CCreateContext<CMyDoc, CMyFormView> *)lpms->lParam;

 

      _ASSERTE(pContext->m_pCurrentView == NULL);

 

      pContext->m_pCurrentView = new CMyFormView;

      m_pView = pContext->m_pCurrentView;

      m_hWndClient = m_pView->Create(m_hWnd, pContext);

 

   bHandled = FALSE;

         return 1;

      }

 

   };

 

-         Main Frame:

 

The main frame inherits from the Doc Manager. At initialisation, it should add the document templates the application will use.

 

class CMainFrame : public CMDIFrameWindowImpl<CMainFrame>,

    public CUpdateUI<CMainFrame>,

          public CMessageFilter,

    public CIdleHandler,

    public CDocManager<CMainFrame>

   {

   public:

      DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)

 

      CCommandBarCtrl                     m_CmdBar;

 

CDocTemplate<CMyDoc, CMyFormView, CMyChildFrame,

     IDR_MYMDICHILD>                m_MyDocTemplate;

CDocTemplate<CMyOtherDoc, CMyOtherView, CMyOtherChildFrame,

IDR_MYOTHERMDICHILD>            m_MyOtherDocTemplate;

 

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)

      {

            -------

            -------

           

         AddDocTemplate(&m_MyDocTemplate);

         AddDocTemplate(&m_MyOtherDocTemplate);

           

            return 0;

      }

   };

 

 

9 – The Code

 

 

#if !defined(DOCVIEW_H__E5FC4C65_09BC_11D4_83E7_00C0DF01F495__INCLUDED_)

#define DOCVIEW_H__E5FC4C65_09BC_11D4_83E7_00C0DF01F495__INCLUDED_

 

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

 

#define     HINT_UPDATE_ALL_DOCUMENTS     -1

#define     HINT_DOCUMENT_MODIFIED        -2

 

/* Forward declarations */

class CDocTemplateBase;

 

/* Helper struct for document creation */

template <class TDoc, class TView>

struct CCreateContext

{

   TDoc* m_pCurrentDoc;

   TView* m_pCurrentView;

   CDocTemplateBase* m_pNewDocTemplate;

 

   CCreateContext()

      { memset(this, 0, sizeof(*this)); }

     

   /* constructor based on a different context handles more than

      one view per document (splitter windows) */    

 

   template <class TOtherView>

   CCreateContext(CCreateContext <TDoc, TOtherView>* pContext)

   {

      m_pCurrentDoc = pContext->m_pCurrentDoc;

      m_pNewDocTemplate = pContext->m_pNewDocTemplate;

   }

};

 

/* abstract class for all the views belonging to a document */

template <class TDoc>

class CView 

{

protected:

   TDoc*    m_pDocument;

 

public:

   virtual void Update(CView <TDoc>* pSender, LPARAM lHint,

LPVOID pHint) = 0;

 

   TDoc* GetDocument()

   {

      return m_pDocument;

   }

 

   void SetDocument(TDoc* pDoc)

   {

      m_pDocument = pDoc;

   }

 

   /* useful function for updating all docs from a particular template

      in MDI architectures */

   void UpdateAllDocs()

   {

      CDocTemplateBase* pDocTemplate = m_pDocument->GetDocTemplate();

      int ndocs = pDocTemplate->GetNumDocs();

      for(int j = 0; j < ndocs; j++)

      {

         TDoc* pDoc = static_cast<TDoc*>(pDocTemplate->GetDocument(j));

         if(pDoc)

            pDoc->UpdateAllViews(this, HINT_UPDATE_ALL_DOCUMENTS);

}

   }

};

 

 

/* base class for view implementations */

template <class T, class TView>

class CViewImpl : public TView

{

public:

   virtual void Update(TView* pSender, LPARAM lHint, LPVOID pHint)

   {

      T* pT = static_cast<T*>(this);

      pT->OnUpdate(pSender, lHint, pHint);

   }

 

   virtual void OnInitialUpdate()

   {

      T* pT = static_cast<T*>(this);

      pT->OnUpdate(0, 0, 0);

   }

 

   HWND GetParentFrame()

   {

      T* pT = static_cast<T*>(this);

      HWND hWnd = pT->GetParent();

      while(!(GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_MDICHILD))

         hWnd = ::GetParent(hWnd);

 

      return hWnd;

   }

 

};

 

 

/* empty class just for polymorphism */

class CDocumentBase

{

};

 

 

/* the document */

template <class T, class TDocTemplate = CDocTemplateBase,

   class TDoc = CDocumentBase>

class CDocument : public TDoc

{

public:

   CDocument()

   {

      m_bModified = FALSE;

   }

 

   void UpdateAllViews(CView <T>* pSender, LPARAM lHint = 0,

LPVOID pHint = NULL)

   {

      int count = m_aViews.GetSize();

      for(int i = 0; i < count; i++)

      {

         CView <T>* pView = m_aViews[i];

         if(pView != (CView <T>*)pSender)

            pView->Update(pSender, lHint, pHint);

      }

   }

 

   CView<T>* AddView(CView<T>* pView)

   {

      pView->SetDocument(static_cast<T*>(this));

      CView <T>* pV = static_cast<CView <T>*>(pView);

      m_aViews.Add(pV);

      return pView;

   }

 

   void RemoveView(CView<T>* pView)

   {

      int count = m_aViews.GetSize();

      for(int i = 0; i < count; i++)

      {

         if(m_aViews[i] == pView)

         {

            m_aViews.RemoveAt(i);

            pView->SetDocument(NULL);

            break;

         }

      }

   }

 

   int GetNumViews() const

   {

      return m_aViews.GetSize();

   }

 

   CView<T>* GetView(const int pos)

   {

      _ASSERTE(pos < m_aViews.GetSize());

           

      return m_aViews[pos];

   }

 

   BOOL IsModified()

   {

      return m_bModified;

   }

 

   void SetModifiedFlag(BOOL bModified = TRUE)

   {

      m_bModified = bModified;

   }

 

   TDocTemplate* GetDocTemplate()

   {

      return m_pDocTemplate;

   }

 

protected:

   CSimpleArray<CView<T>*>    m_aViews;

   BOOL                       m_bModified;

 

public:

   TDocTemplate*              m_pDocTemplate;

 

};

 

 

/* abstract base class for doc templates */

class CDocTemplateBase

{

public:

   virtual CDocumentBase* GetDocument(int pos) = 0;

   virtual CDocumentBase* OpenDocumentFile(LPCTSTR lpszPathName,

BOOL bMakeVisible = TRUE) = 0;

   virtual CDocumentBase* OpenDocument(BOOL bNew = FALSE) = 0;

   virtual int GetNumDocs() const = 0;

};

 

 

/* the doc template */

template <class TDoc, class TView, class TFrame, int nID>

class CDocTemplate : public CDocTemplateBase

{

private:

   CSimpleArray<TDoc*>  m_aDocuments;

 

public:

   HWND     m_hWndClient;

 

   ~CDocTemplate()

   {

      int ndocs = GetNumDocs();

      for(int i = 0; i < ndocs; i++)

      {

         delete m_aDocuments[i];

      }

     

m_aDocuments.RemoveAll();

   }

 

   TFrame* CreateNewFrame(TDoc* pDoc, TFrame* pOther)

   {

      CCreateContext <TDoc, TView> context;

      context.m_pCurrentDoc = pDoc;

      context.m_pNewDocTemplate = this;

           

      TFrame::GetWndClassInfo().m_uCommonResourceID = nID;

      TFrame* pFrame = new TFrame;

      pFrame->CreateEx(m_hWndClient, NULL, NULL, 0, 0, &context);

 

      return pFrame;

   }

 

   TDoc* CreateNewDocument()

   {

      TDoc* pDoc = new TDoc;

      AddDocument(pDoc);

 

      return pDoc;

   }

 

   CDocumentBase* OpenDocumentFile(LPCTSTR lpszPathName,

BOOL bMakeVisible = TRUE)

   {

      TDoc* pDoc = CreateNewDocument();

      TFrame* pFrame = CreateNewFrame(pDoc, 0);

           

      if(!pDoc->OnNewDocument())

         pFrame->DestroyWindow();

 

      return pDoc;

   }

 

   CDocumentBase* GetDocument(int pos)

   {

      return m_aDocuments[pos];

   }

 

   virtual void AddDocument(TDoc* pDoc)

   {

      pDoc->m_pDocTemplate = this;

      m_aDocuments.Add(pDoc);

   }

 

   virtual TDoc* RemoveDocument(TDoc* pDoc)

   {

      int ndocs = GetNumDocs();

      for(int i = 0; i < ndocs; i++)

      {

         if(m_aDocuments[i] == pDoc)

         {

            m_aDocuments.RemoveAt(i);

            break;

         }

      }

      return pDoc;

   }

 

   int GetNumDocs() const

   {

      return m_aDocuments.GetSize();

   }

};

 

 

/* the doc manager */

template <class TMainFrame>

class CDocManager

{

public:

   template <class TDocTemplate>

   int AddDocTemplate(TDocTemplate* pDocTemplate)

   {

      _ASSERTE(pDocTemplate != NULL);

           

      pDocTemplate->m_hWndClient =

   static_cast<TMainFrame*>(this)->m_hWndClient;

CDocTemplateBase* pDocBase =

   static_cast<CDocTemplateBase*>(pDocTemplate);

      m_aTemplates.Add(pDocBase);

 

      return m_aTemplates.GetSize() - 1;

   }

 

   int GetNumTemplates() const

   {

      return m_aTemplates.GetSize();

   }

 

   CDocTemplateBase* GetDocTemplate(const int pos)

   {

      _ASSERTE(pos < m_aTemplates.GetSize());

 

      return m_aTemplates[pos];

   }

 

protected:

   CSimpleArray<CDocTemplateBase*> m_aTemplates;

};

 

 

#endif

// !defined(DOCVIEW_H__E5FC4C65_09BC_11D4_83E7_00C0DF01F495__INCLUDED_)