SDI实现多视图并切换视图

首先在H文件有如下声明:

class CMultiViewApp : public CWinApp

{

public:

       CView* m_pFirstView;

       CView* m_pOtherView;

       int m_currentView;

       CView* m_pView2;

       CView* m_pView1;

       CMultiViewApp();

 

// Overrides

       // ClassWizard generated virtual function overrides

       //{{AFX_VIRTUAL(CMultiViewApp)

       public:

       virtual BOOL InitInstance();

       //}}AFX_VIRTUAL

 

// Implementation

       //{{AFX_MSG(CMultiViewApp)

       afx_msg void OnAppAbout();

       afx_msg void OnViewOtherview();

       afx_msg void OnViewFirstview();

       //}}AFX_MSG

       afx_msg void OnViewChange(UINT nCmdID);

       DECLARE_MESSAGE_MAP()

};

 

其次,在CPP文件有如下消息MAP:

/////////////////////////////////////////////////////////////////////////////

// CMultiViewApp

 

BEGIN_MESSAGE_MAP(CMultiViewApp, CWinApp)

       //{{AFX_MSG_MAP(CMultiViewApp)

       ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

       ON_COMMAND(ID_VIEW_OTHERVIEW, OnViewOtherview)

       ON_COMMAND(ID_VIEW_FIRSTVIEW, OnViewFirstview)

       //}}AFX_MSG_MAP

       // Standard file based document commands

       ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

       ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

       // Standard print setup command

       ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

       ON_COMMAND_RANGE( ID_VIEW_VIEW1, ID_VIEW_VIEW2, OnViewChange)    

END_MESSAGE_MAP()

 

说明:SDI程序在CMyApp::InitInstance()已经通过DocTemplate创建一个关联的视图/文档实例,切显示出来.具体实现如下:

BOOL CMultiViewApp::InitInstance()

{

       AfxEnableControlContainer();

 

       // Standard initialization

       // If you are not using these features and wish to reduce the size

       //  of your final executable, you should remove from the following

       //  the specific initialization routines you do not need.

 

#ifdef _AFXDLL

       Enable3dControls();                 // Call this when using MFC in a shared DLL

#else

       Enable3dControlsStatic();       // Call this when linking to MFC statically

#endif

 

       // Change the registry key under which our settings are stored.

       // TODO: You should modify this string to be something appropriate

       // such as the name of your company or organization.

       SetRegistryKey(_T("Local AppWizard-Generated Applications"));

 

       LoadStdProfileSettings();  // Load standard INI file options (including MRU)

 

       // Register the application's document templates.  Document templates

       //  serve as the connection between documents, frame windows and views.

 

       CSingleDocTemplate* pDocTemplate;

       pDocTemplate = new CSingleDocTemplate(

              IDR_MAINFRAME,

              RUNTIME_CLASS(CMultiViewDoc),

              RUNTIME_CLASS(CMainFrame),       // main SDI frame window

              RUNTIME_CLASS(CMultiViewView));

       AddDocTemplate(pDocTemplate);

 

       // Parse command line for standard shell commands, DDE, file open

       CCommandLineInfo cmdInfo;

       ParseCommandLine(cmdInfo);

 

       // Dispatch commands specified on the command line

       if (!ProcessShellCommand(cmdInfo))

              return FALSE;

 

       CView* pActiveView = ((CFrameWnd*) m_pMainWnd)->GetActiveView();

       m_pFirstView = pActiveView;

       m_pOtherView = (CView*) new COtherView;

 

       CDocument* pDoc = ((CFrameWnd*)m_pMainWnd)->GetActiveDocument();

       //通过CCreateContext实现第二视图和文档的关联

       CCreateContext context;

       context.m_pCurrentDoc = pDoc;

 

       UINT m_ID = AFX_IDW_PANE_FIRST + 1;

       CRect rect; 

       //为了演示第一种多视图是实现方法,把Vew的实例创建放在了这里

       m_pOtherView->Create(NULL, NULL, WS_CHILD, rect, m_pMainWnd, m_ID, &context);

 

       // The one and only window has been initialized, so show and update it.

       m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);

       m_pMainWnd->UpdateWindow();

       m_currentView=1;

       return TRUE;

}

1.     SDI单文档多视图实现方法1
void CMultiViewApp::OnViewOtherview() 

{

       // TODO: Add your command handler code here

       UINT temp = ::GetWindowLong(m_pOtherView->m_hWnd, GWL_ID);

    ::SetWindowLong(m_pOtherView->m_hWnd, GWL_ID, ::GetWindowLong(m_pFirstView->m_hWnd, GWL_ID));

    ::SetWindowLong(m_pFirstView->m_hWnd, GWL_ID, temp);

 

       m_pFirstView->ShowWindow(SW_HIDE);

       m_pOtherView->ShowWindow(SW_SHOW);       

 

       ((CFrameWnd*)m_pMainWnd)->SetActiveView(m_pOtherView);  

       ((CFrameWnd*) m_pMainWnd)->RecalcLayout();

    m_pOtherView->Invalidate();

       

}

 

