Author: Gabriel Kniznik
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