无窗口的激活此选项覆盖COleControl::GetControlFlags,将windowlessActivate标志附加到控件标志中。一旦使此选项有效,包容器就将输入消息送交到控件的IoleInPlaceObjectWindowless接口。此接口ColeControl的实现通过你的消息映射分发消息。你就能通过简单的添加相应的入口到消息映射表,像处理一般windows消息那样处理消息了。
无省略的设备上下文选择了此选项覆盖COleControl::GetControlFlags,关闭clipPaintDC位,从而在ColeControl的 OnPaint函数中去掉了IntersectClipRect调用。如果你确定你的控件并不需要在客户区外部绘图,这个选项就有用了,因为使对IntersectClipRect的调用失效后,有一个明显的速度的提高。
无抖动的激活选择此选项覆盖COleControl::GetControlFlags,将缺省控件标志与noFlickerActivate逐位相或。控件在激活的时候检查此标志以阻止控件在激活和非激活状态转换时被重画。如果你的控件在激活和非激活状态外观一样,那么这个选项就是有用的。
非激活时的鼠标指针通知这个选项覆盖COleControl::GetControlFlags,附加了pointerInactive位。IpointerInactive接口使得一个对象大多数时间保持非激活,然而仍然参加与鼠标的某些操作的交互,例如拖放。
优化的绘图码 这个选项覆盖COleControl::GetControlFlags,打开canOptimizeDraw位,具有优化绘图代码的控件检查这个标志(通过COleControl’的IsOptimizedDraw函数)来确定控件是否需要在完成绘画后将旧的对象复原回设备上下文。
异步加载属性此选项将stock ReadyState属性和stock ReadyStateChange事件加入到控件中去。这将使得控件异步的加载其属性。例如,一个加载大量的数据作为其属性之一的控件会需要很长的时间来加载,而锁住了控件。这个stock属性和事件使得此控件立刻开始加载过程。包容器使用此事件和属性判断控件何时完成加载。
当ControlWizard完成这些事情后,你就得到了编译到一个包含此控件的DLL的源代码(扩展名是.OLX)。由wizard产生的源代码包括一个从ColeControlModule(它是从CwinApp中派生的)中派生的类。这个类包含整个控件模块的初始化代码。接着,wizard为基于ColeControl的表示每个控件的类生成源代码。最后,wizard生成一些ODL代码用来建立类型信息。
一旦wizard产生了控件DLL,你就面临完善这个控件的任务了。这意味着添加渲染代码,开发一个引入接口(方法和属性),rigging up属性页,展示某些事件。但是,在我向你说明所有这些都是如何工作之前,我们先来看一下使用ATL创建一个控件都需要什么。
用ATL开发一个控件
有了基于MFC的控件,你就可以用ATL COM App Wizard得到一个开发基于ATL的控件的触发器。使用ATL来创建控件可以分为两步。虽然MFC的Control Wizard要求你预先确定你希望在DLL中包含多少个控件,ATL COM Wizard简单的创建DLL——你可以以后使用ATL对象选项从Insert菜单添加控件。当创建一个新的基于ATL的DLL时,你可以选择混用MFC支持。你还可以选择在控件的DLL中合并任何proxy/stub代码。这使得如果有人希望远程控制你的控件实现的代码,你只要发布一个文件。
一旦生成了基于ATL的DLL,你就可以开始添加COM类了。Insert New ATL Object菜单条使得这项工作变得十分容易。选择此菜单项显示一个用来创建任何一个COM类的对话框,包括无格式的COM对象,ActiveX控件以及Microsoft事务服务器组件(Windows NT Server的一部分)。
当添加基于ATL的控件到你的工程的时候,ATL Object Wizard比MFC Object Wizard提供了更大范围的选项。对于新手来说,ATL使得你可以选择使用任何现有的线性模型实现你的控件。你可以将你的类标记为或者单线程或者单元线程的。ATL Object Wizard限制你创建一个自由的或者混合线程的控件,因为控件通常是面向UI的。
如果你创建了一个单线程的控件,一个包容的控件的客户将总是将它加载到主、单线程的单元(STA)中。结果,只有在客户进程空间运行的单主线程才会接触到你的对象,这样就免除了你保护你的控件状态不受并发访问干扰的责任。另外,因为你的对象的所有实例将只会被一个线程接触,你将不必担心DLL中的全局数据。如果你的控件是单元线程的,你还免除了保护你的控件的内部状态的大部分负担。然而,你仍然不得不保护DLL中的全局数据。为什么呢?首先,设想你的控件是由客户的单线程创建的。现在假定客户试图创建该控件的另一份拷贝——但是是从一个云向在当前进程的多线程的单元中。通过将你的对象标记为单元线程的,COM被告知你希望你的控件保护免遭并发访问。COM在它加载时为你的控件创建一个新的STA。现在当线程调用到你的对象时,它们只能通过单元边界来访问它,远程层将同步对此对象的调用。然而,当某个控件的状态被保护不被并发访问,作为在一个STA中的副产品,由控件的实例所共享的数据(象在DLL中的全局数据一样)是脆弱的。这是因为你的全局DLL数据(同时为几个对象服务,分别运行在独立的线程中)会被那些多线程同时接触到。
虽然基于MFC的COM类总是可聚合的(内置了对它的支持),ATL ObjectWizard使得你可以指定你的控件支持聚合,只是可聚合的,或者是独立的对象。根据你选择的聚合选项,ATL ObjectWizard使用一个宏来执行聚合策略。例如,缺省的COM类的实现是可聚合的——对象将既运行在独立的模式,又作为一个聚合的一部分。如果你使你的COM对象不可聚合,ObjectWizard把DECLARE_NOT_ AGGREGATABLE宏加到你的类定义中。如果你选择了仅是可聚合的,ObjectWizard把DECLARE_ ONLY_AGGREGATABLE宏加入到类定义中。
这里是宏如何工作的。缺省的对象创建在一个名为_CreatorClass的类中发生。_CreatorClass当被加入到服务器范围的对象映射后(这是OBJECT_ENTRY宏所做的工作的一部分),就成为你的COM类的创建机制。_CreatorClass其实只是一个名为CComCreator2类的别名,此类将两个从CcomCreator类中定制的类作为参数。此宏根据选择的聚合模式来特制CcomCreator类,分别使用CComObject, CComAggObject, CComFailCreator, 或者CcomPolyObject:
#define DECLARE_NOT_AGGREGATABLE(x) public: /
typedef CComCreator2< CComCreator< CComObject< x > >, /
CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
#define DECLARE_AGGREGATABLE(x) public: /
typedef CComCreator2< CComCreator< CComObject< x > >, /
CComCreator< CComAggObject< x > > > _CreatorClass;
#define DECLARE_ONLY_AGGREGATABLE(x) public: /
typedef CComCreator2< CComFailCreator<E_FAIL>, /
CComCreator< CComAggObject< x > > > _CreatorClass;
#define DECLARE_POLY_AGGREGATABLE(x) public: /
typedef CComCreator< CComPolyObject< x > > _CreatorClass;
ATL ObjectWizard Attributes页中最后三个检查框包括对COM例外处理的支持(例如,IsupportErrorInfo接口),连接点以及自由线程集(FTM)。你也可以添加IsupportErrorInfo到控件的继承列表中,提供ISupportErrorInfo::InterfaceSupportsErrorInfo的一个实现。打开连接点将添加IConnectionPointImpl 模板类到控件的继承列表中。
将你的控件聚合到FTM使得单元间(以及Windows 2000的上下文间)的调用更为频繁的发生,如果两个对象正好位于同一个进程中。然而,你在编写控件时不应该检查这一点,因为当你使用FTM的时候,你多少都违反了单元(以及Windows 2000的上下文)规则。关于FTM的更多细节,请参见Don Box的Effective COM (Addison-Wesley Longman, 1998)一书。
除了你可以应用到所有COM对象的一般选项,ATL ObjectWizard还提供了几个控件创建特定的选项。首先,ATL ObjectWizard让你从一个常规控件(例如一个按钮或是一个编辑控件)中进行子类划分。你可以为你的空间指定其它几个选项使得它更加不透明,给它一个更实心的背景,在运行时不可见,或者是你的控件象一个按钮那样的工作。下面是控件属性页提供的选项的一个大纲:
不透明和实心背景如果你希望使所有的包容器都不显示在控件边界之后,选择"opaque"检查框,这是控件传给它的包容器的状态信息。结果是,控件在说明它将画出它的完整矩形。选择此选项设置VIEWSTATUS_OPAQUE位以便IViewObjectExImpl::GetViewStatus向包容器指示一个不透明的控件。你可能还想选择一个实心的背景。这个选项设置VIEWSTATUS_ SOLIDBKGND 位以便GetViewStatus指示控件有一个实心的背景。
运行时不可见此选项使你的控件在运行时不可见。你可以使用不可见控件在后台完成某些操作,例如周期性的激发事件。此选项在它加入到注册表中后使得控件翻转OLEMISC_INVISIBLEATRUNTIME 位。
仿按钮此选项使你的控件象一个按钮那样工作。此时,控件将在包容器周围属性DisplayAsDefault的基础上显示为缺省的按钮。如果控件的位置标记为缺省按钮,控件将显示为一个较厚的框架。选择此选项在它加入到注册表中后使得控件翻转OLEMISC_ACTSLIKEBUTTON 。
仿标签选择此选项使得你的控件取代包容器的内部标签。这使得控件在它加入到注册表中后标记OLEMISC_ACTSLIKELABEL。
在超类基础上添加控件选择此选项使得你的控件根据一种标准window类进行子类划分。下拉列表包含了Windows定义的window类。当你选择这些类名中的一个时,向导添加一个CcontainedWindow成员变量到你的控件类中。CContainedWindow::Create将你指定的window类超类化。
规格化DC选择此选项使得你的控件在被调用来画自己时创建一个规格化的设备上下文。这标准化了控件的外观,但是效率降低了。此选项生成的代码覆盖了OnDrawAdvanced方法(而不是常规的OnDraw方法)。
可插入的选择此选项使得你的控件显示在象Microsoft Excel 和Word 这样的应用的Insert Object对话框中。你的控件就能够被插入到任何支持嵌入对象的应用中了。选择此选项在注册表项中增加了Insertable键。
仅为窗口化的选择此选项迫使你的控件窗口化,即使在支持无窗口对象的包容器中。如果你不选择此选项,你的控件将会自动的适应包容器:在支持无窗口对象的包容器中是无窗口的,在不支持无窗口对象的包容器中是有窗口的。这将使CComControlBase::m_bWindowOnly标志设置为TRUE。ATL使用此标志来决定在控件激活过程中是否要查询包容器的IoleInPlaceSiteWindowless接口。
ATL要求你预先在Stock Properties页中决定你的对象的stock属性,你可以选择Caption或者 Border Color这样的属性,或者通过点击>>按钮一次性选择所有的stock属性。这将向控件的属性映射中添加属性。
在运行ATL COM App Wizard 和 ObjectWizard之后,你就得到了一个完整的DLL,它具有一个COM DLL所必需的所有分支。此控件显示的众所周知的出口包括DllGetClassObject, DllCanUnloadNow, DllRegisterServer,和 DllUnregisterServer。另外,你得到了一个满足COM主要需求的对象——包括一个主流入接口和一个类对象。
一旦你已经使用一个向导开始了一个工程,下一步就是使控件做点有趣的事情了。通常出发点是控件的翻译代码。你立刻得到一些可视化的反馈。让我们来看一下一个基于MFC的控件的翻译是怎样发生的。
控件翻译
MFC和ATL在翻译处理上是相似的。在每一个框架里,实现控件的类具有一个名为OnDraw的虚函数。你只需将你的翻译代码添加到OnDraw函数里。然而,在各框架里,OnDraw函数得工作有所不同。
MFC的OnDraw在两种上下文下调用。第一个上下文发生在控件响应一个WM_PAINT消息时。此时,传递给OnDraw函数的设备上下文代表了真实的设备上下文。如果控件正被要求render它自己作为对客户调用IViewObjectEx::Draw的响应,设备上下文或者是一个元设备上下文,或者是一个常规设备上下文。下面的代码说明了基于MFC的控件是怎样被render的:
void CMFCMsgTrafficCtrl::OnDraw(CDC* pdc, const CRect& rcBounds,
const CRect& rcInvalid)
{
// TODO: 用你自己的绘图代码代替下面的代码
pdc->FillRect(rcBounds,
CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
ShowGraph(*pdc, const_cast<CRect&>(rcBounds), nMessagesToShow);
}
COleControl::OnDraw的签名包括一个代表控件大小的矩形和一个代表控件非法区域的矩形。MFC调用控件的OnDraw函数来响应一个WM_PAINT消息。此时,OnDraw函数接受一个真实的设备上下文来绘图。MFC还调用控件的OnDraw函数来响应IViewObject::Draw中的一个调用。MFC的实现调用COleControl::OnDrawMetafile,它的缺省OnDrawMetafile调用COleControl::OnDraw。当然,这暗示了控件的实时翻译是与控件的元文件表示相同的,该元文件在设计时与包容器一块存储。你可以使得控件的实时的翻译与设计时的翻译不同,这通过重载COleControl::OnDrawMetafile来实现。通过调用你的控件的InvalidateControl方法,你可以强制进行一个重绘。ATL的翻译机制非常类似于MFC。CComControlBase::OnPaint建立一个ATL_DRAWINFO结构,包括创建一个绘图设备上下文。然后ATL调用控件的OnDrawAdvanced函数。OnDrawAdvanced生成元文件,接着调用你的控件的OnDraw方法,它使用ATL_DRAWINFO结构中的信息来知道怎样在屏幕上绘图。下面是ATL_DRAWINFO结构:
struct ATL_DRAWINFO
{
UINT cbSize;
DWORD dwDrawAspect;
LONG lindex;
DVTARGETDEVICE* ptd;
HDC hicTargetDev;
HDC hdcDraw;
LPCRECTL prcBounds; //在其中绘图的矩形
LPCRECTL prcWBounds; //WindowOrg and Ext if metafile
BOOL bOptimize;
BOOL bZoomed;
BOOL bRectInHimetric;
SIZEL ZoomNum; //ZoomX = ZoomNum.cx/ZoomNum.cy
SIZEL ZoomDen;
};
ATL为你填写此结构。当你正在屏幕上绘图时,你所感兴趣的最重要的域是hdcDraw 和 prcBounds。如果你对在一个元文件里绘图感兴趣,或者你需要注意缩放因子等等,那么其它域也是重要的。下面的代码显示了基于ATL的消息流控件是怎样处理绘图的:
HRESULT CATLMsgTrafficCtl::OnDraw(ATL_DRAWINFO& di)
{
RECT& rc = *(RECT*)di.prcBounds;
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(di.hdcDraw, &rc, hBrush);
DeleteObject(hBrush);
Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);
ShowGraph(di.hdcDraw, rc, nMessagesToShow);
return S_OK;
}
注意当你使用ATL的时候,你必须处理设备和GDI句柄。在ATL中,你调用你的控件的FireViewChange函数来强制控件的一次重画。
开发一个流入接口
当开发一个基于MFC的ActiveX控件时,缺省的流入接口是一个分发接口。Visual C++ 和 MFC使得开发一个流入分发接口变得十分简单——只需使用ClassWizard来生成方法和属性。每次你使用ClassWizard添加一个新的属性或方法,它就插入一个入口到你的控件的分发映射中。MFC使用分发映射来满足客户的调用请求。
MFC的缺点是在你的控件中增加一个常规的COM接口是一个枯燥无味的过程。此过程包括使用MFC的COM宏来建立实现接口的嵌套的类。
当为你的基于ATL的COM控件开发主流入接口时,类视是添加属性和方法的最好的手段。一当你为控件生成了代码,ATL ObjectWizard即添加一个缺省的流入接口。这可以是一个双端接口,也可以是一个常规的自定义接口,取决于你先前设定的工程选项。
Visual Studio的类视向你显示了你的工程中包含的所有的类和接口,在类视中右击一个接口的定义时,即可添加一个属性或者方法。使用类视来定义接口非常方便,因为每次你添加一个方法或者属性的时候,类视都会更新IDL,类源代码以及头文件。
不象MFC,给控件添加一个常规COM接口是非常容易的。在ATL中,你只要简单的添加新的接口样板文件连接(goo)(一个GUID,关键字对象和关键字接口)。类视将会显示新的接口,你可以继续添加新的成员。
添加属性
一个ActiveX控件经常包含属性,它们是描述控件的状态的成员变量。给一个基于MFC的控件添加属性的最好的手段是利用ClassWizard。ClassWizard的自动为你添加成员变量,将它们映射到缺省的分发接口。ClassWizard给你提供了两种选择:你可以添加一个成员变量,包括一个变化通告函数,或者你可以添加一对Get/Set函数,手动的添加成员变量。除了给控件添加你自己的定制属性,ClassWizard使你象添加背景和标题一样的添加库存。ClassWizard甚至自动为你的类添加一个成员变量。
为一个基于ATL的控件添加属性有一点不同,你为控件中的每个属性添加单独的存取程序和变异因子函数(propget 和 propput函数)。然而,类视只是定义了接口函数。你还要手工添加数据成员到类中,然后简单的实现这些函数。
基于ATL的控件还支持stock属性,ATL ControlWizard预先要求你确定希望哪些stock属性包括在你的控件中。添加至少一个stock属性到控件中使得控件继承自ATL的CstockPropImpl类。CstockPropImpl是Idispatch的一个实现,优化来显示ActiveX控件的stock属性,为每个标准的stock属性包含了兼容Idispatch的get 和 put函数。
ControlWizard还给控件添加代表stock属性的数据成员,例如,如果你添加了背景颜色的stock属性,ControlWizard添加一个名为m_clrBackColor的数据成员到你的类中。CstockPropImple一次性的为所有标准的stock属性的get 和 put函数添加实现。所有这些函数期望在你的类中看到合适的成员变量(象对应背景颜色的m_clrBackColor)。
编译器将在stock属性没有包括的那些get和put函数上阻塞。实现过程希望在你的类中看到成员变量。为了消除编译器错误,CcomControlBase添加了一个联合结构,它包括了stock的get 和 put函数希望看到的所有成员的名字。然而,给控件添加数据成员重载了联合类型中的名字,CstopPropImpl类在它的get 和 put函数中使用控件的成员变量
如果你忘记了使用ControlWizard预先添加stock属性,你总可以手工添加相关代码——即,从CstockPropImpl继承,然后为你想要显示的属性添加成员变量。
属性持续
MFC的属性持续机制是非常直观易懂的。从编程的观点来看,所有你要做的是填写ControlWizard已经提供的DoPropExchange函数。DoPropExchange将控件属性的状态从某些成员变量移动到持续媒体中。
MFC具有3个属性持续机制,内置于ColeControl:IPersistPropertyBag, IPersistStorage和 IPersistStream[Init]。所有这些持续机制都封装在MFC的CpropExchange类中,与当你需要serialize 一个文档时Carchive为你包装一个文件非常相似。客户方选择使用3个接口中的一个保持对象。不管使用了哪种持续机制,执行总落在控件的DoPropExchange函数中。
下面的代码显示了MFCMsgTraffic控件是怎样将它的颜色和时间间隔属性保存起来的:
void CMFCMsgTrafficCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
PX_Color(pPX, "GraphLineColor", m_graphLineColor);
PX_Long(pPX, "GraphInterval", m_interval);
}
MFC包括了若干PX_函数在控件和存储媒体间转移数据,它们是:
PX_Short
PX_UShort
PX_Long
PX_ULong
PX_Color
PX_Bool
PX_String
PX_Currency
PX_Float
PX_Double
PX_Blob
PX_Font
PX_Picture
PX_IUnknown
PX_VBXFontConvert
PX_DataPath  
说明:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
2/2 首页 上一页 1 2 |