在Visual C++对话框中使用视图我们知道,在使用Visual C++编制.exe程序的时候,有两种模式可供选择:对话框程序和基于文档/视窗模式的程序。对话框有自己的消息循环,编制程序比较简单。文档/视窗模型则从SmallTalk的“文档-控制器-窗口”模型借鉴而来,视窗和文档二者分工明确,相互之间具有一定的独立性,更利于编写大型的文字处理程序。那么,这两种模式之间是否存在着一定的“结合”区域呢?比如,MFC6.0版中带有一个CHtmlView类,它提供了一种简单地显示网页的方法,但它是以视图类(从CView类派生)的形式出现的,在对话框中无法直接使用。一般说来,要使用显示网页的功能可以请求COM服务(IE本身就是一个大的COM客户端)。CHtmlView类的实现也是如此。那么,有没有办法像在下面的图中那样,直接在对话框中使用CView类的派生类呢?
 
其实CView类(及其派生类)并不神秘,它也是窗口,是一种默认风格是WS_CHILD、WS_VISIBLE、WS_BORDERS的窗口,它也能在对话框中创建,甚至可以令它成为应用程序的主窗口。只不过CView类(及其派生类)的构造函数是的访问属性是保护类型,不能够直接生成对象。我们平时使用的时候都是通过类似如下的代码:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TESTTYPE,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
类似这样,有一个CDocTemplate类(或其派生类)替我们做了大部分的工作(还有一个CDocManager类),使得我们不清楚文档和视到底是怎样创建的。其实,手工创建一个视并不困难。首先生成一个CCreateContext对象,用来在创建窗口的时候使用,从CCreateContext对象中的m_pNewViewClass成员就可以生成一个视类的对象,因为它是一个CRuntimeClass类型的指针,可以调用它的CreateObject()函数来生成视类的对象。然后再调用CreateWindow函数创建视的窗口(不是框架窗口,而是视本身可见的部分),这样,视就可以看到了。然后调整它的大小等属性即可。作为一个简单的例子,我们在一个对话框的OnInitDialog函数中写入如下内容:
CRuntimeClass * pViewClass = RUNTIME_CLASS(CHtmlView); //要创建CHtmlView视 //生成一个CCreateContext对象备用
CCreateContext * pContext;
pContext = new CCreateContext;
pContext->m_pNewViewClass = pViewClass;
pContext->m_pCurrentDoc = NULL; // 均填NULL即可
pContext->m_pNewDocTemplate = NULL;
pContext->m_pLastView = NULL;
pContext->m_pCurrentFrame = NULL CWnd* pWnd = NULL;
// 创建视类对象,先将从CObject*变换到CWnd*
pWnd = DYNAMIC_DOWNCAST(CWnd, pViewClass->CreateObject());
pWnd->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), this, 0, pContext);
// CReateContext对象不再需要
delete pContext; CHtmlView * pHtmlView = DYNAMIC_DOWNCAST(CHtmlView, pWnd);
// 现在可以使用视的指针了
pHtmlView->MoveWindow(0,0,200,150);
// 前进到一个地址
pHtmlView->Navigate(_T("http://www.microsoft.com/visualc"));
这段代码正常执行后,将在对话框窗口的(0,0)-(200,150)区域内显示一个子窗口,包含网页的内容或是未连接到Internet的信息。当然,在实际使用的时候,错误捕获和处理是必不可少的。
下面我们就来设计并实现一个实用的方案。
根据面向对象的思想,将所有的功能封装到一个类中,取名为CDialogViewManager。要实现的功能应当有:添加一个视到对话框(AddView函数)和删除一个视(DeleteView函数);为了设计时的方便,视的大小由一个静态(Static)控件来指出;为了管理上的方便,这个类还保存所有已创建的视的指针,可以通过视类来存取它们(GetView函数),另外,还应当能够通过关联的静态控件的ID值来存取这些指针。为了使用上的方便,还增加关联到一个对话框的功能(Install2Dialog函数和Uninstall函数)和一次删除所有视(RemoveAllView函数)的功能。
为提高查找视的指针的效率,使用MFC的映射类来存储它们,以相关联控件的ID作为键的类型,以一个自定义结构(含视类的指针)作为值的类型。
typedef struct _DVMITEM{ //Dialog View Manager Item, dvmi for short
CView * pView;
} DVMITEM,* PDVMITEM;
typedef CMap<UINT,UINT,DVMITEM*, DVMITEM*> CMapID2DVMItem;
将值的类型定义成一个结构是为了方便日后在其中添加其它信息。
最后,编程实现,得到两个文件如下。
DlgView.h文件
// DlgView.h : header file
//
#if !defined(_DLGVIEW_H__INCLUDED_)
#define _DLGVIEW_H__INCLUDED_#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000// to import the CMap<> template
#include "afxtempl.h"/////////////////////////////////////////////////////////////////////////////
// DialogViewManager: the manager of the views in dialogclass CDialogViewManager : public CCmdTarget
{
public:
CDialogViewManager();
virtual ~CDialogViewManager();
DECLARE_DYNCREATE(CDialogViewManager)protected:
// embeded type and structures
typedef struct _DVMITEM{
CView * pView;
// we can add other information here
} DVMITEM,* PDVMITEM;
typedef CMap<UINT,UINT,DVMITEM*, DVMITEM*> CMapID2DVMItem;// Operations
public:
// install/Uninstall manager
void Install2Dialog(CDialog * pParentDialog);
void Uninstall(void); // add a view to the map, defaultly set it active, i.e. it is
// repainted when the dialog is repainted
BOOL AddView(UINT nIDRectangle, CRuntimeClass* pViewClass); // get the view's pointer
CView * GetView(UINT nIDRectangle);
CView * GetView(CRuntimeClass * pViewClass); // delete a view from the map
void DeleteView(UINT nIDRectangle);
void DeleteView(CRuntimeClass * pViewClass); // remove all views in the map
void RemoveAllView(void);protected:
CMapID2DVMItem m_mapID2DVMItem;
CDialog * m_pParentDialog; #ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif};
typedef CDialogViewManager CDlgVuMgr; // short name to use#endif // !defined(_DLGVIEW_H__INCLUDED_)// end of the header file (dlgview.h)
DlgView.cpp文件
// DlgView.cpp : implementation file
//#include "stdafx.h"
#include "afxpriv.h"
#include "dlgview.h"#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif/////////////////////////////////////////////////////////////////////////////
// CDialogViewManagerCDialogViewManager::CDialogViewManager()
{
m_pParentDialog = NULL;
}CDialogViewManager::~CDialogViewManager()
{
Uninstall();
}/////////////////////////////////////////////////////////////////////////////
// CDialogViewManager diagnostics#ifdef _DEBUG
void CDialogViewManager::AssertValid() const
{
CCmdTarget::AssertValid();
}void CDialogViewManager::Dump(CDumpContext& dc) const
{
CCmdTarget::Dump(dc);
}
#endif //_DEBUG/////////////////////////////////////////////////////////////////////////////
// CDialogViewManager custimized functionsvoid CDialogViewManager::Install2Dialog(CDialog *pParentDialog)
{
ASSERT (pParentDialog);
ASSERT (::IsWindow(pParentDialog->GetSafeHwnd())); m_pParentDialog = pParentDialog;
}void CDialogViewManager::Uninstall()
{
RemoveAllView();
m_pParentDialog = NULL;
}BOOL CDialogViewManager::AddView(UINT nIDRectangle,
 CRuntimeClass* pViewClass)
{
BOOL bSuccess = FALSE; DVMITEM * pdvmi;
if ( m_mapID2DVMItem.Lookup(nIDRectangle, pdvmi )
|| (!m_pParentDialog)
|| (!::IsWindow(m_pParentDialog->GetSafeHwnd()))
)
{
/*
condition 1) The rectagle spicefied by the control id already exist
in the map, so another view may be using the rectangle
condition 2) There must be a dialog to install view to
condition 3) The dialog to install view to must be a window
*/
}
else
{
ASSERT_VALID_IDR(nIDRectangle);
ASSERT(pViewClass != NULL ||
pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));
ASSERT(m_pParentDialog != NULL); CWnd* pWnd = NULL;
CCreateContext * pContext = new CCreateContext;
if ( pContext )
{
// fill in the fields
pContext->m_pNewViewClass = pViewClass;
pContext->m_pCurrentDoc = NULL;
pContext->m_pNewDocTemplate = NULL;
pContext->m_pLastView = NULL;
pContext->m_pCurrentFrame = NULL; try
{
// Create the object.
pWnd = DYNAMIC_DOWNCAST(CWnd, pViewClass->CreateObject());
if (!pWnd) AfxThrowUserException(); ASSERT_KINDOF(CWnd, pWnd);
ASSERT(pWnd->m_hWnd == NULL); // not yet created. // Create with the right size and position
CWnd * pCtrl = m_pParentDialog->GetDlgItem(nIDRectangle);
ASSERT( pCtrl );
pCtrl->ShowWindow(SW_HIDE); // hide the control CRect rect;
pCtrl->GetWindowRect(rect);
m_pParentDialog->ScreenToClient(rect); pWnd->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), m_pParentDialog, 0, pContext);
CView * pView = DYNAMIC_DOWNCAST(CView, pWnd);
ASSERT_KINDOF(CView, pView);
pView->MoveWindow(rect); // now add the view to the Map
DVMITEM * pdvmi = new DVMITEM;
pdvmi->pView = pView;
m_mapID2DVMItem[nIDRectangle] = pdvmi; // added bSuccess = TRUE;
// all work done here
}
catch(...)
{
TRACE1("cannot create a view in dialog(%xd).\n",m_pParentDialog);
}
} // if ( pContext ) // clean up
if ( pContext )
delete pContext;
} return bSuccess;
}CView * CDialogViewManager::GetView(UINT nIDRectangle)
{
ASSERT_VALID(this); UINT nID;
DVMITEM * pdvmi;
POSITION pos = m_mapID2DVMItem.GetStartPosition();
while(pos)
{
m_mapID2DVMItem.GetNextAssoc(pos,nID,pdvmi); if ( nID == nIDRectangle )
{
return pdvmi->pView;
}
}
return NULL;
}CView * CDialogViewManager::GetView(CRuntimeClass * pViewClass)
{
ASSERT_VALID(this); UINT nID;
DVMITEM * pdvmi;
POSITION pos = m_mapID2DVMItem.GetStartPosition();
while(pos)
{
m_mapID2DVMItem.GetNextAssoc(pos,nID,pdvmi); if ( pdvmi->pView->IsKindOf( pViewClass) )
{
return pdvmi->pView;
}
}
return NULL;
}void CDialogViewManager::DeleteView(UINT nIDRectangle)
{
ASSERT_VALID(this); UINT nID;
DVMITEM * pdvmi;
POSITION pos = m_mapID2DVMItem.GetStartPosition();
while(pos)
{
m_mapID2DVMItem.GetNextAssoc(pos,nID,pdvmi); if ( nID == nIDRectangle )
{
// delete the view
ASSERT(pdvmi->pView);
ASSERT_VALID(pdvmi->pView);
ASSERT(::IsWindow(pdvmi->pView->m_hWnd)); pdvmi->pView->ShowWindow(SW_HIDE);
pdvmi->pView->SendMessage(WM_CLOSE,0,0);
m_mapID2DVMItem.RemoveKey(nID);
delete pdvmi;
}
}
}void CDialogViewManager::DeleteView(CRuntimeClass * pViewClass)
{
ASSERT_VALID(this); UINT nID;
DVMITEM * pdvmi;
POSITION pos = m_mapID2DVMItem.GetStartPosition();
while(pos)
{
m_mapID2DVMItem.GetNextAssoc(pos,nID,pdvmi); if ( pdvmi->pView->IsKindOf( pViewClass) )
{
// delete the view
ASSERT(pdvmi->pView);
ASSERT_VALID(pdvmi->pView);
ASSERT(::IsWindow(pdvmi->pView->m_hWnd)); pdvmi->pView->ShowWindow(SW_HIDE);
pdvmi->pView->SendMessage(WM_CLOSE,0,0);
m_mapID2DVMItem.RemoveKey(nID);
delete pdvmi;
}
}
}void CDialogViewManager::RemoveAllView(void)
{
ASSERT_VALID(this); UINT nID;
DVMITEM * pdvmi;
POSITION pos = m_mapID2DVMItem.GetStartPosition();
while(pos)
{
m_mapID2DVMItem.GetNextAssoc(pos,nID,pdvmi); ASSERT(pdvmi->pView);
if (::IsWindow(pdvmi->pView->m_hWnd))
{
pdvmi->pView->ShowWindow(SW_HIDE);
pdvmi->pView->SendMessage(WM_CLOSE,0,0);
}
m_mapID2DVMItem.RemoveKey(nID);
delete pdvmi;
}
}// because this is a macro, we can place it anywhere instead of the top end
IMPLEMENT_DYNCREATE(CDialogViewManager, CCmdTarget)// end of the implementation file (dlgview.cpp)
其中,AddView函数的原理在前面已经作过介绍,其它函数的原理并不复杂,读者可以在阅读源码的过程中体会。
要使用这个类,可以先用MFC Appwizard生成一个对话框类型的工程。首先编辑生成的对话框资源,添加一个静态类型的控件,可取其ID为IDC_STATIC_AREA,它的大小和位置就是要创建视图的大小和位置。然后编辑生成的对话框的OnInitDialog函数,在TO DO一行下面添入如下代码:
static CDialogViewManager manager;
manager.Install2Dialog(this);
VERIFY(manager.AddView(IDC_STATIC_AREA,RUNTIME_CLASS(CHtmlView)));
CHtmlView * pHtmlView = DYNAMIC_DOWNCAST( CHtmlView, manager. GetView (IDC_STATIC_AREA ));
pHtmlView->Navigate2(_T("http://www.microsoft.com"),NULL,NULL);
这样,在程序运行时,就会尝试去连接微软公司Visual C++主页。
程序运行的一个例子如文首的图所示(在一个对话框中创建有两个视)。
最后还要说明的一点是,本文一直是以CHtmlView类为例的,但是这个类可以用来在对话框中创建任何CView类的派生类。