实用技巧
再谈“VC++5.0下实现多视”
浙江鄞县钟公庙镇长丰二村
王磊
---- 《计算机世界》前几期介绍了两种实现多视的方法。前者,作者利用MDI方法来实现,编程比较复杂;而后者作者使用CSpillterWnd类来实现多视。
---- 事实上,MFC在CDocument类中已经为我们封装了实现多视的直接方法,如果查阅一下联机文档,可以发现,CDocument中有AddView 和RemoveView两个成员函数,恰恰是这两个函数,为我们实现多视提供了便捷。
---- 下面是关于CDocument::AddView和CDocument::RemoveView的简单介绍,具体请参见联机文档:
void CDocument::AddView( CView* pView );
---- 为当前CDocument类实例加入新的视图。其中参数pView是指向新视图的指针。
Void CDocument::RemoveView ( CView* pView );
---- 从当前的CDocument类实例中移去一个视图。其中参数pView是指向要移去的视图的指针,注意必须在该视图不可见的情况下才能移去,否则会产生异常。
---- 通过这两个函数,我们可以方便的实现增加和移去视图。
---- 下面是实现的一个实例:
---- 本实例实现一文档两视图的功能。视图一由CView派生,用来显示文档中的数据,而视图二由CEditView派生,用来对文档中的数据进行编辑。用“查看”菜单中的视图一、视图二来选择视图。关于如何实现加入新的视图类和菜单请参考其他资料。
---- 下面为主窗口类添加数据成员和消息处理函数:
CDemoView *m_pView1;
//指向视图一的指针,CDemoView由应用向导生成
CMyEditView *m_pView2;
//指向视图二的指针,CMyEditView派生于CEditView
int m_nWhichView;
//指示当前为哪一个视图,并初始化为0
---- 以下是菜单选择视图二的处理函数:
void CMainFrame::OnView2()
{
// TODO: Add your command handler code here
if(m_pView2==NULL)
{
//取得视图一的指针
m_pView1 = (CDemoView *)GetActiveView();
//生成视图二实例对象
m_pView2 = new CMyEditView;
//取得活动文档的指针
CDemoDoc *pDoc = (CDemoDoc*)GetActiveDocument();
//为视图二实例对象创建窗口,
具体参数请参见联机文档的CWin::Create
m_pView2->Create(NULL,NULL,AFX_WS_DEFAULT_VIEW,
rectDefault,this,AFX_IDW_PANE_FIRST+1,NULL);
//下面置换视图一和视图二的ID号
//使得主窗口可以正确的使当前视图占据整个客户区
int nId = m_pView2- >GetDlgCtrlID();
m_pView2- >SetDlgCtrlID(AFX_IDW_PANE_FIRST);
m_pView1- >SetDlgCtrlID(nId);
//显示视图二,隐藏视图一
m_pView2- >ShowWindow(SW_SHOW);
m_pView1- >ShowWindow(SW_HIDE);
//添加新视图
pDoc- >AddView(m_pView2);
//设置视图二位活动视图
SetActiveView(m_pView2);
m_pView2- >SetWindowText(pDoc- >m_Hello);
RecalcLayout();
}
else
if(m_nWhichView==0)
{ //同上,仅仅省略视图二的创建工作
int nId = m_pView2- >GetDlgCtrlID();
m_pView2- >SetDlgCtrlID(AFX_IDW_PANE_FIRST);
m_pView1- >SetDlgCtrlID(nId);
m_pView1- >ShowWindow(SW_HIDE);
m_pView2- >ShowWindow(SW_SHOW);
SetActiveView(m_pView2);
RecalcLayout();
}
m_nWhichView=1;
}
---- 以下是菜单选择视图一的处理函数:
if(m_nWhichView==1)
{
//原理同上
int nId = m_pView1- >GetDlgCtrlID();
m_pView1- >SetDlgCtrlID(AFX_IDW_PANE_FIRST);
m_pView2- >SetDlgCtrlID(nId);
m_pView2- >ShowWindow(SW_HIDE);
m_pView1- >ShowWindow(SW_SHOW);
SetActiveView(m_pView1);
RecalcLayout();
m_nWhichView = 0;
}
---- 然后为文档类CDemoDoc 添加成员数据 CString m_Hello,用于视图一的显示数据和视图二的编辑数据。
---- 最后分别加入视图一和视图二的处理函数。
---- 在视图一中加入OnDraw处理函数用于显示数据,如下:
void CDemoView::OnDraw(CDC* pDC)
{
CDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
RECT rect;
GetClientRect(&rect);
pDC->SetTextColor(RGB(0,128,128));
pDC->DrawText(pDoc- >m_Hello,pDoc- >
m_Hello.GetLength(),&rect,DT_WORDBREAK);
}
---- 在视图二中加入用于相应数据改变的处理函数:
void CMyEditView::OnChange()
{
// TODO: If this is a RICHEDIT control,
the control will not
// send this notification unless you override
the CEditView::OnInitDialog()
// function to send the EM_SETEVENTMASK
message to the control
// with the ENM_CHANGE flag
ORed into the lParam mask.
// TODO: Add your control
notification handler code here
//仅将数据保存到文档
CDemoDoc *pDoc = (CDemoDoc *)GetDocument();
GetWindowText(pDoc- >m_Hello);
}
---- 注意:编译前在MainFrm.cpp中加入DemoView.h 和MyEditView.h 和 DemoDoc.h三个头文件。于是大功告成,可以进行编译测试了。
---- 总结:使用MDI和CSplitter来实现多视,无非是隐含的使用AddView来实现一文档多视,而这里,笔者直接使用了AddView来实现多视。当然,朋友们可能会问,上面这个演示程序,为什么只能在各个视图中切换,而不能同时将其显示在主窗口的客户区。其实,同时显示在客户区是可能的,笔者经过多次试验,发现两个视图完全可以显示在同一主窗口的客户区内,不过视图类窗口不是普通的窗口,没有标题、系统控制菜单和放大缩小按钮,因此它缺少一些普通窗口的属性,如不采用特殊手段不能用鼠标拖动和进行放大缩小操作。当然这可以通过一些API函数来实现,但这可能会太过麻烦。
---- 细心的朋友可能会注意到MDI的每一个视图其实是被放置在CMDIChildWnd 类派生的主帧窗口的客户区,由此来获得普通窗口的各种特性,使得各个视图窗口在同一主窗口下共存,这也是用MDI实现一文档多视的方法。不过,我们完全可以变通一下,自己建立主帧窗口,并在建立视图时将其父窗口指针指向该主帧窗口,从而获得同用MDI实现多视相同的效果,不过这可能会略显复杂。而分隔器窗口也仅仅为显示视图窗口以及为各个视图窗口的重新定位提供了机制,我们也可以不用分隔器窗口而通过重载主窗口的RecalcLayout来实现视图窗口的重新定位,限于篇幅,这里不再作介绍。有兴趣的朋友可以研究一下CMDIChildWnd和CsplitterWnd实现的MFC源程序。