第16章 站上众人的肩膀 -- 使用Components&ActiveX Controls

从Visual Basic开始,可以说一个以components(软件组件)为中心的程序设计时代,逐渐拉开了序幕。随后Delphi和C++ Builder 陆续登场。Visual Basic使用VBX(Visual Basic eXtension)组件,Delphi和C++ Builder 使用VCL(Visual Component Library)组件,Visual C++ 则使用OCX(OLE Control eXtension)组件。如今 OCX 又演化到所谓ActiveX 组件(其实和OCX 大同小异)。

Microsoft 的Visual Basic(使用Basic 语言),Borland 的Delphi(使用Pascal 语言),以及Borland 的 C++ Builder(使用 C++ 语言),都称得上是一种快速开发工具(RAD,Rapid Application Development)。它们所使用的组件都是 PME(Properties-Method-Event)架构。这使得它们的整合环境(IDE)能够做出非常可视化的开发工具,以拖放、填单的方式完成绝大部份的程序设计工作。它们的应用程序开发程序大约是这个样子:

1. 选择一些适当的软件组件(VBX或VCL)。

2. 打开一个form,把那些软件组件拖放到form 中适当的位置。

3. 在Properties 清单中填写适当的属性。例如精确位置、宽度高度、或是让A组件的某个属性连接到B组件...等等。

4. 撰写程序代码(method),做为某种event发生时的处理例程。

依我的看法,Visual C++ 还不能够算是RAD。虽然,MFC程序所能够使用的 OCX 也是PME(Properties-Method-Event)架构,但 Visual C++ 整合环境没有能够提供适当工具让我们以那么可视化的方式(像VB或Delphi或C++ Builder 那样拖放、填单)就几乎完成一个程序。

Component Gallery 是自从Visual C++ 4.0 之后,整合环境中新增的一个东西。你可以把它想象成一个数据库,储存着ActiveX controls 和可重复使用的 C++ 类(也就是本章所谓的components)。VC++ 5.0的Component Gallery 的使用接口和 VC++ 4.x 有某种程度的不同,不过操控原则基本上是一致的。

当你安装了Visual C++ 5.0,Component Gallery 已 经内含了一些微软所提供的components 和ActiveX controls(注:以下我将把这两样东西统称为「组件」)。选按整合环境的【Project / Add To Project / Components and Controls...】选单项目,你就可以看到Component Gallery:

其中有Developer Studio Components 和 Registered ActiveX Controls 两个数据夹,打开任何一个,就会出现目前系统所拥有的「货色」:

如果你以为这些组件储存在两个地方(一个是它本来的位置,另一份拷贝放在 Component Gallery 之中),那你就错了。Component Gallery 只是存放那些组件的位置数据而已。你可以说,只是存放一个「链接」而已。

为什么组件在此分为Components和ActiveX controls 两种?有什么不同。简单地说,Components 是一些已写好的C++ 类。基本上C++ 类本来就具有重复使用性,Component Gallery 只是把它们多做一些必要的包装,连同其它资源放在一起成为一个包裹。当你需要某个component,Component Gallery 给你的是该components 的原始代码。

ActiveX controls 不一样。当你选用某个ActiveX controls,Component Gallery 当然也会为你填入一些代码,但它们不是组件的本体。那些代码只是使用组件时所必须的代码,组件本身在 .OCX 文件中(通常注册后的OCX 文件都放在 Windows\System 磁盘子目录)。

ActiveX controls 是很完整的一个有着PME(Proterties-Method-Event)架构的控制组件,但一般欲被重复使用的C++ 类却不会有那么完整的设计或包装。要把一个C++ 类做成完好的包装,放到Component Gallery 中,它必须变为一个单一文件,内含类资讯以及任何必须的资源。这在过去的Visual C++ 4.x中是很容易的事情,因为每次你使用ClassWizard 新增一个类,就有一个核示盒询问你要不要加到Component Gallery:

Visual C++ 4.x 的ClassWizard 新增类对话窗

