1.44k likes | 1.59k Views
第十二章 ActiveX 控件的使用和创建. 近年来,软件产业已经发生了一场革命性的变化。软件的制 作和打包方式已经不再是所有的应用程序都必须从源代码编译 链接成一个完整的、很大的可执行代码文件,而是大多数应用 程序都可以由一些较小的 构件 组成。这些小的 构件 ,通常称为 组件 。这些 组件 可以用多种不同的程序语言创建,且可以具有 多种不同的的形式。最为流行的 组件 之一便是 ActiveX 控件 。 组 件 不但可以作为 最终软件产品 提供给其他程序设计人员,而且 在 大型软件开发中 ,使用 组件 也是 组织 不同分工的程序设计人
E N D
近年来,软件产业已经发生了一场革命性的变化。软件的制近年来,软件产业已经发生了一场革命性的变化。软件的制 作和打包方式已经不再是所有的应用程序都必须从源代码编译 链接成一个完整的、很大的可执行代码文件,而是大多数应用 程序都可以由一些较小的构件组成。这些小的构件,通常称为 组件。这些组件可以用多种不同的程序语言创建,且可以具有 多种不同的的形式。最为流行的组件之一便是 ActiveX 控件。组 件不但可以作为最终软件产品提供给其他程序设计人员,而且 在大型软件开发中,使用组件也是组织不同分工的程序设计人 员共同完成整个软件设计开发的重要策略和方法。本章的学习 目的是:
掌握如何使用 ActiveX 控件,以便在软件开发中使用第三方提供的产品化组件和如何创建自己的 ActiveX 控件,以便开发产品化组件,提供给其他程序设计者。本章的主要内容包括: ·什么是ActiveX 控件以及它们是如何工作的。 ·如何在项目工作区中添加ActiveX 控件。 ·如何在Visual C++ 应用程序中使用ActiveX 控件。 ·如何调用与ActiveX 控件相关联的各种方法。 ·如何处理由ActiveX 控件激活的事件。 ·如何用Visual C++ AppWizard建立ActiveX 控件项目。 ·如何用ClassWizard 向ActiveX 控件添加属性和方法。 ·如何用Visual C++ 提供的工具测试自己的ActiveX控件。
12.1 什么是 ActiveX 控件 在介绍ActiveX 控件之前有必要了解另外两个编程技术概念: ·OLE(Object Linked and Embeded) 对象连接嵌入是 Microsoft 基 于对象的技术。该技术用于跨越进程和机器边界的数据信息 和操作方法的共享。不过最初的OLE 仅仅允许把不同的应用 程序创建的文档组合成一个单一文档。 ·COM(Component Object Model) 组件对象模型是遵循OLE 基本 技术的对象模型。一个COM 对象是一个对象定义的实例,该 对象定义指定了该对象的数据和一个或多个作用于该对象的 接口执行方法。客户程序与COM 对象之间的相互作用只能通 过 COM 对象的接口实现。
ActiveX 控件就是一组封装在 COM 对象中的功能模块。这个 COM 对象是独立的,但并不能单独运行,而只能在 ActiveX 容器 中运行,如 Visual C++ 或Visual Basic 应用程序,这一点很像在组 合设备中插入具有特定功能的组件,例如在组合式音响中,插 入一个 DVD 播放组件。
12.1.1 ActiveX 和 IDispatch 接口 每个 COM 对象都有一些标准接口,例如, IUnknown接口, 该接口用来询问是否找到了该组件所支持的其他接口。 每个接口支持一组特定的功能,例如,可以用一个接口来处 理控件的可视外观,一个接口来控制控件外观如何与插入该控 件的应用程序进行交互,一个接口来触发插入该控件应用程序 中的事件,等等。 ActiveX 技术是建立在微软的 COM 技术之上,并使用 COM 的 接口和交互模型使 ActiveX 控件与插入控件的应用程序进行完全 无缝的集成。COM 技术奠定了构建 ActiveX对象的方式及设计 ActiveX 接口的方法。ActiveX技术定义了建立于 COM之上的层 面、各种对象应该支持什么样的接口以及如何与不同类型的对 象交互。
ActiveX 控件的关键技术之一是自动。所谓 “自动” 可描述为: ·将一个应用程序中嵌入另一个应用程序。 ·当用户的操作涉及到被嵌入者的功能时,激活被嵌入者,并 控制被嵌入者的用户接口或文档部分,同时进行被嵌入者自 身的更改。 ·当用户将操作转移到应用程序中非嵌入程序的控制部分时, 被嵌入者自行关闭(例如在word 应用程序中自动嵌入Excel 电子表格应用程序)。 实现自动工作的关键之一是特殊(调度)接口IDispatch。
ActiveX 控件可以提供的所有方法有各自的唯一标识值 DISPID。 这些标识值被存放在用来查找特定方法的标识列表中。IDispatch 接口由一个指示方法的标识列表和 IDispatch 接口提供的方法组 成。当获取一个特定方法的 DISPID 之后,就可以将该方法的 DISPID 作为参数,通过调用 IDispatch 接口的方法 Invoke来实现 调用 DISPID 所标识的指定方法。下图示意性描述了 IDispatch 接 口如何使用 Invoke 方法来运行 ActiveX控件提供的方法,实现的 ActiveX控件的自动化。
ActiveX 对象 DISPID1 DISPID2 DISPID3 DISPID4 DISPIDn 调度接口 IDispatch vtable IDispatch::Invoke(DISPID) 客户程序 Invoke(){ switch (DISPID3) { case 1: MethodX(); case 2: MethodY(); case 3: MethodZ(); … }
12.1.2 ActiveX 容器和服务器 任何可以嵌入另一对象的 ActiveX 对象都是 ActiveX服务器, 而无论它是一个完整的应用程序或仅仅是一个 ActiveX 控件。 任何可以包含其他被嵌入 ActiveX 服务器的 ActiveX 对象都是 ActiveX 容器。 注意,不要把术语容器和服务器与上图中的客户程序混淆。客 户程序是指调用其嵌入对象的 IDispatch 接口的对象。容器和服 务器都相互调用对方的 IDispatch 接口,因此它们相互成为对方 的客户程序。
这两种类型的 ActiveX 对象并不互相排斥。ActiveX 服务器同时 也可以是 ActiveX 容器,例如,微软的 Internet ExplorerWeb浏览 器中 Internet Explorer 是一个可以在 ActiveX 容器外壳中运行的 ActiveX 服务器。可以运行该服务器的 ActiveX 容器外壳还可以包 含 Word、Excel、Powerpoint等其他应用程序,同时这些应用程序 还可以作为其他应用程序的 ActiveX服务器。 ActiveX 控件是 ActiveX服务器的一个特例,即该服务器不能 自身运行,必须被嵌入到 ActiveX 容器中。如果在 AppWizard 所 创建的 MFC 应用程序项目中,设置了使用 ActiveX 组件选项,则 该项目所创建的应用程序就自动成为一个 ActiveX 容器。
ActiveX 容器和 ActiveX 控件之间的大多数交互操作是通过三个 IDispatch 接口完成(如下图所示)。这些 IDispatch 接口中的一个 位于控件中,通过该接口,容器可以调用控件的各种方法,为 容器的功能提供服务。 容器也为控件提供两个 IDispatch 接口。其中一个接口用于控 件在容器中触发事件。另一个接口用于容器为控件设置属性, 也就是说 ActiveX 控件的大部分属性实际上由是容器提供,而由 控件实现的。当设置属性时,容器调用控件中一个方法,以便 通知控件从容器中读取所提供的属性。 Visual C++ 创建了一系列 关于 ActiveX 控件接口的 C++ 类,用户只与这些 C++ 类“暴露” 给 用户的方法交互,而不需要直接调用控件的 IDispatch 接口,所 以上述活动中的大部分对用户来说是“透明”的。
IDispatch(事件) IDispatch(属性) IDispatch ActiveX容器 ActiveX控件
12.2 在应用程序项目中添加和使用 ActiveX 控件 使用Visual C++ 使得在应用程序项目添加和使用 ActiveX 控件 变得十分方便。下面通过实例详细讲述如何创建一个可以包含 ActiveX 控件的应用程序项目;如何为这个项目添加 ActiveX 控件 和如何在应用程序中使用所添加的 ActiveX 控件。
12.2.1 创建一个可以包含 ActiveX 控件的应用程序 1 创建一个 MFC 应用程序项目,命名为 “ActiveX”。 2 选择项目类型为 Dialog Based,并在创建过程中注意选择项目 具有 ActiveX Controls 支持状态,其他均可取默认选择。 3 删除缺省对话框模板 IDD_ACTIVEX_DIALOG 中的所有缺省控 件,添加一个命令按钮:标识为 IDC_EXIT,标题为 E&XIT。 4 在缺省创建的 CActiveXDlg 类中,为新添加命令按钮 IDC_EXIT 的 BN_CLICKED 通知消息建立消息映射 ON_BN_CLICKED(IDC_EXIT, OnExit) 和定义消息处理函数 OnExit的原型和定义 afx_msg void OnExit(); void CActiveXDlg::OnExit() { OnOK(); }
12.2.2 注册 ActiveX 控件 在给应用程序项目添加 ActiveX 控件之前,必须在系统中注册控件。 在系统中注册 ActiveX 控件的方法有两种。一种方法是运行 ActiveX 控件的安装程序,进行自动注册。另一种方法是手工注 册 ActiveX 控件。手工注册的步骤如下: 1 进入 DOS 控制台界面。 2 将当前目录改变到 ActiveX 控件文件所在的目录中,例如: Windows\system。
3 执行系统命令 regsvr32,并指定 ActiveX 控件名为该命令的参 数。例如要注册一个文件名为 MYCTL.OCX 的 ActiveX 控件, 假如该控件文件 MYCTL.OCX 在 Windows\system 目录中,则可 执行如下命令: C:\WINDOWS> cd system C:\WINDOWS\SYSTEM> regsvr32 myctl.ocx
注意: ·手工注册可能会导致所注册的控件缺少某些信息,从而在开 发中无法使用,所以建议使用控件所带的安装程序。 ·如果所使用的 ActiveX 控件在系统安装时已经被缺省注册了, 则不需要使用上述方法进行控件的注册。本例中要添加的控 件就是这类 ActiveX 控件。 ActiveX 控件一旦在系统中注册成功,就可以将它添加到应用 程序项目中。 在 Visual C++ 6.0 中注册和添加 ActiveX 控件的步骤如下:
1 选择Project->Add ToProject->Components and Controls。 2 在弹出的“Components and Controls Gallery”对话框中,选择 “Registed ActiveX Controls”文件夹:
3 在该文件夹中,查找并选中要添加的已注册ActiveX 控件,本 例中选择Microsoft FlexGrid Control version 6.0 控件,双击被 选中控件选项,或按《Insert》按钮。
4 在提示是否确实要添加该控件的对话框中,按《OK》按钮。 5 在 “Confirm Classes” 对话框中,单击《OK》按钮添加控件所 包含的全部或部分C++ 类:
6 在 “Components and Control Gallery” 对话框中单击《Close》按 钮完成为项目添加控件的工作。 7 控件 FlexGird 已经被添加到资源编辑器的 “Control Palette” 上:
8 查看工作区的 Class View,发现项目中已自动增加了与 FlexGird 控件相关的类: CMSFlexGrid、COleFont、CRowCursor和 CPicture,每个类中都 提供了相应的方法。
在 Visual C++ .NET 中注册和添加 ActiveX 控件的步骤如下: 1 在 Toolbox 中,单击鼠标右键弹出的环境菜单中选择菜单项 “Choose Items…”:
2 在弹出的属性表《Choose Toolbox Items》中,选择属性页 “COM Components”,在该属性页中选择所需的 ActiveX 控件 “FlexGird” 后,按 OK按钮。
3 添加了 ActiveX 控件 “FlexGird” 后的 Toolbox 如下: 注意,经过上述操作后,并不会在项目中增加封装 “FlexGird” 控 件的类 CMsfgrid(相应的定义文件和实现文件)。只有将控件从 ToolBox 中添加到对话框模板中,控件的类 CMsfgrid(相应的定 义文件和实现文件)才会被自动添加到项目中。
12.2.3 在对话框模板中添加 ActiveX 控件 ActiveX 控件添加到项目中之后,便可以像使用其他标准控件 一样,把它添加对话框模板中。本例中所添加的ActiveX 控件 FlexGird 的主要属性设置如下:
在完成对控件所有属性的设置之后,需要为该控件添加一个数在完成对控件所有属性的设置之后,需要为该控件添加一个数 值类对象m_ctrlFGrid,以便能和代码中的控件进行交互。所添 加的代码如下: class CActiveXDlg : public CDialog { … public: CActiveXDlg(CWnd* pParent = NULL); // standard constructor … enum { IDD = IDD_ACTIVEX_DIALOG }; CMSFlexGrid m_ctrlFGrid; … };
12.2.4 在应用程序中使用 ActiveX 控件 12.2.4.1 与 ActiveX 控件进行交互 本例中将使用添加的 FlexGrid 控件生成一个产品销售数字统 计表,其中包括4 个销售人员在5 个销售区的销售情况。要求能 够在屏幕上滚动显示数据,这些数据应按能区域或产品种类分 类,以比较各个销售人员在每种产品上的销售业绩。为此,首 先调用 FlexGrid 控件的方法 SetTextArray 将要处理、显示的数据 存入到控件的数组中,并将数组中数据将被载入表格的相应单 元格中。调用 FlexGrid 控件的内置排序方法 SetSort,使表格按 升序排列。为了实现这些操作需要为 CActiveXDlg 类添加如下成员函数定义:
1 把数据载入控件 添加一个私有成员函数将数据装载到 FlexGrid 控件中,该函 数命名为 LoadData,函数类型为 void,其定义代码如下: void CActiveXDlg::LoadData() { int liCount; // The grid row count CString lsAmount; // The sales amount // Initialize the random number generator srand((unsigned)time(NULL)); // Create Array in the control for( liCount = m_ctrlFGrid.GetFixedRows(); liCount < m_ctrlFGrid.GetRows(); liCount++ )
{ // Generate the first column (region) values m_ctrlFGrid.SetTextArray( GenID(liCount, 0), RandomStringValue(0)); // Generata the second column (product) values m_ctrlFGrid.SetTextArray( GenID(liCount, 1), RandomStringValue(1)); // Generate the third column (employee) values m_ctrlFGrid.SetTextArray( GenID(liCount, 2), RandomStringValue(2)); // Generata the sales amount values lsAmount.Format( "%5d.00", rand()); // Populate the fourth column m_ctrlFGrid.SetTextArray(GenID(liCount, 3), lsAmount); }
// Merge the Common subsequent rows in these columns m_ctrlFGrid.SetMergeCol(0, TRUE); m_ctrlFGrid.SetMergeCol(1, TRUE); m_ctrlFGrid.SetMergeCol(2, TRUE); DoSort(); // Sort the grid }
代码分析: ·函数循环处理控件中所有行,给每个单元格中放入数据。通 过调用控件的 GetRows方法可获得控件中总行数,而调用控 件的 GetFixedRows 方法可获得有标题行的编号。通过调用控 件的 SetTextArray 方法可把数据添加到控件单元格中,调用 该方法的两个参数是由函数 GetID 获取单元格的 ID 和使用函 数 RandomStringValue 产生要放入控件单元格的数据。这两个 函数都是 CActiveXDlg 类的新增成员函数。 ·把数据放入表格单元格后,调用控件的方法 SetMergeCol,用 于通知控件,如果相邻行有着同样的值,可以把前三列的单 元格合并。 ·最后,使用另一个 CActiveXDlg 类的新增函数来完成单元格数 据的排序。
2 计算单元格 ID 控件 FlexGrid的单元格按从左至右、从上至下编号。计算单 元格 ID 的函数 GetID的访问权限为 private,其定义代码如下: int CActiveXDlg::GenID(int m_iRow, int m_iCol) { int liCols = m_ctrlFGrid.GetCols(); // Get the number of column return (m_iCol + liCols * m_iRow); // Generate an ID } 3 生成随机数据 实现这一功能的函数 RandomStringValue 将根据参数 —— 单 元格的当前列号分别为第一列产生随机的销售区域名,为第二 列产生随机的销售产品名,为第三列产生随机的销售人员名。 该函数的访问权限也为 private,其定义代码如下:
CString CActiveXDlg::RandomStringValue (int m_iColumn) { CString lsStr; // The return string int liCase; // A random value ID // Which column are we generating for switch(m_iColumn) { case 0: // The first column (region) liCase = (rand() % 5); // Generate a random value between 0 and 4 // What value was generated? switch(liCase) { case 0: // 0 - Northwest region lsStr = "Northwest"; break;
case 1: // 1 - Southwest region lsStr = "Southwest"; break; case 2: // 2 - Midwest region lsStr = "Midwest"; break; case 3: // 3 - Northeast region lsStr = "Northeast"; break; case 4: // 4 - Southeast region lsStr = "Southeast"; break; } break; case 1: // The second column (product) liCase = (rand() % 5); // Generate a random value between 0 and 4 // What value was generated?
switch(liCase) { case 0: // 0 - Dodads lsStr = "Dodads"; break; case 1: // 1 - Thingamajigs lsStr = "Thingamajigs"; break; case 2: // 2 - Whatchamacallits lsStr = "Whatchamacallits"; break; case 3: // 3 - Round Tuits lsStr = "Round Tuits"; break; default: // 4 - Widgets lsStr = "Widgets"; } break;
case 2: // The third column (employee) liCase = (rand() % 4); // generate a random value between 0 and 3 // What value was generated? switch(liCase) { case 0: // 0 - Dore lsStr = "Dore"; break; case 1: // 1 - Harvey lsStr = "Harvey"; break; case 2: // 2 - Pogo lsStr = "Pogo"; break; default: // 3 - Nyra lsStr = "Nyra"; }
break; } // Return the generated string return lsStr; }
4 为控件中显示数据排序 为CActivexDlg 类定义私有成员函数DoSort 用以调用FlexGrid 控件的排序函数SetSort 实现对控件的排序。排序的方式是升序 还是降序取决于调用SetSort 的参数。其定义代码如下: void CActiveXDlg::DoSort() { // Set the current column to column 0 m_ctrlFGrid.SetCol(0); // Set the column selection to all columns m_ctrlFGrid.SetColSel((m_ctrlFGrid.GetCols() - 1)); // Generic Ascending sort m_ctrlFGrid.SetSort(1); }
5 修改 CActiveXDlg::OnInitDialog 在此函数中加入对函数LoadData 的调用,实现对FlexGrid 控 件的初始化。 BOOL CActiveXDlg::OnInitDialog() { CDialog::OnInitDialog(); … LoadData(); // Load data into the Grid control … return TRUE; // return TRUE unless you set the focus to a control } 6 编译运行 “ActiveX”
12.2.4.2 响应控件事件 运行上面的程序,你会发现 FlexGrid 控件对任何输入事件都 没有响应。这是因为虽然 ActiveX 控件为 Visual C++ 应用程序提 供了多种事件,但大多数 ActiveX控件并没有与可用事件相关联 的缺省功能,而必须告诉控件在每个事件发生时做些什么。 在本例中,我们将添加两个控件事件的响应,使用户可以按 住鼠标左或右键选中列标题并将它拖动到另一个位置,从而重 新安排列的顺序。实现此功能,必须捕获两个控件事件:鼠标 左或右键被按下和被释放。 ·在鼠标按钮按下事件中:需要检查用户单击了列标题,如果 是,应捕获所选中的列。
·在鼠标按钮释放事件中:需要将所捕获的列移动到鼠标被释·在鼠标按钮释放事件中:需要将所捕获的列移动到鼠标被释 放时所处的列位置。 要完成这项功能,还需要在 CActiveXDlg类中增加一个私有数 据成员,用于保存所捕获列的列号,所添加的代码如下: … private: int m_iMouseCol; 1 捕获所选列 ·使用 ClassWizard 为 IDC_MSFGRID 控件对象的 MouseDown 事件 消息添加响应函数; ·编写函数代码如下:
void CActiveXDlg::OnMouseDownMsfgrid( short Button, short Shift, long x, long y ) { // TODO: Add your control notification handler code here // Did the user click on a data row and not the header row? if(m_ctrlFGrid.GetMouseRow() != 0) { // If so, then zero out the column variable and exit m_iMouseCol = 0; return; } // Save the column Clicked on m_iMouseCol =m_ctrlFGrid.GetMouseCol(); }
2 把列移动到鼠标被释放处 ·使用 ClassWizard 为 IDC_MSFGRID控件对象的 MouseUp 事件消 息添加响应函数; ·编写函数代码如下: void CActiveXDlg::OnMouseUpMsfgrid (short Button, short Shift, long x, long y) { // TODO: Add your control notification handler code here // If the selected column was the first column, there's nothing to do if (m_iMouseCol == 0) return; m_ctrlFGrid.SetRedraw(FALSE); // Turn the control redraw off m_ctrlFGrid.SetColPosition(m_iMouseCol, m_ctrlFGrid.GetMouseCol()); // Change the selected column position
DoSort(); // Resort the grid m_ctrlFGrid.SetRedraw(TRUE); // Trun redraw back on } 这两个事件消息的映射如下: BEGIN_EVENTSINK_MAP(CActiveXDlg, CDialog) //{{AFX_EVENTSINK_MAP(CActiveXDlg) ON_EVENT(CActiveXDlg, IDC_MSFGRID, -605 /* MouseDown */, OnMouseDownMsfgrid, VTS_I2 VTS_I2 VTS_I4 VTS_I4) ON_EVENT(CActiveXDlg, IDC_MSFGRID, -607 /* MouseUp */, OnMouseUpMsfgrid, VTS_I2 VTS_I2 VTS_I4 VTS_I4) //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() 3 编译运行 “ActiveX”
12.3 创建ActiveX 控件 本例通过制作一个能绘制涂鸦的控件实例,描述一个ActiveX 控件的一般创建方法,并通过测试工具和一个应用实例来验证 该控件的设计功能。 ·控件功能:能在一个可确定的矩形区域中通过点击鼠标或通 过其他方法获得模拟的鼠标点击事件,以便随机绘制涂鸦, 并且能将所绘制的涂鸦保存到一个磁盘文件中和能使用该磁 盘文件所保存的数据恢复涂鸦。为实现这些功能,所设计的 控件应具有以下属性、方法和事件:
·控件属性:属性是指控件中可见的、容器应用程序可修改的·控件属性:属性是指控件中可见的、容器应用程序可修改的 属性数据。四种基本的属性类型是: 环境(ambient) 由容器应用程序提供给控件,例如,背景 颜色或缺省字体,使控件看上去就像是容器应用程序的 一部分; 扩展(extended) 由容器应用程序提供并实现的属性,控件 可以对这些属性做一定扩展; 库存(stock) 由ActiveX 控件开发工具实现,例如控件的 字体或控件的背景颜色; 定制(custom) 是最需要关注的属性,因为这些属性是所 设计的控件专有的,并且直接与控件的功能有关。 本例需要三个custom 属性:
NumberSquiggles绘制涂鸦的最多段数; SquiggleLength一段涂鸦的最大长度; KeepCurrentDrawing是否保持当前所绘制的涂鸦。 为了能在容器应用程序中访问和修改这些属性还需要为每个 属性提供相应的Get 和Set方法,即把这些属性“暴露”给容器 应用程序。 ·控件方法:是控件中的能被容器应用程序调用的函数。在本 控件中除了要提供三个属性的访问和设置方法,以及显示控 件版本信息的方法外,还需要提供下列三个与控件功能密切 相关的方法: DoClick用于模拟在控件区域内鼠标点击操作; SaveDrawing用于保存当前的涂鸦为一个磁盘文件; LoadDrawing使用磁盘文件中保存的数据恢复涂鸦。
·控件事件:事件是控件发给容器应用程序的通知消息。它们·控件事件:事件是控件发给容器应用程序的通知消息。它们 用于通知容器应用程序某种事件已经发生,以便容器应用程 序在需要时采取相应的措施。从控件中可以触发两类事件: 库存事件 通过ActiveX 控件开发工具实现,可以在控件内以 函数调用的方式使用,也可以使你触发容器应用程序中 的鼠标或键盘事件、错误或状态的变化。 定制事件 这些事件与控件的特定功能相关联。这类事件可 以指定与事件一起传递给容器的参数,使容器能得到所 需要的数据,以便对事件消息作出反应。 本控件中定义了三个事件: click库存事件,通知在控件区域中发生了鼠标点击; FileStored定制事件,通知当前保存涂鸦已经成功或失败; FileLoaded定制事件,通知当前恢复涂鸦已经成功或失败。