一個單文檔界面中存在多個視圖,並且可以根據需要進行視圖的動態切換,這是當前比較流行的界面風格,它可以滿足許多用戶在操作和顯示方面的需要。這種界面風格的主要代表軟件是Outlook Express。而用VC++實現這種風格的界面有一定難度,筆者就這個問題進行了研究,並歸納總結出兩種實現方法(這些代碼都在VC++ 6.0下調試通過),使用時關鍵注意步驟和實現思路,不必拘泥於代碼的形式。
方法一:靜態創建切換法
步驟描述:
1.在窗口顯示之前先將需要切換的所有的視圖對象創建好,除首先顯示的視圖以外,其他在創建時都設置為不可見屬性。
CMyWinApp::InitInstance()
{ ......
m_pViews[0] = pView1;
m_pViews[1] = (CView*) new CView2;
CDocument* pCurrentDoc = ((CFrameWnd*) m_pMainWnd)->GetActiveDocument();
// 初始化創建上下文相關指針
CCreateContext newContext;
newContext.m_pNewViewClass = NULL;
newContext.m_pNewDocTemplate = NULL;
newContext.m_pLastView = NULL;
newContext.m_pCurrentFrame = NULL;
newContext.m_pCurrentDoc = pCurrentDoc;
// 最初激活視的ID為AFX_IDW_PANE_FIRST,
//對新創建的視圖增加這個值,注意對CSplitterWnd不能這樣使用
UINT viewID[2];
viewID[1] = AFX_IDW_PANE_FIRST + 1;
CRect rect(0, 0, 0, 0);
for ( int nView=1; nView<NUMVIEWS; nView++ ) {
// 創建新的視圖,創建的視圖在應用中永久存在,直到應用程序退出,
//應用程序會自動刪除新創建的視圖
m_pViews[nView]->Create(NULL, NULL,
(AFX_WS_DEFAULT_VIEW & ~WS_VISIBLE),
// AFX_WS_DEFAULT_VIEW代表(WS_BORDER | WS_VISIBLE | WS_CHILD)
rect, m_pMainWnd, viewID[nView], &newContext);
}
// 當文檔模板創建視圖的時候,會自動發送WM_INITIALUPDATE消息,
//因此對於我們自己創建的視圖,需要人工發送這條消息
((CForm2*)m_pViews[1])->OnInitialUpdate();
((CVswapView*)m_pViews[2])->OnInitialUpdate();
......
}
2.視圖的切換
CView* CMyWinApp::SwitchView( UINT nIndex )
{
ASSERT( nIndex >=0 && nIndex < NUMVIEWS );
CView* pNewView = m_pViews[nIndex];
CView* pActiveView =((CFrameWnd*) m_pMainWnd)->GetActiveView();
if ( !pActiveView ) // 當前沒有激活的視圖
return NULL;
if ( pNewView == pActiveView ) // 當前視圖和需要切換的視圖相同
return pActiveView;
// 交換視圖的窗口ID,使RecalcLayout()可以工作
UINT temp = ::GetWindowLong(pActiveView->m_hWnd, GWL_ID);
::SetWindowLong(pActiveView->m_hWnd, GWL_ID, ::GetWindowLong(pNewView->m_hWnd, GWL_ID));
::SetWindowLong(pNewView->m_hWnd, GWL_ID, temp);
// 顯示新的視圖,隱藏前一個視圖
pActiveView->ShowWindow(SW_HIDE);
pNewView->ShowWindow(SW_SHOW);
((CFrameWnd*) m_pMainWnd)->SetActiveView(pNewView);
((CFrameWnd*) m_pMainWnd)->RecalcLayout();
pNewView->Invalidate();
return pActiveView;
}
方法二:動態創建切換法
步驟描述:
1.刪除當前的視圖
首先需要獲得當前視圖的指針,不能使用GetActiveView()和GetActiveDocument()這兩個函數,當前視圖有可能處在未激活狀態,
所以應該使用EnumChildWindows這個Win32API函數,函數定義如下:
BOOL EnumChildWindows(
HWND hWndParent, // 父窗口的句柄
WNDENUMPROC lpEnumFunc, // 用戶自定義回調函數
LPARAM lParam // 傳給回調函數的自定義參數
);
回調函數的定義如下:
BOOL CALLBACK EnumChildProc(
HWND hwnd, // 字窗口的句柄
LPARAM lParam // 自定義參數
);
EnumChildWindows函數遍歷父窗口的所有子窗口,遞歸調用用戶定義的回調函數,當回調函數返回FALSE時,停止遍歷,
至於何時返回FALSE,這根據用戶自己需要編寫的回調函數來決定。
刪除視圖使用DeleteWindow()這個函數,用delete也可以刪除,但還要其他底層的操作,這裡就不詳細介紹了,因為刪除視圖使用DeleteWindow()最合適、方便了。在刪除視圖的時候還要注意不能將文檔同時自動刪除。
刪除視圖的代碼如下:
{ ......
CWnd* pWnd;
CWnd* pWndToDelete;
// 使用EnumChildWindows查找從CView繼承的子窗口
::EnumChildWindows(m_hWnd, MyWndEnumProc, (LPARAM)&(pWnd));
if(pWnd == NULL)
{// 沒有發現子窗口
return FALSE;}
// 發現子窗口,找到級別最高的子窗口,即父窗口為CMainFrame的窗口
while( lstrcmp(pWnd->GetRuntimeClass()->m_lpszClassName, ″CMainFrame″) )
{
pWndToDelete = pWnd;
pWnd = pWnd->GetParent();
}
// 確保視圖被刪除時文檔不被刪除
pDoc->m_bAutoDelete = FALSE;}
// 刪除視圖
pWndToDelete->DestroyWindow();
pDoc->m_bAutoDelete = TRUE;
......
}
用戶定義的回調函數:
BOOL CALLBACK MyWndEnumProc(HWND hWnd, LPARAM ppWndLPARAM)
{
CWnd* pWndChild = CWnd::FromHandlePermanent(hWnd);
CWnd** ppWndTemp = (CWnd**)ppWndLPARAM;
if( pWndChild && pWndChild->IsKindOf(RUNTIME_CLASS(CView)) )
{
// 發現任何從CView繼承的子窗口,將子窗口指針傳遞出去
*ppWndTemp = pWndChild;
// 停止繼續搜索
return FALSE;
}
else
{
*ppWndTemp = NULL;
// 繼續搜索
return TRUE;
}}
2.創建新的視圖
CDocument* pCurrentDoc = ((CFrameWnd*) m_pMainWnd)->GetActiveDocument();
// 初始化創建上下文相關指針
CCreateContext newContext;
newContext.m_pNewViewClass = RUNTIME_CLASS(CView1);
newContext.m_pCurrentDoc = pCurrentDoc;
newContext.m_pNewDocTemplate = NULL;
newContext.m_pLastView = NULL;
newContext.m_pCurrentFrame = NULL;
CView* pNewView = STATIC_DOWNCAST(CView, CreateView(&newContext));
if( pNewView == NULL )
{
return FALSE;
}
// 使用CreateView創建的視圖不能自動調用OnInitialUpdate函數,需要人工調用OnInitialUpdate函數或者發送WM_INITIALUPDATE消息
pNewView->OnInitialUpdate();
// 使用CreateView創建的視圖不會自動顯示並且激活,需要人工操作
pNewView->ShowWindow(SW_SHOW);
SetActiveView(pNewView);
RecalcLayout();
注:RUNTIME_CLASS宏含義
每一個從CObject類繼承的類,在定義DECLARE_DYNAMIC、DECLARE_DYNCREATE、DECLARE_SERIAL三個中任意一個宏時都會產生一個CRuntimeClass結構的靜態對象,RUNTIME_CLASS返回的就是這個對象的指針,這個對象包含了其基類和本身在運行時刻的信息。以上是筆者在編寫多個視應用在單文檔的程序時整理出來的兩種方法,因為VC++的強大和靈活,筆者相信還有更加巧妙的方法實現這個功能,希望這篇文章能夠起到拋磚引玉的作用,也希望廣大VC++編程愛好者對本文能夠給予批評和指正。