SamuelRobinson.com
A resource for Windows programmers
There are times when you'd really like to allow the user to have several very different views of the same document. Print preview is one example of a Microsoft supported version of this. In some cases, you may wish to show settings, text, and perhaps some other view of the same data object. This requires switching between views, or activating multiple views of the same document. In this article we will start by examining what needs to be done to enable multiple views for a document, we will look at code to create and/or activate these views, and then we will look at a more user friendly approach that cuts down on the confusion of having multiple views of the same object open at the same time, particularly in cases where there might be more than one document open.
The first thing that will need to be done is to create a document template to be used for each of the views. Notice in the code fragment below that each of the templates are CMultiDocTemplates. Each of the views uses the same CChildFrame. Each of the views does have it's own menu resource though. I've made the pointers to the templates members of the app. This is reasonably safe since the life of the document manager is the same as that of the app.
// Register the application's document templates. Document templates // serve as the connection between documents, frame windows and views. m_pFirst = new CMultiDocTemplate( IDR_VIEWS_FIRSTVIEW, RUNTIME_CLASS(CVwSwtchDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CVwSwtchView)); AddDocTemplate(m_pFirst); m_pSecond = new CMultiDocTemplate( IDR_VIEWS_SECONDVIEW, RUNTIME_CLASS(CVwSwtchDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CSecondView)); AddDocTemplate(m_pSecond); m_pThird = new CMultiDocTemplate( IDR_VIEWS_THIRDVIEW, RUNTIME_CLASS(CVwSwtchDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CThirdView)); AddDocTemplate(m_pThird);This code is used to create or activate multiple MDI children. It is implemented in the CMainFrame instead of the app object, but it could be elsewhere if that was desireable.
/////////////////////////////////////// void CMainFrame::CreateOrActivateFrame(CDocTemplate* pTemplate,CRuntimeClass* pViewClass) /////////////////////////////////////// // pDocumentTemplate is a CDocTemplate* to the template you wish to use // which is passed into this function. // pViewClass is a CRuntimeClass* to the view class desired which is // passed into the function { // Get the currently active child. CMDIChildWnd* pMDIActive = MDIGetActive(); ASSERT(pMDIActive != NULL); if(pMDIActive == NULL) return; // now get that child's document... you may not need to do this, but if there may // be multiple documents this a way to make sure that the correct set of windows // is being acted on. CDocument* pDoc = pMDIActive->GetActiveDocument(); ASSERT(pDoc != NULL); CView* pView; POSITION pos = pDoc->GetFirstViewPosition(); while (pos != NULL) { pView= pDoc->GetNextView(pos); if (pView->IsKindOf(pViewClass)) { // We found a view for this alreadycreated.pView->GetParentFrame()->ActivateFrame(); return; } } // This view doesn't exist for this document, create it CMDIChildWnd* pNewFrame = (CMDIChildWnd*)(pTemplate->CreateNewFrame(pDoc, NULL)); if (pNewFrame == NULL) return; // not created you may want to assert here ASSERT_KINDOF(CMDIChildWnd, pNewFrame);pTemplate->InitialUpdateFrame(pNewFrame, pDoc); }Finally, here are the handlers that are called to create or activate the views.
voidCMainFrame::OnViewFirstview() { CreateOrActivateFrame(((CVwSwtchApp*)AfxGetApp())->m_pFirst,RUNTIME_CLASS(CVwSwtchView)); } void CMainFrame::OnViewSecondview(){ CreateOrActivateFrame(((CVwSwtchApp*)AfxGetApp())->m_pSecond,RUNTIME_CLASS(CSecondView)); } void CMainFrame::OnViewThirdview() {CreateOrActivateFrame(((CVwSwtchApp*)AfxGetApp())->m_pThird,RUNTIME_CLASS(CThirdView)); }Generally, this is all that is necessary. Some flourishes are obvious though. The first of these is to modify the CreateOrActivateFrame() function so that only one of the views will be shown at a time. This is helpful because it restores proper functionality to the close button. Under the current implementation, the close button only closes the view if more than one view is open for a given document. This might be a desireable behavior in some cases, but in many cases this may be confusing to an end user.
The first step in this process is to use the pointer to the active view to get the WINDOWPLACEMENT for that view. This allows us to get the rect for the window.
WINDOWPLACEMENT WindowPlacement;VERIFY(pMDIActive->GetWindowPlacement(&WindowPlacement)); CRect rectView = WindowPlacement.rcNormalPosition; int iViewWidth = rectView.right - rectView.left; int iViewHeight = rectView.bottom - rectView.top;Next we will want to put our new view in the exact same location as the old one. This code is similar to the code we're currently using, but uses SetWindowPos to place the new frame.
CView* pView; POSITION pos= pDoc->GetFirstViewPosition(); while (pos != NULL){ pView= pDoc->GetNextView(pos); if (pView->IsKindOf(pViewClass)){ // move the new frame to the coordinates oftheold frame.. VERIFY(pView->GetParentFrame()->SetWindowPos( 0, rectView.left, rectViewtop, iViewWidth, iViewHeight,SWP_NOREPOSITION));pView->GetParentFrame()->ActivateFrame(WindowPlacement.showCmd); return; } } CMDIChildWnd* pNewFrame = (CMDIChildWnd*)(pTemplate->CreateNewFrame(pDoc, NULL)); if (pNewFrame == NULL) return; // not created ASSERT_KINDOF(CMDIChildWnd, pNewFrame); // move the new frame to the coordinates of the old frame.. // no need to pass the showCmd member of WindowPlacement. Since this window // was just created, it has the correct showCmd settings already..VERIFY(pNewFrame->SetWindowPos( 0, rSibling.left, rSibling.top, iSibWidth, iSibHeight, SWP_NOREPOSITION));You will also want to do additional testing in your activation handler. In the function below, we get the active document and then walk it's view list until we get the correct view. If no view of the correct type is in the view list for the document, then we will return null. We will use this to decide what to close when we've just called the activation handler.
///////////////////////////////// CView* CMainFrame::GetActiveView(CRuntimeClass* pViewClass) ///////////////////////////////// { CMDIChildWnd* pMDIActive = MDIGetActive(); ASSERT(pMDIActive != NULL); CDocument* pDoc = pMDIActive->GetActiveDocument(); ASSERT(pDoc != NULL); CView* pView; POSITION pos = pDoc->GetFirstViewPosition(); while (pos != NULL){ pView= pDoc->GetNextView(pos);if (pView->IsWindowEnabled()) { if(pView->IsKindOf(pViewClass)){ return pView; } } } return NULL; }CreateOrActivateFrame(((CVwSwtchApp*)AfxGetApp())->m_pFirst,RUNTIME_CLASS(CVwSwtchView)); CView* pCurView = GetActiveView(); if(pCurView.pPrintView->GetParentFrame()->SendMessage(WM_CLOSE);
Please tell me what you think about this content and how I might improve it.