void CMultiViewApp::OnViewFirstview() 

{

      // TODO: Add your command handler code here

    

    UINT temp = ::GetWindowLong(m_pOtherView->m_hWnd, GWL_ID); //GetWindowWord()

    ::SetWindowLong(m_pOtherView->m_hWnd, GWL_ID, ::GetWindowLong(m_pFirstView->m_hWnd, GWL_ID));//SetWindowWord()

    ::SetWindowLong(m_pFirstView->m_hWnd, GWL_ID, temp);//SetWindowWord()

 

      m_pOtherView->ShowWindow(SW_HIDE);

      m_pFirstView->ShowWindow(SW_SHOW);         

      

      ((CFrameWnd*)m_pMainWnd)->SetActiveView(m_pFirstView);  

      ((CFrameWnd*)m_pMainWnd)->RecalcLayout();

    m_pFirstView->Invalidate();

}

2.     SDI单文档多视图实现方法2
void CMultiViewApp::OnViewChange(UINT nCmdID)

{

       //另外一种方法实现SDI的多视图切换

       CView* pViewAdd;

       CView* pViewRemove;

       CMainFrame* pMainFrame=(CMainFrame*)AfxGetMainWnd();

       CDocument* pDoc = pMainFrame->GetActiveDocument();

       

       if((nCmdID == ID_VIEW_VIEW1) && (m_currentView == 1))

              return;

       if((nCmdID == ID_VIEW_VIEW2) && (m_currentView == 2))

              return;

       

       if (nCmdID == ID_VIEW_VIEW2)

       {

              if (m_pView2 == NULL)

              {

                     m_pView1 = pMainFrame->GetActiveView();

                     m_pView2 = new COtherView();

                     

                     //Note that if OnSize has been overridden in CMyView2 

                     //and GetDocument() is used in this override it can 

                     //cause assertions and, if the assertions are ignored,

                     //cause access violation.

                     //使用CCreateContext structure实现view和document的关联

                     CCreateContext context;

                     context.m_pCurrentDoc=pDoc;//m_pView1->GetDocument();

                     

                     m_pView2->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,

                            CFrameWnd::rectDefault, AfxGetMainWnd(), AFX_IDW_PANE_FIRST + 1, &context/*NULL*/);

              }

              pViewAdd = m_pView2;

              pViewRemove = m_pView1;

              m_currentView= 2;

       }

       else

       {

              pViewAdd = m_pView1;

              pViewRemove = m_pView2;

              m_currentView= 1;

       }

       

       // Set the child i.d. of the active view to AFX_IDW_PANE_FIRST,

       // so that CFrameWnd::RecalcLayout will allocate to this 

       // "first pane" that portion of   the frame window's client area 

       // not allocated to control   bars.  Set the child i.d. of the 

       // other view to anything other than AFX_IDW_PANE_FIRST; this

       // examples switches the child id's of the two views.

       

       int nSwitchChildID = pViewAdd->GetDlgCtrlID();

       pViewAdd->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

       pViewRemove->SetDlgCtrlID(nSwitchChildID);

       

       // Show the newly active view and hide the inactive view.

       

       pViewAdd->ShowWindow(SW_SHOW);

       pViewRemove->ShowWindow(SW_HIDE);

       

       // Connect the newly active view to the document, and

       // disconnect the inactive view.

       //通过CCreateContext实现视图View和文档Document的关联

       //就没有必要手动AddView(),如果需要可以进行手动RemoveView()

       //AddView()会在CView::OnCreate()被MFC调用,RemoveView()会在CView::~CView()被调用

       //当然可以根据需要手动调用它们,在本例当中,View都是被创建一次,没有被销毁,所以不会自动

       //调用RemoveView()

       //pDoc->AddView(pViewAdd);

       //pDoc->RemoveView(pViewRemove);

       

       pMainFrame->SetActiveView(pViewAdd);

       pMainFrame->RecalcLayout();

       

       return ;

}

  
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:

 Collapse Copy Code
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):

 Collapse Copy Code
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:

 Collapse Copy Code
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:

 Collapse Copy Code
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:

 Collapse Copy Code
   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):

 Collapse Copy Code
/* 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:

 Collapse Copy Code
   CView* pOldActiveView = SwitchToView(m_pView1);
   if (!pOldActiveView)
      // there was not an active view
   else
      // pOldActiveView is a pointer to the now inactive viewNote that inactive views destroy themselves when their parent window is destroyed, so you don't have to worry about destroying them.
posted on 2010-05-30 21:31  carekee  阅读(1056)  评论(0编辑  收藏  举报