但这一选项已在Visual C++ 5.0 中拿掉(你可以在第 10 章增加对话窗类时看到新的画面)。看来似乎要增加components 不再是那么方便了。这倒也不是坏事,我想许多人在设计程序时忽略了上图那个选项,于是每一个项目中的每一个类,都被包装到Component Gallery 去,而其中许多根本是没有价值的:

Visual C++ 4.x 的Component Gallery。常常因为程序员的疏忽,而产生了一大堆没有价值的components。

使用 Components

当你选择Component Gallery 中的Developer Studio Components 数据夹,出现许多的components。面对形形色色的「货」,你的心里一定嘀咕着:怎么用嘛?幸好画面上有一个【More Info】按钮,可以提供你比较多的信息。以下我挑三个最简单的components做示范。

Splash screen

所谓Splash Screen,你可以说它是一个「炫耀画面」。玩过微软的Office 吗?每一个Office 软件一出场,在它做初始化的那段时间里,都会出现一个画面,就是Splash screen。

Splash Screen 的【More Info】出现这样的画面:

选按上图下方的"Splash Screen Component - Specifics",你会获得一张使用规格说明,大意如下:

欲插入 splash Screen component,你必须:

1. 打开你希望安插Splash Screen component的那个项目。

2. 选择整合环境中的【Project/Add To Project/Components and Controls】选单项目。

3. 选择"Developer's Studio Components" 数据夹。

4. 选择数据夹中的 Splash Screen component 并按下【Insert】钮。

5. 设定必要的 Splash Screen 选项然后按下【OK】钮。

6. 重建(重新编译链接)项目。

如果要把 Splash Screen 加到一个以对话窗为主(dialog-based)的程序中,你必须在插入这个component之后做以下事情:

1. 找到你的 InitInstance 函数。

2. 在你调用:

int nResponse = dlg.DoModal();

之前,加上一行:

spl.ShowSplashScreen(FALSE);

增加这一行代码,可以确保 Splash Screen 在主对话窗被显示之前,会被清除掉。

看来很简单的样子

System Info for About Dlg

看过 WordPad 的【About】对话窗吗:

如果你也想让自己的对话窗有点系统信息的显示能力,可以采用 Component Gallery 提供的这个System Info for About Dlg component。它的规格说明文字如下:

SysInfo component可以为你的程序的About对话窗中加上一些系统信息(可用内存数量以及磁盘剩余空间)。你的程序必须以MFC AppWizard 完成。请参考 WordPad说明文件以获得更多信息。这份规格书不够详细。稍后我会在修改程序代码时加上我自己的说明。

Tip of the Day

看过这种画面吗(微软的Office软件就有):

这就是「每日小秘诀」。Component Gallery 提供的Tips for the Day component 让你很方便地为自己加上「每日小秘诀」。这个component的使用规格是:

小秘诀文字文件(TIPS.TXT):

拥有Tips for the Day component的程序将搜寻磁盘中的工作子目录,企图寻找TIPS.TXT 读取秘诀内容。如果你希望这个秘诀文字文件有不同的名称或是放在不同的位置,你可以修改CTIP.CPP中的CTIP 类构造函数。CTIP是预设的类名称。

(侯俊杰注:最后这句话是错误的。我使用这个 component,接受所有的预设项目,获得的类名称却是 CTIPDLG,文件则为 TIPDLG.CPP)

  • TIPS.TXT 的格式如下:

    1. 文件必须是ASCII 文字,每一个秘诀以一行文字表示。

    2. 如果某一行文字以分号(;)开头,表示这是一行说明文字,不生实效。说明文字必须有自己单独的一行。

    3. 空白行会被忽略。

    4. 每一个小秘诀最多 1000 个字符。

    5. 每一行不能够以空白或定位符号(tab)开始。

  • 小秘诀显示次序:

    预设情况下,小秘诀的出现次序和它们在文件中的排列次序相同。如果全部都出现过了,就再循环一遍。如果文件被更改过了,显示次序就会从头开始。

  • 错误情况:

    这个组件希望在MFC程序中被使用。你的程序应该只有一个派生自 CWinApp的类。如果有许多个CWinApp 派生类,此组件会选择其中第一个做为实现的对象。其他的错误情况包括秘诀文字文件不存在,或格式不对等等。

  • 在程序的【Help】选单中加上 Tip of The Day 项目:

    这个组件会修改主框窗口的 OnInitMenu 函数,并且在你的【Help】选单下加挂一个Tip of The Day 项目。如果你的程序原本没有【Help】选单,此组件就自动为你产生一个。

