Introduction
Sometimes, the way a document is being visualized needs to be significantly changed. For example, in PowerPoint, you can see the slides in a WYSIWYG form or view only the text in a text edit window. In the MFC world, one can find an amazingly large quantity of programs that implement this behavior defining one CView
descendant and making it responsible for all visualization changes. This path has several disadvantages:
- Big, difficult to manage class definition file
- Diminished reusability: you could reuse one big CView descendant or nothing
- Hard to maintain (what if you want to modify or add a new "look" to the document?)
- Wasted memory: some variables (objects) will exist in memory and won’t be used
For an application using the MFC document-view architecture, it is more appropriate to define different view classes and switch between them when necessary. This shall overcome all the disadvantages listed before. There probably will be some features common to all views for the same type of document, so it is a good idea to have a direct CView
descendant that implements all the functionality common to all view types. The views used by the document should be descendants of this class ("grandchildren" of CView
).
The code needed to implement view switching depends on the frame window containing the view. There are three common cases: the view is contained within a CFrameWnd
(SDI application), the view is contained within a CMDIChildWnd
(MDI application) and the view is a pane of a splitter window, either in SDI or MDI applications. In all cases, what we need is a method in our document class to switch to the desired view. This method should receive the new view type as a parameter and return a success flag. The advantage of having this method in the document class becomes obvious when there are several document types each of which can have different view types. Let's start with an SDI application that doesn’t have splitters:
BOOL CMyDocument::SwitchToView(CRuntimeClass* pNewViewClass)
{
CFrameWnd* pMainWnd = (CFrameWnd*)AfxGetMainWnd();
CView* pOldActiveView = pMainWnd->GetActiveView();
if (pOldActiveView->IsKindOf(pNewViewClass))
return TRUE;
::SetWindowLong(pOldActiveView->m_hWnd, GWL_ID, 0);
CCreateContext context;
context.m_pNewViewClass = pNewViewClass;
context.m_pCurrentDoc = this;
CView* pNewView = STATIC_DOWNCAST(CView, pMainWnd->CreateView(&context));
if (pNewView != NULL)
{
pNewView->ShowWindow(SW_SHOW);
pNewView->OnInitialUpdate();
pMainWnd->SetActiveView(pNewView);
pMainWnd->RecalcLayout();
pOldActiveView->DestroyWindow();
return TRUE;
}
return FALSE;
}
In the case of an MDI application (again without splitters):
BOOL CMyDocument::SwitchToView(CRuntimeClass* pNewViewClass)
{
CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd();
CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();
CView* pOldActiveView = pChild->GetActiveView();
if (pOldActiveView->IsKindOf(pNewViewClass))
return TRUE;
BOOL bAutoDelete = m_bAutoDelete;
m_bAutoDelete = FALSE;
pOldActiveView->DestroyWindow();
m_bAutoDelete = bAutoDelete;
CView* pNewView = (CView *)pNewViewClass->CreateObject();
if (pNewView == NULL)
{
TRACE1("Warning: Dynamic create of view type %Fs failed\n",
pNewViewClass->m_lpszClassName);
return FALSE;
}
CCreateContext context;
context.m_pNewViewClass = pNewViewClass;
context.m_pCurrentDoc = this;
context.m_pNewDocTemplate = NULL;
context.m_pLastView = NULL;
context.m_pCurrentFrame = pChild;
if (!pNewView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0, 0, 0, 0), pChild, AFX_IDW_PANE_FIRST, &context))
{
TRACE0("Warning: couldn't create view for frame\n");
delete pNewView;
return FALSE;
}
pNewView->SendMessage(WM_INITIALUPDATE, 0, 0);
pChild->RecalcLayout();
pNewView->UpdateWindow();
pChild->SetActiveView(pNewView);
return TRUE;
}
When the view to replace is a pane of a splitter window, there is also a small difference between SDI and MDI applications, related to the retrieval of the current active view. In the method below, you must comment out what you don’t need depending on your application type:
BOOL CMyDocument::SwitchToView(CRuntimeClass* pNewViewClass)
{
if (pOldActiveView->IsKindOf(pNewViewClass))
return TRUE;
CSplitterWnd* pSplitter = (CSplitterWnd *)pOldActiveView->GetParent();
int row, col;
ASSERT(pSplitter->IsChildPane(pOldActiveView, row, col));
CRect viewrect;
pOldActiveView->GetWindowRect(&viewrect);
m_bAutoDelete = FALSE;
pOldActiveView->DestroyWindow();
m_bAutoDelete = TRUE;
CCreateContext context;
context.m_pNewViewClass = pNewViewClass;
context.m_pCurrentDoc = this;
context.m_pNewDocTemplate = NULL;
context.m_pLastView = NULL;
context.m_pCurrentFrame = NULL;
if (!pSplitter->CreateView(row, col, pNewViewClass, viewrect.Size(),
&context))
return FALSE;
CView* pNewView = (CView *)pSplitter->GetPane(row, col);
pSplitter->GetParentFrame()->SetActiveView(pNewView);
pSplitter->RecalcLayout();
pNewView->SendMessage(WM_PAINT);
return TRUE;
}
Now that we have a method in our document class that will replace the current view, let's use it. The new view type should be decided (in response to a menu selection, for instance), and the function must be called as follows:
CRuntimeClass* pNewViewClass = RUNTIME_CLASS(CMyView);
if (!SwitchToView(pNewViewClass))
else
One final word to the class wizard fans. When you have a descendant of a CView
descendant, the class wizard won’t allow you to edit this class. To change this behavior, change all class wizard comments replacing the name of your direct CView
descendant with CView
. Class wizard will now work.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.