📄 csdn_文档中心_activex control and it's container.htm
字号:
<TD align=middle bgColor=#003399><FONT color=#ffffff>关键字</FONT></TD>
<TD width=500> ActiveX Control and it's
Container</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399 height=10><FONT
color=#ffffff>出处</FONT></TD>
<TD height=10> <A
href="http://www.csdn.net/develop/style="
width:400??>style='width:400'</A></TD></TR>
<TR>
<TD align=middle height=10></TD>
<TD height=10></TD></TR></TBODY></TABLE><!--文章说明信息结束//-->
<TABLE border=0 width=600>
<TBODY>
<TR>
<TD align=left><BR>ActiveX控件和它的容器<BR> <BR>
1.COM基础<BR> 2.ActiveX控件及实现<BR>
3.ActiveX控件容器及实现<BR> 4.总结<BR><BR>1.COM基础<BR><BR>
COM是一种组件开发技术, 它实际上是一种在二进制层上兼容的软件开发方法的规范. COM技术是<BR>与具体的编程语言无关的技术,
只要是支持COM开发的开发工具都可以用来进行COM应用开发, 而它们<BR>在二进制上兼容的要求由各个开发工具来实现,
绝大部分是由编译器实现的.<BR><BR> COM的基础概念有以下几部分组成,1)接口的定义及实现,
2)IUnknown接口, 3)GUID (COM中所<BR>涉及的概念还有很多,具体的可以参阅其他资料 ).
下面分别简单的介绍它们.<BR><BR> 1).接口的定义及实现<BR>
一个接口实际上就是一组定义了具体的功能的函数的集合, 这些定义没有具体的实现. 接口的定<BR>义类似于C++中的纯虚类定义,
它定义了接口函数的返回类型、参数个数及函数的功能, COM组件就是<BR>靠这些接口相互进行通信.
一个简单的例子如下.(MFC为我们提供了许多方便的宏用来定义接口, 而<BR>且在一般情况下, 我们是使用IDL或者是ODL来定义接口,
而不是使用下面这种形式).<BR> <BR>
interface IStack:IUnknown {<BR>
virtual void Pop(int* pValue) =
0;<BR>
virtual void Push(int Value) = 0;<BR>
};<BR> 上面定义的就是一个简单的接口IStack.
它定义了两个方法,并且描述了这两个方法的返回类型<BR>(void), 参数个数和类型,
两个函数都用纯虚函数实现,在定义接口的文件里并不需要这两个函数的<BR>具体实现. 一般情况下接口的实现可以通过对接口的继承来完成,
但在一个组件实现了多个接口的情<BR>况下MFC采用了嵌套子类的实现方法, 具体情况可以参阅其他文档.<BR><BR>
2).IUnknown接口<BR> 在上面的例子中,
IStack从一个叫做IUnknown的接口继承而来, 那么IUnknown接口是一个什么<BR>样的接口呢? 再COM规范中要求,
任何一个COM组件必须实现IUnknown接口,
IUnknown接口的主要作<BR>用是用来维护COM组件的引用计数和对COM组件实现的接口进行查询,
先让我们看一下IUnknown接口的<BR>定义.<BR> <BR>
interface IUnknown {<BR>
virtual void
QueryInterface(REFIID riid, void** ppvObject) = 0;<BR>
virtual HRESULT AddRef() =
0;<BR>
virtual HRESULT Release() = 0;<BR>
};<BR><BR> 在上面IUnknown接口中,
AddRef和Release是用来维护引用计数的. 因为一个COM组件可以同时<BR>为多个应用程序服务,
如果没有一种适当的机制来维护COM组件的生存期的话, 那么当一个使用COM组<BR>件的应用程序结束时, 这个组件也会被同时释放掉,
那么其他使用这个组件的应用程序就会出现要求<BR>访问的组件不存在的错误.所以COM子系统就是用引用计数来解决这个问题,
当一个应用程序要求使用<BR>某个组件时, 它就增加这个组件的引用计数, 当这个应用程序结束时, 它就减少这个组件的引用计<BR>数,
当一个组件的引用计数为0时, COM子系统就会释放这个组件.<BR>
QueryInterface方法是用来在COM组件中查询一个接口是否被实现的方法,
因为每一个接口都拥<BR>有一个能唯一标识它自己的一个ID, 称为IID, 通过传递这IID,
我们就可以查询一个接口是否被该<BR>COM组件实现,
如果该组件实现了该接口,我们就可以利用QueryInterface方法的第二个参数传回的<BR>值来使用这个接口的方法.<BR><BR>
3).GUID<BR> 上面提到, 每个接口都由一个唯一标识自己的ID, IID,
同样每个实现了某个接口的C++类也有一<BR>个ID, 称为CLSID, 在OLE Automation中,
广泛使用了一种称为类型库的技术, 一个类型库包含了一<BR>个COM组件中所有的类型信息, 包括它实现的接口, 枚举类型, 接口的方法,
及接口参数等一些相关<BR>的信息, 同样类型库也是用一个表示自己的ID, LIBID.
COM子统为了能在众多的COM技术中尽快的找<BR>的某个类型的COM组件, 又对COM组件进行了分类管理,
而每个类又有一个类别ID,CATID, 实际上我<BR>们可以利用这个CATID来列出系统中的所有的控件(CATID_Control).
上面说的所有这些ID, 实际上<BR>是一种类型, GUID. 它们只不过是GUID的不同的typedef.<BR>
GUID是一种利用系统时间和网卡具有的唯一编号的特性生成的一个具有128位的数字.
这个数字<BR>在时间和空间上保证了它的唯一性. 所以接口及相关的一些概念都利用GUID来进行区分,
而不是利用<BR>它们的名字.<BR><BR>2.ActiveX控件及实现<BR><BR>
ActiveX控件的最早原型应该是随着VB出现的VBX控件,
由于VBX控件的16位结构并不能适应32位<BR>操作系统的要求,于是就诞生了OCX控件, OCX控件是一种32位的自包含的简单应用,
它实际上是一组<BR>完成指定的功能函数集合.它实际上是DLL的另外一种表现形式. OCX控件可以有自己的界面,也可以没<BR>有界面,
它拥有属性, 方法, 而且一个OCX控件可以触发出某种类型的事件,
用来通知容器它的状态<BR>的改变或者是某种外部状态的改变或事件的发生, 实现一个OCX控件必须实现一系列既定的接口,
这<BR>使得OCX控件显得有些庞大和冗余, 因为有些控件只需要实现这些接口的一部分, 而且对于Internet<BR>来说,
实现这些多余的接口无疑增加了控件的体积.所以在1996年PDC大会上, 微软提出了它的<BR>Activate Internet的概念,
并把它的一些技术改称为ActiveX技术, ActiveX控件就在原先的OCX控<BR>件上经过对要实现的接口的削减而诞生了,
现在只要一个COM组件实现IUnknown接口就可以被称为<BR>ActiveX控件.
所以可以说一个ActiveX控件就是一个实现了IUnknown接口并且支持自注册的简单的<BR>COM组件.<BR><BR>
但是实现一个IUnknown接口的控件显然是没有实际用处的,
所以真正的ActiveX控件还是要实现<BR>原先OCX控件定义的一些接口, 用来和它的容器进行交互操作.
下面简要的说明一下一个真正的<BR>ActiveX控件的实现.除了IUnkown接口外,
一个ActiveX控件一般要实现下面接口中的一部分.
<BR>IOleObject,IOleInPlaceObject,IOleInPlaceActiveObject,IOleControl,
<BR>IDataObject,IViewObject2, IDispatch, IConnectionPointContainer,
ProviderClassInfo<BR>[2], ISpecifyPropertyPages,
IPerPropertyBrowsing, IPersistStream,
<BR>IPersistStreamInit,IPersistMemory,
IPersistStorage,<BR>IPersistMoniker,
IPersistPropertyBag,IOleCache[2],IExternalConnection,IRunnableObject,
IClassFactory实现要求可以查看MSDN.<BR><BR><BR>
一个ActiveX控件通常具有一些属性和事件.控件的属性一般情况下是通过IDispatch接口实现<BR>的.在定义相应的控件属性时,
有一个被称为DISPID的值,这个值是用来被其他使用该控件的容器调用<BR>属性时使用的,
因为它们必须通过IDispatch接口的Invoke方法来调用相应的属性.IDispach的方法Invoke是用来调用响应的属性的关键方法,但是这个方法在调用控件的属性时,
并不是用属性的名<BR>字, 而是被称为DISPID的ID值. 在一般情况下, 一个控件通常有它自己的类型库,
容器通过查询控件<BR>的类型库得到相应的属性和方法及事件的列表,
并取得它们的DISPID,然后就可以通过Invoke方法来<BR>操作它们.<BR><BR><BR>
一个ActiveX控件一般具有三种属性, 固有属性(stock property), 环境属性(ambient
<BR>property), 自定义属性(custom property). 固有属性是大部分ActiveX控件具有的属性,
比如前<BR>景色, 字体等, 环境属性是控件处于容器中时, 有容器提供的一些属性, 如LocaleID, UserMode.
<BR>这些属性具有固定的DISPID值, 在控件中可以通过GetAmbientxxxx方法得到这些属性的值.
自定义<BR>属性是一个控件要实现自己的某些特定的功能特征时,定义的一些属性, 在容器中这些属性可以通过类<BR>型库来得到,
通过对IDispatch接口的调用来处理.<BR><BR><BR><BR>
控件的事件是由控件触发的一个消息或通知, 如果一个控件支持事件,
它必须实现<BR>IConnectionPointContainer和IConnectionPoint接口, 然后控件定义自己的出接口,
这个接口一<BR>般是通过用dispinterface声明, 在容器对控件进行事件响应时,
必须使用IDispatch接口的Invoke<BR>方法进行处理,
根据Invoke调用传进来的DISPID我们就可以知道是控件触发了哪一个事件, 根据其他<BR>信息,
我们就可以对这个事件进行处理.<BR><BR><BR><BR>
下面简单介绍一下如何利用MFC来进行ActiveX控件的开发. 首先我们使用AppWizard来生成<BR>ActiveX控件的框架,
实际上这个框架已经是一个完整的控件,
在向导的帮助下这个控件已经实现了<BR>上面提到的ActiveX控件要实现的接口的一部分重要的接口,
象对事件的基本支持,属性的支持.我们<BR>可以在这个框架的帮助下添加我们自己要实现的功能,
为这个控件添加属性方法和事件.VC中的<BR>ClassWizard在这方面提供大量的方便的操作,
在ClassWizard的AcitveX
Automation页提供了对<BR>ActiveX控件的属性事件方法的添加.<BR><BR><BR>
对于一个ActiveX控件来说你需要首先弄清楚哪些是要在控件中完成,哪些是要在容器中实现.那<BR>么,需要控件完成的你就要考虑用属性或者是方法来实现,而需要容器来完成的你只需将参数通过事件<BR>触发传递给容器,在容器端来实现.<BR><BR>
<BR>
另外,一个比较实际的问题是你的控件将是什么样子.比较简单的方法是在ClassWizard的时候指<BR>定控件将继承自那个类,从而拥有该类的外观.但这种方法不够灵活.如果你想定做控件的外观,那么最<BR>好的方法还是你自己手绘控件,或者是通过在控件内部添加一些控件形成组合控件.你可以在OnDraw<BR>(CDC*
pdc, const CRect& rcBounds, const CRect&
rcInvalid)中来绘制控件.该函数负责控件<BR>的绘制,其中pdc是当前系统用的环境设备,rcBounds是当前控件的rect范围,你可以用它来定位.绘制<BR>控件还是比较简单的,但前提是你必须要了解Windows的绘图机制.主要是会使用CBrush,CDC,CFont等<BR>MFC的基本绘图类.<BR><BR>
<BR> 实际上对于ActiveX
Control来说,在对它编程完全可以像是对一般的程序一样使用各种MFC的<BR>类,但是很多的类将不得不动态的创建,因此你必须掌握好定位.主要是掌握好对各种子类的重绘和刷新<BR>的时机和方法.<BR><BR><BR>
关于属性表的创建,属性表允许控件显示它的各种属性,以供察看和编辑.属性表通常以表的对话<BR>框的形式实现.你可以在这里改变一些控件的属性.对于大多数的控件来说这已经足够了.
<BR><BR><BR> 下面我们来看看VC的AppWinzard
Control都为我们做了些什么.用VC的AppWinzard Control你<BR>可以快速生成一个ActiveX
Control在这里VC自动为我们声称了两个接口:一个用来负责属性和方法.<BR>另一个用来负责事件.这个控件可以在容器运行,但是它什么也不做.并且它的外观也非常简陋.首先让<BR>我们来重新绘制它的外观,这在OnDraw中完成.
<BR><BR><BR><BR>// 设置当前的字体,并保留原字体<BR> CFont*
pOldfont;<BR> pOldfont =
SelectFontObject(pdc,m_customfont);<BR><BR>//
得到当前的各种颜色.其中TranslateColor是为了把OLE_COLOR转换成COLORREF.<BR>
COLORREF textbkcolor = ::GetSysColor(COLOR_BTNFACE);<BR>
COLORREF textforecolor =
this->TranslateColor(this->GetForeColor());<BR><BR>
COLORREF edgebkcolor = ::GetSysColor(COLOR_3DFACE);<BR>
COLORREF edgeforecolor = ::GetSysColor(COLOR_3DFACE);<BR><BR>
COLORREF oldbkcolor = pdc->SetBkColor(textbkcolor);<BR>
COLORREF oldforecolor =
pdc->SetTextColor(textforecolor);<BR><BR>
if(m_brush.m_hObject = NULL)<BR>
m_brush.CreateSolidBrush(textbkcolor);<BR> CBrush* pOldbrush =
pdc->SelectObject(&m_brush);<BR><BR>
pdc->Rectangle(&rcBounds);<BR><BR> CSize osize =
pdc->GetTextExtent(m_cstrCaption);<BR> m_size =
osize;<BR><BR>
pdc->ExtTextOut((rcBounds.right-osize.cx)/2,(rcBounds.bottom-osize.cy)/2,ETO_CLIPPED¦ETO_OPAQUE,rcBounds,<BR>
m_cstrCaption,m_cstrCaption.GetLength(),NULL);
<BR> <BR> UINT borderstyle = EDGE_RAISED;<BR> UINT
borderflags = BF_RECT;<BR><BR>// 画边框<BR>
pdc->SetBkColor(edgebkcolor);<BR>
pdc->SetTextColor(edgeforecolor);<BR><BR>
pdc->DrawEdge((LPRECT)(LPCRECT)rcBounds,borderstyle,borderflags);<BR><BR>//
恢复设置<BR> pdc->SetBkColor(oldbkcolor);<BR>
pdc->SetTextColor(oldforecolor);<BR><BR>
pdc->SelectObject(pOldfont);<BR>
pdc->SelectObject(pOldbrush);<BR>以上代码将为控件绘制一个比较好的外观,当然你可以任意改变直到你满意为止.以后你就可以根据需<BR>要来添加一些属性和方法并写出相对的实现.大部分的定义都是由CLASS
WINZARD来维护的,所以你可<BR>以轻松的添加它们.<BR><BR>
一些建议:在控件的属性页的编写过程中,需要将属性页上的标准控件与ActiveX控件的属性相联<BR>系,这样当你在动态的改变标准控件的值时,ActiveX控件的属性会随之改变.但问题是如果你使用别的<BR>方法来动态改变属性页上的标准控件的值,则ActiveX控件的属性不会随之改变.<BR><BR>原因很简单,ActiveX控件的属性不知道自己已经发生改变,所以没有接受从标准控件传来的值.这一过<BR>程是在DoDataExchange()中的DD_P函数来完成的.由于你手动的改变了标准控件的值,所以你需要使<BR>用SetModified()来通知ActiveX控件的属性发生改变,这样DD_P函数就会有效了.另外,在ActiveX控<BR>件的属性中的数据类型有一些是OLE_XXX类型,这些类型实际上是一些LONG型的值,并且COLECONTROL<BR>中有一些函数用来转换它们.在类型转换过程中尽量不要使用强制转换,这可能会带来一些意想不到的<BR>错误,鼓励使用缓冲区机制.<BR><BR><BR>
关于控件部分其实还有很多东西,可以参阅MFC或其他的文档来了解.
<BR><BR><BR>3.ActiveX控件容器及实现<BR>
ActiveX控件的容器实际上是ActiveX控件的客户端,
它使用ActiveX控件提供的各种功能.但是<BR>它也同时为控件提供了一些属性和其他的特征, 使得控件可以更好的和它进行交互和操作.
ActiveX<BR>控件的容器实际上是一个OLE容器,
然后在实现了相应的接口来支持ActiveX控件后成为ActiveX控件<BR>的容器.
除了IUnknown外,容器程序需要用到下列接口的一部分: IOleInplaceFrame,
<BR>IOleInPlaceUIWindow, IOleClientSite,IOleInPlaceSite,
IAdviseSink, IOleControlSite, <BR>IOleControlSite, IDispatch,
IProperytNotifySink, IStorage,
IOleContainer接口的具体<BR>定义请参照MSDN.<BR><BR><BR>
在MFC附带的例子中有一个很好的例子, 就是VC中附带的工具ActiveX Control Test <BR>Container.
下面就以这个例子来解释一个ActiveX控件容器的实现及对某些问题的处理.<BR><BR>
<BR> 在这个例子中, 使用了VC的向导来生成一个具有Container支持的应用程序,
在生成的类中有一<BR>个用来包装每一个嵌入到问档中的OLE对象的类, 一般被称为xxxCntrItem,
在这个例子中被改名<BR>CTestContainer98Item. 创建每一个ActiveX控件时都是通过这个类来直接生成,
这个类维护了<BR>ActiveX控件的一些属性特征.而且这个类支持序列化,
这样我们就可以通过序列化来保存控件的属性<BR>状态等信息.<BR><BR><BR>
1).动态创建控件.<BR> 这应该是一个ActiveX控件容器最重要的任务. 为了能管理容器中的控件,
首先它必须能动态的<BR>创建控件. 因为每一个COM组件都具有一个唯一的ID, CLSID, ActiveX控件也不例外,
但是针对系统<BR>中成百上千的COM对象, 我们如何确定哪一个是ActiveX控件呢?
在COM基础中我们提到了为了能更快<BR>的定位COM组件并加载它,
COM子系统对COM组件实行了分类别管理即利用CATID来分类各种不同的COM<BR>组件,
ActiveX控件的CATID是CATID_Control,所以我们可以通过这个信息来找到所有在系统中注册<BR>的控件,
一般情况下我们是通过生成一个列表来表示所有这些控件.
下面是经过改写的<BR>CInsertControlDlg::RefreshControlList()函数<BR><BR><BR>CArray<
CATID, CATID& >
m_aImplementedCategories;<BR><BR>CListBox
m_lbControls;<BR>ICatInformationPtr m_pCatInfo;<BR>CList< CLSID,
CLSID& > m_lControls;<BR><BR>void
CInsertControlDlg::RefreshControlList()<BR>{<BR>BOOL
bDone;<BR>HRESULT hResult;<BR>IEnumGUIDPtr pEnum;<BR>ULONG
nImplementCategories;<BR>CATID* pcatidImpl;<BR>CLSID
clsid;<BR>LPOLESTR pszName;<BR>CString strName;<BR>ULONG
iCategory;<BR>int iItem;<BR>POSITION posControl;<BR>CString
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -