用ATL和MFC来创建ActiveX控件(二)

用ATL和MFC来创建ActiveX控件(二)
2010-02-01 15:08

  控件翻译

  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(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

  在ATL中管理控件属性持续涉及到两个步骤。第一步是添加你希望客户能够使用的持续接口的ATL实现。ATL包括了类IPersistStorageImpl, IPersistStreamInitImpl, 和 IPersistPropertyBagImpl, 它们实现了三个主COM持续机制。

  第二步是在控件的属性映射中插入属性。当一个客户请求保存或者加载基于ATL的控件时,ATL检查控件的属性映射表,将控件的属性输出到存储媒介,或者从存储媒介输入。属性映射表是属性名字、DISPIDs的一个表,有时还包括一个属性页面GUID。ATL遍历词表查找该持续哪个属性,并将其持续到合适的媒体。图5显示了继承了持续接口实现和一个属性映射的ATLMsgTraffic控件。

  属性页

  ActiveX控件经常在开发者将控件放到各类容器时提供属性页帮助开发者。将消息流控件放入一个对话框的开发者可能想要配置控件的各个方面,象控件的取样间隔,或者绘图线条的颜色。例如,当控件放在一个对话框中,你想通过右击鼠标得到控件的属性时,Visual Studio显示了一个突出的对话框。这里将说明其工作过程。

  Visual Studio请求控件在一个对话框框架里显示属性页(Visual Studio IspecifyPropertyPages接口请求控件提供一个属性页的清单),属性页显示在Visual Studio中,但是通过控件提供的一个COM接口,保持与控件的连接。每当你完成了属性编辑并从Visual Studio中关闭了对话框,它就会要求属性页更新控件中的属性。

  当你生成一个MFC的控件时,wizard给你一个对话框模板和一个从ColePropertyPage中派生的代表此控件的缺省属性页的类。Visual Studio使得实现一个控件的属性和此属性页中的属性的连接变得容易了。当你使用ControlWizard的Automation tab添加属性到你的基于MFC的控件中的时候,你给了属性一个外部名字。这个名字是外部客户方(包括属性页)用来识别该属性的。

  你按照开发其它任何对话框的方法来开发属性页——将控件添加到对话框模板,将对话框成员变量和控件联系起来。ControlWizard添加DDX/DDV代码在对话框控件和成员变量之间交换数据。然而,当你将成员变量和对话框控件相关联时,ControlWizard给你提供了这样一个机会,你可以将外部属性名字用于对话框的成员变量。此外部名字是你给控件添加属性时键入的字符串。

  当属性页需要将变化应用于控件时(例如当按下Apply按钮时),属性页使用控件的Idispatch接口以及外部名字来修改控件的属性。在MFC中,你可以通过ClassWizard来添加一个新属性,添加一个新的对话框模板到工程中,让ClassWizard创建一个类——要确保是从ColePropertyPage中派生出来的类。然后,为了使新的属性页可以被外界访问到,将它的GUID添加到控件的属性页映射中(在控件的.CPP文件中查找BEGIN_ PROPPAGEIDS 和 END_PROPPAGEIDS两个宏)。不象MFC的ActiveX ControlWizard,ATL COM App Wizard并不向DLL中添加缺省的属性页。这意味着你要自己完成此工作。幸运的是,又一个wizard可以向属性页中添加基于ATL的DLL。只要选择Insert ATL Object,然后找到属性页对象。Wizard将一个对话框模板和一个C++类与所有必要的COM goo一起添加到一个属性页中。让它们完成什么工作是你的事情。

  不幸的是,ATL属性页的wizard驱动特性不如基于MFC的属性页,你得手工完成应用和显示操作。这就意味着提供Apply 和 Show的函数实现到你的属性页类中。Apply函数只是提取对话框中控件的状态,遍历属性页拥有的指向控件的接口指针列表,使用接口指针来修改控件属性。Show函数通常提取控件的状态,然后以次来组织对话框的控件。下面的代码显示了基于ATL的属性页是怎样处理Apply函数的:

STDMETHOD(Apply)(void)

{

    long nInterval = GetDlgItemInt(IDC_EDITINTERVAL);

    ATLTRACE(_T("CMainPropPage::Apply/n"));

    for (UINT i = 0; i < m_nObjects; i++)

    {

        IATLMsgTrafficCtl* pATLMsgTrafficCtl;

        m_ppUnk[i]->QueryInterface(IID_IATLMsgTrafficCtl,

                                   (void**)&pATLMsgTrafficCtl);

        if(pATLMsgTrafficCtl) {

            pATLMsgTrafficCtl->put_Interval(nInterval);

            pATLMsgTrafficCtl->Release();

        }

    }

    m_bDirty = FALSE;

    return S_OK;

}

  为基于ATL的控件提供一个属性页的第二步是确保属性页的CLSID出现在控件的属性映射中,图5中列出的属性持续代码提供了它的一个例子。消息映射表明了控件的图线颜色,被标准的颜色属性页管理。控件的取样间隔由控件的主属性页来管理。

  Window 消息

  MFC和ATL在它们处理window消息方面有很多共同之处,都使用消息映射,都有wizards来生成代码处理window消息。在MFC中,消息映射可以添加到任何一个CcmdTarget派生的类中,然后你就可以用ClassWizard来建立你的控件的事件处理器了。图6显示了基于MFC的控件怎样处理WM_ TIMER消息。另外,MFC提供了处理命令和控件通告的宏。象MFC一样,ATL通过消息映射来处理window消息,只要你的类是从CwindowImpl派生的,而且包含ATL的消息映射宏,你就可以使用类视来建立事件处理器。图7显示了ATL消息流控制是怎样处理WM_TIMER消息的。

  ATL使用MESSAGE_HANDLER宏将标准的window消息映射到一个C++类。此宏简单的产生一个将window消息和类的成员函数关联的表。除了常规消息,消息映射还可以处理其它类型的事件。图8显示了能参与消息映射的各种宏。

  连接和事件

  最后要进行的比较是MFC和ATL各是怎样处理连接点和事件集的。为了管理连接点和事件集,需要一个COM类来实现IconnectionPointContainer,然后创建一种提供指向IconnectionPoint的指针给客户的方法。MFC的主控件类,ColeControl,已经有了内置的IconnectionPointContainer,MFC通过连接映射提供了连接点。MFC已经为IPropertyNotifySink定义了连接点和控件的缺省事件集。

  为了完善一个基于MFC的控件的缺省事件集,你只要简单的使用ClassWizard’s ActiveX Event tab。在你使用ClassWizard添加事件的时候,Visual Studio更新你的控件的.ODL文件,为潜在的包容器描述外出事件。另外,Visual Studio添加一个函数到你的类中,你可以调用它反过来向包容器激发事件。图9演示了基于MFC的控件的事件触发机制。基于MFC的控件的事件触发函数只是由包容器在它和控件建立连接点时提供的一个Idispatch指针的一些简单的包裹器。

  在基于ATL的控件中建立事件则有所不同。在基于ATL的控件中,你从定义控件的.IDL文件中的事件开始。接着你建立了类型库的编译工程文件。图10显示了在IDL中描述的基于ATL的控件的事件集。

  一但类型库编译通过,你就可以通过在类视中选择控件的类,在类上右击,然后选择Implement Connection Point,让类视来为你创建一个回调代理了。Visual Studio弹出一个对话框,列出控件类型库中所有可访问的事件接口。你选择那些你希望回调代理做的,Visual Studio就为你写一个代理。图11显示了基于ATL的消息流控制的回调代理。Visual Studio产生的回调代理代表了一个C++友好的函数集,被客户实现的接口所调用。

  MFC的IconnectionPointContainer实现是硬分布到ColeControl中,并且每个连接点是由一个连接映射处理的,而ATL的实现是用多重继承处理的。你的控件类继承IconnectionPointContainerImpl和类视生成的代理。如果你开始一个工程的时候,选择了"Supports connection points",ObjectWizard就为你添加IconnectionPointContainerImpl。如果你忘了标记检查框,你可以写进去。此代码显示了连接点机制是怎样加入一个控件中的。类ATL_NO_VTABLE CATLMsgTrafficCtl :

{

•••

   public IConnectionPointContainerImpl,

   public CProxy_DATLMsgTrafficEvents

•••

    {

    LRESULT OnTimer(UINT msg, WPARAM wParam,

                    LPARAM lParam,

                    BOOL& bHandled) {

    //•••

        if(nMessagesToShow > m_threshold)

        {

            Fire_ExceededThreshold(nMessagesToShow, m_threshold);

        }

    //•••

    }

};

  作为一个应用框架ATLMFC的比较

  最近,许多开发者开始对使用ATL作为框架来开发应用和控件感兴趣了。当然,MFC已经使用了很长时间了,是一个能够开发可双击的基于Windows的应用的非常成熟的框架。例如,MFC包括了这样的特性作为一个总体文档/视体系结构:Object Linking 和 Embedding支持, 以及工具条和状态栏。

  然而,所有这些功能都是有代价的。一些更加普遍的抱怨是MFC的比较深的足迹(无论是在DLL中,或是在静态连接的版本中),以及自身的某种相互依存性。例如,买入MFC的一种特性意味着买入MFC的对象连接和嵌入,这意味着买入MFC的文档/视结构。另一方面,ATL是一个原始的框架,没有任何应用框架goodies。

  正象你已经看到的,两个框架都提供创建控件的可行途径。然而,两者都各有千秋,也各有弊病。用MFC编写控件通常更加容易——尤其是如果你不是在开发COM集中的应用并且你需要windowing和drawing支持。ATL的体系架构更加靠近COM的核心,你还会经常发现自己在编写很多的SDK类型的代码——就是说,你在回过头来用window和设备上下文句柄。ATL为更广范围的控件类型提供了很大的支持,象复合控件,基于HTML的控件,没有design-time接口的轻量级的控件等等。MFC仅提供完全成熟的控件。

  对ATL进行分支的实现是非常直接的。例如,增加一个接口通常是一件添加接口到继承表,在COM映射中添加一个入口然后实现接口函数的工作。分支MFC的实现通常是一种折磨。例如,添加一个接口到基于MFC的控件意味着处理所有那些接口映射宏。

  最后,ATL提供了大量的调试支持,包括接口引用计数以及QueryInterface调试支持,这在MFC中是没有的。

  这两种体系架构的区别是非常明显的。通常,MFC使得你很快完成你的工程并更快的运行起来,但是牺牲了灵活性。ATL没有那样快,那样容易使用,但是它是COM友好的。而且,好像随着ATL的成熟,它将会越来越容易使用。

 

转贴出处:

http://hi.baidu.com/xibin/blog/item/449eb45171b31713377abe58.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值