Components 实际运用:ComTest 程序

现在,动手吧。首先利用MFC AppWizard 产生一个项目,就像第4章的 Scribble step0 那样。我把它命名为ComTest(放在书附光盘的ComTest.17 子目录中)。然后,不要离开这个项目,启动Component Gallery,进入 Developer Studio Components数据夹,分别选择Splash Screen 和System Info for About Dlg和Tips of the Day三个组件,分别按下【Insert】钮。Splash Screen 和 Tips of the Day 组件会要求我们再指定一些消息:

新增文件

这时候 ComTest 项目中的原始代码有了一些变动(被 Component Gallery 改变)。被改变的文件是:

STDAFX.H
RESOURCE H
COMTEST.H
COMTEST.CPP
COMTEST.RC
MAINFRM.H
MAINFRM.CPP
SPLASH.H
SPLASH.CPP
SPLSH16.BMP
TIPDLG.CPP
TIPDLG.H

选按整合环境的【Build/ Build ComTest.Exe】,把这个程序建造出来。建造完毕试执行之,你会发现在主窗口出现之前,一开始先有一张画面显现:

然后是每日小秘诀:

然后才是主窗口。至于About对话窗,画面如下(没啥变化):

看来,我们只要修改一下Splash Screen 画面,并增加一个TIPS.TXT 文字文件,再变化一下About 对话窗,就成了。程序编修动作的确很简单,不过我还是要把这三个组件加诸于你的程序的每一条痕印都揭发出来。

相关变化

让我们分析分析Component Gallery为我们做了些什么事情。

STDAFX.H(阴影部份为新增内容)

...
#include <afxwin.h> // MFC core and st
#include <afxext.h> // MFC extensions
#include <afxdisp.h> // MFC OLE automat
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC
#endif // _AFX_NO_AFXCMN_SUPPORT
#include <H:\u002p\prog\ComTest.16\TipDlg.h>
...

RESOURCE.H

下面是针对三个组件新增的一些常数定义。凡是稍后修改程序时会用到的常数,我都加上批注,提醒您特别注意。

...
#define IDB_SPLASH 102 //Splash screen 所加,代表一张16色bitmap 画面
#define CG_IDS_PHYSICAL_MEM 103
#define CG_IDS_DISK_SPACE 104
#define CG_IDS_DISK_SPACE_UNAVAIL 105
#define IDB_LIGHTBULB 106
#define IDD_TIP 107
#define CG_IDS_TIPOFTHEDAY 108//Tips 所加,一个字符串。稍后我要把它改为中文内容。
#define CG_IDS_TIPOFTHEDAYMENU 109
#define CG_IDS_DIDYOUKNOW 110//Tips 所加,一个字符串。稍后我要把它改为中文内容
#define CG_IDS_FILE_ABSENT 111
#define CG_IDP_FILE_CORRUPT 112
#define CG_IDS_TIPOFTHEDAYHELP 113
#define IDC_PHYSICAL_MEM 1000 //SysInfo 所加,代表「可用内存」这个static字段
#define IDC_BULB 1000
#define IDC_DISK_SPACE 1001 // SysInfo所加,代表「磁盘剩余空间」这个static字段
#define IDC_STARTUP 1001
#define IDC_NEXTTIP 1002
#define IDC_TIPSTRING 1004
...
COMTEST.H(阴影部份为新增内容)
class CComTestApp : public CWinApp
{
public:
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    CComTestApp();
    ...
private:
    void ShowTipAtStartup(void);
private:
    void ShowTipOfTheDay(void);
}

COMTEST.CPP(阴影部份为新增内容)

COMTEST.RC(阴影部份为新增内容)

MAINFRM.H(阴影部份为新增内容)

