Replacing a view in a doc-view application

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 wont 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 implement 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. Lets start with an SDI application that doesnt have splitters:


BOOL CMyDocument::SwitchToView(CRuntimeClass* pNewViewClass)
{
	CFrameWnd* pMainWnd = (CFrameWnd*)AfxGetMainWnd();
	CView* pOldActiveView = pMainWnd->GetActiveView();

	// If we're already displaying this kind of view, no need to go further.
	if (pOldActiveView->IsKindOf(pNewViewClass))
		return TRUE;
	
	// Set the child window ID of the active view to AFX_IDW_PANE_FIRST.
	// This is necessary so that CFrameWnd::RecalcLayout will allocate
	// this "first pane" to that portion of the frame window's client
	// area not allocated to control bars.  Set the child ID of
	// the previously active view to some other ID.
	::SetWindowLong(pOldActiveView->m_hWnd, GWL_ID, 0);

	// create the new view
	CCreateContext context;
	context.m_pNewViewClass = pNewViewClass;
	context.m_pCurrentDoc = this;
	CView* pNewView = STATIC_DOWNCAST(CView, pMainWnd->CreateView(&context));
	if (pNewView != NULL)
	{
		// the new view is there, but invisible and not active...
		pNewView->ShowWindow(SW_SHOW);
		pNewView->OnInitialUpdate();
		pMainWnd->SetActiveView(pNewView);
		pMainWnd->RecalcLayout();

		// destroy the old view...
		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();
	// Get the active MDI child window.
	CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();
	// Get the active view attached to the active MDI child window.
	CView* pOldActiveView = pChild->GetActiveView();
	// If we're already displaying this kind of view, no need to go further.
	if (pOldActiveView->IsKindOf(pNewViewClass))
		return TRUE;
	
	// Set flag so that document will not be deleted when view is destroyed.
	BOOL bAutoDelete = m_bAutoDelete;
	m_bAutoDelete = FALSE;
	// Delete existing view
	pOldActiveView->DestroyWindow();
	// restore flag
	m_bAutoDelete = bAutoDelete;
	
	// Create new view.
	CView* pNewView = (CView *)pNewViewClass->CreateObject();
	if (pNewView == NULL)
	{
		TRACE1("Warning: Dynamic create of view type %Fs failed\n", pNewViewClass->m_lpszClassName);
		return FALSE;
	}

	// Draw new view.
	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);  // WM_INITIALUPDATE is defined in afxpriv.h
	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 dont need depending on your application type:


BOOL CMyDocument::SwitchToView(CRuntimeClass* pNewViewClass)
{
/*	Uncomment this if this is a SDI application

	CFrameWnd* pMainWnd = (CFrameWnd*)AfxGetMainWnd();
	CView* pOldActiveView = pMainWnd->GetActiveView();
*/
/*	Uncomment this if this a MDI application

	CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd();
	// Get the active MDI child window.
	CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();
	// Get the active view attached to the active MDI child window.
	CView* pOldActiveView = pChild->GetActiveView();
*/
	// If we're already displaying this kind of view, no need to go further.
	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);

	// set flag so that document will not be deleted when view is destroyed
	m_bAutoDelete = FALSE;    
	// Delete existing view 
	pOldActiveView->DestroyWindow();
	// set flag back to default 
	m_bAutoDelete = TRUE;
 
	// Create new view                      
	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;

	// Set active 
	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, lets 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))
		// failed
	else
		// succeeded

One final word to the class wizard fans. When you have a descendant of a CView descendant, the class wizard wont 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.


Last updated: January 15, 1999



