這裏寫一下窗口的切換於分割。一般這裏說的是單文檔界面或者多文檔界面的各種分割與切換。多文檔的作法和單文檔沒有什麼區別,這裏就以單文檔爲例。在本文最後我會列一個分割對話框的例子。這部份內容不是很少,在書上查得到的我就不詳細說了。
一般常用的MFC視窗結構是文檔/視窗結構(document/view architecture)。有很多人說這個結構浪費不少資源,不夠節約。但我覺得作到界面這一級浪費點資源沒什麼太大問題。只要不漏內存,不影響效率就已經足夠好了。何況這是微軟最推崇的標準界面。
文檔/視窗(document/view architecture)結構主要由四個class組成。document類,view類,framework類和app類。app類是程序的引擎,在MFC中是最不不要關心的一個類。framwork是窗口的框架,在程序運行開始的時候先生成框架,然後是document class,這裏是用來存儲數據的。然後是view類,用來顯示數據同時作數據交換的。單文檔界面只有一個document class,但可以有多了view class。至少有一個view class是active的。可以用GetActiveView()得到它的指針。沒個和document class 關聯的view class都有一個control ID,這個ID是一個整數。如果總共只顯示一個view class,這個class的control ID是AFX_IDW_PANE_FIRST,如果同時顯示好幾個view class就需要用分割器(splitter)割開。class 名字叫CSplitterWnd。CSplitterWnd有兩種不同的切割framework的方式。一種叫動態的,用Create()來實現,切的很不理想。沒見過多少class用這種切法。真正應用廣泛的是靜態切割,用CReateStatic實現。當然從名字上就可以看出靜態切割的缺點,就是不能動態重新切分。在本文中我會介紹一個可以實現靜態切割的程序。被分割器隔開的窗口的Control ID可以通過IdFromRowCol(row, col)函數得到,row和col是窗口的行數和列數。其數值也是在AFX_IDW_PANE_FIRST。也是一個比較大的數字。所以隱藏當前不想顯示的view時把他的control ID改成一個1,2,3之類的很小的數就可以了。
基本知識就說這些,肯定不夠詳細,大家可以參照Visual C++的各種教程找到詳細資料。下面開始說一些具體問題了。從單窗口開始。
1。在Framework中顯示一個View。通過菜單或按鈕切換成不同的view。假設有三種view: CViewA, CViewB,CViewC。用三個常數表示他們不顯示時的control ID.
enum eView {ViewA, ViewB, ViewC};
在CMainFrame加上下面一個函數就可以實現不同窗口的切換了。很易懂,唯一沒有說的就是CCreateContext context,這是每次Create一個view時必須設定的。其實也就是m_pCurrentDoc這個指向當前document class的指針需要設定,其它的取默認值就可以了。
void CMainFrame::SwitchToView(eView nView)
{
CView* pOldActiveView = GetActiveView();
CView* pNewActiveView = (CView*) GetDlgItem(nView);
if (pNewActiveView == NULL)
{
switch (nView)
{
case ViewA:
pNewActiveView = (CView*) new CViewA;
break;
case ViewB:
pNewActiveView = (CView*) new CViewB;
break;
case ViewC:
pNewActiveView = (CView*) new CViewC;
break;
}
CCreateContext context;
context.m_pCurrentDoc = pOldActiveView->GetDocument();
pNewActiveView->Create(NULL, NULL, WS_BORDER|WS_CHILD,
CFrameWnd::rectDefault, this, nView, &context);
pNewActiveView->OnInitialUpdate();
}
SetActiveView(pNewActiveView);
pNewActiveView->ShowWindow(SW_SHOW);
pOldActiveView->ShowWindow(SW_HIDE);
pOldActiveView->SetDlgCtrlID(m_nCurrentView);
pNewActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
m_nCurrentView = nView;
RecalcLayout();
}
2。顯示1個,2個或4個窗口。
需要用splitter class,這裏就不詳細說了,任何Visual C++書上都有。無論是Dynamic的還是Static的。
3。顯示1個,2個或4個窗口。同時窗口可以切換。
這裏只講靜態窗口的切換,動態的效果不是很好,用戶不想切的時候也會自動切。
靜態窗口的切換的效果就是Window Explorer那樣,左邊的目錄欄一點右面就跟着變了。這裏需要在已有的CSplitterWnd的基礎上寫一點小小的增強。需要一個切換功能。從CSplitterWnd繼承出一個class,例如叫CDynViewSplitter。
BOOL CDynViewSplitter::ReplaceView(int row, int col,CRuntimeClass * pViewClass,SIZE size)
{
CCreateContext context;
BOOL bSetActive;
// Get pointer to CDocument object so that it can be used in the creation
// process of the new view
CDocument * pDoc= ((CView *)GetPane(row,col))->GetDocument();
CView * pActiveView=GetParentFrame()->GetActiveView();
if (pActiveView==NULL || pActiveView==GetPane(row,col))
bSetActive=TRUE;
else
bSetActive=FALSE;
// set flag so that document will not be deleted when view is destroyed
pDoc->m_bAutoDelete=FALSE;
// Delete existing view
((CView *) GetPane(row,col))->DestroyWindow();
// set flag back to default
pDoc->m_bAutoDelete=TRUE;
// Create new view
context.m_pNewViewClass=pViewClass;
context.m_pCurrentDoc=pDoc;
context.m_pNewDocTemplate=NULL;
context.m_pLastView=NULL;
context.m_pCurrentFrame=NULL;
CreateView(row,col,pViewClass,size, &context);
CView * pNewView= (CView *)GetPane(row,col);
if (bSetActive==TRUE)
GetParentFrame()->SetActiveView(pNewView);
RecalcLayout();
GetPane(row,col)->SendMessage(WM_PAINT);
return TRUE;
}
這裏對用完了的view是destroy掉了,處理和第一種不大一樣。其它的沒什麼值得說的。
4。這是個以前沒有想過的問題,靜態窗口的重新切分,時分時合。由於有了上面兩個例子結合一下就可以了。需要知道的是CSplitterWnd在最開始切分窗口CreateStatic的時候不可以切成一行一列,也就是不切。CreateStatic一定要作真正的切割。這給整個問題帶來了不少麻煩。好在CSplitterWnd的員程序全都可以讀到,只有兩千多行。看一看construct之後作的事情的確很多,但desctructor很簡單,所以合併之前把自己的CSplitterWnd刪掉就可以了。下面是這個例子可以在當窗口CViewA,單窗口CViewB,雙窗口CViewMenu/CViewA之間互相切換,在窗窗口的時候還可以實現右邊窗口CViewA到CViewB的切換。
5。多個窗口的分割,不只1X1,1X2,2X1,2X2。可以分得十分複雜,比VC IDE上的窗口還多都可以。這時需要用多個Splitter。
6。對話框的切分,沒有標準的MFC class,需要自己寫一個。
5和6的例子我回頭加上。