MAINFRM.CPP(阴影部份为新增内容)

SPLASH.H(全新内容)

SPLASH.CPP(全新内容)

TIPDLG.H(全新内容)

TIPDLG.CPP(全新内容)

0001 #include "stdafx.h"

修改 ComTest 程序内容

以下是对于上述新增文件的分析与修改。稍早我曾分析过,只要修改一下 Splash Screen画面,增加一个TIPS.TXT 文字文件,再变化一下About 对话窗,就成了。

COMTEST.RC

要把自己准备的图片做为「炫耀画面」,有两个还算方便的作法。其一是直接编修Splash Screen 组件带给我们的Splsh16.bmp 的内容,其二是修改RC檔中的IDB_SPLASH 所对应的文件名称。我选择后者。所以我修改RC檔中的一行:

IDB_SPLASH  BITMAP  DISCARDABLE  "Dissect.bmp"

Dissect.bmp 图文件内容如下:

此外我也修改RC文件中的一些字符串,使它们呈现中文:

IDD_TIP DIALOG DISCARDABLE  0, 0, 231, 164
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "今日小秘诀"
FONT 8, "MS Sans Serif"
BEGIN
CONTROL       "",-1,"Static",SS_BLACKFRAME,12,11,207,123
LTEXT         "Some String",IDC_TIPSTRING,28,63,177,60
CONTROL       "程序启动时显示小秘诀",IDC_STARTUP,"Button",
BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,13,146,85,10
PUSHBUTTON    "下一个小秘诀",IDC_NEXTTIP,109,143,50,14,WS_GROUP
DEFPUSHBUTTON "关闭",IDOK,168,143,50,14,WS_GROUP
CONTROL       "",IDC_BULB,"Static",SS_BITMAP,20,17,190,111
END
STRINGTABLE DISCARDABLE
BEGIN
...
//  CG_IDS_DIDYOUKNOW     "Did You Know..."
CG_IDS_DIDYOUKNOW     "侯俊杰著作年表..."
END

增加一个TIPS.TXT

这很简单,使用任何一种文字编辑工具,遵循前面说过的 TIPS.TXT 文件格式,做出你的每日小秘诀。

修改RC檔中的About对话窗画面

我增加了四个static控制组件,其中两个做为标签使用,不必在乎其ID。另两个准备给ComTest程序在【About】对话窗出现时设定系统资讯使用,ID分 别设定为IDC_PHYSICAL_MEM和IDC_DISK_SPACE,配合System Info for About Dlg 组件的建议。

COMTEST.CPP