Comments

  • Nice one there

    Posted by Slalaleasyday on 03/12/2013 02:32pm

    Nice Post. ---------- I love http://youtube.com

    Reply
  • MDI switch view without destroying window (and adding new view properly)

    Posted by mschuck on 07/05/2005 05:15pm

    After messing arround with the code supplied and some other code i found from microsoft i came up with this. don't forget this implementation is for MDI applications i recommend moving this function to the CMDIChildWnd subclass for a few reasons: 1. A document should not be controling it's views (that's the job of the parent * (ie : CMDIChildWnd) 2. it just plain makes more sense *a view should not manage it's document, as this is also the job of the parent. because the view is not destroyed i found it more suitable in my code to maintain the references to the views elsewhere and pass a reference to the new view rather then the CRuntimeClass. if you manage the references in the same class that controls switching views and each View (class) has only one instance (one to one relationship) then you could pass the runtime class and iterate over your collection of views to find the coresponding view void CMDIChildWndSubclass::SwitchToView(CView * pNewView) { //get main wnd CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd(); // Get the active MDI child window. CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive(); // Get the active view attached to the active MDI child window. CView* pOldActiveView = pChild->GetActiveView(); //Get the coresponding document CDocument * doc = pChild->GetActiveDocument(); // If we're already displaying this kind of view, no need to go further. if (pOldActiveView == pNewView) return; //this ensures proper client area it used for RecalcLayout int oldID = pNewView->GetDlgCtrlID(); pNewView->SetDlgCtrlID(AFX_IDW_PANE_FIRST); pOldActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST + 1); //don't show the old view pOldActiveView->ShowWindow(SW_HIDE); //disconnect all views (the old view) doc->DisconnectViews(); //add the new view doc->AddView(pNewView); //show the new view pNewView->ShowWindow(SW_SHOW); //recalc the layout pChild->RecalcLayout(); //invalidate the new view to ensure it is repainted in UpdateWindow (in case the document was changed while it wasn't active) pNewView->Invalidate(); //Update the view pNewView->UpdateWindow(); //set the active view pChild->SetActiveView(pNewView); } if you're wondering about how to create the view... it really doesn't matter as long as the view is valid and has been created (view->Create(...)) i can only assume this works (i had to remove a few things unique to my application) and so i leave no guarantee whatsoever to this code, use at your own risk. Bugs? Questions? Comments? email me: ark_schuck@hotmail.com Mark

    Reply
  • VC++ .NET int* vs int for IsChildPane and OnInitialUpdate

    Posted by rschultz333 on 05/17/2004 03:34am

    Worked great however, for VC++.NET(7) I had to
    substitute:
    ASSERT(pSplitter->IsChildPane(pOldActiveView, &row, &col));
     for
    ASSERT(pSplitter->IsChildPane(pOldActiveView, row, col));
    I'm not sure if this is a change in IsChildPane, a typo, or my mistake but passing the address works for me.
    
    Additionally, I had a similar problem with initializing various controls in the OnInitialUpdate function.  It seems
    as though it was not getting called for the non-default View.  Anyway, it was fixed by performing the following:
    
    CRuntimeClass* pNewViewClass = RUNTIME_CLASS(CForm);
    	if (!SwitchToView(pNewViewClass))
    		TRACE ("Didn't Do CForm\n");
    	else
    	{
    		CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd();
    		// Get the active MDI child window.
    		CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();
    		// Get the active view attached to the active MDI child window.
    		CView* pActiveView = pChild->GetActiveView();
    		pActiveView->OnInitialUpdate();
    		TRACE ("CForm Worked \n");
    	}

    Reply
  • Timing Problems with InitialUpdate( )

    Posted by Legacy on 08/29/2003 12:00am

    Originally posted by: Heiko Kortlang

    I have a problem with the InitialUpdate Function in my FormView class, cause i initialize my ComboBoxes in the InitialUpdate(). In release version the program crashes because the ComboBoxes are not created.

    In my FormView class i added the
    ON_MESSAGE(WMU_INITIALUPDATE, OnInitialUpdate)

    How can i delay the event to trigger the InitialUpdate()

    Reply
  • Thank You !! That was EXACTLY what i needed !

    Posted by Legacy on 08/22/2003 12:00am

    Originally posted by: Filip Rak

    Thanks for a piece of great work, ive found it after sleepless night when trying to do it alone !!
    Agan - thank You !!

    Reply
  • Switch to other view from CHtmlView

    Posted by Legacy on 05/14/2003 12:00am

    Originally posted by: Lie Gu

    You cannot simplly switch to CHtmlView from other views. I found this problem when I was trying to write an outlook style interface. Here is the solution. 
    
    

    First let us take a look at the source code of ChtmlView in MFC:

    /////////////////////////////////////////////////////////////////////////////
    // CHtmlView message handlers

    void CHtmlView::OnDestroy()
    {
    RELEASE(m_pBrowserApp);
    }

    The amazing happens here, it seems it dose not call the CView.OnDestory function before release m_pAPP. Let's go on to look CView::OnDestroy() function:

    void CView::OnDestroy()
    {
    CFrameWnd* pFrame = GetParentFrame();
    if (pFrame != NULL && pFrame->GetActiveView() == this)
    pFrame->SetActiveView(NULL); // deactivate during death
    ~~~~~~~~~~~~~~~~~~~~~~~~~
    CWnd::OnDestroy();
    }

    When you destroy a ChtmlView, the "ActiveView" remains unchanged. That's where the exception comes from. So the solution is very simple, inherited from CHtmlView and rewrite the OnDestroy function.

    class CHtmlView2 : public CHtmlView
    {
    protected:
    CHtmlView2(); // protected constructor used by dynamic creation
    DECLARE_DYNCREATE(CHtmlView2)
    …………………..
    …………………..


    // Generated message map functions
    //{{AFX_MSG(CHtmlView2)
    afx_msg void OnDestroy();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

    /////////////////////////////////////////////////////////////////////////////
    // CHtmlView2 message handlers

    void CHtmlView2::OnDestroy()
    {
    CHtmlView::OnDestroy();
    CView::OnDestroy();
    // TODO: Add your message handler code here

    }


    • Thankx

      Posted by anilincodeguru on 03/13/2006 07:14am

      I was searching for this problem and suddenly found it from your post. So can't stop myself to thank you.

      Reply
    Reply
  • Maximized and Normal Position Error

    Posted by Legacy on 04/11/2003 12:00am

    Originally posted by: Oliver

    I use your code and I'm very satisfied with it. But I have one problem. If I maximize a view and the change to another view, this next view is not maximized but the system menu (minimize, normal and close button) are located at the position of a maximized view. This means these buttons are at the right hand side of the programs menu.

    How to solve this problem? How can I ensure that a maximize view is shown as maximized (using the whole area) or how to force the new view to be shown in a normal size?

    Oliver

    Reply
  • Creating a second recordview and switching to and fro.

    Posted by Legacy on 02/24/2003 12:00am

    Originally posted by: DGK

    Is it possible to create a second and third recordview, each with its own recordset and how does one switch between these views?

    Reply
  • Doesnt work with usercontrol

    Posted by Legacy on 07/23/2002 12:00am

    Originally posted by: Freudi

    if i have a usercontrol on one of the forms you cant create the pane. Any idea.
    I use vc60 under XP/2000

    Reply
  • Switch from RecordView to RecordView

    Posted by Legacy on 07/17/2002 12:00am

    Originally posted by: Dark

    works not, perhaps because CRecordView is from CFormView?

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds