Click here to Skip to main content
16,014,709 members
Articles / Desktop Programming / MFC
Article

Switching to other views in a doc-view application

Rate me:
Please Sign up or sign in to vote.
4.50/5 (10 votes)
28 Dec 1999 195.2K   76   26
Switching to other views in a doc-view application.

Introduction

In a previous article, we discussed how to replace views in a document-view application. The views to replace were destroyed, and new views were created to replace them. Sometimes it's better to preserve the views and use them later rather than to destroy and recreate them. In this article, we show how to switch to other views without destroying the old ones, so we could use them later.

In the code below, the inactive views are disconnected from the document (so they don't receive OnUpdate messages) using CDocument::RemoveView. When needed, they are reconnected to the document and resynchronized using CDocument::AddView. In order to keep the inactive views synchronized with the document, we should have to implement custom hints (using the pHint parameter in the CView::OnUpdate member function). The views are hidden and shown using the CWnd::ShowWindow member function.

The Code

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 as a parameter and return the view that was replaced. This returned view is not contained in the document's list anymore. 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:

CView* CMyDocument::SwitchToView ( CView* pNewView )
{
   CFrameWnd* pMainWnd = (CFrameWnd*)AfxGetMainWnd();
   CView* pOldActiveView = pMainWnd->GetActiveView();
   ASSERT(pOldActiveView != NULL);
   ASSERT_VALID(pOldActiveView);
   ASSERT(pOldActiveView->GetDocument() == this); // must be attached to us

   /* 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);
   ::SetWindowLong(pNewView->m_hWnd, GWL_ID, AFX_IDW_PANE_FIRST);

   // Show the newly active view and hide the inactive view.
   pNewView->ShowWindow(SW_SHOW);
   pOldActiveView->ShowWindow(SW_HIDE);

   // Connect the newly active view to the document,
   // and disconnect the inactive view
   AddView(pNewView); 
   RemoveView(pOldActiveView);
   pMainWnd->SetActiveView(pNewView);
   pMainWnd->RecalcLayout();

   return pOldActiveView;
}

In the case of an MDI application (again without splitters):

CView* CMyDocument::SwitchToView ( CView* pNewView )
{
   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();

   // Set flag so that document will not be deleted when view is dettached.
   BOOL bAutoDelete = m_bAutoDelete;
   m_bAutoDelete = FALSE;

   // Dettach existing view
   RemoveView(pOldActiveView);

   // restore flag
   m_bAutoDelete = bAutoDelete;

   // Show the newly active view and hide the inactive view.
   pNewView->ShowWindow(SW_SHOW);
   pOldActiveView->ShowWindow(SW_HIDE);

   // Attach new view
   AddView(pNewView);

   pChild->RecalcLayout();
   pNewView->UpdateWindow();
   pChild->SetActiveView(pNewView);
   return pOldActiveView;
}

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:

CView* CSDISplitDoc::SwitchToView ( CView* pNewView )
{
/* Uncomment this if this is a SDI application
   CFrameWnd* pMainWnd   = (CFrameWnd*)AfxGetMainWnd();
   CView* pOldActiveView = pMainWnd->GetActiveView();
*/

/* Uncomment this if this is 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();
*/

   CSplitterWnd* pSplitter = (CSplitterWnd *)pOldActiveView->GetParent();
   int row, col;
   ASSERT(pSplitter->IsChildPane(pOldActiveView, row, col));

   // set flag so that document will not be deleted when view is destroyed
   m_bAutoDelete = FALSE;    

   // Dettach existing view
   RemoveView(pOldActiveView);

   // set flag back to default 
   m_bAutoDelete = TRUE;
 
   /* Set the child window ID of the active view to the ID of the corresponding
      pane. Set the child ID of the previously active view to some other ID.
   */
   ::SetWindowLong(pOldActiveView->m_hWnd, GWL_ID, 0);
   ::SetWindowLong(pNewView->m_hWnd, GWL_ID, pSplitter->IdFromRowCol(row, col));

   // Show the newly active view and hide the inactive view.
   pNewView->ShowWindow(SW_SHOW);
   pOldActiveView->ShowWindow(SW_HIDE);

   // Attach new view
   AddView(pNewView);

   // Set active 
   pSplitter->GetParentFrame()->SetActiveView(pNewView);
   
   pSplitter->RecalcLayout(); 
   pNewView->SendMessage(WM_PAINT); 

   return pOldActiveView;
}

The SwitchToView functions above receive a pointer to an existing view, so a view must have already been created without attaching it to a document. Note that this imposes restrictions on view creation code, which should not make use of the document in any way (for example, the OnInitialUpdate member function). Otherwise, exceptions might occur. The newly activated view is shown before it is attached to the document, so functions in the view that respond to Windows messages such as WM_SIZE or WM_GETMINMAXINFO should not make use of the document either.

The view must be created with correct parent window and window ID. Both parameters depend on the frame windows containing the view, just the same as the SwithToView function. The non-active views could be created the first time the menu to select one of them was selected or somewhere in the document initialization code. Supposing we have a m_pView1 member in the document class that is a pointer to a view, this is how it should be created in a SDI application:

if (!m_pView1)
{
   // create the new view
   m_pView1 = new CView1;
   m_pView1->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CFrameWnd::rectDefault, 
   AfxGetMainWnd(), AFX_IDW_PANE_FIRST+1, NULL);
}

