《計算機世界》第12 期的" 編程技巧" 欄目刊登了"VC ++5.0 下實現多視" 一文。在該文中,作者以實現一個文檔對應三個視圖為例說明了實現一檔多視的一般方法。筆者讀后以為這種借助于多文檔(MDI) 框架來實現單文檔多視圖的方法是行之有效的,但卻比較復雜。事實上,在單文檔(SDI) 框架下實現一檔多視也并非難事。
一、關于CSplitterWnd 類
CSplitterWnd
類支持動態和靜態兩種分隔器窗口。分隔器窗口是含有多個窗格的窗口,在其中的每個窗格都有各自的窗口ID
號,并且通常對應于一個CView
派生類的對象。該類的對象通常被嵌入到程序CFrameWnd 或CMDIChildWnd
的派生類中。
創建與使用一個支持靜態分隔器窗口的CSplitterWnd
對象應遵循以下步驟:
在CFrameWnd 派生類中定義一個類型為CSplitterWnd 的成員變量;
在CFrameWnd 派生類中重載CFrameWnd::OnCreateClient()成員函數,并在該函數中先后調用CSplitterWnd
類的CreateStatic() 和CreateView() 函數。
CSplitter::CreateStatic( )
原型:BOOL CreateStatic(
CWnd *pParentWnd,
int nRows, int nCols,
DWORD dwStyle=WS_CHILD| WS_VISIBLE,
UINT nID = AFX_IDW_PANE_FIRST );
返回值:成功則為非0,否則為0。
參數:
pParentWnd 要分割的窗口指針。
nRows 分割的行數,最大為16。
nCols 分割的列數,最大為16。
dwStyle 窗口風格。
nID 該窗口的子窗口ID。
如果此分隔器窗口不是嵌套
在另一分隔器窗口中,
則用默認值。
CSplitter::CreateView( )
原型:virtual BOOL CreateView(
int row, int col,
CRuntimeClass *pViewClass,
SIZE sizeInit,
CCreateContext *pContext );
返回值:成功則為非0,否則為0。
參數:
row 新視圖的行位置,以0 為基數。
col 新視圖的列位置,以0 為基數。
pViewClass 通常是RUNTIME_CLASS( 新視圖類名)。
sizeInit 新視圖的大小。
pContext 使用OnCreateClient() 傳遞的相應參數。
二、例程的基本框架
本例程利用SDI
框架實現了一檔兩視的功能。例程中的文檔類為CDemoDoc,它主要記錄某一周七天中的日平均氣溫。另外,例程中的視圖類有兩個,即主視圖CMainView
與次視圖CSubView,其中主視圖用于輸入及修改原始數據,而次視圖則以圖形方式對數據進行顯示。
例程基本框架的實現步驟如下:
1. 創建新項目Demo
創建一個新的項目,名稱為Demo,使用的向導類型為MFCAppWizard
(.EXE)。
需要注意的是:首先在向導的第一步中應選擇"Single
document";其次在向導的最后一步(Step 6) 中將CDemoView
類的類名及其相關文件名分別改為:CMainView MainView.h 和MainView.cpp.,另外還應將其基類改為CformView,至于其它各步采用默認值即可。
2. 添加并修改類CsubView
利用類向導(ClassWizard) 對話框中"Add class" 下的"New"
選項添加一個新類:類名為CSubView,基類為CView。
并給其添加如下成員函數:
聲明:
public:
CDemoDoc *GetDocument();
定義:
CDemoDoc *CSubView::GetDocument()
{
ASSERT(m_pDocument ->IsKindOf(
RUNTIME_CLASS(CDemoDoc)));
return (CDemoDoc *)m_pDocument;
}
接著,在SubView.h 中的適當位置加入"class CDemoDoc;",如下所示:
// CSubView view
class CDemoDoc;
class CSubView : public CView
{
...............
}
最后,在SubView.cpp 中的開頭加入語句:
#include "DemoDoc.cpp"
3. 修改類CmainFrame
首先為該類添加一成員變量,其聲明為:
public:CSplitterWnd m_hSplitterWnd;
接著利用ClassWizard 為該類的OnCreateClient
消息添加處理函數,并對其編輯如下:
BOOL CMainFrame::OnCreateClient(
LPCREATESTRUCT lpcs,
CCreateContext *pContext)
{
// TODO: Add your specialized
code here and/or call the base class
// 獲得要被分割窗口的大小
CRect rc;
GetClientRect( &rc);
// 將窗口分割為:2 行1 列
if (!m_hSplitterWnd.CreateStatic(this,2, 1))
{
TRACE0("Failed to CreateStaticSplitter\n");
return FALSE;
}
//pContext ->m_pNewViewClass
是創建SDI 時用到文檔類的指針
// 即將MainView 與窗格(0,0)
對應,注意以0 為基數
if (!m_hSplitterWnd.CreateView(0, 0,
pContext ->m_pNewViewClass,
CSize(rc.Width(), rc.Height()/2),
pContext))
{
TRACE0("Failed to create first pane\n");
return FALSE;
}
// 將SubView 與窗格(1,0)對應
if (!m_hSplitterWnd.CreateView(1, 0,
RUNTIME_CLASS(CSubView),
CSize(0,0),
pContext))
{
TRACE0("Failed to create second pane\n");
return FALSE;
}
// 激活與窗格(0,0)對應的視圖類,
即MainView
SetActiveView((CView *)
m_hSplitterWnd.GetPane (0,0));
return TRUE;
}
注意:請參照實例理解
前面提到的兩個函數。
最后,在MainFrm.cpp 的開頭加入語句:
?。nclude "SubView.cpp"
三、例程基本框架的擴展
在前面我們已完成了一檔兩視應用程序的最基本框架,但這離我們的實際應用還有很大的距離,為此需要將其擴展??紤]到一檔多視這一類應用程序的通用性,在這里僅結合例程對該類程序設計過程中所遇到的一般問題進行說明。
對于一檔多視應用程序而言,關鍵的問題在于如何理順視圖與文檔以及視圖與視圖之間的關系,比如說:視圖如何得到文檔中的存放的原始數據,文檔如何得到在視圖中輸入或修改后的數據,以及當一個視圖中的內容發生變化時另外的視圖如何感知這一變化等等。
以本文所附的例程為例,對于某周星期一的日平均氣溫這一數據對象,文檔類CDemoDoc
與之對應的成員變量為m_docDay1,主視圖類CMainView 為m_mainDay1,而次視圖類CSubView
則為m_subDay1。
1. 當視圖(如CMainView)要得到文檔中的存放的原始數據(如m_docDay1)時,可在該類適當的地方加入語句:
m_mainDay1=GetDocument() ->m_docDay1;
2. 當在視圖(如CMainView)中輸入或修改數據后,需要對文檔中的相應數據(如m_docDay1)進行賦值或重新賦值時,可在該類適當的地方加入語句:
GetDocument() ->m_docDay1=m_mainDay1;
3. 當在視圖(如CMainView)中修改文檔數據后,需要通知另外的視圖(如CSubView)時,可在該類適當的地方加入語句:
GetDocument() ->UpdateAllViews(this);
4. 當視圖(如CMainView)修改文檔數據( 如m_docDay1)
并通知其它視圖后,另外的視圖(如CSubView)要對此作出反應,
如讀取新數據時,可在該類的OnUpdate() 函數中加入語句:
m_subDay1=GetDocument() ->m_docDay1;
說明: OnUpdate() 函數為CView 類的OnUpdate 消息處理函數,
讀者可由類向導添加。
前面涉及到的四個問題是很富有代表性的,它們各自的解決方案對于一般的一檔多視應用程序而言s是通用的?!?/p>