《计算机世界》第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 的开头加入语句:
#include "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是通用的。