視窗切換分割詳解
這裡寫一下視窗的切換於分割。一般這裡說的是單文件介面或者多文件介面的各種分割與切換。多文件的作法和單文件沒有什麼區別,這裡就以單文件為例。在本文最後我會列一個分割對話方塊的例子。這部份內容不是很少,在書上查得到的我就不詳細說了。
一般常用的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的例子我回頭加上。