在MS Windows
中,一個窗口可以分割成若干個子窗口,每一個子窗口稱作一個窗片(pane),每個窗片可以獨立控制,這給界面設計提供了很大的方便。
利用VC
可以很方便地實現分割窗口。分割的方法有兩種:動態和靜態。動態分割時可以根據用戶的需要分割成數目不同的窗片,但所有窗片的屬性和父窗口都是一樣的;而靜態分割的窗片的數目在程序中指定,運行時是固定的,但每個窗片可以有各自不同類型的視(View),因此其使用范圍更為廣泛。本文所討論的問題僅限于靜態分割。
窗片中視的類型大多是在主窗口的創建過程中指定的。這也就意味著,一個窗片雖然可以顯示任意類型的視,但是這種類型一旦確定,在程序運行過程中就難以改變。
一、我要的是這樣的!
但是我們有時確實需要改變一個窗片所顯示的視的類型,也就是說,需要讓一個窗片顯示多種類型的視。例如一個窗口被分割成兩部分,一邊是命令窗口,另一邊是工作窗口,根據命令窗口中發出的不同命令,需要變換不同的工作類型,這就需要工作窗口中能夠顯示多種類型的視窗,那么,如何做到這一點呢?
二、你可以這樣做!
從圖1 中可以看到,本程序共有三個視類,分別是:
* 命令視類CCmdView:用來控制右邊窗片中不同視的顯示;
* 選項按鈕視類CRdiView:顯示在右窗片中的選項視類;
* 檢查按鈕視類CChkView:顯示在右窗片中的檢查視類。
這三個視類都是CFormView 的子類。
下面我們來看如何在右窗片內進行兩類視間的切換。實際上,由視A
切換到視B 的原理很簡單,那就是:
1. 從窗片中刪除視A;
2. 往窗片中添加視B。
步驟1 的實現非常簡單,僅用一條語句即可:
m_wndSplitter.DeleteView(0, 1);
但它是必不可少的,因為你不能讓一個窗片同時包含兩個視。我本來希望往一個窗片中添加新的視時,VC
會自動將原來的視刪掉,可是它不干。
我們來看如何實現步驟2,當一個窗片是空的時候,怎樣往里面添加一個視呢?其實這樣的功能在程序里我們已經用過了,看下面的語句:
BOOL CMainFrame::OnCreateClient
(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
......
if (!m_wndSplitter.CreateView(0, 0,
pContext->m_pNewViewClass,
size,
pContext))
......
}
是的,用的就是CSplitterWnd::CreateView(),要注意的是它共有五個參數,其中前兩個用來指定分割窗口的窗片,第三個用來指定視的類型,第四個指定視的大小。最后的一個我們暫時用不上,用空值NULL
就可以了。
這樣我們就可以編寫視切換的代碼了。因為視切換要操縱m_wndSplitter,而它是主窗口的成員,因此切換過程最好設計為主窗口的成員函數。但是切換命令是CCmdView
接受的,因而可以讓CCmdView
接受到視更改消息后,將消息傳給主窗口,由主窗口完成視更改。具體的代碼是這樣的:
命令視類中的消息映射:
BEGIN_MESSAGE_MAP(CCmdView, CFormView)
......
ON_BN_CLICKED(IDC_CHECK, OnSwitchToCheckView)
ON_BN_CLICKED(IDC_RADIO, OnSwitchToRadioView)
......
END_MESSAGE_MAP()
命令視類中的消息響應:
void CCmdView::OnSwitchToCheckView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_CHECK);
}
void CCmdView::OnSwitchToRadioView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_RADIO);
}
主窗口中的消息映射:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
......
ON_COMMAND(ID_CHECK, OnSwitchToCheckView)
ON_COMMAND(ID_RADIO, OnSwitchToRadioView)
......
END_MESSAGE_MAP()
主窗口中的消息響應:
void CMainFrame::OnSwitchToCheckView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CChkView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
好啦,運行一下這個程序,感覺是否不錯?看來大功告成了,可是……
三、還有一個問題
在運行我們辛辛苦苦編出來的程序時,回頭看看VC
的調試窗口,你會發現有很多行這樣的話:
Create view without document.
這是說我們創建了視,可是沒有相應的文檔。好在這只是警告信息,不是什么錯誤,如果你不需要相應的文檔,就完全不用去管它??墒?,VC
中一種很重要的結構就是文檔-
視結構,利用這種結構,對數據操縱起來非常方便。如果需要建立與視相對應的文檔,應該怎么辦呢?
這就涉及到VC 中文檔-
視結構的知識,不過不用怕麻煩,與本文有關的就只有這么兩點而已:
1. 利用VC 創建的應用程序一般都會管理一些文檔模板(Document
Template),文檔類和視類的對應關系就是在文檔模板里描述的。
2.
一個文檔可以有多個視,創建視的時候,需要根據文檔和視的對應關系,給出它所依附的文檔。
怎樣實現上述第一點呢?
首先建立相應的文檔類:CRdiDoc 和CChkDoc。
其次是定義相應的文檔模板,這是應用類的成員變量。因為在別的類中要使用它們,我們將之定義為公共類型:
class CViewSwitcherApp : public CWinApp
{
......
public:
CSingleDocTemplate* m_pRdiDocTemplate;
CSingleDocTemplate* m_pChkDocTemplate;
......
}
然后創建這兩個文檔模板,并加入到模板列表中:
BOOL CViewSwitcherApp::InitInstance()
{
......
m_pRdiDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CRdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CRdiView));
AddDocTemplate(m_pRdiDocTemplate);
m_pChkDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CChkDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CChkView));
AddDocTemplate(m_pChkDocTemplate);
......
}
至于第二點,是在創建視時完成的。還記得創建視的情況么?當時有一個叫做pCreateContext
的參數,我們將之置為空,這里就要用到它了。
pCreateContext 是一個指向被稱作" 創建上下文"(CreateContext)
結構的指針,這個結構中保存一些與創建視相關的內容。在創建主窗口時,系統會構造這樣一個結構,并將它作為參數傳遞到與創建視有關的函數中。但現在我們不創建主窗口,因此不得不自己構造這樣一個結構。實際上,該結構中我們所要使用的字段只有三個:
1. 新視所屬的文檔模板m_pNewDocTemplate;
2. 新視的類型m_pNewViewClass;
3. 新視所屬的文檔m_pCurrentDoc;
其中僅有第三項需要新建,前兩項都是已知的,只要指定即可。以切換到選項視為例,修改后的代碼是:
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
CCreateContext createContext;
// 定義并初始化CreateContext
// 獲取新視所屬的文檔模板
CSingleDocTemplate* pDocTemplate =
((CViewSwitcherApp*)AfxGetApp())-> m_pRdiDocTemplate;
// 創建新文檔并初始化
CDocument* pDoc = pDocTemplate->CreateNewDocument();
pDoc->OnNewDocument();
// 設置CreateContext 相關字段
createContext.m_pNewViewClass = RUNTIME_CLASS(CChkView);
createContext.m_pCurrentDoc = pDoc;
createContext.m_pNewDocTemplate = pDocTemplate;
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
&createContext);
m_wndSplitter.RecalcLayout();
}
四、最后的修改
為了使這個程序更符合要求,我們還要做一些與更換視無關的修改。在這個程序中我們一共定義了三種類型的文檔,程序啟動時一般要新建一個文檔開始工作,可是它不知道要選擇哪一種,就彈出一個對話框來詢問。而這是我們不希望看到的。修改的方法是不讓VC
選擇新文檔類型,而我們指定創建哪一種類型的文檔,即把CViewSwitcherApp::CViewSwitcherApp()
中的語句
if (!ProcessShellCommand(cmdInfo)) return FALSE;
更改為
m_pDocTemplate->OpenDocumentFile(NULL)。