在CAboutDlg::OnInitDialog中利用SetDlgItemText 设定稍早我们为对话窗画面新增的两个static 控制组件的文字内容(Component Gallery 已经为我们做出这段程序代码,只是暂时把它标记为说明文字。我只要把标记符号//去除即可):

ComTest 修改结果

一切尽如人意。现在我们有了理想的 Splash Screen 画面如前所述,也有了 Tips of the Day 对话窗:

以及一个内含系统信息的 About 对话窗:

使用 ActiveX Controls

Microsoft的Visual Basic自1991年推出以来,已经成为Windows应用软件开发环境中的佼佼者。它的成功极大部份要归功于其开放性质:它所提供的 VBXs 被认为是一种极佳的面向对象程序设计架构。VBX 是一种动态链接函数库(DLL),类似Windows的订制型控制组件(custom control)。

VBX 不适用于32 位环境。于是Microsoft 再推出另一规格OCX。不论是 VBX 或OCX,或甚至Borland 的VCL,都提供Properties-Method-Event(PME)接口。Visual Basic 之于VBX,以及 Borland C++ Builder 和 Delphi 之于 VCL,都提供了整合开发环境(IDE)与 PME 接口之间的极密切结合,使得程序设计更进一步到达「以拖拉、填单等简易动作就能够完成」的可视化境界。也因此没有人会反对把Visual Basic 和Delphi和C++ Builder 归类为RAD(Rapid Application Development,快速软件开发工具)的行列。但是Visual C++ 之于OCX,还没能够有这么好的整合。

我怎么会谈到OCX 呢?本节不是ActiveX Control 吗?噢,OCX 就是 ActiveX Control!由于微软把它所有的Internet 技术都称为ActiveX,所以 OLE Controls 就变成了ActiveX Controls。

我不打算讨论ActiveX Control 的撰写,我打算把全部篇幅用到ActiveX Control 的使用上。

如果对ActiveX Control 的开发感兴趣,Adam Denning 的ActiveX Control Inside Out是一本很不错的书(ActiveX 控制组件彻底研究,侯俊杰译/松岗)

ActiveX Control 基础观念:Properties、Methods、Events

你必须了解ActiveX Control 三种接口的意义,并且充份了解你打算使用的某个 ActiveX Control 有些什么特殊的接口,然后才能够使用它。

基本上你可以拿你已经很熟悉的 C++ 类来比较 ActiveX control。类也是一个包装良好的组件,有它自己的成员变量,以及处理这些成员变量的所谓成员函数,是个自给自足的体系。ActiveX control 的三个接口也有类似性质:

  • property - 相当于 C++ 类的成员变量

  • method - 相当于 C++ 类的成员函数

  • event - 相当于 Windows 控制组件发出的 notification 消息

ActiveX Control规格中定有一些标准的(库存的)接口,例如BackColor和FontName等properties,AddItem和Move和Refresh 等methods,以及CLICK 和KEYDOWN 等events。也就是说,任何一个ActiveX Control 大致上都会有一些必备的、基础的性质和能力。

以下针对ActiveX Control 的三种接口与C++ 类做个比较。至于它们的具体展现以及如何使用,稍后在实例中可以看到。

methods

设计自己的C++ 类,你当然可以在其中设计成员函数。此一函数之调用者必须在编译时期知道这一函数的功能以及它的参数。搭配Windows内建之控制组件(如 Edit、Button)而设计的类(如CEdit、CButton),内部固定会设计一些成员函数。某些成员函数(如CEdit::GetLineCount)只适用于特定类,但某些根类的成员函数(例如CWnd::GetDlgItemText)则适用于所有的子类。

ActiveX Control 的 method 极类似 C++ 类中的成员函数。但它们被限制在一个有限的集合之中,集合内的名单包括 AddItem、RemoveItem、Move 和 Refresh 等等。并不是所有的 ActiveX Controls 都对每一个 method 产生反应,例如 Move 就不能够在每一个ActiveX Control 中运作自如。

properties

基本上properties用来表达 ActiveX Control 的属性或数据。一个名为 Date的组件可能会定义一个所谓的 DateValue,内放日期,这就表现了组件的数据。它还可能定义一个所谓的 DateFormat,允许使用者取得或设定日期表现形式,这就表现了组件的属性。

你可以说ActiveX Control的properties 相当于C++ 类的成员变量。每一个 ActiveX Control可以定义属于它自己的properties,可以是一个字符串,可以是一个长整数,也可以是一个浮点数。有一组所谓的properties 标准集合(被称为 stock properties),内含BackColor、FontName、Caption 等等 properties,是每个ActiveX control 都会拥有的。

一般而言 properties 可分为四种类型:

  • Ambient properties

  • Extended properties

  • Stock properties

  • Custom properties

events

Windows 控制组件以所谓的notification(通告)消息送给其父窗口(通常是对话窗),例如按钮组件可能传送出一个BN_CLICKED。ActiveX Control 使用完全相同的方法,不过现在notification 消息被称为event,用来表示某种状况发生了。Events 的发射可以使ActiveX Control 有能力通知其宿主(container,也就是VB 或VC 程序),于是对方有机会处理。大部份 ActiveX Controls 送出标准的 events,例如 CLICK、KEYDOWN、KEYUP等等,某些 ActiveX Controls 会送出独一无二的消息(例如 ROWCOLCHANGE)。

一般而言 events 可分为两种类型:

  • Stock events

  • Custom events

ActiveX Controls 的五大使用步骤

欲在程序中加上 ActiveX Controls,基本上需要五个步骤:

1. 建立新项目时,在 AppWizard 的步骤3中选择【ActiveX Controls】。 这会使程序代码多出一行:

BOOL COcxTestApp::InitInstance()
{
    AfxEnableControlContainer();
    ...
}

2. 进入Component Gallery,把ActiveX Controls 安插到你的程序中。

3. 使用ActiveX Controls。通常我们在对话窗中使用它。我们可以把资源编辑器的工具箱里头的 ActiveX Controls 拖放到目标对话窗中。

4. 利用ClassWizard 产生对话窗类,并处理相关的Message Maps、消息处理例程、变量定义、对话框函数等等。

5. 编译链接。

我将以系统内建(已注册过)的 Grid ActiveX Control 做为示范的对象。Grid 具有小型电子表格能力,当然它远比不上 Excel(不然 Excel 怎么卖),不过你至少可以获得一个中规中矩的 7x14 电子表格,并且有基本的编辑和运算功能。

容我先解释我的目标。图16-1 是我期望的结果,这个电子表格完全为了家庭记账而量身设计,假设你有五种收入(真让人羡慕),这个表格可以让你登录每个月的每一种收入,并计算月总收入和年总收入,以及各分项总收入。

图16-1 在对话窗中使用Grid ActiveX control。

每一横列或纵行的最后一栏都是总和。

由于Grid 本身并不提供编辑能力,我们以电子表格右侧的一个edit字段做为编辑局部。使用者所选择的方格的内容会显示在这edit字段中,并且允许被编辑内容。数值填入后必须按下<Enter> 键,或是在【Update Value】钮上按一下,电子表格内容才会更新。如果要直接在电子表格字段上做编辑动作,并不是不可以,把edit不偏不倚贴到字段也就是了!

本书进行到这里,我想你对于工具的使用应该已经娴熟了,我将假设你对于像「利用ClassWizard 为CMainFrame 拦 截一个ID_GridTest命令,并指名其处理常式为OnGridTest」这样的叙述,知道该怎么去动手。

使用Grid ActiveX Control:OcxTest程序

首先利用MFC AppWizard 做出一个OcxTest 项目。记得在步骤3选择【 ActiveX Controls】:

然后进入Component Gallery,将Grid安插到项目中:

你必须回答一个对话窗:

对话窗的设计

产生一个崭新的对话窗。这个动作与你在第10章为Scribble加上"Pen Width" 对话窗的步骤完全一样。请把新对话窗的ID从IDD_DIALOG1改变为 IDD_GRID。

从工具箱中抓出控制组件来,把对话窗布置如下。

虽然你把Grid 拉大,它却总是只有2x2 个方格。你必须使用右键把它的 Control Properties 引出来(如下),进入 Control 附页,这时候会出现各个 properties:

Control附页在中文Windows中竟然变成「一般」。这是否也算是一只臭虫?

现在选择Rows,设定为14,再选择Cols,设定为7。你还可以设定行的宽度和列的高度,以及方格初值...。噢,记得给这个 Grid 组件一个ID,叫做 IDC_GRID 好了。

整个对话窗的设计规格如下:

对象               ID                文字内容
对话窗             IDD_GRID        ActiveX Control (Grid) Testing
OK按钮            IDOK             OK
Cancel按钮        IDCANCEL         Cancel
Edit               IDC_VALUE
Update Value按钮  IDC_UPDATEVALUE  Update Value
Grid              IDC_GRID

现在准备设计IDD_GRID 的对话窗类。这件事我们在第10章也做过。进入CLassWizard,填写【Add Class】对话窗如下,然后按下【OK】钮:

回到ClassWizard主画面,准备为组件们设计消息处理例程。步骤是先选择一个组件ID,再选择一个消息,然后按下【Add Function】钮。注意,如果你选择到一个ActiveX Control,"Messages" 清单中列出的就是该组件所能发出的events。

本例的消息处理例程的设计规格如下:

对象ID                         消息                处理函数名称

到此为止,我们获得这些新文件:

RESOURCE.H
OCXTEST.RC
GRIDCTRL.H     <-- 本例不处理这个文件
GRIDCTRL.CPP   <-- 本例不处理这个文件
FONT.H         <-- 本例不处理这个文件
FONT.CPP       <-- 本例不处理这个文件
PICTURE.H      <-- 本例不处理这个文件
PICTURE.CPP    <-- 本例不处理这个文件
GRIDDLG.H      <-- 本例主要的修改对象
GRIDDLG.CPP    <-- 本例主要的修改对象

其中重要的相关程序代码我特别挑出来做个认识:

OCXTEST.RC

GRIDDLG.H

GRIDDLG.CPP

为对话框加上一些变量

进入ClassWizard,进入【Member Variables】附页,选按其中的【Add Variable】钮,为OcxTest 加上两笔成员变量。其中一笔用来储存目前被选中的电子表格方格内容,另一笔数据用来做为 Grid 对象,其变量类型是 CGridCtrl:

这两个动作为我们带来这样的程序代码:

GRIDDLG.H

class CGridDlg : public CDialog
{
    // Dialog Data
    //{{AFX_DATA(CGridDlg)
    enum { IDD = IDD_GRID };
    CGridCtrl m_OcxGrid;
    CString m_cellValue;
    //}}AFX_DATA
    ...
};

GRIDDLG.CPP

CGridDlg::CGridDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGridDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CGridDlg)
    m_cellValue = _T("");
    //}}AFX_DATA_INIT
}
void CGridDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CGridDlg)
    DDX_Control(pDX, IDC_GRID, m_OcxGrid);
    DDX_Text(pDX, IDC_VALUE, m_cellValue);
    //}}AFX_DATA_MAP
}