In a MDI application:

   CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd();
   // Get the active MDI child window.
   CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();

   if (!m_pView1)
   {
   // create the new view
   m_pView1 = new CView1;
   m_pView1->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), 
                   pChild, AFX_IDW_PANE_FIRST, NULL);
}

And finally, if the view is a pane of a splitter window (read the comments to difference between SDI and MDI applications):

/* Uncomment this if this is a SDI application
   CFrameWnd* pMainWnd = (CFrameWnd*)AfxGetMainWnd();
   CView* pActiveView = pMainWnd->GetActiveView();
   CSplitterWnd* pSplitter = (CSplitterWnd *)pActiveView->GetParent();
*/

/* Uncomment this if this is a MDI application
   CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd();
   CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();
   CView* pActiveView = pChild->GetActiveView();
   CSplitterWnd* pSplitter = (CSplitterWnd *)pActiveView->GetParent();
*/

   if (!m_pView1)
   {
      // create the new view
      m_pView1 = new CView1;
      m_pView1->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, 
               CRect(0, 0, 0, 0),  pSplitter, 0, NULL);
   }

When we already have an existing view (m_pView1 in our example), we can make this view active as follows:

CView* pOldActiveView = SwitchToView(m_pView1);
if (!pOldActiveView)
   // there was not an active view
else
   // pOldActiveView is a pointer to the now inactive view

Note that inactive views destroy themselves when their parent window is destroyed, so you don't have to worry about destroying them.

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


Written By
Cuba Cuba
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA12-Oct-23 18:18
professionalȘtefan-Mihai MOGA12-Oct-23 18:18 
QuestionA Sample App Pin
Maddison Joyce17-Feb-09 0:35
Maddison Joyce17-Feb-09 0:35 
GeneralDialog inside CView Pin
guyjasper7-Nov-07 6:09
guyjasper7-Nov-07 6:09 
GeneralRe: Dialog inside CView Pin
sharon3-Apr-08 19:52
sharon3-Apr-08 19:52 
GeneralRe: Dialog inside CView Pin
Nigel Quinnin28-Aug-08 8:25
Nigel Quinnin28-Aug-08 8:25 
Questionexample project? Pin
forhug3-Apr-06 22:06
forhug3-Apr-06 22:06 
Generalvc++ help Pin
Anonymous30-Dec-04 18:47
Anonymous30-Dec-04 18:47 
GeneralSwitchView , big problem Pin
New Student10-Dec-04 13:17
New Student10-Dec-04 13:17 
GeneralCreating and switching views: a possible solution (updated) Pin
T.T.H.28-Oct-04 4:54
T.T.H.28-Oct-04 4:54 
Okeydokey, there is a helpful article written by Jorge but for me it left some questions open, basically the question "where to create the views and how to get the pointers to them?". So I investigated elsewhere and found a solution to my personal problem and now I try to share my found knowledge with you.

First of all: I do have Visual Studio .NET 2003 and I (still) write MFC applications. My future application should have multiple documents and for each document multiple (and different!) views should be displayable - not all views at once like in a splitter but "switchable" by some buttons in the toolbar. For achieving this I used Jorge's advices for MDI applications without a splitter but added (next to two days of searching in the internet) a lot of my own code. I will now present everything in a "do this, then do that, afterwards do..." manual. So 'ere we go:

With you project wizard create an MFC application (in my case it is called "TestManyViews") with MDI support. I derived the main view from CView but you can choose something else of course.

In the function "CTestManyViewsView::OnDraw" insert the following (if you hate big, white areas as I do):
pDC->TextOut(10, 10, "this is the original CView derived view");


Now insert another MFC class - in my case derived from a CFormView and called "CSomeFormView". Please note: do not check the checkbox "create DocTemplate ressources"!. You will recieve a new class and a new dialog resource (in my case called "IDD_SOMEFORMVIEW") by doing this.

In "CTestManyViewsDoc.h" add at top:
#include "SomeFormView.h"

In the class definition of CTestManyViewsDoc add the following:
private:
  CView* m_pOriginalView;
  CView* m_pSomeFormView;

Update 6 April 2005: In "CTestManyViewsDoc.h" add at top:
#define CTRLID_ORIGINALFORMVIEW AFX_IDW_PANE_FIRST + 1
#define CTRLID_SOMEFORMVIEW AFX_IDW_PANE_FIRST + 2

In the constructor of "CTestManyViewsDoc" add the following:
CTestManyViewsDoc::CTestManyViewsDoc()
{
  // play it safe and initialize the pointers
  m_pOriginalView = NULL;
  m_pSomeFormView = NULL;
}

In the document's function "OnNewDocument" add the following:
Update 6 April 2005: AFX_WS_DEFAULT_VIEW replaced with CTRLID_SOMEFORMVIEW
BOOL CTestManyViewsDoc::OnNewDocument()
{
  if (!CDocument::OnNewDocument())
    return FALSE;

  // important note: I do no error handling in this example...

  // get pointer to the original view and remember it
  POSITION pos = GetFirstViewPosition();
  if (pos != NULL)
  {
    m_pOriginalView = GetNextView(pos);
  }

  // get the active MDI child window.
  CMDIFrameWnd* pMainWnd = (CMDIFrameWnd*)AfxGetMainWnd();
  CMDIChildWnd* pChild = (CMDIChildWnd*)pMainWnd->MDIGetActive();

  // allocate a new view
  CSomeFormView* pSomeFormView = new CSomeFormView;

  // create the new view
  pSomeFormView->MyCreate(NULL, NULL, CTRLID_SOMEFORMVIEW, CRect(0, 0, 0, 0), 
    pChild, AFX_IDW_PANE_FIRST, NULL);

  // hide the new view
  pSomeFormView->ShowWindow(SW_HIDE);

  // remember the pointer to the new view
  m_pSomeFormView = pSomeFormView;

  return TRUE;
}

In the class "CTestManyViewsDoc" add the following (private) function (which is from Jorge's article):
Update 6 April 2005: I've ran into trouble with Jorge's solution and found out that the active view must have the control ID AFX_IDW_PANE_FIRST. So before removing the old view and attaching the new view you must exchange the control IDs.
CView* CTestManyViewsDoc::SwitchToView(CView* pNewView)
{
  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();

  // Exchange control ID of old view
  // note: if you have more than two view you have to remember which view you switched to
  // so you can set it's old control ID correctly
  if (pNewView == m_pOriginalView) pOldActiveView->SetDlgCtrlID(CTRLID_SOMEFORMVIEW);
  if (pNewView == m_pSomeFormView) pOldActiveView->SetDlgCtrlID(CTRLID_ORIGINALFORMVIEW);

  // Exchange control ID of new new
  // note: the control ID of the active view must always be AFX_IDW_PANE_FIRST
  pNewView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
  
  // Set flag so that document will not be deleted when view is dettached.
  BOOL bAutoDelete = m_bAutoDelete;
  m_bAutoDelete = FALSE;

  // Dettach existing view
  RemoveView(pOldActiveView);

  // restore flag
  m_bAutoDelete = bAutoDelete;

  // Show the newly active view and hide the inactive view.
  pNewView->ShowWindow(SW_SHOW);
  pOldActiveView->ShowWindow(SW_HIDE);

  // Attach new view
  AddView(pNewView);

  pChild->RecalcLayout();
  pNewView->UpdateWindow();
  pChild->SetActiveView(pNewView);
  return pOldActiveView;
}

Now to the form view: in "SomeFormView.h" there are the following lines within the class definition:
protected:
  CSomeFormView(); // dynamic creation uses protected constructor
  virtual ~CSomeFormView();

Just change the word "protected" in "public", otherwise you can not allocate this new class (please pardon me if I break some "clean coding" rules by doing this, but I simply don't know better).

Additionally add the following (public) function to the class "CSomeFormView" (based on the article comment from Rolando E. Cruz-Marshall):
Update 6 April 2005: Jorge mentions in his article that it's tricky to create a view without an attached document and that you have to care that the view's function don't call the document and that even OnInitialUpdate might be dangerous. Since I don't know better I can just warn you. In my application it currently works with and without OnInitialUpdate within MyCreate.
BOOL CSomeFormView::MyCreate(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle,
  const RECT &rect, CWnd *pParentWnd, UINT nID, CCreateContext *pContext)
{
  BOOL bOK = Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
  // important warning: Jorge says calling OnInitialUpdate within a view without
  // an attached document is dangerous! I'm not sure whether it is bad or good to
  // call OnInitialUpdate here!
  OnInitialUpdate();
  return bOK;
}

Now in the resources of your project in the menu called "IDR_TestManyViewsTYPE" under "View" add the entries
"Show View 0" ("ID_VIEW_SHOWVIEW0") and "Show View 1" ("ID_VIEW_SHOWVIEW1").

In the class CTestManyViewsDoc add handler function for those two IDs:
void CTestManyViewsDoc::OnViewShowview0()
{
  if (m_pOriginalView != NULL) SwitchToView(m_pOriginalView);
}

void CTestManyViewsDoc::OnViewShowview1()
{
  if (m_pSomeFormView != NULL) SwitchToView(m_pSomeFormView);
}

Of course you can now add some buttons to the your toolbar, too


Voila! Should be done now. Compile and run.


You get a simple application where each document can be viewed with different views, switchable by buttons on the toolbar


Please notice that there are some open issues:

The title names of the windows has some hickups I haven't investigated yet.

The memory allocation of the CSomeFormView class is probably incomplete - but my Visual Studio doesn't tell me of any memory leaks so I dare to post it here even in the knowledge that I didn't understand view creation and handling completely.


Hope that helped,
Matthias / T.T.H.
GeneralNew Document Pin
Fad B19-Sep-03 1:30
Fad B19-Sep-03 1:30 
GeneralSwitching to other view with CRecordView class Pin
Rod Enns27-Jun-03 10:43
Rod Enns27-Jun-03 10:43 
GeneralSplitter in one of the switching views Pin
Anonymous9-Apr-03 19:41
Anonymous9-Apr-03 19:41 
GeneralProblem creating view Pin
VanHlebar29-Nov-02 9:35
VanHlebar29-Nov-02 9:35 
GeneralRe: Problem creating view Pin
Rolando Cruz1-Dec-02 16:20
Rolando Cruz1-Dec-02 16:20 
GeneralRe: Problem creating view Pin
T.T.H.5-Apr-05 23:56
T.T.H.5-Apr-05 23:56 
QuestionMore than one View at one time ? Pin
akraus2-Oct-01 1:14
akraus2-Oct-01 1:14 
Questionbarcode ?? Pin
islm7810-Aug-00 17:03
islm7810-Aug-00 17:03 
AnswerRe: barcode ?? Pin
11-Jan-01 11:18
suss11-Jan-01 11:18 
GeneralRe: barcode ?? Pin
Jose Cruz9-Jul-01 13:42
Jose Cruz9-Jul-01 13:42 
GeneralChanging Documents Pin
FranzAKlein9-Jul-00 1:24
FranzAKlein9-Jul-00 1:24 
Questionwindows 2000? Pin
Jeff Ellis26-Jun-00 17:30
sussJeff Ellis26-Jun-00 17:30 
AnswerRe: windows 2000? Pin
28-Feb-01 23:50
suss28-Feb-01 23:50 
GeneralThis is a very useful sample Pin
Jeff23-May-00 17:33
Jeff23-May-00 17:33 
Generalpassing CView* to document class Pin
Alicia4-Apr-00 19:04
Alicia4-Apr-00 19:04 
GeneralRe: passing CView* to document class Pin
jose26-Jul-00 2:34
jose26-Jul-00 2:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.