{S0}简介
本文介绍了一套可用于构建用户界面动态类。代码是围绕使用空闲池经理一个CWnd派生的控件,这有助于减少在某些UI场景中GDI资源的使用。要在行动中表现出的类,我演示MDI应用程序,它只是允许您打开XML文件。每个XML文件定义为一个单一的MDI子窗口的布局和UI控件的属性。虽然代码VC6,示范项目,可转换为VS 2003和VS 2005以及。UI场景
有一个共同的UI场景可能从空闲池的概念中受益的情侣。第一个例子是网络管理中的应用,使运营商能够控制许多不同类型的远程设备。每个设备都有一个可读取或近实时的参数设置。这种类型的应用的一个可能的UI模型是你的基本的MDI外壳,使您可以控制单个设备实例打开一个MDI子窗口。因为每个设备可能有众多的参数(几十甚至上百个),UI控件(或设备)在每个MDI子窗口组织成逻辑分组如下图所示的标签。
实施为每个设备类型的用户界面,典型的做法是创建一个单独的对话或每个选项卡的属性页的控制。这种方法是直接实现的,但它不能很好地扩展。考虑一个情况下,你需要支持200个参数设备类型。假设在设备窗口中的每个标签可容纳最多20个参数的控制布局。因此,10个选项卡或对话框,需要创建。现在,如果你认为每个参数可能需要将其自己的说明性文字标签配对,代表整个设备所需的UI控件的数量可能超过400。此外,对于某些参数,UI控件可能不能简单只要您的基本CButton的或CEdit。可以想见,第三方计ActiveX控件(您需要为您的项目使用),或总类似于Windows窗体用户控件。因此,GDI资源需要实行单一的设备窗口可能会相当高,并成为一个限制因素时,运营商需要同时打开很多这些设备上的Windows。
第二个例子是选项对话框(如在VS 2005中的"选项"对话框)。这种类型的对话,通常由一个左侧的树视图,并在右边的设置UI控件。由于在树视图中选择改变时,设置的右侧是动态变化的控制。这个UI场景实际上是非常相似的选项卡式设备窗口的第一个例子。主要区别是在选择或分组机制(例如,树与视图选择标签选择)。
{S2}CWnd的免费游泳池
的方式来减少标签设备窗口资源需求,以消除需要单独的对话框或属性页。这样就可以实现只使用一个单一的对话和实施机制,UI控件根据当前选定的选项卡显示或隐藏。相同数量的UI控件需要创建,但我们保存的需要对话框。
进一步减少资源的使用可以实现,如果我们认识到,往往在多个选项卡中的UI控件相同类型。换句话说,而不只是隐藏控件标签选择改变时,我们可以存储在一个自由,使他们当切换到不同的标签,可重复使用的池或缓存隐藏控件。这使我们能够重用"选项卡上选择的用户界面控件实例。例如,如果一个标签使用一个CButton和第二个选项卡还使用一个CButton,它应该只必要创建一个CButton的一个实例,并使用相同的用户界面的实例,这两个标签。通过这种方法,在每个设备窗口所需的UI控件可以节省显著。例如,作为一个最好的情况下,考虑10个参数分组(标签)和200个参数,每个参数代表一个TrackBar控制设备。如果我们还对每一个相应的文本标签控制的TrackBar,那么总共需要400 UI控件使用一个典型的多对话框实现。然而,如果我们从一个标签重用trackbar和标签控件下,设备窗口将需要最多20 trackbar和20个文本标签控件,从而减少资源的使用了10倍。
为了实现这一复用机制,我们首先通过定义一个CWndFreePool类,只是跟踪CWnd的实例是免费的,可用。在池中引用的每个CWnd的是搭配,显示UI控件对应的CWnd类型的字符串。例如,一个"按钮"类型的字符串表示配对的CWnd实际上是一个CButton的实例(即与BS_PUSHBUTTON风格创建的)。除了内置的MFC控件,如CButton的,空闲池,也可以参考ActiveX控件,因为Visual Studio可以生成MFC包装类是从CWnd派生的ActiveX控件。 CWndFreePool类的公共接口如下所示。
对照班// CWndFreePool keeps references to CWnds which have been
// created but are unused (hidden). The pool maintains ownership<
/span>
// of the CWnds which are still in the pool and deletes them in
span>
// its destructor.
class CWndFreePool
{
public:
// Constructor / destructor.
CWndFreePool();
CWndFreePool();
// Public methods.
CWnd* GetWnd(const CString& strType);
void AddWnd(const CString& strType, CWnd* pWnd);
};
为了重用UI控件的实例,我们需要另一个节能控制的状态,然后再返回到空闲池,并为恢复状态,当控制是从池中再次获得也机制。要做到这一点,我们可以定义一个类层次平行的支持的MFC类,如CButton和CSliderCtrl控制。这个层次的基类是CWndControl和其公共接口,以供参考。你可以认为这些CWndControl类是为他们的MFC的简单包装。{C}
CWndControl派生类的实例可以创建应用程序代码,只需使用新营办商。但是,一个CWndFactory类也提供了允许创建给定一个字符串类型的CWndControl实例。这个工厂类被设计主要是为了让动态创建控件的XML规范。CWnd的集装箱
实际重用逻辑是实施CWndContainer类。这个类是动态UI层的心脏,因为它管理更新到空闲池,使用工厂类和调度事件。 CWndContainer可以被看作是一个helper类,以添加动态UI支持可连接到任何CDialog的。例如,在一个CDialog类,只需创建一个CWndContainer实例,并将它附加到这个指针。一旦容器被附加到对话框,CWndControl实例可以被创建,然后添加到容器中(如代码示例中的{A}所示)。
补充说,当一个CWndControl实例的容器使用其内部的尝试,并获得适当类型的现有的CWnd空闲池。如果找到的CWnd从池中删除,可见,CWndControl的属性,然后应用到这个CWnd实例。另一方面,在池中发现,如果没有合适的的CWnd,容器将使用工厂类创建一个新的CWnd实例。
当一个CWndControl实例从容器中删除,其关联的CWnd是分离的,隐蔽性,并返回空闲池重用。下面是CWndContainer类的公共接口,以供参考。
事件处理// CWndContainer manages a collection of CWndControl instances and
// is designed for attachment to a CDialog such as CControlDlg.
span>
// When a control is added to the container, the free pool is used
// to acquire an appropriate CWnd for attachment to the control.<
/span>
// If none is available, the container will create a new CWnd for
// it by using the factory class. When a control is removed from<
/span>
// the container, its CWnd is detached and added to the free pool
// for later reuse.
class CWndContainer
{
public:
CWndContainer();
CWndContainer();
// Attach to CDialog.
void AttachWnd(CWnd* pWnd);
void DetachWnd();
// Set resource ID range for control CWnds.
void SetResourceIdRange(UINT minResourceId, UINT maxResourceId);
// Control management.
void AddControl(CWndControl* pControl);
void AddControls(const std::list<CWndControl*>& controlList);
void RemoveControl(CWndControl* pControl);
void RemoveAllControls();
// Find controls.
CWndControl* GetControl(const CString& controlName);
CWndControl* GetControl(UINT resourceId);
void GetControls(std::list<CWndControl*>& controlList) const;
// Message handling.
BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo);
};
当MFC控件在对话框中创建动态(例如,通过使用新的,然后调用的Create()方法),可以截获这些控件的消息通过重写CDialog类的OnCmdMsg()虚方法。这就是为什么CWndContainer类也定义一个OnCmdMsg()方法。在任何带有附加CWndContainer实例的CDialog的,你可以重写对话框的OnCmdMsg()方法和简单的转发CWndContainer的OnCmdMsg()实现调用。容器的实施,将消息分派到适当的CWndControl储存容器内。这CWndControl然后发出一个CWndEvent通知它的每一个事件处理程序。
对于任何CWndControl实例,你可以添加一个或多个事件处理程序,将获得其相应的MFC控件发出的事件。事件处理程序对象实施IWndEventHandler接口如下所示。// IWndEventHandler interface.
class IWndEventHandler
{
public:
virtual void HandleWndEvent(const CWndEvent& ev) = 0;
};
事件的性质是封装的CWndEvent类:// CWndEvent class.
class CWndEvent
{
public:
// Constructor / destructor.
CWndEvent(CWndControl* sender, const CString& text);
CWndEvent();
// Public methods.
CWndControl* GetSender() const;
CString GetText() const;
void AddProperty(const CString& name, const CString& value);
bool GetProperty(const CString& name, CString& value) const;
};
下面的代码示例显示如何添加动态UI支持一个CDialog类。在这个例子中,我们只需添加一个"Hello World!"按钮,一个对话框。当按下按钮时,一个消息框显示在下面的截图所示。
{S3}
对话框的包含文件中的有关变化首先出现:// Filename: MyDlg.h
...
#include "WndEvent.h"
// Forward declarations.
class CWndContainer;
class CWndButton;
// CMyDlg class.
class CMyDlg : public CDialog, public IWndEventHandler
{
DECLARE_DYNAMIC(CMyDlg)
public:
CMyDlg(CWnd* pParent = NULL);
virtual CMyDlg();
// IWndEventHandler overrides.
virtual void HandleWndEvent(const CWndEvent& ev);
...
protected:
virtual BOOL OnInitDialog();
virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo);
...
private:
CWndContainer* m_container;
CWndButton* m_button;
...
};
...
在这里,对话框的源文件的有关变化:
控制面层// Filename: MyDlg.cpp
#include "stdafx.h"
#include "MyDlg.h"
#include "WndContainer.h"
#include "WndControl.h"
...
CMyDlg::CMyDlg(CWnd* pParent /*=NULL*/)
: CDialog(CMyDlg::IDD, pParent)
{
m_button = NULL;
// Create an instance of the container
// and attach it to the dialog.
m_container = new CWndContainer;
m_container->AttachWnd(this);
}
CMyDlg:: CMyDlg()
{
// Detach the container from the dialog
// and then delete it.
m_container->DetachWnd();
delete m_container;
// Delete the button.
delete m_button;
}
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Create a CWndButton and set its properties.
m_button = new CWndButton;
m_button->SetName(_T("Button1"));
m_button->SetText(_T("Hello World!"));
m_button->SetLocation(CPoint(10,10));
m_button->SetSize(CSize(100,24));
// Attach an event handler to the button.
m_button->AddEventHandler(this);
// Add the button to the container.
m_container->AddControl(m_button);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
BOOL CMyDlg::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
// Let the container handle the message.
if ( m_container != NULL )
{
BOOL isHandled = m_container->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
if ( isHandled )
return TRUE;
}
return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
void CMyDlg::HandleWndEvent(const CWndEvent& ev)
{
if ( ev.GetSender()->GetName() == _T("Button1") )
{
MessageBox(ev.GetText(), _T("CMyDlg"));
}
}
...
对话框的例子是相当基本的,并显示如何创建一个动态用户界面。然而,为了证明空闲池机制对资源的使用效益,我们需要一种方法CWndControl实例在运行时从容器中添加和删除。这是最好的说明使用在它控制划分成组(其中只有一组控件可以一次显示)的情况下,存在一个组选择的机制(如使用树视图或选项卡控件)。为此,我已经添加了一个类实现了"控制窗口"包含的内容,可以通过XML定义的第二层。我的这个类的主要目标是显示一个非常具体的UI方案,可用于实现资源的节约。控制面类简要介绍如下。
CTreeWnd:一个树控件的CWnd包装。用来实现在控制窗口的树视图。
CListWnd:一个列表控件的CWnd包装。用来实现在控制窗口的事件区域。
CControlDlg:这是对话框类使用CWndContainer实例。它的表面创建CWnd的控制,显示,或隐藏的实际控制。
CMarkup:从本科比的 XML解析器类。这是一个易于使用的类没有外部的依赖,它只是两个源文件(版本6.5精简版版)组成。
CControlGroup:表示"控制组",这是类似于在一个文件系统中的文件夹。一个对照组可能包含其他群体,也可能包含控件(类似于文件系统中的的文件)。
CControlXml:这是XML引擎使用CMarkup来解析XML文件,并生成对照组和控制实例。
CControlWnd:一个CWnd的派生类实现一个窗口,一个树视图的左侧,右侧的内容控制组成,和一个小的事件窗口展示事件处理。这是顶级类,它是由TestFreePool演示应用程序使用。TestFreePool应用
演示项目(TestFreePool)是我最初使用Visual Studio生成一个MDI应用程序。此应用程序只允许您打开XML文件定义为MDI子窗口的UI内容。在每个子窗口,您可以访问上下文菜单,其中包含的选项,"显示CWnd的计数"。此函数计算的实际使用水平的CChildView实例(作为资源使用情况的一个粗略估计)开始的窗口CWnd对象总数。 CChildView类是由Visual Studio生成和集成与控制面层(即CControlWnd)的MDI应用程序代码的主要点。下面的截图显示,组织示范项目是如何。
的
下载ZIP文件中包括一个发布版本的TestFreePool应用程序。如果你想自己建立示范项目,请注意,我已经排除了两个源文件,Markup.h和Markup.cpp从zip文件中,由于许可限制。请下载从CMarkup 源代码,然后放在建设与Visual Studio之前Markup.h和Markup.cpp TestFreePool项目文件夹中的文件。如果您是使用VS 2005转换和建立示范项目,您也可能遇到Markup.cpp,725线的一个编译错误C2440。为了解决这个问题,你可以只添加(_TCHAR *)适当投以避免错误。
下图说明了每个演示应用程序的MDI子窗口的窗口层次结构。
{五}XML文件
在TestFreePool文件夹中,有三个示例演示应用程序可以通过打开的XML文件。下表介绍了每个文件还给出了一个指示,以实现资源的节约使用空闲池机制(基于总CWnd的计数)。 XML格式的选择是相当武断的 - 它基本上允许你定义一个控制组的层次结构,其中每个组可以包含零个或更多的儿童群体,零个或更多的控制。文件名说明
最大的CWnd计数不使用空闲池的情况下预计的CWnd计数
Example1.xml显示每个所支持的UI控件类型。30
41Example2.xml显示12个控制组,每组包含10个标签和10个按钮。27259Example3.xml从2005年的VS选项"对话框中显示3页。3048
注意Example1.xml最大的CWnd计数可能会有所不同,取决于如何配置您的系统(因为支持的控件之一是微软WebBrowser2 ActiveX控件)上的Internet Explorer。
下面是一个该Example2.xml文件的截图演示应用程序加载。
{中六}摘要
这篇文章的目的是演示如何动态创建UI在某些情况下,同时尽量减少资源的使用。只有有限的一组控件和属性的代码来说明这个概念,是不打算例如一个通用或完整的XML表单库等,目前支持,并且是非常简单的事件处理机制。 XML的支持,增加一条,作为一个便捷的方法,演示和测试,但不是我想要的东西,以目前的主要焦点。源代码可能会更对你有用,但如果你能适应自己的特定的应用需求。例如,您可能要添加支持MFC控件,甚至是你自己的自定义控件。在示范项目的文件夹,其中概述加入新的控制支持的步骤有一个文本文件。历史2007年1月6日,日初步修订。