新增一个选单项目

利用资源编辑器,将选单修改如下:

注意,我所改变的选单是IDR_MAINFRAME,这是在没有任何子窗口存在时才会出现的选单。所以如果你要执行 OcxTest 并看到 Grid 组件,你必须先将所有的子窗口关闭。

现在利用 ClassWizard 在主窗口的消息映射表中拦截它的命令消息:

获得对应的程序代码如下:

MAINFRM.H

MAINFRM.CPP

为了让这个新增选单命令真正发挥效用,将 Grid 对话窗唤起,我在 OnGridTest 函数加两行:

现在,将OcxTest编译连结一遍,得到一个可以顺利执行的程序,但Grid 之中全无内容。

Grid 相关程序设计

现在我要开始设计Grid 相关函数。我的主要的工作是:

准备一个二维(7x14)的 DWORD 数组,用来储存 Grid 的方格内容。

  • 程序初始化时就把二维数组的初值设定好(本例不进行文件读写),并产生 Grid对话框。

  • 对话框一出现,程序立刻把电子表格的行、列、宽、高,以及字段名称都设定好,并且把二维数组的数值放到对应方格中。初值的总和也一并计算出来。

  • 把计算每一列每一行总和的工作独立出来,成立一个ComputeSums 函数。为了放置电子表格内容,必须设计一个7x14 二维数组。虽然电子表格中某些方格(如列标题或行标题)不必有内容,不过为求简化,还是完全配合电子表格的大小来设计数值数组好了。注意,不能把这个变量放在 AFX_DATA 之内,因为我并非以 ClassWizard 加入此变量。

GRIDDLG.H

为了设定Grid 中的表头以及初值,我在OnInitDialog 中先以一个for loop设定横列表头再以一个for loop 设定纵行表头,最后再以巢状(两层)for loop 设定每一个方格内容,然后才调用ComputeSums 计算总和。

当使用者选择一个方格,其值就被 OnSelchangeGrid 拷贝一份到 edit 字段中,这时候就可以开始输入了。

OnUpdatevalue(【Update Value】按钮的处理例程)有两个主要任务,一是把edit字段内容转化为数值放到目前被选择的方格上,一是修正总和。

OnOk 必须能够把每一个方格内容(一个字符串)取出,利用atof转换为数值,然后储存到m_dArray二维数组中。

GRIDDLG.CPP

#0001
#0002  BOOL CGridDlg::OnInitDialog()
#0003  {
#0004      CString str;
#0005      int   i, j;

#0143      }
#0144  }

下图是OcxTest 的执行画面。