Page 1

中国计算机函授学院图书编写中心组编 国家教育部电教办计算机培训基地指定培训用书

 电脑工程师丛书

Visual FoxPr o 高级应用实例 刘

上海交通大学出版社


内 容

本书精心安排了八个完整的 Visual FoxPro 应用实例,主要内容包括 GUI 设计、 数据库系统的快速开发、外部资源的使用、共享数据访问、OLE 与自动化、C/S 体系 架构、基于 Web 的数据库应用系统和产品级企业应用的开发,每个实例均有详细

的技术分析和说明,并给出了完整的源程序。全书知识覆盖面广,条理清晰,结构

编程技术人员,也可作为大专院校相关专业师生的进阶参考书。

严谨,深入浅出,融理论性和实践性于一体,适用于具备一定基础的 Visual FoxPro

图书在版编目(CIP)数据 Visual FoxPro 高级实用实例 / 刘斌编 . —上海:上海交通大学出版社, 2004 (电脑工程师丛书) ISBN 7 - 313 - 03566 - 7 Ⅰ . V. . . Ⅱ . 刘 . . . Ⅲ . 关系数据库—数据库管理系统,Visual FoxPro Ⅳ . TP311 . 138 中国版本图书馆 CIP 数据核字(2004)第 000842 号

Visual FoxPro 高级应用实例 刘

上海交通大学出版社出版发行 (上海市番禺路 877 号 电话: 64071208

出版人:张天蔚

合肥学苑印刷厂印刷 开本: 787 × 1092(mm)1 /16 2004 年 1 月第 1 版

邮政编码 200030) 全国新华书店经销 印张: 19

字数: 456 千字

2004 年 1 月第 1 次印刷

印数: 1 ~ 10000 ISBN 7 - 313 - 03566 - 7 /TP・592 版权所有

定价(含光盘): 29 . 00 元

侵权必究


光阴荏苒,斗转星移,历史的巨轮已然驶进了 21 世纪的 港湾。作为横跨两个世纪的中坚一代,作为肩负新经济发展 使命的新兴一族,也许你曾为许多梦想而孜孜不倦,抑或曾为 某个特定的目标而苦苦求索。面对日益激烈的生存竞争,面 对空前的机遇与挑战,也许你早已有意无意地把自己和 IT、 电脑、互联网等象征时髦与潮流的词儿结合在一起了。的确, 21 世纪是信息时代,信息无所不在,掌握了计算机技术,就掌 握了开启新时代大门的金钥匙。在信息时代这个丰富多彩的 广阔天地里,笑傲群雄的是各类精英人物。高能力、高收入、 良好的工作环境总是与“精英”联系在一起,技艺高超的“电脑 工程师”就是信息时代的这样一群精英,你想成为他们中的一 员吗? 为了满足各界朋友想成为电脑“高手”的愿望,中国计算 机函授学院图书编写中心把眼光定位于计算机的中高级用 户, 经过了周密的策划,组织了大量具有实际工作经验的专 家、学者和长期从事计算机应用的工程技术人员,将他们平时 工作和教学中用到的精彩范例,加以精炼、提高,编写了这套 “电脑工程师”丛书。丛书秉承了中国计算机函授学院图书编 写中心“出好书、出精品书”的一贯宗旨,内容侧重于创意分 析、 技巧点拨,抛砖引玉,使已掌握入门知识的朋友,通过对 “实例”的学习,能够迅速提高自身的动手能力,独当一面地进 行工作。丛书内容通俗易懂,图文并茂,配套光盘,素材齐全。 最后衷心感谢参与本套丛书写作的全体老师和创作人 员,衷心祝愿本套丛书的读者早日成为电脑“高手”。 中国计算机函授学院图书编写中心 2003 年 7 月


编 者 的 话

在本书的编写过程中,作者假设本书的读者可以毫不费力地写出一个完整的“IF …ELSE… ENDIF”语句,并且能列举出三个以上数据库表与自由表的区别,如果你自 认为做不到这一点,那么很遗憾,你目前需要的是一本入门级的 Visual FoxPro 参考 书。 如果你只是打算通过 Visual FoxPro 的计算机二级考试,那么本书中介绍的很多 内容对你来说可能也是多余的。本书完全面向利用 Visual FoxPro 进行数据库应用系 统开发的工程技术人员,当然并不一定非得是计算机专业人员,实际上作者自己就不 是。如果你曾经使用过 Foxbase + 或者 FoxPro,希望能够进一步学习 Visual FoxPro 的 可视化、面向对象的程序设计;如果你已经在课堂或书本上学习过 Visual FoxPro 的基 础知识,但是还没有实际的开发经验;如果你已经开始用 Visual FoxPro 开发项目,但 是经常被一些问题困扰而又找不到系统的解答;如果你不想永远在那些技术的细枝 末节上纠缠不清,希望能够站在更高的层次上全面了解 Visual FoxPro 的方方面面,那 么,本书中也许会有你想要的东西。 本书以“理论 + 实践”的形式向读者展示了用 Visual FoxPro 进行数据库应用系统 开发的各个环节,所选用的例子尽可能地具有代表性和完整性,大部分例子之间是 “弱耦合”的。大致地翻阅一下本书的目录就可以对全书的内容体系有一个初步的了 解,从而可以跳跃式地阅读你所感兴趣的章节。如果有必要的话,还可以把每个例子 都动手实践一遍,这样即使你还没有任何实际项目的开发经验,看上去也会像一个 Visual FoxPro 的老手,事实上,你已经是老手了。 本书虽然专注于 Visual FoxPro 的技术讨论,但在具体内容上不局限于此,有些内 容可能会涉及到相关的一些编程语言或开发工具(如 Visual Basic、Visual C + + 、VBA、 HTML、ASP 等),还有一些内容需要借助于专门的软件(如 MS Office 办公软件、IIS 信 息服务器组件、SQL Server 数据库管理系统、ERWin 数据建模工具等),它们是构成本 书完整性不可或缺的一部分。由于篇幅的限制,书中对这些内容的介绍视其与 Visual FoxPro 的关系密切程度而有所不同,有些只是一笔带过,有些则会进行细致的讲 解。 本书共有八个完整的实例,这些实例的源代码包含在本书附带的光盘中。为最 大限度地保证兼容性,它们均在 Visual FoxPro 6 . 0 中文版的开发环境下调试通过,如 果你的计算机上安装的是高版本的 Visual FoxPro,无需任何修改即可正常工作。部分 实例的运行需要安装特定的软件,如实例 5 需要安装 MS Word 文字处理软件,实例 6


要求正确地安装并设置好 MS SQL Server 数据库,实例 7 需要 Windows IIS 或者 PWS 组 件的支持,详细情况请参考书中的相应内容和说明。 本书在编排和格式上遵循如下约定: “文件”  “”用于标识界面上呈现的专有名词,如“项目管理器”对话框、 菜单、 “预览”按钮等。  程序代码或需要手工输入的命令,采用 Courier 等宽字体并加注灰色底纹,保 留字均采用大写,所有注释遵循 Visual FoxPro 的语法规则,并用斜体表示,如:  ActiveX控件事件  LPARAMETERS Node && 节点引用  在命令及函数语法介绍中,用户自定义部分为斜体,非必须选项用[]表示,所 有可能的选项用“| ”分隔,如: DEFINE BAR nMenuItemNumber | SystemItemName OF MenuName PROMPT cMenuItemText [KEY KeyLabel [,cKeyText ]]  用“ - > ”表示多级菜单的层次,如“文件”菜单 - >“新建”菜单项。  所有的键盘功能键用 < > 标识,如 < Ctrl > 、< Alt > 、< Tab > 等。 古人云:三人行,必有我师。又云:学无止境。书中的错误疏漏之处在所难免,敬 请广大读者批评指正,欢迎发送 E-mail 至 l— b@263 . net 与作者进行直接的交流。

刘斌 2003 年 7 月


灵活的 GUI 设计 1.1

本章提要 …………………………………………………………………… (1)

1.2

技术分析 …………………………………………………………………… (2)

1.2.1

创建表单 ……………………………………………………………… (2)

1.2.2

表单的属性、方法与事件 ……………………………………………… (4)

1.2.3 1.2.4

SDI 与 MDI 表单 ……………………………………………………… (6) 使用表单控件 ………………………………………………………… (7)

1.2.5

菜单设计 ……………………………………………………………… (11)

1.2.6

工具栏设计 …………………………………………………………… (16)

1.2.7

在表单中协调菜单和工具栏 ………………………………………… (17)

1.2.8

表单模式与事件驱动模型 …………………………………………… (19)

1 . 2 . 9 表单绘图 ……………………………………………………………… (20) 1 . 3 实例制作 …………………………………………………………………… (21) 1.3.1

制作表单 ……………………………………………………………… (22)

1.3.2

设计工具栏 …………………………………………………………… (23)

1.3.3

设计菜单 ……………………………………………………………… (24)

1.3.4

编写程序代码 ………………………………………………………… (26)

1 . 3 . 5 程序运行及说明 ……………………………………………………… (32) 1 . 4 本章小结 …………………………………………………………………… (32)

数据库应用系统的快速开发 2.1

本章提要 …………………………………………………………………… (33)

2.2

技术分析 …………………………………………………………………… (34)

2.2.1

需求分析 ……………………………………………………………… (34)

2.2.2

数据库设计 …………………………………………………………… (36)

2.2.3

数据维护模块设计 …………………………………………………… (37)

2.2.4

报表设计 ……………………………………………………………… (43)

2 . 2 . 5 用户权限管理 ………………………………………………………… (45) 2 . 3 实列制作 …………………………………………………………………… (45) 2.3.1

数据维护界面设计 …………………………………………………… (46)


2.3.2

报表设计 ……………………………………………………………… (51)

2.3.3

系统管理功能设计 …………………………………………………… (54)

2 . 3 . 4 多用户权限控制 ……………………………………………………… (59) 2 . 4 本章小结 …………………………………………………………………… (62)

调用外部库资源 3.1

本例提要 …………………………………………………………………… (63)

3.2

技术分析 …………………………………………………………………… (63)

3.2.1

动态链接库的调用 …………………………………………………… (64)

3.2.2

访问 Visual FoxPro 库 ………………………………………………… (68) 定制自己的函数库 …………………………………………………… (69)

3.2.3

3 . 2 . 4 MCI 简介 ……………………………………………………………… (75) 3 . 3 实例制作 …………………………………………………………………… (78) 3.3.1

控件设计 ……………………………………………………………… (78)

3.3.2

表单设计 ……………………………………………………………… (80)

3.3.3

编写代码 ……………………………………………………………… (81)

3.3.4

制作菜单 ……………………………………………………………… (88)

3 . 3 . 5 联编程序 ……………………………………………………………… (89) 3 . 4 本章小结 …………………………………………………………………… (89)

ActiveX 控件的使用(兼谈共享数据访问) 4.1

本章提要 …………………………………………………………………… (90)

4.2

技术分析 …………………………………………………………………… (91)

4.2.1 4.2.2 4.2.3 4.2.4 4.2.5

ActiveX 技术概述 …………………………………………………… (91) 使用 ActiveX 控件 …………………………………………………… (92) TreeView 控件 ………………………………………………………… (96) ImageList 控件 ………………………………………………………… (99) CSCommand 控件 …………………………………………………… (100)

4 . 2 . 6 数据共享访问及缓冲机制 ………………………………………… (101) 4 . 3 实例制作 ………………………………………………………………… (106) 4.3.1

建立数据库 ………………………………………………………… (107)

4.3.2

制作表单 …………………………………………………………… (107)

4.3.3

快捷菜单设计 ……………………………………………………… (109)

4 . 3 . 4 编写代码 …………………………………………………………… (110) 4 . 4 本章小结 ………………………………………………………………… (117)


OLE 与自动化 5.1

本例提要 ………………………………………………………………… (119)

5.2

技术分析 ………………………………………………………………… (119)

5.2.1

什么是 OLE ………………………………………………………… (119)

5.2.2

对象的链接和嵌入 ………………………………………………… (121)

5.2.3

OLE 自动化 ………………………………………………………… (124) 定制的自动化服务 ………………………………………………… (129)

5.2.4

5 . 2 . 5 OLE 拖放 …………………………………………………………… (140) 5 . 3 实例制作 ………………………………………………………………… (146) 5.3.1

获得 Word 常数 ……………………………………………………… (147)

5 . 3 . 2 制作 Word 格式报表 ………………………………………………… (149) 5 . 4 本章小结 ………………………………………………………………… (151)

采用两层 C / S 体系架构 6.1

本例提要 ………………………………………………………………… (152)

6.2

技术分析 ………………………………………………………………… (153)

6.2.1 6.2.2

C/S 体系综述 ……………………………………………………… (153) SQL Server 简介 ……………………………………………………… (154)

6.2.4

SQL Server 的安装与管理 …………………………………………… (157) 数据库的升迁 ……………………………………………………… (167)

6.2.5

远程视图 …………………………………………………………… (179)

6.2.3

6 . 2 . 6 SQL Pass - Through 技术 …………………………………………… (184) 6 . 3 实例制作 ………………………………………………………………… (190) 6.3.1

准备工作 …………………………………………………………… (190)

6.3.2

参数设置模块 ……………………………………………………… (191)

6 . 3 . 3 设备数据维护模块 ………………………………………………… (194) 6 . 4 本章小结 ………………………………………………………………… (201)

基于 Web 的数据库应用系统开发 7.1

本章提要 ………………………………………………………………… (202)

7.2

技术分析 ………………………………………………………………… (203)

7.2.1

准备工作 …………………………………………………………… (203)

7.2.2

活动文档 …………………………………………………………… (205)

7.2.3

Web 发布向导 ……………………………………………………… (208) 创建基于 FoxISAPI 的 Web 应用 …………………………………… (211)

7.2.4


7 . 2 . 5 和 ASP 配合开发三层架构应用程序 ……………………………… (217) 7 . 3 实例制作 ………………………………………………………………… (220) 7.3.1

实现查询的 COM 组件 ……………………………………………… (221)

7 . 3 . 2 编写 Web 页面 ……………………………………………………… (223) 7 . 4 本章小结 ………………………………………………………………… (225)

开发产品级企业应用 8.1

本章提要 ………………………………………………………………… (226)

8.2

MIS 系统概述 …………………………………………………………… (227) 数据建模 ………………………………………………………………… (228)

8.3

8.3.1

数据模型 …………………………………………………………… (228)

8.3.2

强大的数据建模工具——— ERWin ………………………………… (229)

8.3.3

创建人事劳资系统数据模型 ……………………………………… (238)

8 . 3 . 4 导出 Visual FoxPro 数据库 ………………………………………… (239) 8 . 4 功能设计 ………………………………………………………………… (241) 8.4.1

整体设计 …………………………………………………………… (241)

8 . 4 . 2 功能实现 …………………………………………………………… (243) 8 . 5 系统优化 ………………………………………………………………… (255) 8.5.1

Rushmore 技术 ……………………………………………………… (255) 其他的优化措施 …………………………………………………… (257)

8.5.2 8 . 6 多人协作开发 …………………………………………………………… (258) 8.6.1

Visual FoxPro 与 Visual SourceSafe 的整合 ………………………… (259) 在源代码管理系统下管理 Visual FoxPro 项目 …………………… (261)

8.6.2 8 . 7 后期制作 ………………………………………………………………… (262) 8.7.1

联机说明文档的制作 ……………………………………………… (262)

8 . 7 . 2 创建安装程序 ……………………………………………………… (266) 8 . 7 本章小结 ………………………………………………………………… (272)

附录

Visual FoxPro 错误信息代码及含义 …………………………………… (273)

后记

……………………………………………………………………………… (292)


灵活的 GUI 设计 ———从涂鸦板程序开始

1

本章技术要点:  表单的属性、事件和方法。  SDI 与 MDI 表单。  表单控件的使用。  动态菜单设置。  表单、菜单和工具栏之间的协调。  表单模式与事件驱动模型。  表单屏幕绘图技巧。

1.1

本章提要

众所周知,Visual FoxPro 是一个数据库应用系统的快速开发工具,但是本书的第 1 章却打 算把数据库暂时放在一边,从一个看似没有多少实用价值的涂鸦板程序开始我们的 Visual FoxPro 之旅。 GUI 是 Graphic User Interface 的缩写,即图形用户界面。 GUI 设计几乎在所有的 Windows 应 用程序开发中均占有重要地位,设计良好的软件界面可以指导用户正确地使用程序提供的功 能并方便地进行数据交互。 Visual FoxPro 目前虽然已经从微软的 Visual Studio 系列可视化开发 工具家族中独立出来,但是仍冠以 Visual 的头衔,因为它支持所见即所得的开发模式。丰富的 GUI 设计工具集(如表单设计器、控件工具栏、菜单设计器以及大量的生成器和向导),可以大 大地缩减开发应用程序界面所需要的时间。 GUI 设计的核心是表单,它是构成应用程序界面的基础,而菜单、工具栏等则是构成应用 程序界面不可缺少的组成部分。 Visual FoxPro 开发人员通常会在表单设计器中完成主要工作, 包括功能的实现、代码的编写等。不仅如此,表单设计还将是开展后续章节学习的基础,为此, 有必要首先把这部分内容系统地介绍给读者。 GUI 设计是很多从 Foxbase 和 FoxPro For DOS 的程序员转向 Visual FoxPro 后遇到的第一只 拦路虎,对于熟悉 Visual Basic 或 Delphi 的开发人员来说同样如此。例如,在 Visual FoxPro 开发 的应用程序中总是存在着一个主窗体(- SCREEN),它容易对初学者造成很多困惑;Visual FoxPro 虽然采用面向对象的架构模式和设计思路,但它的菜单系统设计是结构化的,这给菜单与 其他面向对象的 GUI 元素之间的协调带来了一定的困难;工具栏的设计以可视类库的形式从 表单设计中分离出来,这需要更好地理解类的设计思想;项目联编后脱离 Visual FoxPro 运行环 境往往无法正确执行,这是对表单模式及 Visual FoxPro 的事件驱动模型缺乏深入的了解……


电脑工程师丛书

2

Visual FoxPro 高级应用实例

类似的问题不一而足,相信读者在阅读完本章的内容后,问题均可迎刃而解。 本例运行效果如图 1 . 1 所示。

图 1.1

1.2

涂鸦板运行效果图

技术分析 1 . 2 . 1 创建表单

表单(Form),提供一个标准的 Windows 窗 体界面,可以让用户在一个熟悉的环境下与应 用程序完成数据交互和信息管理。同时,表单 通常还是其他窗体对象的容器,因此创建表单 是 GUI 设计中的第一步。 单击“文件”菜单 - >“新建”菜单项,弹出 的“新建”对话框,如图 1 . 2 所示。 创建表单有两种方式,可以直接通过单击 “新建文件”按钮创建,也可以单击“表单向导” 按钮创建,后一种方法将在第 2 章中进行介绍。 在“新建”对话框的“文件类型”区域中选中“表 单”选项,并单击“新建文件”按钮,即可创建一 个标准的 Visual FoxPro 表单,该表单有着如图 1 . 3 所示的外观。 图 1 . 2 “新建”对话框


灵活的 GUI 设计

图 1.3

3

新建的 Visual FoxPro 表单

其中的标题栏包含默认的狐狸图标、标题说明文字和一组最大化、最小化和关闭按钮,和 我们通常所见到的 Windows 窗口没有什么区别。表单区域中还包含网格线,用于对齐其中将 要包含的表单控件。网格线在运行时是不显示的,如果要全局性设置网格线的水平和垂直间 距,可以通过单击“工具”菜单 - >“选项”菜单项,在“表单”选项卡中进行设置,也可以单击“格 式”菜单 - >“设置网格刻度”菜单项,在弹出的对话框中修改当前表单的设置,如图 1 . 4 所示。

图 1.4

设置表单网格刻度

在新建表单的同时,还会自动运行表单设计器,它是修改表单外观和行为方式的主要设计 工具。表单设计器的功能是非常强大的,它使得设计表单的工作变得又快又容易。在表单设 计器中可以处理下列内容: ① 表单中不同类型的对象。 ② 与表单相关联的数据。 ③ 同时操作多个表单。 ④ 基于自定义模板的表单。 表单设计器实际上包含一系列的设计工具,其外观如图 1 . 5 所示。 下面对表单设计器中各个设计工具的用途作一个简要介绍。 ① 表单窗口:一个所见即所得的表单设计区域,用于直观地反映表单对象的外观,可以在 其中添加对象并对其进行各种操作。 ② 属性窗口:用于修改对象的数据、方法、布局及其他一些属性设置。 ③ 代码编辑窗口:为对象写入各种事件代码和方法程序代码。 “查看类”、 “生成器锁 ④ 表单控件工具栏:共包含 25 个按钮,除首尾两排的“选定对象”、 定”和“按钮锁定”之外,其余按钮为控件定义按钮,用于向表单中添加各种标准的表单控件。 此外,还可以在控件工具栏中添加可视类库及 ActiveX 控件。 ⑤ 布局工具栏:为合理安排控件的位置,可以对多个控件进行对齐和分布,还可以调整控


电脑工程师丛书

4

Visual FoxPro 高级应用实例

图 1.5

表单设计器

件的大小及叠放层次。 ⑥ 调色板工具栏:用于设置表单及表单控件的前景色和背景色等颜色属性。 ⑦ 数据环境设计器:每一个表单或表单集中都包括一个数据环境。数据环境是一个对 象,它可以包含与表单相互作用的表或视图,以及它们之间的关系。可以在数据环境设计器中 直观地设置数据环境,并与表单一起保存。 ⑧ 表单设计器工具栏:通常情况下并不需要同时显示以上所有设计工具,可以用该工具 栏控制它们的显示或隐藏,它自身的显示或隐藏可以在“显示”菜单 - >“工具栏”子菜单中设 置。

1 . 2 . 2 表单的属性、方法与事件 如果对面向对象程序设计有所了解的话,属性、方法和事件应该不会令你感到陌生,对于 表单来说,它实际上就是一个可视的对象,必须通过设置属性、调用方法和编写事件代码来完 成对表单的控制。表单的大部分属性、事件是 Visual FoxPro 通用的,也就是说,Visual FoxPro 程 序员会经常和这些属性、事件打交道而不仅仅局限于在表单中。为此,下面把它们择要列举出 来,在表单设计器的属性窗口中可以很容易地找到它们。 1 . 表单的常用属性 属性就是对象中包含的数据,而且对外是可见的。大部分属性既可以在设计器中进行修 改,也可以在运行时通过代码访问和修改,表单的主要通用属性如表 1 . 1 所示。


灵活的 GUI 设计

表 1.1

表单对象的通用属性

属性名称

描述

BackColor

表单的背景色,使用 RGB 颜色表示,如: 212, 208, 200

Caption

表单的标题栏文字

Comment

关于表单的注释信息

Enabled

指定表单能否响应用户引发的事件

Height

表单的高度,以像素为单位

Left

表单的左边到主窗体的距离,以像素为单位

Name

表单对象的名称,通过它可以在其他对象中引用该表单

Tag

预留的属性,用来存储额外的数据

Top

表单的顶边到主窗体的距离,以像素为单位

Visible

表单是可见的还是隐藏的

Width

表单的宽度,以像素为单位

2 . 表单的主要事件 事件是对象对系统消息或用户输入的响应,表单对象的通用事件如表 1 . 2 所示。 表 1.2

表单对象的通用事件

事件名称

触发条件

Load

在创建之前执行

Init

初始化事件,在表单创建时执行

Activate

在表单创建完毕后执行

Destroy

表单从内存中被清除时执行

Error

表单在运行时出错执行

Click

用户在表单区域单击鼠标时执行

DblClick

用户在表单区域双击鼠标时执行

RightClick

用户在表单区域右击鼠标时执行

GotFocus

表单得到焦点时执行

LostFocus

表单失去焦点时执行

KeyPress

用户按下并释放一个键盘按键时执行

MouseDown

鼠标左键被按下时执行

MouseMove

鼠标在表单区域内移动时执行

MouseUp

鼠标左键被释放时执行

5


电脑工程师丛书

6

Visual FoxPro 高级应用实例

要为某个事件编写相应的程序代码,只要在属性窗口的“方法程序”选项卡中找到该事件 并双击即可。 对象还包含方法,也就是特定的功能。通常不同的对象包含的方法是不同的,本章中将要 实现的涂鸦板程序主要通过表单一系列与绘图有关的方法来实现,对此在后文中将有详细的 论述。方法和事件一样也包含在属性窗口的“方法程序”选项卡中,惟一的区别是:名称中包含 有“Event”的是事件,反之则是方法。也可以通过双击方法名称来添加相应的代码,实际上是 用自定义的程序功能来代替对象原有的功能,这在面向对象程序设计中称之为方法重载。

1.2.3

SDI 与 MDI 表单

在一个应用程序中通常具有多个表单,这些表单之间以及它们与主窗口之间的层次关系 往往容易让人产生迷惑,因为这其中牵涉到表单的诸多属性。在 Visual FoxPro 中允许创建两 种不同类型的应用程序: ① 多文档界面(MDI)程序。 应用程序由单一的主窗口组成,同时还有一些子窗口包含在主窗口中或浮动在主窗口顶 端。Visual FoxPro 自身就是一个 MDI 应用程序,它带有包含在主窗口中的命令、编辑和设计器 等窗口。 ② 单文档界面(SDI)程序。 应用程序由一个或多个独立窗口组成,这些窗口均在 Windows 桌面上单独显示。 Windows 的 IE 浏览器即是一个 SDI 应用程序的例子,在 IE 中打开的每个页面均显示在自己独立的窗 口中。 为了支持这两种类型的界面,Visual FoxPro 允许创建以下三种类型的表单: 1 . 顶层表单 没有父表单的独立表单,用于创建一个 SDI 应用程序,或用作 MDI 应用程序中其他表单的 父表单。顶层表单与其他 Windows 应用程序同级,可出现在其前台或后台,并且显示在 Windows 任务栏中。要把一个表单设置为顶层表单,需要把它的 ShowWindow 属性设置为“2 - 作 为顶层表单”。 2 . 子表单 包含在另一个窗口中,用于创建 MDI 应用程序的子窗口。子表单不可移至父表单(顶层 表单或主窗体)边界之外,当其最小化时将显示在父表单的底部。若父表单最小化,则子表单 也一同最小化。 创建一个子表单不仅需要指定它应在另外一个表单中显示,而且还需指定是否是 MDI 类 的子表单,即指出表单最大化时是如何工作的。如果子表单是 MDI 类的,它会包含在父表单 中,并共享父表单的标题栏、标题、菜单以及工具栏。非 MDI 类的子表单最大化时将占据父表 单的全部用户区域,但仍保留它本身的标题和标题栏。 子表单的 ShowWindow 属性可以设置为: “0 - 在屏幕中(默认)”和“1 - 在顶层表单中”, 对于前一种情况子表单的父表单将成为 Visual FoxPro 的主窗口,对于后一种情况子表单的父 表单则是活动的顶层表单。如果希望子窗口出现在顶层表单窗口内,而不是出现在 Visual


灵活的 GUI 设计

7

FoxPro 主窗口内时,可选用该项设置。如果希望子表单最大化时与父表单组合成一体,可设置 表单的 MDIForm 属性为“真” (. T.);如果希望子表单最大化时仍保留为一独立的窗口,可设置 表单的 MDIForm 属性为“假” (. F.)。 3 . 浮动表单 属于父表单或主表单的一部分,但并不是包含在父表单中。而且,浮动表单可以被移至屏 幕的任何位置,但不能在父窗口后台移动。若将浮动表单最小化时,它将显示在桌面的底部。 若父表单最小化,则浮动表单也一同最小化。浮动表单也可用于创建 MDI 应用程序。 浮动表单与子表单的区别在于 Desktop 属性,对于浮动表单来说,需要把该属性设置为 “真” (. T.)。 表单的 ShowWindow 属性、Desktop 属性和 MDIForm 属性共同决定了表单三种不同的行为 方式,图 1 . 6 比较了它们的区别。

图 1.6

三种不同类型的表单

1 . 2 . 4 使用表单控件 创建好表单后,就应该向表单中添加控件了。 Visual FoxPro 提供了 21 种通用的表单控件, 分为输出类、输入类、控制类、容器类和连接类五大类。这些控件排列在控件工具栏上,如图 1 . 7 所示。

图 1.7

表单控件工具栏


电脑工程师丛书

8

Visual FoxPro 高级应用实例

① 输出类表单控件:包括标签控件、图像控件、线条控件和形状控件,主要用于在表单上 显示图形或文字。 ② 输入类表单控件:包括文本框控件、编辑框控件、列表框控件、组合框控件和微调控件, 主要用于接收用户输入信息。 ③ 控制类表单控件:包括命令按钮控件、命令按钮组控件、复选框控件、选项按钮组控件 和计时器控件,主要用于对表单或程序流程的各种控制和操作。 ④ 容器类表单控件:包括容器控件、页框控件和表格控件,主要用于作为其他控件的容 器。 ⑤ 连接类表单控件:包括 ActiveX 控件、ActiveX 绑定控件和超级链接控件,主要用于连接 外部程序的数据和功能。 Windows 用户对以上这些控件中的大部分应该不会感到陌生。单击想要在表单上添加的 控件,然后在表单上绘制出控件的大小和摆放位置即可。 1 . 控件的布局 很多时候,一个应用程序中只有表单对最终用户来说是可见的,因此表单设计的好坏成了 用户直观判定该程序好坏的第一标准,为此,合理的规划和布局表单控件就显得尤为重要了。 首先,应该把完成相同或相似功能的表单控件放在一起,并合理安排控件的大小、间距和 对齐方式。如果表单上的控件很多,还可以利用布局工具栏进行自动控制。例如,可以一次选 中多个表单控件,并设置其顶边对齐或者具有相同的水平间距。另外,还可以用具有特殊视觉 效果的 Shape 控件把一组控件组合起来并加以必要的文字说明,这样设计的表单将会非常清 晰。试对比图 1 . 8 中两个不同布局的表单,很显然,前一个表单要比后一个表单友好得多。 如果表单上需要布局的控件很多,可以一次选中多个表单控件,然后利用布局工具 栏对齐进行自动布局,包括上下左右对齐、居中对齐、垂直和水平均匀分布、自动调整大小 等。


灵活的 GUI 设计

图 1.8

9

布局合理与不合理的表单对比

2 . 使用容器类控件 合理布局还有一种有效的方法是使用容器类控件,表格控件大多和某个数据源相关联,在 第 2 章中将对其进行详细的介绍,这里将介绍页框控件和容器控件的使用。 (1)页框控件 Visual FoxPro 的项目管理器就是页框容器的一个很好的例子。一个页框容器可以有多个 页面(Page),每个页面可以放置不同的控件,因此,页框容器实际上扩展了一个表单的面积。 如果需要在一个面积有限的表单中放置大量的控件,通常把这些控件按照一定的规律分组并 放置到一个页框容器的不同页面中,用户可以通过页框上的选项卡来选择不同的页面。对于 用户来说,只有当前选中的选项卡中的控件才是可见的。页框的页数可以从 0 到 99。 要向页框容器中添加控件或切换页面,必须首先激活它的编辑状态:在页框上单击 鼠标右键,从快捷菜单中选择“编辑”命令,此时页框容器周围会被一圈粗线条包围,如 图 1 . 9 所示。页框未被激活的状态下添加的控件只是以当前表单为容器。任何一种容 器控件均遵循该规则。

图 1.9

激活为容器的页框控件

(2)容器控件 命令按钮组和选项按钮组控件其实也可以算作容器控件,只不过它包含控件的类型是固 定的,前者是多个按钮控件的容器,后者是一组选项按钮控件的容器。而容器控件则能包含多 个不同类型的控件,如既可以包含复选框控件,也可以包含列表框控件,甚至还能包含其他的 容器控件。 Container 控件还有一些特殊的用途:由于 Container 控件也能响应 Click 事件,因此它也可 以扮演 Command 按钮的角色,不过它比 Command 控件多了一些特殊的属性,如 BackColor 属性、


电脑工程师丛书

10

Visual FoxPro 高级应用实例

BackStyle 属性和 SpecialEffect 属性,因此可以在容器控件表面填充颜色,并且还可以有两种不 同的状态:凸起和凹下,可以用于制作开关按钮。 3 . 控件的掩码 很多控件都有掩码(Mask)属性,该属性用于定义控件的输入及显示格式。通常情况下,用 户的输入具有不可预知性,因此要编写一个健壮的应用程序,应该考虑到用户输入的所有可 能。在程序中对所有的可能进行判断通常不是一个最好的方法,在这种情况下应该考虑使用 控件的输入掩秒(InputMask)。定义控件的输入掩码可以采用的格式字符如表 1 . 3 所示,这些 格式字符可以应用在列表框、组合框、微调和文本框的输入掩码属性中。 表 1.3

输入掩码的格式字符

定义

描述

X

所有的字符

9

数字或符号,如“ - ”

#

数字、符号或空格

S|

在固定的位置显示货币符号(默认的货币格式用 SET CURRENCY 指定)

S|S|

在最靠近数字的位置显示货币符号

在值的左边显示星号

.

指定小数点的位置

显示分隔数字的逗号

例如,将 InputMask 属性设置为 999,999 . 99 可限制用户只能输入具有两位小数并小于 1000000 的数值,每千位用逗号分隔,在用户输入任何值之前,逗号和小数点就已经显示在文本 框中,如果用户按一个字符键,这个字符将被拒绝输入到该控件中。 有时,不仅需要控制用户输入的格式,还需要对其范围进行设定,常用的方法是在 Valid 事 件(控件失去焦点时发生)或者 InterActiveChange 事件(用户通过键盘或鼠标修改控件的值时发 生)中添加自定义代码。如果采用微调控件,则可以更容易地实现这一点。微调控件的 KeyboardHighValue 和 SpinnerHighValue 属性可以设置在微调控件中输入的最大值,KeyboardLowValue 和 SpinnerLowValue 属性可以设置在微调控件中输入的最小值,通过设置其 Value 属性,还可 以用于日期型等非数值型数据的输入和显示。 还有一类特殊的掩码,称之为图像掩码。通常一个 bmp 图片会带有白色空白,不规则形 状图像周围的白边会严重影响控件的外观,如果不希望这些空白出现在控件中,可以为 bmp 图片创建一个自定义的掩码。 在 Windows 的画图程序或其他的位图编辑程序中打开该文件,将所有希望在 bmp 文件中 显示的区域涂黑,而让希望透明显示的区域保留白色,然后在同一目录下保存这个文件,文件 名与 bmp 文件相同,但扩展名改为 . msk。当 Visual FoxPro 加载一个由 Picture 属性指定的 bmp 文件时,它会在相同的目录下寻找相匹配的 msk 文件。如果这个目录中存在与这个 bmp 文件 同名的 msk 文件,Visual FoxPro 就会自动的把它作为这个 bmp 文件的图像掩码,msk 图片中所 有白色区域在 bmp 图片中都为透明,而 msk 图片中的黑色区域则按照 bmp 图片中的内容显 示。图 1 . 10 对比了两种不同情况下 bmp 图片的显示效果。需要注意的是,bmp 图片和 msk 图 片必须具有相同尺寸,以便掩码能表示 bmp 图片的区域。对于非 bmp 格式的图片不支持图像


灵活的 GUI 设计

11

掩码,但某些图片格式本身支持透明的背景,如 ICO 文件和 GIF 文件。

图 1 . 10

图像掩码效果

4 . 控件的易用性 设计良好的界面可以让用户尽可能容易地了解和使用控件,使用 Tab 键顺序、访问键和控 件提示文本能帮助你做到这一点,同时也会让应用程序显得更为专业,更具人性化。 (1)Tab 顺序 Tab 顺序是指用户通过键盘的“Tab”键在表单中切换焦点控件的顺序。控件的默认 Tab 键顺序是控件添加到表单时的顺序,通过设置控件的 Tab 键顺序可以使用户按照逻辑顺序在 控件之间切换焦点。在“表单设计器”工具栏中单击“设置 Tab 键次序”按钮,此时表单上所有 的控件都会显示出它当前的 Tab 顺序,在某个控件的 Tab 顺序上双击可以把该控件的 Tab 顺 序设置为 1,也就是表单在打开时具有最初焦点的控件,然后顺序单击各个控件,即可完成对 表单控件的 Tab 顺序设定。 对于添加在容器中的控件,首先设置容器的 Tab 键顺序,然后激活容器使之处于编辑状 态,再设置容器中包含的每个控件的 Tab 键顺序。 (2)访问键 控件设置了访问键后,能在表单中的任何地方通过按 < ALT > + 访问键来快速选择一个 控件。为控件指定访问键的方法是在控件的 Caption 属性中,在准备作为访问键的字母前键入 一个反斜杠和一个小于符号( h < )。例如,对于一个“Open”命令按钮,把它的 Caption 属性设 置“ h < Open”,此时在字母 O 下方会显示一条下划线,实际显示效果是: “Open”,用户能够在 表单中任何地方按 < ALT > + O 组合键来快速选中这个命令按钮。 对于文本框或编辑框之类没有 Caption 属性的控件,要设定它们的访问键必须要先添加一 个标签控件,并在作为访问键的字母前键入一个反斜杠和一个小于符号( h < ),同时确保该标 签的 Tab 键顺序在文本框或编辑框之前并且相邻,由于标签控件无法成为焦点,因此对该标签 的访问键会把事件焦点自动转移到文本框或输入框中。 (3)控件提示文本 大多数在运行时可视的控件都有一个 ToolTipText 属性,当用户的鼠标指针在控件上停留 时,将显示这个属性指定的文本。提示文本对于仅有图标而没有文本的按钮特别有用。此外 还有 StatusBarText 属性,可以在该控件获得焦点时在主窗体的状态栏中显示相应的信息。表 单的 ShowTips 属性决定是否显示 ToolTipText 内容,而 SET TALK ON/OFF 命令决定了是否在状 态栏显示提示信息。

1 . 2 . 5 菜单设计 在 Windows 系统中,菜单无所不在。例如,Windows 95 带给我们的除了 32 位的操作系统,


电脑工程师丛书

12

Visual FoxPro 高级应用实例

还有特色鲜明的“开始”菜单,人们现在已经习惯于用鼠标和菜单来进行各种操作。在应用程 序设计过程中,把菜单系统设计好了,用户只需要根据菜单的组织形式和内容就可以很好地理 解应用程序的功能,而且能为应用程序增添 Windows 的桌面风格。 1 . 菜单设计通常遵循的原则 ① 按照用户所要执行的任务组织菜单系统。这样,只要查看菜单和菜单项,用户就应该 可以对应用程序的组织结构有一个感性认识。因此,要设计好这些菜单和菜单项,必须清楚用 户思考问题的方法和完成任务的途径。 ② 给每个菜单一个有意义的菜单标题。 ③ 按照估计的菜单项使用频率、逻辑顺序或字母顺序组织菜单项。 如果不能预计频率,也无法确定逻辑顺序,则可以按字母顺序组织菜单项。当菜单中包含 有八个以上的菜单项时,按字母顺序特别有效。太多的菜单项需要用户花费一定的时间才能 浏览一遍,而按字母顺序则便于查看菜单项。 ④ 在菜单项的逻辑组之间放置分隔线。 ⑤ 将一个菜单中包含的菜单项限制在一个屏幕之内。 如果菜单项的数目太多以致超过了一屏,则应为其中的一些菜单项创建子菜单。 ⑥ 为菜单和菜单项设置访问键或键盘快捷键。 某些常见的菜单会有一些约定俗成的访问键或快捷键,例如,< ALT > + F 通常作为“文 件”菜单的访问键,< Ctrl > + C 通常作为“复制”菜单的快捷键,在设计时应尽量遵循这些惯 例。 ⑦ 使用能够准确描述菜单项的文字。描述菜单项时,应尽量避免使用晦涩难懂的计算机 术语。 2 . 菜单设计器 创建和修改菜单的主要工作是在菜单设计器中完成的,它可以用来设计下拉式菜单和快 捷菜单。设计一个菜单的通常步骤是:打开菜单设计器 → 进行菜单设计 → 保存菜单定义 → 生成菜单程序。菜单设计器如图 1 . 11 所示。

图 1 . 11

菜单设计器

在菜单设计器中可以对以下内容进行设计:


灵活的 GUI 设计

13

(1)菜单名称 菜单设计器中的“菜单名称”列用于定义菜单栏或菜单项的名称,该名称只用于显示,而非 程序中的菜单名。在菜单名称中也可以用“ h < ”来定义访问键,如果需要设计一个菜单分隔 符,可以把它的菜单名称指定为“ h - ”。 (2)结果 “结果”列用于指定菜单的性质,其中有四个选项: ① 命令:该选项用于为菜单项定义一条命令,菜单项的动作即是执行用户定义的命令,只 需把命令输入到组合框右边的文本框中即可。 ② 过程:有时一个菜单项的动作往往不止一条命令,此时可以用该选项为菜单项定义一 个过程。一旦选定了过程选项,组合框右边就会出现一个“创建”或“编辑”按钮,单击相应的按 钮即可在文本编辑窗口中输入多条命令或程序代码。 ③ 子菜单:该选项供用户定义当前菜单的子菜单,可以单击右边的按钮切换到子菜单页, 在其中建立或修改子菜单。如果需要从下级菜单返回上级菜单,可在菜单设计器右侧的“菜单 级”组合框中进行切换。 ④ 填充名称或菜单项:该选项让用户定义第一级菜单的菜单名或子菜单的菜单项序号。 如果希望采用自定义的菜单名或序列可以采用此选项。 可以在菜单设计器中插入快速菜单和系统菜单栏。在新建菜单后,选定“菜单”菜单中的 “快速菜单”命令,可以把一个与 VFP 系统菜单相同的菜单自动填充到菜单设计器中,用户可 在此基础上进行修改;同时,也可以利用“插入栏”按钮向当前菜单行之前插入一个系统菜单 栏,它提供了一组与系统菜单相同的菜单项供用户选择。 (3)选项 单击每个菜单行的无符号按钮,将会出现“提示选项”对话框,可在其中指定快捷键、跳过 选项及显示状态栏信息。 快捷键与访问键的区别在于:在菜单未打开时按快捷键即可直接执行菜单项。“键标签” 用于设定菜单项的快捷键,把光标定位到“键标签”的文本框中,然后按下某一组合键。例如, 按下 < Ctrl > + X 组合键,字符串“Ctrl + X”就会自动填入到文本框中,如果要取消快捷键只需 要按下空格键即可。同时还可以用“键说明”来告诉用户应该用什么快捷键来访问该菜单项。 如果希望在某些情况下禁止用户使用某些菜单,可以在选项中设定“跳过”。“跳过”文本 框用于设置菜单项的跳过条件,可以在其中输入一个条件表达式,当该表达式的值为“真” (. T.)时,该菜单项呈灰色不可用状态,如图 1 . 12 所示。

图 1 . 12

跳过的菜单项

在菜单设计器中完成菜单设计后,需要及时保存菜单的定义,并单击“菜单”菜单 - >“生 成”菜单项来生成菜单命令程序。


电脑工程师丛书

14

Visual FoxPro 高级应用实例

在 Visual FoxPro 中用 mnx 文件保存菜单结构定义,用 mpr 文件保存菜单命令程序,在 应用程序中执行的是 mpr 文件,因此对菜单进行了修改后一定要重新生成菜单程序,否则 所做的修改将无法反映在应用程序中。

3 . 动态菜单设计 如果需要用程序来动态创建菜单栏、子菜单和菜单项,可以使用 DEFINE 命令。 (1)创建菜单栏 创建菜单栏命令的基本语法为: DEFINE PAD MenuTitle OF MenuBarName PROMPT cMenuTitleText [KEY KeyLabel [,cKeyText ]] MenuTitle:菜单的名称,通过它可以得到菜单的引用。 MenuBarName:菜单栏名称,表示在哪个菜单栏中放置该菜单,如果希望放置在系统菜单栏 中则可以设置为 - MSYSMENU。 cMenuTitleText:菜单上显示的文字。 KeyLabel 和 cKeyText:分别为菜单的键标签和键说明。 (2)创建子菜单 创建子菜单命令的基本语法为: DEFINE POPUP MenuName [KEY KeyLabel ] MenuName:子菜单名称。 KeyLabel:键标签。 (3)创建菜单项 创建菜单项命令的基本语法为: DEFINE BAR nMenuItemNumber1 | SystemItemName OF MenuName PROMPT cMenuItemText [KEY KeyLabel [,cKeyText ]] nMenuItemNumber1:菜单项编号。 SystemItemName:系统菜单项编号。 MenuName:菜单名称。 cMenuItemText:菜单项的说明文字。 下面这段程序定义了一个包含一个子菜单及三个菜单项的菜单, 运行结果如图 1.13 所示。

图 1 . 13

DEFINE PAD MyPad OF

-

由程序动态创建的菜单

MSYSMENU PROMPT "我的菜单";

KEY ALT + C,"" ON PAD MyPad OF

-

MSYSMENU ACTIVATE POPUP myitems


灵活的 GUI 设计

15

DEFINE POPUP myitems DEFINE BAR 1 OF myitems PROMPT "Item 1" DEFINE BAR 2 OF myitems PROMPT "Item 2" DEFINE BAR 3 OF myitems PROMPT "Item 3" 4 . 把菜单添加到应用程序中 要在应用程序中使用一个设计好的下拉式菜单,需要在程序中执行如下命令: DO 菜单名 . MPR 值得注意的是:扩展名 MPR 不能省略。 默认情况下菜单将显示在 Visual FoxPro 的主窗口中。如果希望下拉式菜单显示在表单 中,需要把表单设置为顶层表单,并且指明菜单的运行位置为顶层表单:修改表单的 ShowWindow 属性为“2 - 作为顶层表单”,并打开菜单设计器,单击“显示”菜单 - >“常规选项”菜单项, 在“常规选项”对话框中选中“顶层表单”选项,如图 1 . 14 所示。

图 1 . 14

菜单设计器“常规选项”对话框

然后在表单的 Init 事件中添加如下命令: DO 菜单名 . MPR WITH THIS,. T. 请牢记一点:只有顶层表单才能显示下拉式菜单,而且一旦菜单被设置为置于顶层表单中 执行,它将无法在主窗体或别的非顶层表单中执行。 弹出式菜单和下拉式菜单在设计过程中几乎完全相同,在程序中使用弹出式菜单则更为 简单一 些,只 需 要 把 执 行 菜 单 程 序 的 命 令 添 加 到 表 单 或 表 单 控 件 的 相 应 事 件(通 常 是 RightClick 事件)中即可。 菜单被设计成在顶层表单中运行后,可能会出现这样的问题:在菜单设计器中进行 了修改并重新生成菜单程序后,运行效果却不发生变化,原因是 Visual FoxPro 会把菜单 程序 mpr 编译成 mpx 目标文件,并最终执行该文件,在某些情况下系统无法保证目标文 件的版本是最新的,可以通过打开系统的开发模式解决这个问题,在命令窗口中运行如 下命令: SET DEVELOPMENT ON


电脑工程师丛书

16

Visual FoxPro 高级应用实例

1 . 2 . 6 工具栏设计 如果应用程序中包含一些用户经常重复执行的任务,那么可以在自定义工具栏上添加相 应的快捷按钮以简化用户的操作复杂度。在大多数的可视化开发工具中,工具栏类似于一个 容器控件,可以直接添加在表单上并进行设计,但是在 Visual FoxPro 中,工具栏不是表单控件 中的一种而是以可视类库的形式出现的。 1 . 可视类库 类(Class)是面向对象编程中一个非常重要的概念,简单地说,类是对共性与个性的一种描 述。例如 Visual FoxPro 中的表单,它们的形式可能千差万别,但是都有一些共同的属性、方法 和事件,因为 Visual FoxPro 为我们设计好了一个基类:表单类,这就是它们共同的祖先,也就是 共性;同时,我们可以在表单中添加自己的控件和事件代码,使得表单能够按照设计意图完成 特定的任务,这就是它们的个性。可见,设计一个类通常分成两部分:继承共性和定制个性。 可视类是指能够显示在用户界面上,并且以可视化的方式进行设计的类,表单类就是一个 可视类,在保存表单的时候可以选择把它“另存为类”。一个可视类必须包含在一个 vcx 文件 中,在 Visual FoxPro 中这样的文件称之为类库文件。一个 vcx 文件可以包含一个或者多个类的 定义。 工具栏(Toolbar)类是 Visual FoxPro 提供的一个基类,它有着和表单类非常相似的属性、方 法和事件。为应用程序设计一个工具栏,实际上就是在工具栏基类的基础上定制一个自己的 可视类。 2 . 类设计器 为应用程序创建工具栏需要新建一个类,并且选择它的基类为 Toolbar(见图 1 . 15),同时 还需要为它定义一个类名并指定存储于哪个类库文件中。

图 1 . 15

新建工具栏类

一旦创建了自定义的工具栏类后,就可以在类设计器中对它进行设计和修改。类设计器 的界面与表单设计器的界面基本相同,可以在上面添加表单控件,在属性窗口中查看和修改它 们的属性,在代码编辑窗口中编写事件或过程代码。 有些时候,一个类可能会有自己的数据或功能,此时需要在类设计器中新建类的属性或方 法。可以利用“类”菜单中提供的“新建属性”和“新建方法程序”两个菜单项来创建属性和方 法,还可以通过“修改属性 / 方法程序”菜单项来修改它们。在新建属性或方法的时候,可以选


灵活的 GUI 设计

17

择它们的可视性为“公共”、 “保护”和“隐藏”。“公共”指该属性或方法可在应用程序的任何位 置被访问, “保护”的属性或方法只能被该类定义内的方法程序或该类的子类所访问, “隐藏” 的属性或方法则只能被该类的定义内成员所访问,该类的子类不能“看到”或引用它们。采用 可视性约定的好处是可以避免用户对一些私有的数据进行访问和修改,或者调用一些超出作 用域的方法程序。 有些程序员习惯于在程序中定义全局变量或全局的过程、函数,这是一种结构化的程序设 计思想,虽然有时候会带来某种方便,并且在 Visual FoxPro 中也没有禁止这种做法,但是它不 应该被提倡。因为在程序中需要时刻关注变量、过程及函数的作用域,而且 Visual FoxPro 在内 存处理方面功能较弱,如果频繁定义全局变量,在每一个模块运行、退出时有可能无法清除内 存,给系统的稳定性埋下隐患。把数据或方法封装到对象中有助于提高代码的健壮性、可重用 性和易维护性。 3 . 在表单中使用工具栏 在类设计器中创建了一个工具栏类,相当于设计好了该类的“图纸”,要把它应用到表单 中,还需要创建类的实例,也就是根据“图纸”来“盖房子”。类和实例的区别,就相当于“图纸” 和“房子”的区别。要在表单中使用设计好的工具栏,通常需要把它们添加到同一个项目中,然 后打开表单设计器,从项目管理器中把工具栏类拖放到表单中即可。如果当前表单没有创建 表单集,系统将会提示创建它。 也可以采用如下命令在程序中为表单动态添加工具栏: SET CLASSLIB TO 类库文件名 ADDITIVE THIS. 工具栏实例名 = CREATE("工具栏类名") THIS. 工具栏实例名 . SHOW RELEASE CLASSLIB 类库文件名 第一行代码用于加载工具栏类库文件,其中包含了工具栏类的定义,第二、三行代码则在 当前表单中创建一个工具栏类的实例并且显示它,最后释放该工具栏类库。 工具栏类有几个不同于表单类的属性和方法:Docked 属性、DockPosition 属性和 Dock 方法。 工具栏通常停放在窗口主菜单的下方(如 Visual FoxPro 的系统工具栏),这种行为方式称之为 入坞(Dock),否则工具栏将呈现为一个浮动的面板窗口。 Docked 属性就是用来判断工具栏当 前的入坞状态的。工具栏也可以停放在屏幕的两边和底部,从 DockPosition 属性就可以得知它 的停放位置,其含义为:- 1—未入坞、 0—顶部、 1—左边、 2—右边、3—底部。需要注意一点,这 两个属性都是只读属性,如果需要修改工具栏的停放状态及位置,可以使用 Dock 方法,如 Toolbar. Dock(0)将会把工具栏停放在窗体的顶部,Toolbar . Dock( - 1)将取消工具栏的入坞状 态。 默认情况下添加到表单中的工具栏类和表单是一种“平行”的关系,如果调用 Dock 方法, 它实际上会入坞到 Visual FoxPro 的主窗体中,稍后将会看到如何把一个工具栏类添加到一个 顶层表单中并入坞在该表单内。

1 . 2 . 7 在表单中协调菜单和工具栏 通常情况下,表单中的菜单和工具栏不是孤立的,它们需要在运行时相互协调,如把某个


电脑工程师丛书

18

Visual FoxPro 高级应用实例

菜单项的动作关联到工具栏的某个命令按钮上,或者在某些情况下动态修改菜单的显示效果 等。由于菜单的定义是结构化的,这为我们协调菜单和工具栏带来了一定的困难,为此有必要 搞清楚 Visual FoxPro 各个对象的层次关系。 图 1 . 16 是一个 Visual FoxPro 应用程序的框架结构模型及其对应的对象层次。

图 1 . 16

Visual FoxPro 的应用程序模型及其对象层次

从图中不难发现,除了系统菜单和用户菜单的定义为结构化的之外,主窗体、表单及工具 栏均遵循一定的对象层次,而系统变量 - SCREEN 在菜单和表单、工具栏之间架起了一座桥梁, 因为在任何地方均可以访问该对象。如果需要的话,还可以通过 - MSYSMENU 和 - SYSTOOLBARS 来与系统菜单和工具栏发生交互。 要在表单或工具栏中访问用户菜单,只要通过菜单程序中为菜单、子菜单及菜单项定义的 名称即可获得相应的引用。以前面创建的动态菜单(见图 1 . 13)程序为例,如果希望在程序中 动态地为第三个菜单项添加 check 标记,可以在相应的事件中添加如下代码: SET MARK OF BAR 3 OF myitems TO . T. SET MARK 命令用于为菜单项添加 Check 标记,myitems 是菜单程序中定义的子菜单名, BAR 3 表示该子菜单的第三个菜单项。注意,如果有分割线的话它也要占据一个栏位。 利用菜单设计器设计的菜单,其菜单栏名称、子菜单名称及菜单项序号均为系统自动分配 的,为了方便在程序中引用它们,也可以在“选项”中指定菜单栏名称及菜单项序号,如图 1 . 17 所示。

图 1 . 17

在菜单设计器中指定菜单栏名称及菜单项序号

比较图 1 . 13 和图 1 . 17,可以得知图 1 . 17 中的“图形”菜单 - >“矩形”菜单项打上 Check 标记的命令为: SET MARK OF BAR 3 OF 图形 S TO . T. 反过来,如果需要在菜单中访问表单或工具栏,可以通过 - SCREEN 这个全局的系统变量。 例如,要在某菜单项中的“跳过”条件表达式中引用同一表单上工具栏的某个控件的 Enabled 属


灵活的 GUI 设计

19

性,可以表示为: -

SCREEN. ActiveForm. 工具栏名称 . 控件名称 . Enabled

这样,表单上的这个控件被禁用时,相应的菜单项也将不可用。 表单和工具栏类之间的协调要相对简单一些,因为两者都是对象,把工具栏加入表单后, 会自动生成一个表单集,利用 THISFORMSET 对象即可以相互引用。不过要把工具栏类添加在 顶层表单中,需要在生成工具栏类实例的代码时,向工具栏类传递一个顶层表单的引用参数, 告诉工具栏类在哪个父窗体中显示。为了接收该引用,需要在工具栏类的定义中添加一个自 定义的 oFormRef 属性,同时在顶层表单中添加一个自定义的 oToolbar 属性,分别用来保存对方 的引用。在顶层表单中用代码生成工具栏类实例的时候,把表单自身的引用通过参数传递给 工具栏类,在该类的初始化事件中,通过参数接受该引用,即可实现把工具栏添加到顶层表单 中,具体代码参见实例中的程序代码。 这个过程实际上也是表单与工具栏类之间的动态参数传递。由于工具栏类是类似于表单 的一种可视化类,这个方法同样适用于表单与表单之间的参数传递。 至此,在程序中协调菜单和表单、工具栏的问题已经很好地解决了。

1 . 2 . 8 表单模式与事件驱动模型 很多初学者都会遇到这样一个问题:完成了一个应用程序的设计后,在项目管理器中把它 联编成可执行文件并执行它,却发现窗口只是一闪而过。要解决这个问题,需要来讨论一下表 单的模式以及事件驱动模型。 表单的 WindowType 属性可以有两种取值: “0 - 无模式”和“1 - 模式”。当一个表单设置 为无模式时,它的活动不影响主窗体中其他组件(如表单、菜单)的使用;而设置为模式表单的 话,该表单将独占焦点,也就是说当该表单活动时,其他对象都不能被用户访问。例如,Visual FoxPro 的命令窗口就是一个无模式窗口,而“打开”对话框则是一个模式窗口。模式窗口通常 在执行一个特定的任务时使用,如本实例中“关于”对话框就是一个模式对话框,它可以避免用 户多次从菜单或工具栏中执行该表单,而一般情况则使用无模式表单。 脱离了开发环境后,应用程序实际上是在 Visual FoxPro 的运行环境(Runtime Environment) 中运行,该运行环境主要靠 VFP6R. DLL 和 VFP6RXXX. DLL(XXX 取决于语言版本)两个动态库 提供。在这种条件下,仅由无模式表单组成的应用程序将无法正确运行,因为 Visual FoxPro 应 用程序的执行是通过事件驱动的,只有当用户单击鼠标或按下键盘后程序才会执行相应的操 作,而无模式表单无法建立起事件循环来等待用户操作,因此就会出现上文提到的程序一闪而 过的现象。解决这个问题有两种方法可供选择:对于一个 SDI 表单,可以通过设置它的 WindowType 属性为“1 - 模式”,模式表单会始终处于活动状态并等待用户的交互动作;另一种方 法是从一个建立了事件循环的程序中执行无模式表单。 建立事件循环需要使用 READ EVENTS 命令。从该命令的执行开始,应用程序的所有处理 将被挂起,等待鼠标单击、键击等用户事件,一旦截获到用户事件即开始相应的命令或过程处 理,也就是说,在启动了事件循环之后,应用程序将处在最后显示的用户界面元素的控制之下。 用 READ EVENTS 命令建立了事件循环后,必须要有相应的命令结束事件循环,否则系统 将提示无法退出应用程序。结束事件循环的命令是 CLEAR EVENTS。典型情况下,可以使用 一个菜单项或表单上的按钮执行 CLEAR EVENTS 命令。 CLEAR EVENTS 命令将挂起 Visual


电脑工程师丛书

20

Visual FoxPro 高级应用实例

FoxPro 的事件处理过程,同时将控制权返回给执行 READ EVENTS 命令的程序。 因此,建立一个应用程序的最基本步骤是: ① 初始化应用程序运行环境,如隐藏主窗体、系统菜单压栈等。 ② 调用一个表单或菜单建立用户界面。 ③ 执行 READ EVENTS 命令以建立事件循环。 ④ 从用户界面的某个事件中(如“退出”菜单)执行 CLEAR EVENTS 退出事件循环。 ⑤ 如果需要的话可以重复第二步操作,显示另外的用户界面。 ⑥ 所有操作执行完毕后,恢复默认的环境,应用程序退出。 包含了 READ EVENTS 的程序通常被设置为主文件,也就是应用程序最先被执行的文件。 主文件设置可以在项目管理器中完成,并以加粗的方式显示其文件名。

1 . 2 . 9 表单绘图 下面将要介绍一些和屏幕绘图有关的表单属性、方法和事件,本例中通过它们完成涂鸦板 的主要绘图功能,如果需要设计具有动画效果的表单界面,这些介绍也有很重要的参考价值。 1 . 与绘图有关的表单属性 (1)DrawMode 属性 该属性决定了表单的绘图模式,它的取值及对应含义如表 1 . 4 所示。 表 1.4 取值

含义

表单 DrawMode 属性 取值

含义

1

画笔恒为黑色

16

画笔恒为白色

2

与屏幕 Merge 的反转色

15

与屏幕 Merge

3

反相与屏幕 Mask

14

与屏幕反相的 Merge

4

前景色的反转色

13

前景色(默认值)

5

与屏幕反相的 Mask

12

反相与屏幕 Merge

6

背景色的反转色

11

不变色

7

屏幕 XOR 运算

10

屏幕 XOR 运算的反转色

8

与屏幕 Mask 的反转色

9

与屏幕 Mask

在 Visual FoxPro 中,颜色实际上用三组八位二进制数表示,分别代表红、绿、蓝三色,加在 一起就是通常所说的 24 位色(在 Visual FoxPro 中不支持 Alpha 通道,所以不支持 32 位色)。所 谓 Merge 是指取并集,Mask 是指取交集,其中很有用处的是属性“10”,因为两次 XOR 运算后的 结果相当于把上一次绘制的图形擦除。 (2)DrawStyle 属性 该属性决定屏幕绘图的线条样式,可设置为:0 - 实线、1 - 虚线、2 - 点线、3 - 点划线、4 双点划线、 5 - 透明线、 6 - 内实线。 (3)DrawWidth 属性 该属性决定屏幕绘图的线条宽度,可以从 1 到 32 767,单位是像素。


灵活的 GUI 设计

21

(4)FillColor 属性 该属性决定在屏幕上绘制封闭图形时的填充色,同样采用 RGB 颜色表示,如 RGB(0,0,0) 代表黑色,RGB(255, 0, 0)代表红色,RGB(255,255,255)代表白色。更多的情况下是通过调用 系统的调色板来获取颜色属性的,相应的内部函数为 GETCOLOR()。 (5)ForeColor 与 BackColor 属性 屏幕绘图的前景色与背景色。 2 . 与绘图有关的表单方法 (1)Form. Box(nXCoord1,nYCoord1,nXCoord2,nYCoord2) 在表单上绘制一个矩形,nXCoord1、nYCoord1、nXCoord2、nYCoord2 分别为矩形对角线上两 个顶点的坐标。 (2)Form. Circle(nRadius[,nXCoord ,nYCoord[,nAspect ]] ) 在表单上绘制一个椭圆,nRadius 为椭圆的半长轴,nYCoord1、nXCoord2 为椭圆圆心坐标, nAspect 为椭圆的纵轴与横轴的比例。缺省 nAspect 参数为 1,表示绘制的是圆。 (3)Form. Line(nXCoord1,nYCoord1,nXCoord2,nYCoord2) 在表单上绘制一条直线,nXCoord1、nYCoord1、nXCoord2、nYCoord2 分别为直线的起点和终 点坐标。 (4)Form. Cls 清除当前绘制在表单上的图像和文字。 3 . 与绘图有关的表单事件 (1)MouseDown 事件 触发 MouseDown 事件后,系统将会自动返回以下参数: LPARAMETERS nButton,nShift,nXCoord,nYCoord nButton 表明按下的是哪个鼠标按键,1 为左键, 2 为右键,4 为中键;nShift 表明在鼠标按下 时键盘上 < Shift > 、< Ctrl > 和 < Alt > 功能键的状态,按下 < Shift > 键返回 1,按下 < Ctrl > 键返 回 2,按下 < Alt > 键返回 4,同时按下多个键则返回相应键值的和。 nXCoord 和 nYCoord 分别代 表鼠标按下时的位置,nXCoord 为鼠标按下点距离表单左边的水平距离,nYCoord 为鼠标按下点 距离表单顶边的垂直距离。 (2)MouseMove 和 MouseUp 事件 返回参数及含义与 MouseDown 事件完全相同,不再赘述。在这些事件中结合返回的参数 添加相应的程序代码,即可完成绘图功能了。

1.3

实例制作 本节将要设计一个类似于 Windows 画图程序功能的应用程序,允许用户在窗口上拖动鼠

标绘制图形,同时可以选择绘图的形状,线条的样式、粗细、颜色以及封闭图形的填充颜色等, 当然,这些并不是编写该程序的最终目的,只是希望能通过这个程序来说明表单、用户菜单、自 定义工具栏的设计过程以及使用到的一些技巧。


电脑工程师丛书

22

Visual FoxPro 高级应用实例

本实例将要制作的文件包括: ① 项目文件:ex1 . pjx。 ② 主表单:ex1 . scx。 ③ 关于对话框表单:about . scx。 ④ 主菜单:mainmenu . mnx 并生成菜单程序 mainmenu . mpr。 ⑤ 工具栏可视类库:oToolbar . vcx。 ⑥ 主程序:main . mpr(项目主文件)。 ⑦ 图像文件:about . gif、paint . bmp(掩码文件 paint . msk)。 这些文件位于本书配套光盘的 h ex1 目录下。

1 . 3 . 1 制作表单 单击“文件”菜单 - >“新建”菜单项,在“新建”对话框中选择“文件类型”为“项目”,单击 “新建文件”按钮新建一个项目,并保存为 ex1 . pjx。在项目管理器中新建两个表单,分别保存 为 ex1 . scx 和 about . scx。 ex1 表单为绘图区域,不需要添加任何控件,about 表单为“关于”对话 框。依照表 1 . 5 设置 ex1 表单的属性。 表 1.5

ex1 表单主要属性设置

表单属性

BackColor

255, 255, 255

Caption

实例一

Name

FormMain

ShowWindow

2 - 作为顶层表单

WindowType

2 - 最大化

mDown(自定义属性)

(用于保存当前鼠标状态)

first(自定义属性)

. T.(鼠标按下后第一次绘图不需要擦除,用此标志判断)

oToolbar1(自定义属性)

(用于保存即将制作的工具栏类实例的引用)

pStyle(自定义属性)

1(当前屏幕绘图的样式, 1 - 线条, 2 - 直线, 3 - 矩形, 4 - 椭圆)

sx,sy(自定义属性)

(按下鼠标时的屏幕坐标)

tx、ty(自定义属性)

(鼠标移动时的上一次屏幕绘图坐标)

在表单设计器中打开 about 表单并按图 1 . 18 所示添加控件,其中的分隔线由两个 Shape 控 件组成,两者高度均为一个像素,垂直坐标相差一个像素,颜色分别为灰色和白色,这样就实现 了一个具有三维视觉效果的分隔线。控件工具栏中的分隔符控件只能添加在工具栏类中而不 能添加在表单中。按照表 1 . 6 设置 about 表单及其控件的属性。


灵活的 GUI 设计

图 1 . 18 表 1.6 控件名称

控件类型

About 表单

about 表单及控件的主要属性设置 成员或属性 BorderStyle

Form1

Form

Caption MaxButton BackColor Height

Shape1、Shape2

Shape

Left Top

Image1

Image

23

值 2 - 固定对话框 关于 . F. - 假 128, 128(Shape1) 128, 255, 255, 255(Shape2) 1 12 239(Shape1) 240(Shape2)

Width

349

Height

64

Picture

paint. bmp

Width

64 确定

Caption

Command1

Command

TabIndex

1

PageFrame1

PageFrame

PageCount

2

Page1 . Edit1

Edit

SrcollBars

Page2 . Label1

Label

Caption

Page2 . Label2

Label

Caption

2 - 垂直 = "计算机名:" + GETENV("computername") = "操作系统: " + OS(1)

Page2 . Label3

Label

Caption

Page2 . Label4

Label

Caption

= "Windows 路径: " + GETENV("windir") = "当前路径: " + SYS(5)+ SYS(2003)+ " h "

1 . 3 . 2 设计工具栏 在项目管理器的“类库”中新建一个类,类名为 oToolbar,选择基类为“Toolbar”,并保存在 otoolbar . vcx类库文件中。在类设计器中添加表单控件(见图 1 . 19),并按照表 1 . 7 修改工具栏


电脑工程师丛书

24

Visual FoxPro 高级应用实例

及其控件的属性。

控件名称

图 1 . 19

类设计器中的自定义工具栏

表 1.7

工具栏及其控件的主要属性

控件类型

ShowWindow oToolbar

oToolbar

Caption oFormRef(自定义属性) BaseClass

OptionGroup

Container1

Container

Container2

Container

Check1

Spinner1

Check

Spinner

Command1

Command

Command2

Command

Separator1、 2、 3、 4

Separator

. F.(用于加入顶层表单) Toolbar

ClassLibrary

otoolbar. vcx

ButtonCount

4

Height

24

Value OptionGroup1

1 - 最上层表单 自定义工具栏

Option1

Caption

1 线条

Option2

Caption

直线

Option3

Caption

矩形

Option4

Caption

椭圆

BackColor SpecialEffect BackColor SpecialEffect Caption Value

0, 0, 0 0 - 凸起 128, 128, 128 0 - 凸起 填充 . F.

KeyBoardHighValue

99

KeyBoardLowValue

0

SpinnerHighValue

99 . 00

SpinnerLowValue

0 . 00

Value

1

InputMask

99

Caption

清除

Caption

(无)

Picture

about. gif

(放置在相应的位置上用于分隔控件)

1 . 3 . 3 设计菜单 在项目管理器中新建一个下拉式菜单并保存为 mainmenu . mnx,在菜单设计器中修改其结 构(见表 1 . 8)。


灵活的 GUI 设计

表 1.8 菜单栏 文件 ( h < F) 图形 ( h < S)

线型 ( h < L)

菜单级

子菜单

文件 F

清除( h < C) 关闭( h < Q)

图形 S

线型 L

线条( h < F) 直线( h < L) 矩形( h < B) 椭圆( h < C)

( h < C) 线宽 ( h < W) 帮助 ( h < H)

颜色 C

线宽 W 帮助 H

主菜单结构 命令或过程

-

SCREEN. ActiveForm. cls

-

SCREEN. ActiveForm. release

-

SCREEN. ActiveForm. oToolbar1 . Optiongroup1 . Value = 1

-

SCREEN. ActiveForm. oToolbar1 . Optiongroup1 . Value = 2

-

SCREEN. ActiveForm. oToolbar1 . Optiongroup1 . Value = 3

-

SCREEN. ActiveForm. oToolbar1 . Optiongroup1 . Value = 4

实线

-

虚线

-

点线

-

SCREEN. ActiveForm. oToolbar1 . Combo1 . DisplayValue = "实线" SCREEN. ActiveForm. oToolbar1 . Combo1 . DisplayValue = "虚线" SCREEN. ActiveForm. oToolbar1 . Combo1 . DisplayValue = "点线" SCREEN. ActiveForm. oToolbar1 . Combo1 . DisplayValue = "点划线"

点划线

-

双点划线

-

无线

-

SCREEN.ActiveForm. oToolbar1. Combo1.DisplayValue = "双点划线" SCREEN. ActiveForm. oToolbar1 . Combo1 . DisplayValue = "无线"

内实线

-

SCREEN. ActiveForm. oToolbar1 . Combo1 . DisplayValue = "内实线"

( h < L) 填充色

-

Screen. ActiveForm. oToolbar1 . Container1 . Click

( h < F) 增大

-

Screen. ActiveForm. oToolbar1 . Container2 . Click

线条色 颜色

减小 关于( h < A)

25

(见下文过程 1) (见下文过程 2) -

Screen. ActiveForm. oToolbar1 . Command2 . Click

其中“线型”菜单中的“实线”、 “虚线”、 “点线”、 “点划线”、 “双点划线”、 “无线”和“内实线” 菜单项的“跳过”选项均设置为: !- SCREEN. ActiveForm. oToolbar1 . Combo1 . Enabled “线宽”菜单中的“增大”和“减小”菜单项的“跳过”选项分别设置为: -

SCREEN. ActiveForm. oToolbar1 . spinner1 . value = 99(线宽达到 99,不再增大)

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value = 0(线宽最小为 0,不再减小)

过程 1 的代码如下: if

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value < 99

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value = ;

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value + 1

endif 过程 2 的代码如下: if

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value > 0

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value = ;

-

SCREEN. ActiveForm. oToolbar1 . spinner1 . value - 1

endif 最后在“常规选项”中设置该菜单在顶层表单中执行,保存以上修改后,单击“菜单”菜


电脑工程师丛书

26

Visual FoxPro 高级应用实例

单 - >“生成”菜单项生成菜单程序 mainmenu . mpr。

1 . 3 . 4 编写程序代码 1 . 为工具栏及其控件添加事件代码 在类设计器中为 oToolbar 的 Init 事件中添加如下代码: # DEFINE ERR - NOFORMPARM- LOC "必须为工具栏传递一个表单引用参数!" PARAMETER oForm IF TYPE("m. oForm")# "O" OR ISNULL(m. oForm)OR; UPPER(oForm. BASECLASS) # "FORM" MESSAGEBOX(ERR - NOFORMPARM- LOC) RETURN . F. ENDIF THIS. oFormRef = oForm 以上代码和表单中动态生成 oToolbar1 实例的代码是对应的,用于判断该工具栏显示在哪 一个顶层表单中。 OptionGroup1 控件用于控制绘图的形状,包括任意形状的线条、直线、矩形和椭圆,除了要 把表单的 pStyle 属性赋予相应的值之外,还需要为主菜单 mainmenu 中相应的菜单项打上 Mark 标记。由于它有可能被用户通过单击相应的 Option 按钮修改,也可能在单击菜单时被程序修 改,因此需要在该控件的 InterActiveChange 事件及 ProgrammaticChange 事件中均添加如下代码: THISFORM. oFormRef. pStyle = THIS. VALUE DO CASE CASE THIS. VALUE = 1 SET MARK OF BAR 1 OF 图形 S TO . T. SET MARK OF BAR 2 OF 图形 S TO . F. SET MARK OF BAR 3 OF 图形 S TO . F. SET MARK OF BAR 4 OF 图形 S TO . F. CASE THIS. VALUE = 2 SET MARK OF BAR 1 OF 图形 S TO . F. SET MARK OF BAR 2 OF 图形 S TO . T. SET MARK OF BAR 3 OF 图形 S TO . F. SET MARK OF BAR 4 OF 图形 S TO . F. CASE THIS. VALUE = 3 SET MARK OF BAR 1 OF 图形 S TO . F. SET MARK OF BAR 2 OF 图形 S TO . F. SET MARK OF BAR 3 OF 图形 S TO . T. SET MARK OF BAR 4 OF 图形 S TO . F. CASE THIS. VALUE = 4 SET MARK OF BAR 1 OF 图形 S TO . F.


灵活的 GUI 设计

27

SET MARK OF BAR 2 OF 图形 S TO . F. SET MARK OF BAR 3 OF 图形 S TO . F. SET MARK OF BAR 4 OF 图形 S TO . T. ENDCASE Combo1 控件可以让用户选择绘图线条的样式,在它的 Init 事件中添加如下代码: THIS. CLEAR THIS. ADDITEM("实线", 1) THIS. ADDITEM("虚线", 2) THIS. ADDITEM("点线", 3) THIS. ADDITEM("点划线", 4) THIS. ADDITEM("双点划线", 5) THIS. ADDITEM("无线", 6) THIS. ADDITEM("内实线", 7) THIS. REFRESH 当用户通过菜单或工具栏修改绘图线条的样式时,需要修改表单的 DrawStyle 属性,并为 主菜单中的相应菜单项打上 Mark 标记,它的 InterActiveChange 事件及 ProgrammaticChange 事件 代码如下: DO CASE CASE THIS. DISPLAYVALUE = "实线" THISFORM. oFormref. DRAWSTYLE = 0 CASE THIS. DISPLAYVALUE = "虚线" THISFORM. oFormref. DRAWSTYLE = 1 CASE THIS. DISPLAYVALUE = "点线" THISFORM. oFormref. DRAWSTYLE = 2 CASE THIS. DISPLAYVALUE = "点划线" THISFORM. oFormref. DRAWSTYLE = 3 CASE THIS. DISPLAYVALUE = "双点划线" THISFORM. oFormref. DRAWSTYLE = 4 CASE THIS. DISPLAYVALUE = "无线" THISFORM. oFormref. DRAWSTYLE = 5 CASE THIS. DISPLAYVALUE = "内实线" THISFORM. oFormref. DRAWSTYLE = 6 ENDCASE FOR i = 0 TO 6 IF i = THISFORM. oFormref. DRAWSTYLE SET MARK OF BAR i + 1 OF 线型 L TO . T. ELSE SET MARK OF BAR i + 1 OF 线型 L TO . F. ENDIF ENDFOR


电脑工程师丛书

28

Visual FoxPro 高级应用实例

Container1 控件允许用户选择绘图的线条色,单击该控件将弹出系统的调色板,并把用户 选择的颜色反应在控件上。为该控件的 Click 事件添加如下代码: THIS. BACKCOLOR = GETCOLOR() THISFORM. oFormref. FORECOLOR = THIS. BACKCOLOR 同理,为 Container2 控件的 Click 事件添加如下代码,只是这时修改的是封闭绘图形状的填 充色,即表单的 FILLCOLOR 属性。 THIS. BACKCOLOR = GETCOLOR() THISFORM. oFormRef. FILLCOLOR = THIS. BACKCOLOR Check1 控件用于判断绘制封闭形状时是填充颜色还是透明,如果为透明则禁用 Container2 控件。在该控件的 InterActiveChange 事件及 ProgrammaticChange 事件中添加如下代码: IF THIS. VALUE = . F. THISFORM. Container2 . ENABLED = . F. THISFORM. oFormRef. FILLSTYLE = 1 SET MARK OF BAR 3 OF 颜色 C TO . F. ELSE THISFORM. Container2 . ENABLED = . T. THISFORM. oFormRef. FILLSTYLE = 0 SET MARK OF BAR 3 OF 颜色 C TO . T. ENDIF Spinner1 控件决定了绘图线条的宽度,取值范围从 0 到 99 像素。在 Visual FoxPro 中,只有 线宽为 1 的线条才能使用线条样式。因此在线宽大于 1 的情况下禁用 Combo1 控件。 Combo1 的 Enabled 属性和相应菜单项的“跳过”表达式是相对应的,因此不需要在程序中启用或禁用菜 单项。为该控件的 InterActiveChange 事件及 ProgrammaticChange 事件添加如下代码: THISFORM. oFormRef. DRAWWIDTH = THIS. VALUE IF THIS. VALUE # 1 THISFORM. Combo1 . ENABLED = . F. ELSE THISFORM. Combo1 . ENABLED = . T. ENDIF 单击 Command1 控件将清除当前表单上绘制的图形,为该控件的 Click 事件添加如下代码: THISFORM. oFormRef. CLS 单击 Command2 控件将弹出“关于”对话框,即执行 about 表单,为该控件的 Click 事件添加 如下代码: DO FORM about 2 . 编写主表单事件代码 表单的 Init 事件通常用于执行菜单程序和完成必要的初始化,在其 Init 事件中添加如下 代码:


灵活的 GUI 设计

29

DO mainmenu . mpr WITH THIS,. T. THIS. pStyle = 1 SET MARK OF BAR 1 OF 图形 S TO . T. SET MARK OF BAR 1 OF 线型 L TO . T. 需要注意的是,动态生成工具栏类实例的代码不能放在表单的 Init 事件中。因为此时表 单对象正在创建,无法向工具栏类传递自身的引用作为参数。解决的办法是把该代码放在表 单的 Activate 事件中,在该事件中表单对象已经创建完毕。表单的 Activate 事件代码如下: IF TYPE("this . oToolbar1")= "O" AND; ! ISNULL(THIS. oToolbar1) RETURN ENDIF SET CLASSLIB TO oToolbar1 ADDITIVE  向顶层表单中添加工具栏 THIS. oToolbar1 = CREATE("oToolbar",THIS) THIS. oToolbar1 . DOCK(0)

&& 入坞到顶层表单的顶部

THIS. oToolbar1 . SHOW THIS. oToolbar1 . Combo1 . DISPLAYVALUE = "实线" RELEASE CLASSLIB oToolbar1 主表单从内存中清除后,这个应用程序的运行也就结束了,因此需要在 Destroy 事件中清 除主程序的事件循环,从而退出程序,代码如下: CLEAR EVENTS 下面开始绘图代码的编写。根据鼠标的拖动轨迹绘制图形的基本方法是:在按下鼠标时 获得此时的屏幕坐标并把它保存到表单的自定义属性 sx 和 sy 中作为起点坐标,在释放鼠标时 获得终点的屏幕坐标,把起点及终点坐标代入到表单的绘图方法中即可完成一个最基本屏幕 绘图。 在一般的情况下,我们还希望能在按下并移动鼠标的过程中动态地绘制此时的屏幕图形, 这就要利用到表单的 DrawMode 属性。在按下并移动鼠标过程中,首先利用当前屏幕坐标及起 点坐标绘制一个临时图形,绘图完毕后把此时的屏幕坐标作为临时数据保存到 tx、ty 两个自定 义属性中。当鼠标移动到下一个位置时,首先根据 tx、ty 属性和起点位置擦除上一次绘制的临 时图形,此时的 DrawMode 属性需要设置为 10;再根据当前的屏幕坐标绘制第二个临时图形,依 此类推,一直到用户释放鼠标为止。在这个过程中就实现了动态地绘制鼠标轨迹。 在 MouseDown 事件中添加如下代码: LPARAMETERS nButton,nShift,nXCoord,nYCoord THIS. sx = nXCoord THIS. sy = nYCoord THIS. tx = nXCoord THIS. ty = nYCoord THIS. mDown = . T.


电脑工程师丛书

30

Visual FoxPro 高级应用实例

在 MouseMove 事件中添加如下代码: LPARAMETERS nButton,nShift,nXCoord,nYCoord IF THIS. mDown = . T. DO CASE CASE THIS. pStyle = 1

&& 线条

THIS. DRAWMODE = 13 THIS. LINE(THIS. tx,THIS. ty,nXCoord,nYCoord) THIS. tx = nXCoord THIS. ty = nYCoord CASE THIS. pStyle = 2 IF ! THIS. FIRST

&& 直线

THIS. DRAWMODE = 10 THIS. LINE(THIS. sx,THIS. sy,THIS. tx,THIS. ty) ENDIF THIS. LINE(THIS. sx,THIS. sy,nXCoord,nYCoord) THIS. tx = nXCoord THIS. ty = nYCoord THIS. FIRST = . F. CASE THIS. pStyle = 3 IF ! THIS. FIRST

&& 矩形

THIS. DRAWMODE = 10 THIS. BOX(THIS. sx,THIS. sy,THIS. tx,THIS. ty) ENDIF THIS. BOX(THIS. sx,THIS. sy,nXCoord,nYCoord) THIS. tx = nXCoord THIS. ty = nYCoord THIS. FIRST = . F. CASE THIS. pStyle = 4 IF ! THIS. FIRST

&& 椭圆

THIS. DRAWMODE = 10 radius = MAX(ABS(THIS. sy - THIS. ty)/2,; ABS(THIS. sx - THIS. tx)/2) cx =(THIS. sx + THIS. tx)/2 cy =(THIS. sy + THIS. ty)/2 Aspect = ABS(THIS. sy - THIS. ty)/ABS(THIS. sx - THIS. tx) THIS. CIRCLE(radius,cx,cy,Aspect) ENDIF radius = MAX(ABS(THIS. sy - nYCoord)/2,ABS(THIS. sx - nXCoord)/2) cx =(THIS. sx + nXCoord)/2


灵活的 GUI 设计

cy =(THIS. sy + nYCoord)/2 Aspect = ABS(THIS. sy - nYCoord)/ABS(THIS. sx - nXCoord) THIS. CIRCLE(radius,cx,cy,Aspect) THIS. tx = nXCoord THIS. ty = nYCoord THIS. FIRST = . F. ENDCASE ENDIF 在 MouseUp 事件中添加如下代码: LPARAMETERS nButton,nShift,nXCoord,nYCoord IF THIS. mDown = . T. DO CASE CASE THIS. pStyle = 1 && 线条 THIS. LINE(THIS. tx,THIS. ty,nXCoord,nYCoord) CASE THIS. pStyle = 2 && 直线 IF ! THIS. FIRST THIS. DRAWMODE = 13 THIS. LINE(THIS. sx,THIS. sy,nXCoord,nYCoord) ENDIF CASE THIS. pStyle = 3 && 矩形 IF ! THIS. FIRST THIS. DRAWMODE = 13 THIS. BOX(THIS. sx,THIS. sy,nXCoord,nYCoord) ENDIF CASE THIS. pStyle = 4 && 椭圆 IF ! THIS. FIRST THIS. DRAWMODE = 13 radius = MAX(ABS(THIS. sy - nYCoord)/2,; ABS(THIS. sx - nXCoord)/2) cx =(THIS. sx + nXCoord)/2 cy =(THIS. sy + nYCoord)/2 Aspect = ABS(THIS. sy - nYCoord)/ABS(THIS. sx - nXCoord) THIS. CIRCLE(radius,cx,cy,Aspect) ENDIF ENDCASE ENDIF THIS. mDown = . F. THIS. FIRST = . T.

31


电脑工程师丛书

32

Visual FoxPro 高级应用实例

3 . 编写主程序 主程序 main . mpr 是程序开始运行时首先执行的程序代码,在该程序中主要完成系统环境 的设置,执行程序的主表单,并建立事件循环,代码如下: SET TALK OFF SET DEVELOPMENT ON SET DEFAULT TO SYS(5)+ SYS(2003)+ " h " - SCREEN. HIDE DO FORM ex1 READ EVENTS - SCREEN. SHOW RETURN

1 . 3 . 5 程序运行及说明 在项目管理器中设置 main . mpr 程序为主文件并运行之,如果运行正常的话,本实例的制 作就完成了。试着修改 about 表单的 ShowWindow、Desktop 等属性,观察表单的行为方式会有什 么不同。 “关于”对话框中还显示了一些常用的系统信息,该功能通常需要调用一些 Windows API 函数来实现,关于 API 函数的调用将在第 3 章中详细介绍。本例通过 Visual FoxPro 的系统函数 来获得一些基本的系统信息。 获得系统信息的第一个途径是访问系统的环境变量,Visual FoxPro 提供了一个专用函数: GETENV(),该函数可以返回特定 的 MS - DOS 环 境 变 量。其 中 特 别 有 用 的 是 COMSPEC 和 PATH,从前一个环境变量可以得知系统的 cmd . exe 所在的路径,从后一个环境变量可以得知 系统的默认搜索路径。 另一个得到系统信息的途径是 Visual FoxPro 的 SYS()函数。例如,SYS(5)可以返回当前的 驱动器名称,SYS(13)可以得到当前的打印机状态,SYS(2022,"C")可以得到逻辑驱动器 C 的磁 盘簇大小等。如果需要查阅更多的 SYS()函数的功能,可以在命令窗口中输入并选中 SYS() 函数,然后按“F1”键(前提条件是 MSDN 已正确安装)。本例中利用 SYS(5) + SYS(2003)得到 程序当前执行的路径。

1.4

本章小结

本章着重介绍的是界面设计过程中面向对象的程序设计思想与方法,实际上,面向对象的 思想将贯穿本书的始终。虽然本章没有过多地讨论面向对象的基本概念,但是在工具栏的设 计中体现了对象之间的继承关系,在类设计器的属性和方法定义中,体现了数据的封装,而继 承与封装正是面向对象程序设计的精髓所在。通过工具栏与表单的结合,我们还掌握了一种 对象之间的通信机制,而屏幕绘图程序本身就是事件驱动模型的一个经典应用。此外,本章的 难点是各个 GUI 元素(如表单、控件、菜单及工具栏)之间的相互协调问题,针对这个问题,实例 中介绍了很多实用的编程技巧。在深入理解本章所介绍的概念和设计思想后,读者在开发自 己的应用程序过程中对 Visual FoxPro 的 GUI 设计将会感到游刃有余。


数据库应用系统的快速开发 ———具体而微的设备管理系统

2

本章技术要点:  数据一致性维护。  数据环境。  绑定型表单控件的使用。  报表设计。  多用户权限管理。

2.1

本章提要 快速开发数据库应用系统是 Visual FoxPro 的强项,在大量的设计器、生成器及向导的帮助

下,可以在很短时间内开发出功能强大的数据库应用程序。快速,是本章的主题,但并不等同 于“一蹴而就”,它是建立在大量的前期准备、自顶向下的设计模式以及面向对象程序设计的基 础上,充分利用可视化的设计工具形象直观地完成系统的开发,在这个过程中只需要编写很少 量的代码。 不同于一般的应用程序,数据库应用系统通常由比较固定的几个功能模块组成:最核心的 部分是数据的增、删、改模块,再加上浏览、查询、统计和报表功能,就构成了一个完整的数据库 应用系统。这些功能模块之间相对独立,联系它们的纽带就是数据库。本章将设计一个包含 以上功能的小型企业设备管理系统,从中可以充分领略到 Visual FoxPro 快速开发数据库应用 系统的精妙之处。 本章实例的运行效果如图 2 . 1 所示。


电脑工程师丛书

34

Visual FoxPro 高级应用实例

图 2.1

2.2

设备管理系统运行效果图

技术分析 2 . 2 . 1 需求分析 开发一个项目之前,需求分析是必不可少的,匆匆忙忙地开始编写程序只会导致事倍功

半。对客户的要求进行分析和总结,尽可能地写成文档并得到客户的认可,这个步骤会为后面 的开发减少不必要的麻烦,因为客户的需求决定了系统的整体架构。而在项目的开发过程中 应该尽量避免修改它。 数据库应用系统通常可以分为两类:以数据为中心或以处理为中心。前者的重点是数据 的获得与组织,包括数据的原始采集、处理、建库以及数据库的维护等,它主要应用在专门的信 息服务部门,数据量一般较大;而后者的重点在于数据的使用,包括查询、统计、打印报表等,适 用于一般的企事业单位进行自动化的信息管理,相对于前者来说数据量要小得多。开发一个 以处理为中心的数据库应用系统通常有以下步骤:需求分析、数据库设计、功能模块设计、系统 调试、运行和维护等,图 2 . 2 说明了它的流程。 以企业的设备信息管理系统为例,它要求把企业日常的设备管理工作从手工转向计算机 自动化管理。从用户的角度看,该系统实际上就是一个存储信息的仓库,程序开发人员为用户 统筹安排好信息存放的范围和具体位置,并制定出信息处理的完整解决方案。用户在平时可 以通过程序界面方便、快捷地完成设备信息的登记、修改或删除,从而完成信息的存储,在必要 的时候,又可以从中查询数据、获得统计信息或打印报表。


数据库应用系统的快速开发

图 2.2

35

数据库应用系统开发流程

对于开发人员来说,需要把技术、经济和管理等融为一体,全盘考虑,尽可能地满足企业的 管理需求,收集第一手资料就显得尤为重要。对于一个企业设备管理系统而言,现有的设备登 记资料将是必不可少的。假设该企业的设备登记册中包含的设备信息如表 2 . 1 所示。 表 2.1

企业设备登记本 设备编号:BA980905006

设备名称

YX52 异步电机

部门

动力车间

主要设备

购买时间

1989 . 8 . 15

登记时间

1989 . 9 . 5

价格

55000 . 00

产地

湖南

制造商

宏星电机厂

设备状况

堪用

维修情况

1995 . 4 更换主轴

备注

这些设备信息将为数据库的设计提供重要的依据。

登记员:林国伟(005)

此外,还应该设计好系统的整体框架:功能模块的划分、数据和操作流程等。在本实例中 将要实现的功能主要包括以下四个部分: ① 可通过本系统完成企业各个部门的设备登记,包括数据的录入、修改和查询。 ② 可打印设备清单及汇总报表。 ③ 系统用户可分为普通用户和管理员,后者可以使用系统管理功能。 ④ 必要的系统维护功能。 系统的总体框架如图 2 . 3 所示。


电脑工程师丛书

36

Visual FoxPro 高级应用实例

图 2.3

设备管理系统结构框图

2 . 2 . 2 数据库设计 数据分析也就是建立数据原型,需要在原始数据的基础上进行归纳和组织,而且往往要兼 顾到系统的数据需求和功能需求。 对于一个关系型数据库,首先要保证数据之间的相对独立性。从实例的要求中我们可以 发现,这个设备管理系统中应该包含两部分数据:设备数据和用户数据,前者是该系统的主要 信息载体,而后者是为了实现多用户的权限管理。 表 2 . 1 直观地体现了设备数据中应该包含的内容,但是从维护数据一致性的角度来考虑, 还需要对数据进行一定的组织。所谓数据一致性,是指对相同含义的数据也应该有相同的表 现形式,而原始数据中往往并非如此。例如,部门的名称可能会有全称和简称之分, “动力车 间”可能和“三车间”是同一部门,再比如对于一个设备状况的描述, “良好”和“正常”也许是一 个含义。这种数据描述的非一致性会对数据处理造成一定的困难,如要统计所有工作正常的 设备,到底什么样的设备状况才算是正常的呢?为了避免这种数据的非一致性,需要对数据的 表现形式作出一定的规范,采用词典是一种简便可行的方案。在本例中,设立一个专门的表用 于存放字典,并设立“部门”和“设备状况”字段,把所有的部门描述都放在“部门”字段中,把所 有设备描述都放在“设备状况”字段中,如良好、堪用、待修、报废等。用户只能使用字典中所指 定的内容描述一个字段,从而保持数据的一致性。 对于用户信息,应该包含用户名和密码,以及用户的权限标识。此外,从系统的安全性考 虑,密码不能以明文存放在数据库中,而应进行加密。 数据分析结束后,还需要用数据库的物理模型来实现它,把数据分析中的二维关系结构转 换成数据库中的表和字段,把每一个实体(如一条设备信息或一个用户)转换成数据库中相应 的记录,同时把关系结构中的联系转换成表与表之间的关联。为了确保数据的一致性,还需要 合理设置参照完整性。为了实现以上功能,大多数情况下是采用数据库表而不是自由表。对 于数据模型的设计在最后一章中将有更深入的论述。 本例中的数据模型并不复杂,在项目管理器中添加数据库命名为 equipment . dbc,并在数据 库设计器中设计如表 2 . 2 ~ 表 2 . 4 所示的三个数据库表。


数据库应用系统的快速开发

表 2.2 字段

设备表 registry. dbf

字段名

类型

宽度

1

设备编号

字符型

12

2

设备名称

字符型

20

3

部门

字符型

10

4

主要设备

逻辑型

1

5

购买时间

日期型

8

6

登记时间

日期型

8

7

价格

货币型

8

8

产地

字符型

10

9

制造商

字符型

30

10

设备状况

字符型

10

11

维修情况

备注型

4

12

备注

备注型

4

13

登记员编号

字符型

10

14

登记员姓名

字符型

10

表 2.3 字段

索引

普通索引

普通索引

用户表 user. dbf

字段名

类型

宽度

索引

字符型

10

普通索引

10

1

用户名

2

密码

字符型

3

管理权限

逻辑型

1

4

用户描述

字符型

50

表 2.4 字段

37

字典表 dict. dbf 类型

宽度

索引

1

部门

字段名

字符型

10

普通索引

2

设备状况

字符型

10

普通索引

2 . 2 . 3 数据维护模块设计 1 . 数据环境 对数据的操作少不了窗口界面(即表单),而数据环境就是泛指定义表单时使用的数据源, 包括数据表、视图和关系等。数据环境及其中的表与视图都是对象,数据环境一旦建立后,打 开或运行表单时,其中的表或视图也自动打开,而关闭或释放表单时,表或视图也随之关闭。 数据环境设计器用来可视化地创建或修改数据环境,它包含以下几个功能: ① 添加数据表或视图到数据环境中。


电脑工程师丛书

38

Visual FoxPro 高级应用实例

② 移去数据环境选中的表或视图。 ③ 浏览选中的表或视图。 ④ 体现表或视图之间的关系。 2 . 数据绑定 数据的增、删、改涵盖了通过表单操纵数据库数据的基本内容,表单和数据库之间通常利 用数据绑定联系起来。所谓数据绑定,是指将控件与某个数据源联系在一起,数据源通常可以 是字段或内存变量。通过设置控件的“ControlSource”属性即可实现该控件与某个数据源的绑 定,控件的值与数据库中的数据将自动保持一致而不需要用 REPLACE 命令进行替换。 输入类控件,包括文本框、编辑框、列表框和组合框,都可以和数据源建立数据绑定,它们 的“ControlSource”属性和“Value”属性是修改或访问数据源的关键。对于数据库中的不同类型 数据,应该选择不同的输入类控件。 (1)文本框控件 文本框控件通常用于绑定简 单数据类型的字段,如字符型、数 值型、逻 辑 型 或 日 期 型。获 取 或 修改控件的值可以通过 Value 属 性, 该属性的数据类型 随 绑 定 的 字段类型不同而不同,因 此 在 必 要的时候需要利用内部函数进行 数据类型转换。 在表 单 上 添 加 文 本 框 控 件 后,除了可以在表单设计器的“属 性”窗口中设置属性外,还可以利 用生成器来设置控件 的 属 性,但

图 2.4

文本框生成器“格式”选项卡

是生成器只能设置常 用 属 性,不 能包括所有的属性,如图 2 . 4 ~ 图 2 . 6 所示。

图 2.5

文本框生成器“样式”选项卡


数据库应用系统的快速开发

图 2.6

39

文本框生成器“值”选项卡

图 2 . 4 所示的“格式”选项卡主要用来设置文本框的各种格式选项及输入掩码, “数据类 型”组合框中有数值型、字符型、日期型和逻辑型等选项,分别能使文本框的 Value 属性为 0、 (无)、 {}和 . F. 。如果数据类型为数值型,还能选择“是否显示前导零”复选框,如果选中该复 选框并与数值型字段绑定后,在运行期间会显示前导零直至补足字段宽度。图 2 . 5 所示的“样 式”选项卡用于设置文本框控件的边框、外观及文本对齐方式。“值”选项卡包含一个“字段名” 组合框,用于指定表或视图中的字段,等同于设置 ControlSource 属性进行数据绑定。 (2)编辑框控件 编辑框控件主要用于显示或编辑多行文本数据,因此适合于绑定较长的字符型字段或备 注字段,其生成器与文本框大致相同,只是无法指定数据类型,因为该控件的 Value 属性只能 为字符型。 (3)列表框控件 列表框的数据源可以是表或视图中的字段,也可以是数组或者手工输入的数据。数据源 不同,列表框生成器的“列表项”选项卡外观也不同,如图 2 . 7 ~ 图 2 . 9 所示。另外,和文本框生 成器相比多了一个“布局”选项卡。

图 2.7

列表框生成器


电脑工程师丛书

40

Visual FoxPro 高级应用实例

图 2.8

列表框生成器

图 2.9

列表框生成器

在“列表项”选项卡中,如果 列表框的数据类型选择为表或视 图中的字段,则直接把 字 段 中 的 内容 填 充 到 列 表 框 中,如 图 2 . 7 所示。如 果 选 定 多 个 字 段,则 列 表框的每一选项将按这些字段的 次序显示字段值,默认 情 况 下 从 第一列的选定项中返 回 数 据,即 该控件的 Value 属性。如果列表 框的数据类型为手工 输 入 数 据, 则允许在设计时键入数据并填充 到列表框中,并可以指 定 列 表 框

图 2 . 10

列表框生成器

的列数,如图 2 . 8 所示。如果列表框的数据类型为数组中的值,则可以把数组内容或其中的一 部分填充列表框(见图 2 . 9),数组通常要在运行时利用代码建立。 在“样式”选项卡中,可以指定列表框的样式、显示的行数以及是否允许递增搜索等设置, 如图 2 . 10 所示。需要说明的是,在有些情况下指定的行数与实际的行数不符,这是因为 Visual


数据库应用系统的快速开发

41

FoxPro 按照 7 号字的大小来计算行数,如果实际字体偏大或偏小都将导致实际行数的不准确。 “布局”选项卡用于直观地控制列表框的宽度和显示,如图 2 . 11 所示。双击列标题可以隐 藏该列,表单执行时该列不显示,但其中的数据仍然有效。

图 2 . 11

列表框生成器

在“值”选项卡中除了可以指定数据源之外,还可以指定从哪一列中返回值,即列表框的 BoundColumn 属性,如图 2 . 12 所示。

图 2 . 12

列表框生成器

除了上面提到的三种数据类型之外,列表框还可以填充其他一些数据类型,具体的类型由 RowSourceType 属性指定,共包含九类,如表 2 . 5 所示。 表 2.5 取值

类型

列表框的 RowSourceType 属性及含义 说明

0

缺省值,需要在程序中动态添加列和列表项

1

用逗号分割的数据项填充 RowSource

2

别名

RowSource 为表的别名,ColumnCount 决定字段数

3

SQL 语句

RowSource 为 SQL Select 语句的执行结果

4

查询

RowSource 为数据库的一个查询文件


电脑工程师丛书

42

Visual FoxPro 高级应用实例

(续表) 取值

类型

说明

5

数组

RowSource 为数组名

6

字段

RowSource 为逗号分割的字段列表

7

文件

RowSource 为文件路径,以目录及文件名填充列表

8

结构

RowSource 为表名,以字段名称填充列表框

9

弹出式菜单

为兼容性而设

(4)组合框控件 组合框分为下拉式组合框以及下拉式列表框,前者既可以通过下拉式列表选择数据,也可 以直接键入输入,后者则只能通过下拉式列表选择数据。组合框控件的用法与列表框控件基 本相同。 在表单设计器中,还可以直接把数据环境中显示的字段拖动到表单上,这个操作将会在表 单上添加一个标签控件和输入类控件,标签控件显示的文本即该字段的名称,而输入类控件的 类型取决于该字段的数据类型,并且自动与该字段建立数据绑定。 3 . 记录导航 通常情况下,可以在表单中针对数据表中的不同字段添加相应的数据绑定控件,表单在运 行时即可实现对数据库的访问和操作了。但是数据库中往往具有多条记录,而该表单每次只 能显示或修改其中的一条记录,为此,表单上还需要有控制表记录指针移动的导航功能。 一个导航条通常包含四种记录指针定位功能:定位到第一条记录、定位到上一条记录、定 位到下一条记录和定位到最后一条记录,这些导航功能利用 Visual FoxPro 的 GO 和 SKIP 命令 即可实现。命令如下: GO TOP SKIP - 1 SKIP 1

&& 定位到第一条记录 && 向表头方向移动一条记录 && 向表尾方向移动一条记录 && 定位到最后一条记录

GO BOTTOM 需要注意的是,在执行这些命令的之前需要判断当前表记录指针的位置,以防记录范围越 界。例如,当前的记录指针已经在表尾(EOF()= . T.),执行 SKIP 1 命令将会出错。 4 . 表格控件 如果表的数据和结构比较简单,可以利用表格控件直接显示和操作数据表,与列表框不同 的是,表格控件中的列表项是可以直接被用户修改的。 表格控件由以下四个部分组成: ① 表格(Grid):一个或多个列。 ② 列(Column):可显示一个字段,包含列标题和列控件。 ③ 列标题(Header):默认显示字段名。 ④ 列控件。 表格控件每列必须设置一个列控件,该列的单元格可以用此控件来显示字段值。列控件 默认为文本框控件,但允许修改为与本列字段数据的类型相容的控件。


数据库应用系统的快速开发

43

从数据环境中把数据表或视图拖动到表单上即可建立一个类似于 Browse 窗口的表格控 件,该控件中包含了数据表或视图中包含的所有字段。也可以利用表格控件生成器来创建一 个数据绑定的表格控件。表格控件生成器中最重要的是“布局”选项卡,如图 2 . 13 所示。

图 2 . 13

表格生成器“布局”选项卡

在“布局”选项卡中可以指定列标题及列控件,对于不同字段的不同数据类型有以下几种 列控件可供选择: ① 文本框:可用于所有数据类型的字段。 ② 编辑框:只能用于字符型和备注型字段。 ③ 微调框:可用于数值型、整数型、浮点型和双精度型字段。 ④ 复选框:只能用于逻辑型字段。 ⑤ OLE 绑定型控件:只能用于通用型字段。 在本章的实例中将以字典维护模块演示表格控件的具体使用方法。

2 . 2 . 4 报表设计 Visual FoxPro 同样也提供了可视化的报表设计工具,在报表中可以插入文本和图片,按照 一定的规律显示数据库中的数据,设置字体格式等。报表设计主要包含两部分:数据源和布 局。数据源通常是数据库中的表,但也可以是视图、查询或临时表。视图和查询对数据库中的 数据进行筛选、排序、分组,而报表布局定义了报表的打印格式。对于某些数据库应用系统(如 一个企业的设备管理系统),部门领导所能看到的也许仅仅是最终的设备报表,因此报表的设 计是至关重要的。 创建报表主要有三种方法: ① 用报表向导创建简单的报表,它自动提供报表设计器的定制功能。 ② 直接利用报表设计器创建报表。 ③ 用快速报表命令为一个数据表创建简单报表。 这里着重介绍第二种方法。 1 . 报表设计器 用向导或快速报表命令生成的报表通常无法满足特定的报表要求,因此需要在报表设计


电脑工程师丛书

44

Visual FoxPro 高级应用实例

器中修改生成的报表文件。打开一个已经存在的报表文件或者新建报表都会自动显示报表设 计器。 (1)报表带区 默认情况下,报表设计器中将包含三个基本带区,实际上,Visual FoxPro 允许九种类型的报 表带区,每种带区的建立方法以及作用如表 2 . 6 所示。 表 2.6 带区名称 标题 页标头

报表带区的建立及作用

带区的建立 “报表”菜单 - >“标题 / 总结”子菜单 默认存在

打印周期

打印位置

整表一次

最先,可占一页

每页一次

标题后,页首

列表头

“文件”菜单 - >“页面设置”子菜单

每列一次

页标头后

组标头

“报表”菜单 - >“数据分组”子菜单

每组一次

每组之前

每记录一次

页标头或组标头之后

细节

默认存在

组注脚

“报表”菜单 - >“数据分组”子菜单

每组一次

细节后

列注脚

“文件”菜单 - >“页面设置”子菜单

每列一次

组注脚后

每页一次

页末

整表一次

页注脚后,可占一页

页注脚 总结

默认存在 “报表”菜单 - >“标题 / 总结”子菜单

每个带区之间有一个分割条,用鼠标拖动分割条可以修改报表带区的高度,双击分割条还 可以设置带区高度的具体数值。 (2)报表控件 报表控件工具栏提供了六种报表控件,它们的作用如下: ① 标签控件:用于显示说明性文字,不能被用户修改。 ② 域控件:用于显示表字段、内存变量或其他表达式的内容。 ③ 线条控件:用于在表单上绘制各种线条样式。 ④ 矩形控件:用于在表单上绘制矩形。 ⑤ 圆角矩形控件:用于在表单上绘制椭圆和圆角矩形。 ⑥ 图片 /ActiveX 绑定控件:用于在表单上显示图片或通用数据字段的内容。 2 . 设计报表 报表的带区可以看成是一块画布,而报表控件则是绘图的笔,有了“画布”和“笔”就可以在 上面绘制报表了。通常情况下,报表中包含表格线、文字说明和数据库中的数据。表格线可以 用线条、矩形或圆角矩形控件绘制而成,文字说明大多采用标签控件,而域控件则是显示数据 的关键。 在报表设计器中同样存在数据环境设计器,在数据环境中可以加载表、视图或查询等数据 源,域控件可以和数据源中的字段或者内存变量建立数据绑定。根据数据显示要求的不同,报 表控件添加的带区位置也不同,如果需要显示数据源中的每条记录数据,通常相应的报表控件 放在细节带区中;如果希望对数据进行分组,则把相应的报表控件放在组带区中;如果报表控 件较多,可以使用布局设计器进行控制,用法与表单设计器中的布局设计器完全相同。报表控 件还可以设置其显示格式,包括字体及文本对齐方式等。


数据库应用系统的快速开发

45

3 . 预览和打印报表 Visual FoxPro 在菜单和工具栏中都提供了报表的预览和打印功能,前者可以使用户在屏幕 上观察报表的设计效果,并且与打印结果是完全一致的。也可以通过命令方式预览或打印报 表,基本命令格式如下: REPORT FORM报表文件名[记录范围子句 ][FOR条件 ] [RANGE开始页[, 结束页 ]] [PREVIEW[[IN]WINDOW]| IN SCREEN] 使用 PREVIEW 子句只预览报表而非送到打印机中打印,制作报表时通常需要在设计和预 览这两个步骤上多次反复,直到效果完全符合要求为止。 总的来说,Visual FoxPro 的报表设计器功能是非常强大的,但是它采用结构化的数据存储 方式(报表文件 frx 文件实际上就是一个具有特定表结构的 dbf 文件),而且所有的布局必须在 设计期设计完成,因此如果需要在程序中动态设计报表就显得不太方便了。在最后一章中将 会介绍一些关于动态报表设计的高级技巧。

2 . 2 . 5 用户权限管理 通常一个数据库应用系统会要求对用 户的身份进行验证,只有被授权的用户才 能使用该系统,并且不同用户有不同的权 限。实现这个功能的途径有很多种,下面 仅介绍本例中采用的方案,其基本流程如 图 2 . 14 所示。 把授权用户的用户名、密码及权限级 别保存在一个单独的表中,并在程序运行 之初提供一个登陆表单,提示用户输入用 户名和密码,对比输入数据和数据库中保 存的信息即可判断该用户的合法性。对于 授权用户还可以从数据库中得到该用户的 权限级别,并根据该级别启用相应的功能 菜单项,用户通过执行菜单命令即可运行

图 2 . 14

用户权限管理流程

系统提供的相应功能。 Visual FoxPro 的数据库本身不具有加密功能,虽然可以利用一些手段对存储的信息进行加 密,但不能防止用户在程序外修改数据库中的内容,因此从总体上来说其安全保密性不是太 好,本方案只能应用在对安全性要求不是很高的场合。

2.3

实例制作 在实际的界面及功能模块的编写过程中,一般遵循从主要到次要、从全局到细节的自顶向


电脑工程师丛书

46

Visual FoxPro 高级应用实例

下设计原则,逐步地实现应用程序的功能并加以完善。在本例中,数据维护及报表是两个最重 要的功能模块,在此基础上再实现必要的系统管理功能,最后再把这些功能添加到主菜单中。 本实例将要制作的文件包括: ① 项目文件:ex2 . pjx。 ② 数据库文件 equipment . dbc 及表 registry. dbf、user . dbf 和 dict . dbf。 ③ 表单文件:login . scx、datamanage . scx、usermanage . scx、dictmanage . scx 和 changepasswd . scx。 ④ 主菜单:mainmenu . mnx 并生成菜单程序 mainmenu . mpr。 ⑤ 导航条可视类库:wizbtns . vcx。 ⑥ 报表文件:listreport . frx 和 bmgroup . frx。 ⑦ 程序文件:main . mpr(项目主文件)和 cleanup . prg。 这些文件位于本书配套光盘的 h ex2 目录下。

2 . 3 . 1 数据维护界面设计 上文已经提到了数据维护表单的基本制作方法,包括数据环境的设置、添加表单控件并与 数据源绑定、提供记录导航等,可以手工完成以上步骤,但较为繁琐,更为简便快捷的办法是使 用表单向导自动完成以上步骤,然后在向导生成的表单基础上进行必要的修改。这样既可以 减少工作量,又能避免书写大量的程序代码,因为向导中已经把必要的功能封装在 Visual FoxPro 的向导基类中了。 1 . 利用表单向导快速生成数据维护表单 在项目管理器中新建一个表单,并选择表单向导,该向导将会一步一步地指导用户以交互 式的方式快速生成一个满足基本功能的表单。根据将要处理的数据表性质,该向导又可分为 “表单向导”和“一对多表单向导”,前者适用于单表表单,后者适用于具有一对多关系的两个表 的表单,在本例中选择前者。 (1)步骤一:字段选取 首先需要选择在表单 中进行处理的数据表,单击 图 2 . 15 中“数据库和表”下 拉列表框,可以在其中选择 自由表或数据库,默认情况 下同一项目中的数据库会 自动地添加在该列表框中。 选中其中的设备表 registry. dbf,并 把 其 中 包 含 的 字 段 通过单击“ > ”和“ > > ”按 钮添加到“选定字段”列表 中,也 可 以 单 击“ < ”和 “ < < ”按钮把字段从“选定 字段”列表中删除。本例通

图 2 . 15

表单向导的字段选取


数据库应用系统的快速开发

47

过单击“ > > ”按钮把设备表中的所有字段均添加到“选定字段”列表中。 (2)步骤二:选择表单样式 表单样式主要决定了表单的外观,同时可以确定表单上的导航条采用何种类型的按钮。 选择样式后会在左上角出现一个预览。图 2 . 16 中选择的样式为“浮雕式”,按钮类型为“图片 按钮”。

图 2 . 16

表单向导的表单样式选取

(3)步骤三:排序次序 图 2 . 17 所示的对话框用于选择字段或索引标识来为记录排序。若按字段排序,则最多可 以选择三个字段,排在上面的字段优先;若以索引标识排序则只能选择一个。此处把字段列表 框中的“购买时间”和“登记时间”添加到“选定字段”列表中,排序方式为“升序”。

图 2 . 17

表单向导的排序次序设置

(4)步骤四:完成 在“请键入表单标题”文本框中输入“设备数据维护”,如图 2 - 18 所示。可以单击“预览” 按钮观察所设计的表单,如果需要修改前面的设计可以单击“上一步”按钮重新设计,完成后单


电脑工程师丛书

48

Visual FoxPro 高级应用实例

击“完成”按钮并保存为 datamanage . scx。

图 2 . 18

表单向导的完成设置

在表单设计器中打开这个由向导生成的表单,它的外观如图 2 . 19 所示。此时可以试着运 行一下该表单,在表单的最下方有一排图形按钮,前四个用于移动表记录指针,通常称之为导 航按钮组;后面有“搜索”和“打印”按钮,可以用于搜索记录或打印报表;此外还有“添加”、 “编 辑”和“删除”按钮,用于对记录的增、删、改操作。需要注意的是,要修改一条记录必须要先单 击“编辑”按钮以激活表单的编辑状态,否则表单上的输入控件都是只读的。添加或修改记录 后,可以单击“保存”或“取消”按钮。工具条的最后还有“退出”按钮,用于关闭表单。至此,我 们没有编写一行程序就基本实现了实例的第一个要求。

图 2 . 19

由表单向导生成的表单

2 . 数据维护界面的改进 表单向导虽然可以非常方便地设计一个表单,但是也存在一些不足之处,主要表现在向导 的设计过程中可定制性不强,为此很多人放弃使用向导。实际上,完全可以在向导生成的表单


数据库应用系统的快速开发

49

基础上再进行修改,从而兼顾灵活性和开发效率。 (1)导航条的改进 向导生成的导航条从功能上说是非常强大的,不过在外观上只能选择图片或文字两种形 式,因此可以对此进行一定的改进,让它同时显示图片和文字。查看导航条的属性可以发现, 它其实是一个叫作 wizbtns 的可视类库,位于 Visual FoxPro 的安装目录的 wizards 子目录下。在 对该可视类库进行修改之前,如果不希望所进行的修改覆盖系统的默认设置,最好把它备份到 自己的工作目录下再进行修改。 把 wizbtns . vcx、wizbtns . vct 文件以及 Wizbmps 目录下所有的 bmp 文件和 msk 文件复制到项 目文件所在的目录下,并把 wizbtns 添加到项目的“类库”中。该类库中包含很多的可视类,其 中 txtbtns 就是我们想要修改的。在类设计器中打开它,如图 2 . 20 所示。

图 2 . 20

图片导航按钮组

通过设置按钮的 Picture 属性为每个按钮添加相应的图片,并修改按钮的大小,使之外观 如图 2 . 21 所示。

图 2 . 21

修改后的图片导航按钮组

同时还需要注意一点, “添加”按钮和“编辑”按钮在单击后会显示为“保存”和“还原”,再次 单击后回复原状态。这需要修改该类的 setcaption 方法,该方法用户在切换了 EditMode 之后刷 新每个按钮的文字和图片显示。更改代码如下: # DEFINE ADD - CAPTION - LOC " h < 添加" " h < 编辑" # DEFINE EDIT - CAPTION - LOC # DEFINE REV - CAPTION - LOC " h < 还原" # DEFINE SAVE - CAPTION - LOC " h < 保存" IF THIS. EditMode THIS. cmdAdd . Caption = SAVE - CAPTION - LOC THIS. cmdEdit . Caption = REV - CAPTION - LOC THIS. cmdAdd . Picture = "wzsave . bmp" THIS. cmdEdit . Picture = "wzundo. bmp" THIS. cmdAdd . DownPicture = "wzsave . bmp" THIS. cmdEdit . DownPicture = "wzundo. bmp" ELSE THIS. cmdAdd . Caption = ADD - CAPTION - LOC THIS. cmdEdit . Caption = EDIT - CAPTION - LOC THIS. cmdAdd . Picture = "wznew. bmp" THIS. cmdEdit . Picture = "wzedit . bmp" THIS. cmdAdd . DownPicture = "wznew. bmp" THIS. cmdEdit . DownPicture = "wzedit . bmp" ENDIF


电脑工程师丛书

50

Visual FoxPro 高级应用实例

保存以上所作的修改,并且删除表单中原始的导航按钮组并添加自定义的导航按钮组,添 加方法和实例 1 中添加工具栏类的方法相同,至此就完成了对导航工具栏的定制。由向导生 成的表单主要是由很多这样的类库组成的,同样可以按照这个方法进行修改。 (2)利用字典表(dict . dbf)维护数据一致性 在数据分析中曾经提到数据一致性的问题,但是目前还没有在设备数据维护表单中体现 它,因为用户现在还是可以自由地输入部门和设备状况信息,下一步要用字典表中存储的数据 来规范用户的输入,基本的思路就是让用户通过下拉列表框而不是文本框来输入信息。 在开始设计之前,先在字典表中添加一些必要的记录(见表 2 . 7),以便于设计时观察效 果。其中的具体内容还可以在后续的系统管理模块中进行修改。 表 2.7

字典表中的初始记录

记录号

部门

设备状况

1

财务处

良好

2

职工处

堪用

3

厂办

待修

4

宣传处

报废

5

人事处

6

后勤部

7

一车间

8

二车间

9

三车间

打开数据环境设计器,在其中添加 dict . dbf,然后把表单上的“部门 1”和“设备状况 1”两个 文本框删除并重新添加两个组合框,修改其主要属性,如表 2 . 8 所示。 表 2.8 控件名

表单新增控件属性

控件类别

属性 ControlSource

部门 1

Combo

RowSourceType RowSource style

Combo

dict. 部门 2 - 下拉列表框

RowSource

registry. 设备状况 6 - 字段 dict. 设备状况

style

2 - 下拉列表框

ControlSource 设备状态 1

值 registry. 部门 6 - 字段

RowSourceType

运行表单即可发现,部门和设备状况两个下拉列表框中输入的内容只能是字典表中所规 定的内容了。但是因为 Combo 控件的 ReadOnly 属性只能设置为 . F. ,而向导所生成的表单是 通过 ReadOnly 属性来控制用户只能在编辑状态(EditMode)下改变控件中的值,这样就导致了 这两个控件不受这个条件的约束,改进的办法是修改 txtbtns 类的 buttonrefresh 方法代码。该方 法用于在切换 EditMode 之后控制表单控件的 ReadOnly 属性,需要在其中添加代码来控制两个 下拉列表框的 Enabled 属性,代码如下:


数据库应用系统的快速开发

51

IF SELECT()# THIS. nWorkArea SELECT(THIS. nWorkArea) ENDIF THIS. SetAllProp() THIS. cmdFind . Enabled = !THIS. EditMode THIS. cmdPrint . Enabled = !THIS. EditMode THIS. cmdExit . Enabled = !THIS. EditMode THIS. cmdDelete . Enabled = !THIS. EditMode AND !ISREADONLY() THIS. Parent . 部门 1 . Enabled = THIS. EditMode THIS. Parent . 设备状况 1 . Enabled = THIS. EditMode THIS. SetCaption() 这样,自行添加的下拉列表框也能和别的表单输入控件同步工作了。 最后还有一个小问题:由于字典中部门和设备状况的记录数不一定完全相同,如本例中部 门字段比设备状况字段多五条记录,把它们直接和 RowSource 绑定会带来一些多余的空行,为 此,可以用一个临时表存储非空且不重复的记录,然后把该表与 Combo 控件的 RowSource 控件 绑定。在表单设计器中清空“部门 1”和“设备状况 1”控件的 RowSource 属性,并在表单的 Init 事件中添加如下代码: SELECT dict SELECT DIST 部门 FROM dict WHERE !ISBLANK(部门)INTO CURSOR cu 部门 THIS. 部门 1 . ROWSOURCE = ' cu 部门' SELECT DIST 设备状况 FROM dict WHERE !ISBLANK(设备状况); INTO CURSOR cu 设备状况 THIS. 设备状况 1 . ROWSOURCE = ' cu 设备状况' THIS. REFRESH SELECT registry 至此,数据维护表单就制作完成了,在这个过程,主要以表单向导自动生成的表单为基础, 并按照某些特定的要求进行了定制,所编写的代码只有数十行,功能却是非常强大的,包括数 据的增、删、改、记录导航和查询定位功能。由此不难看出 Visual FoxPro 作为一个数据库快速 开发工具所具备的优越性。

2 . 3 . 2 报表设计 1 . 设备清单报表 设备清单以 registry. dbf 作为数据源,在项目管理器中新建一个报表,在数据环境中添加 registry. dbf,然后按照表 2 . 1 的格式在三个基本带区上绘制表格线,并添加标签和域控件,如图 2 . 22 所示。 在绘制表格线和添加报表控件时,请注意使用布局工具栏的一些布局工具,并可以用“格 式”菜单中的一些相关菜单项设置表格线、标签或域控件的格式。对于某些类型的字段,可能


电脑工程师丛书

52

Visual FoxPro 高级应用实例

图 2 . 22

设备清单报表设计

还需要进行一定的类型转换或处理。例如,价格字段为货币型,可以用 STR(价格,8,2)函数把 它转换成固定宽度的字符串。另外,主要设备字段为逻辑型,直接打印的话显示的是“. T.”或 “. F.”,显得不够直观,可以用 IIF(主要设备,"是","否")把它转换成通俗易懂的形式。设计完 毕后可以进行预览,效果如图 2 . 23 所示(仅截取了细节带区的一部分及页标题和页注脚)。如 果有不满意的地方可返回报表设计器进行修改,设计完毕后保存为 listreport . FRT。

图 2 . 23

设备清单报表预览

2 . 部门汇总报表 如果需要打印分类表、汇总表等报表,可以利用 Visual FoxPro 报表向导或数据分组功能进 行设计,也可以在查询或视图中先对数据进行分类汇总,再以该查询或视图作为报表的数据源


数据库应用系统的快速开发

53

进行设计。相对来说,后一种方法灵活易行,因此本例采用后者。 要实现对部门设备进行分类汇总,计算每个部门的设备数和资产总数,并列出其中的主要 设备和非主要设备的数量,可以把以上信息放置在一个本地视图中。在项目管理器中新建一 个本地视图,在视图设计器中添加数据表 registry. dbf,在“字段”选项卡中添加相应的字段,并 设置相应的排序和分组条件,如表 2 . 9 所示。 表 2.9 选项卡

部门汇总视图设计

字段或表达式

说明

registry. 部门

部门字段

SUM(IIF(主要设备, 1, 0))as 主要设备数

汇总主要设备数

SUM(IIF(主要设备, 0, 1))as 非主要设备数

汇总非主要设备数

SUM(1)as 总设备数

汇总部门设备数

SUM(registry. 价格)as 总资产

汇总部门总资产

排序

registry. 部门

按照部门字段排序

分组

registry. 部门

按照部门字段分组

字段

运行该视图后的结果如图 2 . 24 所示。

图 2 . 24

部门汇总视图

保存该视图为 bmgroup,并在报表设计器中新建一个报表文件,把该视图作为数据源并添 加相应的报表控件,如图 2 . 25 所示。其中的域控件表达式如表 2 . 10 所示。

图 2 . 25

部门汇总报表设计器


电脑工程师丛书

54

Visual FoxPro 高级应用实例

表 2 . 10

部门汇总报表域控件表达式

域控件

表达式

计算

时间

date()

部门

bmgroup. 部门

主要设备数

bmgroup. 主要设备数

非主要设备数

bmgroup. 非主要设备数

部门设备数

bmgroup. 总设备数

部门资产

str(bmgroup. 总资产, 8, 2)

总设备数

bmgroup. 总设备数

求和

设备总资产

bmgroup. 总资产

求和

保存该报表为 bmgroup . frx,预览效果如图 2 . 26 所示。

图 2 . 26

部门汇总报表预览

2 . 3 . 3 系统管理功能设计 用户管理和字典管理也属于对数据库表的维护,可以同样利用表单向导快速地生成它的 维护表单,但是由于这两个表的结构较为简单,可以直接采用前面文中介绍的方法来实现它 们。 1 . 用户管理 在实现用户管理功能模块之前,首先需要实现必要的密码加密。以明文的形式把密码保 存在数据库中是非常不安全的,因为用户可以在程序外直接打开数据表。 实现加密最主要的思路是不可逆,也就是无法从密文中得到明文。有很多加密算法可以 实现这一点。例如,常用的 MD5(Message - Digest Algorithm 5)信息摘要算法,它可以对字符串 进行加密生成一个惟一的 128 位二进制数。该算法加密强度较大,可以应用在对安全性要求


数据库应用系统的快速开发

55

较高的场合,在下一章中对此算法的实现有详细的介绍。在本例中,采用 Visual FoxPro 提供的 字符串校验函数 SYS(2007)来进行简单的加密,该函数的作用是计算一个字符串的校验和,如 对字符串"abc"进行校验和计算:SYS(2007,"abc"),将返回 20810,从该数值中无法得到原始的字 符串"abc"。因此,把用户的密码进行校验和计算后存放在数据库中,在用户登陆时对其密码也 同样进行校验和计算,如果与数据库中保存的数值相同即为合法用户,否则为非法用户,从而 实现了简单的加密功能。 用户管理模块应该包含浏览用户信息、添加用户、修改用户和删除用户几个子模块。下面 分别介绍其实现过程。 (1)用户浏览界面 在该界面中,使用一个 List 控件显示用户列表及相关信息,但是不显示用户密码,事实上 密码已经被加密,即使显示密码也是毫无意义的。 在项目管理器中新建一个表单并保存为 usermanage . scx,修改其外观(见图 2 . 27),其中白 色空白区域为 List 控件。在数据环境中添加 user . dbf 文件,并设定 List 控件的 RowSource 属性 为“user . 用户名,管理权限,用户描述”。 List 控件的字段标题是通过 Shape(或者 Line)和 Label 控件组合而成的。

图 2 . 27

用户浏览界面

(2)添加用户界面 添加用户需要输入一些用户的基本信息,包括用户名、密码、用户权限以及用户描述等,并 把这些信息进行一定处理后保存到数据库中。一般来说,在密码的输入过程中,并不明文地显 示在屏幕上,而以“”代替,这需要设置输入框的 PasswordChar 属性为“”。同时,为了防止 在输入密码时因看不到明文而导致出错,应该要求用户输入两次密码,两次的输入相同才算有 效。 通过“表单”菜单在刚才新 建 的 usermanage 表 单 中 创 建 表 单 集,并 添 加 新 表 单,命 名 为 Form2。在表单上添加相应的控件,如图 2 . 28 所示。 编写“确定”按钮的 Click 事件代码如下: IF THISFORM. Text2 . TEXT # THISFORM. Text3 . TEXT MESSAGEBOX("两次输入的密码不一致!") ELSE INSERT INTO USER(用户名,密码,管理权限,用户描述);  密码用 SYS(2007)加密


电脑工程师丛书

56

Visual FoxPro 高级应用实例

图 2 . 28

添加用户界面

VALUES(THISFORM. Text1 . TEXT,SYS(2007,; ALLTRIM(THISFORM. Text2 . TEXT)),; THISFORM. Check1 . VALUE,THISFORM. Text4 . TEXT) THISFORMSET. form1 . List1 . REFRESH THISFORM. HIDE ENDIF (3)修改用户界面 在用户浏览界面中,用鼠标在 List 控件中选中不同的用户记录会自动移动表记录指针,因 此在修改该用户时,只需要简单地把当前用户表中的数据显示在修改界面上供管理员修改即 可。修改完成后再把数据更新到该记录中。用户密码同样也不显示在修改界面上,如果需要 修改用户密码,必须输入两次新密码,否则留空该输入框。在表单集中添加新表单 Form3 并修 改其外观,如图 2 . 29 所示。

图 2 . 29

该表单的 Show 事件代码如下: LPARAMETERS nStyle This . Text1 . Value = 用户名 This . Text4 . Value = 用户描述 This . check1 . Value = 管理权限 “确定”按钮的 Click 事件代码如下:

修改用户表单


数据库应用系统的快速开发

57

IF THISFORM. Text2 . TEXT # THISFORM. Text3 . TEXT MESSAGEBOX("两次输入的密码不一致!") ELSE IF ALLTRIM(THISFORM. Text2 . TEXT) = "" REPLACE 用户名 WITH THISFORM. Text1 . TEXT,; 管理权限 WITH THISFORM. Check1 . VALUE,; 用户描述 WITH THISFORM. Text4 . TEXT THISFORMSET. form1 . List1 . REFRESH THISFORM. HIDE ELSE REPLACE 用户名 WITH THISFORM. Text1 . TEXT,; 密码 WITH SYS(2007,ALLTRIM(THISFORM. Text2 . TEXT)); 管理权限 WITH THISFORM. Check1 . VALUE,; 用户描述 WITH THISFORM. Text4 . TEXT THISFORMSET. form1 . List1 . REFRESH THISFORM. HIDE ENDIF ENDIF (4)删除用户界面 删除用户只需要简单地进行一次确认即可,因此添加 Form1 的“删除”按钮的 Click 事件代 码如下: IF MESSAGEBOX("确认删除该用户吗?", 1 + 48 + 256,"确认删除")= 1 DELETE THISFORM. list1 . REFRESH ENDIF 删除用户只是在该记录打上删除标记而不是物理删除,为了不显示被删除的记录,需要在 表单集的 Init 事件中添加 SET DELETE ON 命令。 2 . 字典管理 在项目管理 器 中 新 建 一 个 表 单 并 保 存 为 dictmanage . scx,在 数 据 环 境 中 添 加 数 据 库 表 dict . dbf。字典管理中的数据较为简单,可以直接用表格控件进行维护。在表单上用表格生成 器添加一个表格控件 Grid1,在该控件的“表格项”选项卡中指定需要添加到表格中的数据源及 其字段,本例中需要添加 dict 数据库表中的“部门”和“设备状况”字段;在“样式”选项卡中可以 指定表格的外观,有四种风格可供选择,本例选择浮雕型; “布局”选项卡可以为表格的每列指 定一个标题及编辑控件类型,如对字符类型字段,可以选择文本框或编辑框,对逻辑型字段则 可以选择文本框或复选框。 利用生成器完成主要的属性设置后,还可以在“属性”窗口中进一步修改其他的属性。例 如,如果希望两个列的背景色有所区别,可以在该控件上单击鼠标右键,在快捷菜单中选择“编 辑”以激活其容器编辑状态,单击标题或所在的列,在“属性”窗口中即可设置其 BackColor 属


电脑工程师丛书

58

Visual FoxPro 高级应用实例

性。以上操作完成后,该表单上的表格控件外观如图 2 . 30 所示。

图 2 . 30

字典维护界面

在表单上新增两个按钮“追加”和“删除”,其中“追加”按钮的 Click 事件代码如下: APPEND BLANK THISFORM. grid1 . REFRESH THISFORM. grid1 . SETFOCUS “删除”按钮的 Click 事件代码如下: DELETE THISFORM. grid1 . RECORDSOURCE = "dict" THISFORM. grid1 . REFRESH THISFORM. grid1 . SETFOCUS 3 . 数据清理 到目前为止,所有的数据库表均在表单中以共享的方式打开,对数据的删除操作仅限于逻 辑删除而非物理删除,这会占用一些不必要的系统资源。同时,若打开一个表而不打开其相应 的索引文件,并更改表中与索引相关的关键字段内容,则索引文件就会过时。当系统被破坏或 者从非 Visual FoxPro 程序中访问和更新表索引文件时,都可能导致索引文件无效。因此,在必 要的时候需要进行一些数据清理工作,主要内容包括压缩逻辑删除记录和重建索引。完成这 些操作需要以独占方式打开数据库表。关于数据的共享和独占访问方式在第 4 章中将有进一 步的探讨。 在项目管理器新建一个程序文件保存为 cleanup . prg,编写如下代码: IF MESSAGEBOX("确实要清理数据并重建索引吗", 1 + 48 + 256,"数据清理")= 1 USE registry EXCLUSIVE PACK REINDEX USE dict EXCLUSIVE PACK REINDEX


数据库应用系统的快速开发

59

USE user EXCLUSIVE PACK REINDEX USE MESSAGEBOX("数据清理完毕!", 0 + 48 + 256,"成功") ENDIF

2 . 3 . 4 多用户权限控制 一般来说,用户需要通过程序主菜单来调用以上功能模块,如果需要禁止用户使用某些功 能,可以把对应的菜单项设置为不可用的菜单项。在主程序中设置全局变量用于标识用户名 及其权限,在用户的登陆界面中进行初始化,并根据该变量设置菜单项的“跳过”表达式,即可 实现多用户的权限管理。 1 . 登陆界面设计 在项目管理器中新建一个表单并保存 为 login . scx,添加必要的控件,如图 2 . 31 所 示。 其中 的 Text2 也 同 样 需 要 设 置 PasswordChar 属性。为“登陆”按钮的 Click 事件 编写如下代码:

图 2 . 31

用户登陆界面

SET EXACT ON SET ORDER TO TAG 用户名 SEEK THISFORM. Text1 . VALUE DO CASE CASE !FOUND() MESSAGEBOX("没有该用户", 0 + 48,"错误") CASE SYS(2007,ALLTRIM(THISFORM. Text2 . VALUE)) # 密码 MESSAGEBOX("密码错误", 0 + 48,"错误") CASE !管理权限 manage = 1 && 普通用户 username = THISFORM. Text1 . VALUE RELEASE THISFORM CASE 管理权限 manage = 2 && 管理员 username = THISFORM. Text1 . VALUE RELEASE THISFORM ENDCASE


电脑工程师丛书

60

Visual FoxPro 高级应用实例

2 . 修改密码界面设计 对于普通用户来说,还应该允许他们修改自己的密码,不过从安全角度考虑,在修改密码 之前应该要求输入原密码。在项目管理器中新建表单并保存为 changepasswd . scx,修改其界面 如图 2 . 32 所示。

图 2 . 32

修改密码界面

为“确定”按钮的 Click 事件编写如下代码: SET ORDER TO TAG 用户名 SEEK username IF SYS(2007,ALLTRIM(THISFORM. Text1 . TEXT)) # 密码 MESSAGEBOX("原密码不正确!") RETURN ENDIF IF THISFORM. Text2 . TEXT # THISFORM. Text3 . TEXT MESSAGEBOX("两次输入的密码不一致!") RETURN ENDIF REPLACE 密码 WITH SYS(2007,ALLTRIM(THISFORM. Text2 . TEXT)) MESSAGEBOX("密码修改成功!") RELEASE THISFORM 3 . 主菜单设计 以上所有的功能模块完成后,最后通过编写菜单程序以允许用户在应用程序中调用它们。 在项目管理器中新建下拉式菜单 mainmenu . mnx,在菜单设计器中按照表 2 . 11 所示的内容设计 菜单,并生成 mainmenu . mpr 文件。


数据库应用系统的快速开发

表 2 . 11 菜单栏

用户( h < U)

h退出 清除( h < A)

编辑( h < E)

日常操作( h < O) 报表打印( h < R)

系统维护( h < S)

主菜单设计

菜单项 登陆( h < L) 修改密码

h复制( h < C) 粘贴( h < P)

61

命令 / 菜单项

跳过

do form login do form changepasswd

manage < 1

clear events - med - clear -

med - copy

-

med - paste

-

med - cut

剪切( h < T) 数据维护( h < D)

do form registry

manage < 1

设备清单( h < L)

report form registry preview

manage < 1

h部门汇总( h < S) 数据清理( h < C)

report form bmgroup preview

manage < 1

do cleanup

manage < 2

h用户管理( h < U) 字典维护( h < D)

do form usermanage

manage < 2

do form dictmanage

manage < 2

其中的“跳过”条件表达式保证了只有合法的用户才能使用相应的功能,是实现多用户权 限管理的关键环节。 4 . 主程序设计 主程序是进行应用程序初始化及定义全局变量的地方,在项目管理器中新建一个代码程 序并保存为 main . prg,其代码如下: PUSH MENU - MSYSMENU SET TALK OFF SET DEFAULT TO SYS(5)+ SYS(2003)+ " h " WITH - SCREEN . AUTOCENTER = . T. . WINDOWSTATE = 2 . CAPTION = "多用户设备管理系统" ENDWITH PUBLIC manage,username && manage: 0 - 未登陆、 1 - 普通用户、 2 - 管理员 manage = 0 DO mainmenu . mpr DO FORM login READ EVENTS POP MENU - MSYSMENU RELEASE ALL USE RETURN


电脑工程师丛书

62

Visual FoxPro 高级应用实例

为了确保程序第一次运行时能够正常登陆,首先需要运行用户管理表单,在系统中添加不 同权限等级的用户信息。在项目管理器中设置 main . mpr 为主文件,运行该文件并试着用不同 的用户登陆,观察相应的功能模块执行是否正确。

2.4

本章小结 本实例“麻雀虽小,五脏俱全”,包含了数据库的设计、表单制作、报表设计、权限管理及数

据维护等诸多内容。从中不难发现,熟练用好各种可视化设计工具是开发数据库应用系统事 半功倍的关键,也是 Visual FoxPro 程序员必须具备的基本功。 在实际的应用中,数据量往往更大,功能也更复杂。仍以企业设备管理系统为例,除了基 本的设备信息登记和数据维护之外,还包含设备的使用、保养、维修和故障管理等信息,同时分 类也更细,普通设备与特殊设备、固定资产设备与低值易耗品,都应该分门别类地进行管理。 但总的来说,设计的基本思路不会有太大变化,方法和步骤也大同小异,在本章介绍的内容基 础上,结合特定应用领域的要求和特点,读者完全可以编写出功能完善的数据库应用系统来。


调用外部库资源 ———功能强大的多媒体播放程序

3

本章技术要点:  动态链接库的调用。  参数传递及类型转换。  使用和定制 Visual FoxPro 专用外部库。  MCI 命令简介。

3.1

本例提要

如果在数据库应用系统的开发过程中需要实现 Visual FoxPro 不直接提供的功能,或者该 功能通过其他的开发工具可以更好、更方便地实现,则可以在 Visual FoxPro 中调用外部库:它 可能是现有的系统函数或资源,也可能是用第三方开发工具定制的功能模块。充分利用好外 部库可以快速获得功能的扩展,从而实现很多在 Visual FoxPro 中看似不可能实现的功能。 外部库又分为专用型和通用型,前者只能被 Visual FoxPro 调用,后者则对开发工具没有特 定的要求。本章首先介绍调用这些外部库的规则和语法,并针对 Visual FoxPro 专用外部库介 绍其基本的制作方法和步骤,最后利用媒体控制接口(MCI)和 Win32 API 函数来实现一个功能 强大的多媒体播放程序,该程序可以播放多种音、视频格式并具备各种控制功能。 本章实例运行效果如图 3 . 1 所示。

3.2

技术分析

Visual FoxPro 可以使用以下三种外部库资源: ① 动态链接库(. dll 文件):一个 dll 文件就是一个函数库,这些函数可以在 Visual FoxPro 中被调用,就像调用 Visual FoxPro 中的自定义函数一样。事实上,许多 Windows 程序(包括 Windows 本身)都是使用动态链接库来实现各种功能的。例如,通过链接一个系统的 dll 文件, 可以调用其中的系统函数,从而获得操作系统的一些底层功能。 ② Visual FoxPro 专用外部库(. fll 文件):fll 文件和 dll 文件很相似,但它使用特殊的协议和 Visual FoxPro 共享数据,同时经常会调用内部的 Visual FoxPro 函数。因此,fll 文件不像 dll 文件 那样可以被任意的 Windows 程序调用,而只能由 Visual FoxPro 调用。在 Visual FoxPro 中,可以 像调用其他用户自定义函数一样调用 fll 文件中的函数。 ③ ActiveX 控件(. ocx 文件):ActiveX 控件是包含能完成特定任务的对象。一般来讲,将一


电脑工程师丛书

64

Visual FoxPro 高级应用实例

图 3.1

实例运行效果

个 ActiveX 控件和对象合并到 Visual FoxPro 后,就可以像使用任何 Visual FoxPro 基类一样来使 用其中的对象。关于 ActiveX 控件的详细说明将在下一章中介绍。

3 . 2 . 1 动态链接库的调用 在调用一个 dll 函数之前,必须了解该函数的调用协议,包括函数的名称,参数的数目和 类型以及返回值类型。通常情况下,包含所要注册函数的 dll 文件必须存放在默认目录中,如 Windows、System、或 System32 目录。 1 . 函数的声明与释放 (1)声明函数(DECLARE - DLL) 如果需要调用的函数在某个 dll 文件中,需要首先声明该函数。在 Visual FoxPro 中声明函 数的语法是: DECLARE[cFunctionType]FunctionName IN LibraryName [AS AliasName] [cParamType1[@],cParamType2[@],. . . ] 说明: ① cFunctionType:指定该函数返回值的类型,其类型代码如下: SHORT 16 位整数 INTEGER 32 位整数 DOUBLE

32 位浮点数 64 位浮点数

STRING

字符串

SINGLE

② FunctionName:指定要注册使用的 32 位函数名,注意 FunctionName 区分大小写。 ③ LibraryName:指定该函数所在的外部动态链接库文件名,如果指定 WIN32API 为库名 称,Visual FoxPro 将在 Kernel32 . dll、Gdi32 . dll、User32 . dll、Mpr . dll 和 Advapi32 . dll 中查找被调用


调用外部库资源

65

的 32 位 Windows DLL 函数。 ④ AliasName:当 API 函数的名称与 Visual FoxPro 的保留字或已经存在的函数(本地函数或 者前面声明的 dll 函数)重名时,或者该函数名不符合 Visual FoxPro 的函数命名规则时,应当用 AliasName 另取别名,别名是不区分大小写的。 ⑤ cParamType1[@],cParamType2[@],. . . :指定传递给被调函数的参数类型,关于参数 传递的问题在下文中进行详细介绍。 例如,在 Visual FoxPro 的命令窗口中执行如下命令: DECLARE INTEGER GetActiveWindow IN win32api ?GetActiveWindow() 第一行命令中声明了 win32api 中的一个系统函数 GetActiveWindow(),该函数实际上包含 在 User32 . dll 文件中,返回值为整数且不需要参数。第二条命令调用该函数即可得到当前活 动窗口,即 Visual FoxPro 主窗口的句柄。 (2)释放函数声明 在退出 Visual FoxPro 之前,所链接的 DLL 函数一直保持有效,因此,在每个工作期中只需 声明一次。如果不再想调用 DLL 中的函数,可以执行 CLEAR DLLS 命令将其从内存中清除以 释放资源。 2 . 参数传递 参数传递是使用动态链接库的难点,原因是 dll 中的函数通常遵循 C/C + + 语言中的数据 类型规则,这和 Visual FoxPro 中的规定有所不同,因此在传递参数之前需要进行适当的类型转 换。 (1)传值参数与引用参数 在向一个动态链接库的函数传递参数时,可以使用传值参数和引用参数两种不同形式的 参数。有些读者对这两种参数形式缺乏足够的了解,实际上,通过一个简单的例子我们就可以 清楚地看到它们之间的区别所在。编写一个函数 func1,代码如下: parameters a a=a+1 return a 在命令窗口中执行如下命令: a=1 ? func1(a) ?a func1(a)的输出是 2,这是显而易见的,执行完该函数后变量 a 的值是多少呢?答案是 1, 可见变量 a 传递给函数 func1 后在函数体内部并不被修改,这样的参数称之为传值参数。 重新在命令窗口中执行如下命令: a=1 ? func1(@a) ?a 此时 func1(a)的输出不变,但是变量 a 在执行完该函数后变成了 2,可见它在函数体内部


电脑工程师丛书

66

Visual FoxPro 高级应用实例

被修改了,这样的参数称之为引用参数。 传值参数与引用参数的区别其实是由它们的工作机理不同造成的:对于传值参数而言,函 数在接受到该参数后把它复制了一份副本保存在自己的本地变量里,函数体内的所有运算或 操作均基于该副本,在函数运行完毕后自动释放副本所占用的内存,因此不会对传值参数本身 进行修改;引用参数则不同,向函数传递一个引用参数时实际上传递的是变量的内存地址,函 数在接受到该参数后直接在该地址上进行运算或操作,而不需要重新为它分配内存,因此变量 有可能在函数体内被修改。 简单地说,如果变量需要在函数体内部被修改,或者函数需要一个地址型的参数(如 C 语 言中的指针类型变量),那么需要在声明函数时指定使用引用参数,指定引用参数的方法是在 参数的类型声明后面加上一个“@”符号。在调用该函数时,一般也需要在相应的变量名前加 上“@”符号。 (2)参数类型转换 在声明一个函数的参数类型时,可以使用以下几种数据类型: 32 位整数。 ① INTEGER: ② SINGLE:32 位浮点数。 ③ DOUBLE:64 位浮点数。 ④ STRING:字符串。 如果要把以上四种类型之外的数据传递给动态链接库中的函数,必须进行适当的类型转 换。例如,对于日期型数据,可以采用如下类型函数进行转换: cDate = SYS(11,date()) nDate = VAL(cDate) 这样就把当前的日期转换成符合 Julian 格式的数值型数据。 如果 DLL 函数需要一些比较复杂的数据类型作为参数,则需要对该数据的内部结构有清 楚的认识,然后在向 DLL 函数传递之前或从 DLL 函数返回之后,转换为 Visual FoxPro 中相应的 数据类型。 例如,Windows 系统函数 GetSystemTime()需要一个指向结构的指针,该结构包含八个字或 16 位的无符号整数,分别代表年、月、日等,其结构定义如下: SYSTEMTIME{ WORD wYear;

typedef struct

-

WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; }SYSTEMTIME 为了在 Visual FoxPro 和 GetSystemTime()函数之间传递数据,首先必须创建一个 40 字节的 字符串缓冲区(初始时其内容为空格),然后把这个字符串作为引用参数传递给该函数,让函数 向这个字符串填写要返回的数据。返回之后,必须以两个字符为一单元进行分析,然后提取出


调用外部库资源

67

结构的各个部分。下面的语句描述了如何提取该结构的三个域:  声明函数,参数类型为字符型引用参数 DECLARE INTEGER GetSystemTime IN win32api STRING @ cBuff = SPACE(40)&& 初始化 = GetSystemTime(@cBuff)&& cBuff 中保存了当前的系统时间 tYear = ALLTRIM(STR(ASC(SUBSTR(cBuff, 2)) ; 256 + ASC(SUBSTR(cBuff, 1)))) tMonth = ALLTRIM(STR(ASC(SUBSTR(cBuff, 4)) ; 256 + ASC(SUBSTR(cBuff, 3)))) tDOW = ALLTRIM(STR(ASC(SUBSTR(cBuff, 6)) ; 256 + ASC(SUBSTR(cBuff, 5)))) 如果 DLL 函数需要的是一个数组型的参数,那么在声明该函数时必须指定为 STRING 类 型的参数,在传递该参数之前,遍历该数组并把它连接成一个 C 语言样式的字符串。例如,某 DLL 函数需要{a1,a2,a3,a4,a5}这样一个大小为 5 的一维数组,Visual FoxPro 程序中现有的数 组为 A(5),那么以下代码可以把该数组转换成 DLL 函数中可使用的 STRING 参数: B = "" FOR i = 1 TO 5 B = B + A(i) ENDFOR 如果 DLL 函数需要 16 位或 32 位的值,则在连接成字符串之前,还必须将该值转换为等价 的十六进制的形式,下面的函数代码即可实现该功能: PARAMETERS lnNum LOCAL i,ntmp,cRes cRes = "" FOR i = 3 TO 0 STEP - 1 ntmp = INT(lnNum / 256 i) lnNum = lnNum - ntmp  (256 i) cRes = CHR(ntmp) + cRes ENDFOR RETURN cRes 综合上述内容,可以总结出几条基本的规律:对于简单的数值型数据一般可以使用 INTEGER 类型参数;对于 LPCTSTR、LPTSTR 等字符型数据一般使用 STRING 类型参数;对于结构或 数组等复杂数据类型一般使用 STRING 类型参数并进行必要的结构转换。如果传递的参数不 是 DLL 函数所需要的数据类型,Visual FoxPro 将会产生一个错误。


电脑工程师丛书

68

Visual FoxPro 高级应用实例

3 . 2 . 2 访问 Visual FoxPro 库 1 . 调用 fll 函数 像 dll 一样,Visual FoxPro 库(. fll 文件)包含了可调用的函数。因为 fll 文件是专门为 Visual FoxPro 内部调用而建立的,因而更容易和 fll 文件进行数据传递。 如果要使用一个 Visual FoxPro 库,首先应指定 fll 文件的名称,然后调用此函数。和注册 dll 函数不同,不需要单独注册 fll 文件中的每个函数,也没有必要指定该函数所需参数和数据 类型。 注册 fll 库的命令格式是: SET LIBRARY TO fll 文件名[ADDITIVE] 如果所注册的 fll 文件不只一个,需要在 SET LIBRARY 命令中包含 ADDITIVE 关键字。否 则,前面注册的 fll 文件将被清除,被最近注册的文件所代替。一旦完成了 fll 库的注册后,就可 以像调用系统的内部函数一样调用库中的任意函数。如果函数名称和 Visual FoxPro 中已存在 的函数名称冲突,则最后定义的函数优先;如果 fll 库中的函数名称和 Visual FoxPro 中的内部 函数名称冲突,则内部函数优先。 在退出 Visual FoxPro 之前,fll 文件中的函数一直保持有效,因此在每个工作期中只需注册 一次。如果不想再使用 . fll 文件中的函数,可以使用 RELEASE LIBRARY、RELEASE ALL 或者 SET LIBRARY TO 命令将其从内存中清除,以释放资源。 2 . 使用 FOXTOOLS 库 FOXTOOLS 是 Visual FoxPro 自带的 API 函数库,通过 FOXTOOLS 库中的函数可以设置和查 询文件信息、管理路径和文件名、使用系统警告以及执行其他许多的函数,并且通过该库还能 获得对 FoxPro 2 . 6 版本以及 16 位平台的向后兼容性。 FOXTOOLS 通常被安装在 Visual FoxPro 的主目录下,因此要使用 FOXTOOLS 中的函数,首 先需要执行如下命令: SET LIBRARY TO HOME()+ " h FOXTOOLS. FLL" 在 Visual FoxPro 中只能直接调用 32 位的 DLL 库文件,对于 16 位的 DLL 库文件需要通过 FOXTOOLS 中的相关函数来注册和调用,具体的用法如下。 (1)RegFn()———注册一个 DLL 库文件 语法:RegFn(cFunctionName,cArgTypes,cReturnType,cDLLName) cFunctionName:指定要注册函数的名称或序号(后一种方法不被推荐)。 cArgTypes:说明函数接受的参数类型,允许下列值: I

整型(32 位)

L

长整型

C

字符串

F

浮点数

D

双精度浮点数

S

短整型(16 位)


调用外部库资源

69

每种参数类型都可以在前面加一个“@”符号,以表明该参数为引用参数。 cReturnType:用于指定函数的返回值类型,它的允许值与 cArgTypes 参数类型相同,只是不 能以引用传递方式返回。 cDLLName:指定包含 此 函 数 的 dll 库 名,如 果 函 数 库 包 含 在 Win16 平 台 下 的 User . exe、 Krnl386 . exe 和 Gdi . exe 中,则 DLLName 是可选的。 返回值:数值型,如果调用成功,则 RegFn()返回一个数,此数可用于将来调用 CallFn()时 引用此函数,如果库不能打开,则返回 - 1,另外还将显示一个信息框。 该函数也可以用于 32 位 DLL 库文件的注册,用法与此相仿。 (2)CallFn()———调用一个已注册的 DLL 库函数 语法:CallFn(nFunctionHandle,Arg1[,Arg2[,. . . ]] ) nFunctionHandle:前一次调用 RegFn()时的返回值,即函数句柄。 Arg1,Arg2,. . . . :引用的函数所需的参数,必须传递与注册函数时声明的数目相等的参 数,否则将发生错误,并且所有参数都必须与它们声明的类型相匹配,如下所示: F,D———必须是浮点数。 I,L———必须是整数。 C———必须是以传值参数传递的字符串,或 0(Null)。如果是 0,则传递一个空指针。

3 . 2 . 3 定制自己的函数库 如果现有的外部库资源无法满足应用程序中的功能要求,而该功能又不便直接用 Visual FoxPro 实现,则可以利用别的开发工具定制自己的函数库。可以选择开发通用的 DLL 库,也可 以选择开发 Visual FoxPro 专用的 FLL 库。前者实际上已经超出了本书的讨论范围,在介绍 Win32 编程的参考书中一般对此有详细的论述,本节仅介绍 FLL 库的开发。在介绍之前,我们 假设读者对 C/C + + 语言以及 Visual C + + 开发工具有必要的了解。 1 . FLL 库模板 实际上,FLL 库就是调用 Visual FoxPro API 函数的 DLL 库,每个 FLL 库有着相同的结构,包 括以下基本要素: ① # include 语句:除了函数库自身要用到的头文件之外,还应当包含 Visual FoxPro API 库 的头文件 Pro - exe . h,该文件位于 Visual FoxPro 安装目录的 API 子目录下。 ② 函数定义及实现:实现函数功能的程序代码,需要自行编写的主要部分,可以有多个自 定义函数。 ③ FoxInfo 结构:库函数必须通过 FoxInfo 结构与 Visual FoxPro 进行通信。 Visual FoxPro 可 以通过这个结构确定函数名、参数的个数和类型。一个标准的 FoxInfo 结构如下所示: FoxInfo arrayname[ ] = { {funcName1,FPFI function1,parmCount1,parmTypes1 }, {funcName2,FPFI function2,parmCount2,parmTypes2 }, ... {funcNameN,FPFI functionN,parmCountN,parmTypesN }, };


电脑工程师丛书

70

Visual FoxPro 高级应用实例

arrayname:一个数据类型为 FoxInfo 的数组变量,一般来说数组元素的个数取决于该 FLL 函数库中定义的函数个数,每一个函数对应数组中的一个 FoxInfo 结构行。 funcName:供 Visual FoxPro 用户调用时使用的函数名称(大写字母并且不得超过 10 个字 符),又称为外部函数名。 function:在这个 C/C + + 程序中的函数的名称(区分大小写),又称为内部函数名。 parmCount:指定参数的个数,或者下列标志值之一: INTERNAL:表示该函数为内部函数,不能直接从 Visual FoxPro 调用。 CALLONLOAD:表示在加载库时调用例程。 CALLONUNLOAD:表示该例程在卸载库时,或者发出 Visual FoxPro 的 QUIT 命令时被 调用。 parmTypes:描述每个参数的数据类型,下面列出了 parmTypes 的有效值及其含义: "" ———无参数。 "?" ———能传递任意类型,在函数体中,必须检查传递过来的参数类型。 "C" ———字符型参数。 "D" ———日期型参数。 "I" ———整型参数。 "L" ———逻辑型参数。 "N" ———数值型参数。 "R" ———引用。 "T" ———日期时间型参数。 "Y" ———货币型参数。 "O" ———对象类型参数。 应该为每个传递给库函数的参数指定一个类型值。例如,若创建的函数接受一个字符和 一个数值参数,那么可用“CN”作为它的 parmType。若要标明一个参数是可选的,可以在前面 加上一个句点“.”,第一个参数不能省略。 ④ FoxTable 结构:FoxTable 是一个链表结构,保持对给定库中所有 FoxInfo 结构的跟踪。它 在 C + + 中的标准语法如下: extern "C"{ FoxTable

FoxTable = { (FoxTable )0,sizeof(myFoxInfo)/sizeof(FoxInfo),myFoxInfo }; -

} 一般情况下,只需要把其中的 myFoxInfo 换成在 FoxInfo 中定义的数组名即可。 2 . 参数的传递 内部函数一般情况下需要在外部函数被调用时通过 FoxInfo 传递参数,一般内部函数的声 明采用如下方式: void function - name(ParamBlk  parm) parm 是一个类型为 ParamBlk 的数组,parm - > p[0]、parm - > p[1]. . . 分别代表第一个、 第二个……参数。 ParamBlk 的定义包含在 Pro - exh . h 头文件中,由于参数可以是传值参数或 者引用参数,它的类型定义中也包含两部分:


调用外部库资源

71

typedef union{ Value val; Locator loc; }Parameter; val 是传值参数内容,loc 是引用参数的内容,ParamBlk 就是这两者的联合,当用户传递的 是传值参数时,需要访问 val,即 Value 结构,否则访问 loc,即 Locator 结构,下面分别介绍这两种 结构。 (1)Value 结构 Value 结构在 Pro - exh . h 头文件中的定义如下: Typedef struct{ char

ev - type;

char

ev - padding; ev - width;

short long

ev - length; ev - long;

double

ev - real;

CCY

ev - currency; ev - handle;

unsigned

MHANDLE ULONG }Value;

ev - object;

参数的类型不同,Value 结构接受和传递的值也有所不同。表 3 . 1 列出了对于不同的数据 类型可以使用的 Value 的结构域及其含义。 表 3.1 数据类型

结构域 ev - type

字符型

ev - length ev - handle ev - type

数值型

整型

日期型 日期时间型

ev - width ev - length

取值及含义 “C” 字符串长度 指向字符串的内存句柄(MHANDLE) “N” 显示宽度 小数位

ev - real

双精度

ev - type

"I" 显示宽度

ev - width ev - long

长整型

ev - type

"D" 日期(双精度浮点公历算法表示)

ev - real ev - type ev - real ev - type

货币型

不同数据类型的 Value 结构域及含义

"T" 日期 + (秒 /86400 . 0)

ev - width

"Y" 显示宽度

ev - currency

货币值


电脑工程师丛书

72

Visual FoxPro 高级应用实例

(续表) 数据类型 逻辑型

结构域

取值及含义

ev - type

"L" 0或1 “M”

ev - length ev - type

备注型

通用型

对象型

ev - wdith ev - long

FCHAN 备注字段的长度

ev - real

备注字段的偏移量

ev - type

"G"

ev - wdith

FCHAN 通用字段的长度

ev - long ev - real

通用字段的偏移量

ev - type

"O" 对象识别符

ev - object

(2)Locator 结构 Locator 结构在 Pro - exh . h 头文件中的定义如下: typedef struct{ char l - type; short l - where, l - NTI, l - offset, l - subs, l - sub1,l - sub2; }Locator;

/  数据库编号,如果是内存变量则为 - 1  / /  变量名称表的偏移量 / /  在数据库中的索引 / /  指定的下标 0 < = x < = 2  / /  下标整型值  /

Locator 结构中的域的含义如表 3 . 2 所示。 表 3.2 Locator 域 l - type

Locator 域及其用途 域的用途

“R”

l - where

包含此域的表的编号,- 1 代表一个内存变量

l - NTI

名称表索引。Visual FoxPro 内部使用

l - offset

表中域的编号。Visual FoxPro 内部使用

l - subs

只用于内存变量,下标的数目(0 ~ 2)

l - sub1

只用于内存变量,若 l - subs 非 0,则代表第一个下标

l - sub2

只用于内存变量,若 l - subs 为 2,则代表第二个下标

3 . 从 FLL 库中返回值 如果需要从一个 FLL 函数中返回值,不要使用 C/C + + 语言的 return 语句,而应该使用 Pro - exh . h 头文件中所提供的 API 函数,如表 3 . 3 所示。


调用外部库资源

表 3.3 函

73

从 FLL 库中返回值的 API 函数

-

RetChar(char  string)

将函数的返回值设置为一个以 null 值结尾的字符串

-

RetCurrency(CCY cval,int width)

将函数的返回值设置为一个货币值

-

RetDateStr(char  string)

将函数的返回值设置为一个日期值

-

RetDateTimeStr(char  string)

将函数的返回值设置为一个日期时间值

-

RetFloat(double flt,int width,int dec) 将函数的返回值设置为一个浮点值

-

RetInt(long ival,int width)

-

RetLogical(int flag)

-

RetVal(Value  val)

将函数的返回值设置为一个整型值 将函数的返回值设置为一个逻辑值。零被认为是 FALSE, 任何非零值被认为是 TRUE 传递一个完整的 Value 结构。除了备注字段,任何 Visual FoxPro 数据类型都可以返回

若要返回一个对象数据类型的值,必须使用 - RetVal( )函数,该函数将填充在 Value 结构 的 ev - object 域中。 4 . 一个实现 MD5 算法的 FLL 库示例 在第 2 章中曾提到,对于密码的安全性要求较高的场合可以使用 MD5 算法对密码进行加 密。该算法的 C 语言代码是公开的,下面将利用 Visual C + + 把该算法制作成 FLL 库,以供在 Visual FoxPro 中调用其函数,以此说明 FLL 库制作的全过程。 (1)获得 MD5 算法的 C 语言源程序 关于 MD5 算法的介绍参见 RFC1321(http:/ /www. ietf. org/rfc /rfc1321 . txt),该 RFC 文档给 出了 MD5 算法的实现原理及步骤,并附有相应的 C 语言源程序:global . h、md5 . h 和 md5c . c 。 该源程序可以直接在 FLL 库中使用,惟一需要修改的地方是 global . h 文件中的“ # define PROTOTYPES 0”这一行,必须把 0 改为 1,否则在 Visual C + + 中编译无法通过,原因参见其注释。 在 md5c . c 中给出了三个关键的函数:MD5Init( )、MD5Update()和 MD5Final( ),实际上也 是实现 MD5 算法的三个主要步骤,具体的参数说明参见 md5 . h 以及示例中的源程序,本文对 此不展开探讨。 (2)在 Visual C + + 中制作 FLL 库 在 Visual C + + 中新建一个“Project”,项目的类型选择“Win32 Dynamic - Link Library”,即 32 位动态链接库,填好“Project name”以及“Location”之后单击“OK”按钮,动态库的类型选择为 “An empty DLL project”,单击“Finish”后即完成了项目的创建。 把 global . h、md5 . h 和 md5c . c 三个 C 语言源程序以及 Visual FoxPro 安装目录的 API 子目录 下的 PRO - EXT. H 和 WINAPIMS. LIB 文件都复制到项目的路径中,并把其中的 . c 文件和 . h 文件添加到项目中。 新建一个 C + + 源文件,命名为 md5 . cpp,编写其代码如下: / / VFP API 头文件 # include "pro - ext . h" # include < stdio. h >


电脑工程师丛书

74

Visual FoxPro 高级应用实例

/ / MD5 的 C 语言头文件 extern "C"{ # include "global . h" # include "md5 . h" } / / MD5String 函数接受的第一个参数 # define p0 parm - > p[0] / / 以上为 FLL 的 C + + 模板的第一部分 void MD5String(ParamBlk  parm) { if(p0 . val . ev - type = = ' C' ){ / / 只接受 C 型参数 MD5 - CTX context; / / digest 保存 MD5 的计算结果,共 128 位, 16 字节 unsigned char digest[16]; / / hexstr 保存十六进制表示的校验和字符串,根据 digest 的长度可知需要 32 字节 [2]; char hexstr[16] unsigned int i; / / 开始计算字符串的 MD5 哈希值 MD5Init(&context); MD5Update( &context, / / val 结构中只包含字符串的句柄,需转换成指针 (unsigned char )- HandToPtr(p0 . val . ev - handle), p0 . val . ev - length); / / 计算结果以二进制形式保存在 digest 中 MD5Final(digest,&context); / / 把计算结果以十六进制形式保存在 hexstr 中 for(i = 0;i < 16;i + + ){ ); sprintf(hexstr[i],"%02x",digest[i] } / / 返回十六进制表示的校验和字符串 ); - RetChar(hexstr[0] return; }else{ / / 如果传递的参数不是 C 型,则返回 Null / / 返回 Null 只能使用 - RetVal()函数 Value val; val . ev - type = ' 0' ; val . ev - length = ' C' ; -

RetVal(&val);


调用外部库资源

75

return; } } / / 以上为 FLL 的 C + + 模板的第二部分,即函数主体 FoxInfo MD5FoxInfo[] = { / / MD5 为外部函数名,即在 Visual FoxPro 中调用的函数名 {"MD5",(FPFI)MD5String,1,"C"}, }; / / 以上为 FLL 的 C + + 模板的第三部分,即填充 FoxInfo 结构 / / 并确定外部函数名、参数个数及类型 extern "C"{ FoxTable

-

FoxTable = {

(FoxTable )0, sizeof(MD5FoxInfo)/sizeof(FoxInfo),MD5FoxInfo }; } / / 以上为 FLL 的 C + + 模板的第四部分 在开始编译 FLL 文件之前,还需要对编译器进行一定的设置。单击“Project”菜单的“Settings”子菜单,在“C/C + + ”选项卡中使用如下“Project Options”: /nologo /Gr /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D " - WINDOWS" /Fp"Release / md5 . pch" /YX /Fo"Release /" /Fd"Release /" /FD /c 其中“/MD”是 pro - ext . h 文件的必需选项,否则编译无法通过。最后还要把 winapims . lib 文件加入到链接库中:在“Link”选项卡中设置“Output file name”为“Release /md5 . fll”,在“Object / “Project Options”设置为: library modules”文本框中加入 winapims . lib, kernel32 . lib user32 . lib gdi32 . lib winspool . lib comdlg32 . lib advapi32 . lib shell32 . lib ole32 . lib oleaut32 . lib uuid . lib odbc32 . lib odbccp32 . lib winapims . lib /nologo /subsystem:windows /dll /incremental:no /pdb:"Release /md5 . pdb" /machine:I386 /out:"Release /md5 . fll" /implib:"Release /md5 . lib" 最后,编译该 FLL 库,在该项目的 Release 子目录下将生成 md5 . fll 文件。 (3)在 Visual FoxPro 中调用 FLL 函数 FLL 库文件制作完毕后可验证其功能实现是否正确,在 Visual FoxPro 中执行如下命令: SET LIBRARY TO md5 . fll ?md5("abc") 得到的输出为“900150983cd24fb0d6963f7d28e17f72”,与 RFC1321 中给出的计算结果完全相 同。至此,一个实现 MD5 算法的 FLL 外部库就制作完成了。

3.2.4

MCI 简介

MCI 是 Media Control Interface 的缩写,即媒体控制接口,它提供了与硬件无关的对多媒体 设备的控制能力,通过它可以在应用程序中播放音频、视频、MIDI、WAV、CD 音轨等多种媒体格


电脑工程师丛书

76

Visual FoxPro 高级应用实例

式。实际上要实现这些功能有多种途径,但是直接调用 MCI 能够获得比较高的程序执行效 率,这对大数据量的媒体播放来说是至关重要的。而且 Win32 系统都支持 MCI 而不需要发布 额外的文件,可以有效地提高程序的兼容性。 MCI 包含在系统的 winMM. dll 中,本实例将通过 它来演示如何在 Visual FoxPro 中调用 DLL 库文件。 1 . 多媒体设备 此处所说的多媒体设备通常是指一个包含多媒体信息的文件,也可以是一张音乐 CD,或 者是连接在电脑上的录像机等。 MCI 中支持以下几种类型的多媒体设备: ① cdaudio :激光唱盘播放设备。 ② digitalvideo :动态数字视频图像设备,如一个 avi 文件。 ③ overlay :模拟视频图像叠加设备,如一个视频叠加卡。 ④ sequencer :MIDI 音序发生器,如一个 MID 文件。 ⑤ vcr :磁带录像机。 ⑥ videodisc :激光视盘机,如 VCD。 ⑦ waveaudio :数字化波形音频,如一个 WAV 文件。 ⑧ other :其他未给出标准定义的 MCI 设备。 2 . MCI 函数的注册 在 MCI 中封装了很多 API 函数,其中最重要的有 mciSendString 和 mciGetErrorString,前者用 于向一个 MCI 设备发送一个命令字符串,后者可以把一个错误号转换成相应的错误信息。它 们的函数原型如下: MCIERROR mciSendString( LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback ); BOOL mciGetErrorString( DWORD fdwError, LPTSTR lpszErrorText, UINT cchErrorText );

/ / 命令字符串 / / 返回信息字符串的指针,可以为空 / / 返回信息的长度 / / Callback 窗口句柄(命令中使用了 notify 标记)

/ / 错误号 / / 错误描述字符串指针 / / 错误描述字符串的长度

针对以上 API 函数原型,可以得知它们在 Visual FoxPro 中的注册代码为: DECLARE INTEGER mciSendString IN WinMM. DLL ; STRING,STRING @,INTEGER,INTEGER DECLARE INTEGER mciGetErrorString IN WINMM. DLL ; INTEGER,STRING @,INTEGER 3 . MCI 命令字符串 对多媒体设备的控制主要通过 MCI 命令字符串实现,如打开设备、播放、暂停、结束等操


调用外部库资源

77

作,都需要使用相应的命令及参数,然后通过 mciSendString 函数执行该命令。下面介绍本例中 将要用到的一些 MCI 命令。 (1)OPEN———打开多媒体设备 命令格式:OPEN lpszDevice lpszOpenFlags lpszFlags lpszDevice:多媒体设备名,可以是一个文件名,或者 SYSTEM. INI 中注册的设备名。 “shareable” lpszOpenFlags:打开设备的标识及参数,如“alias XXX”可以为该设备指定别名, 指定以共享方式打开, “style”指定打开窗口的样式, “parent”指定父窗体句柄。可以同时使用 以上多个标识或参数。 “NOTIFY”或“TEST”,用于指定执行该命令的方式。 lpszFlags:命令标识,可以是“WAIT”、 “WAIT”表示该命令执行完毕后再把控制权交还给应用程序,因为在某些情况下打开一个设备 需要较长的时间,立即返回控制权可能带来不稳定因素; “NOTIFY”表示在该命令执行完毕后 给出一个提示信息,一般用于程序调试阶段; “TEST”用于判断该命令能否正确执行,如果失败 则给出一个出错信息。别的 MCI 命令中的 lpszFlags 含义与此相同,故不再赘述。 举例: “OPEN filename alias FoxMedia style child parent hwnd WAIT” filename 是一个包含全路径的文件名,该命令将以子窗口的形式打开该文件并取别名为 FoxMedia,父窗口句柄为 hwnd,该命令执行完毕后交出程序控制权。 (2)STATUS———获取多媒体设备的状态信息 命令格式:STATUS lpszDeviceID lpszRequest lpszFlags lpszDeviceID:设备 ID,在该设备被打开时得到或者是被指定的别名,下同。 “length”—设备的长度; “position”—当前的播 lpszRequest:需要查询的信息标识,常用的有: 放位置; “ready”—设备是否准备就绪; “window handle”—视频文件的窗口句柄; “mode”—查询设 “paused”、 “playing”、 “stopped”、 “open”、 备当前 的 状 态,可 以 返 回 的 信 息 包 括“not ready”、 “parked”、 “recording”和“seeking”等状态标识。需要注意的是,有些查询标识只能针对特定的设 备。 (3)SET———多媒体设备的相关设置 命令格式:SET lpszDeviceID lpszSetting lpszFlags “audio/video on /off”—打开 / 关闭音频 / 视频; “door open / lpszSetting:设置选项,常用的有: “time format”—指定时间格式,可以是“frame”、 “hms”、 closed”—打开 / 关闭光驱设备的仓门; “milliseconds”或“track”。在默认情况下对不同的设备用“STATUS”查询其“length”得到的结果会 有不同的含义,如果需要在程序中获得多媒体设备的播放时间,通常先用“set time format milliseconds”指定其时间格式,然后再用“STATUS”查询其“length”得到的就是设备的播放时间了。 (4)WINDOW———控制视频播放窗口 命令格式:WINDOW lpszDeviceID lpszWindowFlags lpszFlags lpszWindowFlags:指定视频播放窗口外观或行为方式,如“stat show/hide”—显示 / 隐藏视频 播放窗口, “show maximized /minimized /normal”—最大化 / 最小化 / 正常显示播放窗口, “text caption”—指定播放窗口的标题。 (5)PLAY———播放多媒体设备 命令格式:PLAY lpszDeviceID lpszPlayFlags lpszFlags “to position”—指定 lpszPlayFlags:指定播放选项,如“from position”—指定播放的起始位置, 播放的终止位置, “fullscreen”—全屏幕播放(该设备必须包含视频), “repeat”—循环播放。


电脑工程师丛书

78

Visual FoxPro 高级应用实例

(6)PAUSE———暂停播放多媒体设备 命令格式:PAUSE lpszDeviceID lpszFlags (7)STOP———停止播放多媒体设备 命令格式:STOP lpszDeviceID lpszStopFlags lpszFlags lpszStopFlags:对于数字视频文件,可以加上“hold”选项使之保留定格画面。 (8)SEEK———定位多媒体设备 命令格式:SEEK lpszDeviceID lpszSeekFlags lpszFlags “to start”、 “to end”,如果用“set time format” lpszSeekFlags:确定要定位的位置,如“to position”、 命令指定了时间格式后,position 参数也需要使用相应的格式。 (9)CLOSE———关闭多媒体设备 命令格式:CLOSE lpszDeviceID lpszFlags (10)SETAUDIO———音频设置 命令格式:SETAUDIO lpszDeviceID lpszAudio lpszFlags “volume to factor”— lpszAudio:音频设置选项,如“left /right on /off”—打开 / 关闭左 / 右声道, 指定音量(仅对数字视频设备有效,如果需要设置系统音量请使用与 mixer 有关的 API 函数,如 mixerSetControlDetails)。 MCI 的功能是极其强大的,因此它的命令及参数也非常复杂。以上仅介绍了一些最基本 的命令和参数,更详细的内容请查阅 MSDN 或其在线手册:http:/ /msdn . microsoft . com/library/en - us /multimed /mci - 04dv. asp。

3.3

实例制作 在本例中,将模仿 Windows Media Play 6 . 4 的外观设计一个多媒体播放机,在这个过程中将

使用到 Win32API 函数、MCI 控制命令和 FOXTOOLS. FLL 外部库。其中还需要自行设计一些表 单控件。 本实例将要制作的文件包括: ① 项目文件:ex3 . pjx。 ② 表单文件:mplayer . scx。 ③ 主菜单:mainmenu . mnx 并生成菜单程序 mainmenu . mpr。 ④ 可视类库:mylib . vcx。 ⑤ 主程序:main . mpr(项目主文件)。 ⑥ 图像文件:end . bmp、open . bmp、pause . bmp、play. bmp、slider . bmp、sound . bmp、stop . bmp、 top . bmp。 这些文件位于本书配套光盘的 h ex3 目录下。

3 . 3 . 1 控件设计 在表单设计之前,首先为应用程序设计两个自定义控件:控制和显示播放进度的滑轨 (Slider)控件,控制音量大小的 Mixer 控件。


调用外部库资源

79

1 . 滑轨控件 Windows Media Play 中的进度条滑轨在 Visual FoxPro 中没有现成的控件可供使用,此时可 以求助于第三方的 ActiveX 控件,这将是下一章将要讨论的内容。但对于像本实例中的滑轨控 件这样并不复杂的控件,完全可以通过一些基本的控件加上少量的代码设计出理想的效果,而 且往往使用更为灵活。 在项目管理器中新建一个可视类,类名为 Slider,选择它的基类为 Control,表示新建的是一 个控件,它只继承一些最基本的控件特性,然后把它保存在 mylib . vcx 中。 首先在类设计器中添加两个容器(Container)控件,选择使用容器控件的原因仅仅是为了 满足视觉效果:其中一个细长型为 Container1,并设置“SpecialEffect”属性为“1 - 凹下”,它的作 用仅仅是用来作为滑轨的轨道;另一个容器控件为小方块 Container2,并设置“SpecialEffect”属 性为“0 - 凸起”,之所以不选择普通按钮的原因是容器控件无法获得焦点,因此在拖动滑块 的过程中不会被加上黑边框。最后还需要添加一个定时器(Timer)控件,它将定时地根据播放 进度移动滑块。以上步骤完成后的类设计器如图 3 . 2 所示。

图 3.2

定制滑轨控件

下面将编写一些代码来允许用户利用鼠标拖动滑块,如果大家对实例一中的绘图代码理 解了的话,应该不难自行实现该功能。 为 slider 类添加两个私有属性:lx 和 mdown,前者用于保存滑块的临时横坐标,后者记录当 前鼠标的按下状态。为 Container2 的 MouseDown 事件添加如下代码: THIS. PARENT. MDOWN = . T. THIS. PARENT. lx = nXCoord 为 MouseMove 事件添加如下代码: LPARAMETERS nButton,nShift,nXCoord,nYCoord IF THIS. PARENT. MDOWN AND THIS. PARENT. ENABLED IF nXCoord > THIS. PARENT. lx && 向右移动滑块 IF THIS. LEFT < 320 && 尚未超出滑轨的最右端  把滑块横向移动到当前坐标 THIS. LEFT = THIS. LEFT + nXCoord - THIS. PARENT. lx IF THIS. LEFT > 320 THIS. LEFT = 320

&& 避免滑块超出滑轨

ENDIF ENDIF ELSE IF THIS. LEFT > 10 THIS. LEFT = THIS. LEFT + nXCoord - THIS. PARENT. lx IF THIS. LEFT < 10


电脑工程师丛书

80

Visual FoxPro 高级应用实例

THIS. LEFT = 10 ENDIF ENDIF ENDIF && 保存当前的横向坐标供下一此移动时比较

THIS. PARENT. lx = nXCoord ENDIF 为 MouseUp 事件添加如下代码:

LPARAMETERS nButton,nShift,nXCoord,nYCoord THIS. PARENT. MDOWN = . F. 保存 Slider 类,把它添加到表单中并运行表单,试着用鼠标来拖动它。如果读者有兴趣的 话,还可以自行完善这个控件,如允许用户在滑轨上单击来控制滑块的位置。 2 . 音量控制(Mixer)控件 Mixer 控件和 Slider 控件并没有本质上的区别,只是 在外观上它的滑轨呈三角形,以表示音量变化的大小趋 势。对于这样一个凹陷的三角形滑轨可以用三条直线 图 3 . 3 定制音量控制控件 (Line)控件来模拟,其中斜线的 BorderColor 属性设置为 灰色:RGB(128, 128, 128),另两条直线的 BorderColor 属性设置为白色:RGB(255,255,255),最终

在类设计中外观如图 3 . 3 所示。 其中滑块的代码与 Slider 控件相仿, 只需要修改一下左右边界的坐标而已, 请读者自行完成。

3 . 3 . 2 表单设计 新建表单并保存为 mplayer . scx,表单外观如图 3 . 4 所示。因为还要在表单上添加菜单,所 以在表单下方需要留出一定的区域。

图 3.4

播放器表单界面


调用外部库资源

81

其中表单控件的主要属性设置如表 3 . 4 所示。 表 3.4 控件名称

控件类型

播放器表单控件属性设置 属性 BorderStyle

Form1

Form

Caption MaxButton ShowWindow BackColor

值 2 - 固定对话框 实例三:多媒体播放器 . F. - 假 2 - 作为顶层表单 255, 255 255, 平面 2 -

Container1

Container

Slider1

Slider(自定义)

Enabled

. F. - 假

cmdOpen cmdPlay、cmdPause、

Command

Picture

open. bmp

cmdStop、cmdTop、

Command

Picture

play. bmp、pause. bmp、stop. bmp、top. bmp、end. bmp

SpecialEffect

Enabled

cmdEnd Mixer1

Mixer(自定义)

Enabled

Container2

Container

BackColor Caption

lblFile

Label

lblNow

Label

Caption

lblTime

Label

Caption

. F. - 假 . F. - 假 0, 0, 0 ( 无) " " “00: 00 /” “00: 00 /”

3 . 3 . 3 编写代码 1 . 表单代码 表单中首先需要完成外部库的注册, 并且把发送 MCI 命令的功能利用 mciSendString 函数编 写成表单的 domci 函数, 这样表单控件只需要调用该函数即可完成某一控制功能,在执行 MCI 命 令时可能会发生错误, 这就需要把返回的错误号用 mciGetErrorString 函数转换成相应的错误信息 描述, 并通过 showmcierror 反馈给用户,错误号及错误信息保存在自定义属性 mcierror 和 mcier为了统一调度各个按钮的 Enabled 属性, 还需要增加一个 disable 方法。 rorstring 中。此外, 在表单的 Init 事件中注册即将用到的外部库及其函数,代码如下: DECLARE INTEGER mciSendString IN WinMM. DLL ; STRING cMCIString,; STRING @cRetString,; INTEGER nRetLength,; INTEGER hInstance DECLARE INTEGER mciGetErrorString IN WINMM. DLL ; INTEGER nErrorno,; STRING @cBuffer,; INTEGER nBufSize  SetWindowPos 函数用于设置播放视频文件的窗口的位置。


电脑工程师丛书

82

Visual FoxPro 高级应用实例

 该位置由 Container1 控件的位置决定。 DECLARE INTEGER SetWindowPos IN User32 ; INTEGER,INTEGER,INTEGER,INTEGER,INTEGER,INTEGER,INTEGER  SetWindowPos 函数需要得到当前窗口的句柄 hWnd。  获得窗口句柄的函数包含在 FOXTOOLS. FLL 中。 SET LIBRARY TO "FOXTOOLS. FLL" EXTERNAL PROCEDURE

-

WhToHwnd

&& 获得 VFP 主窗口的 hWnd 句柄 && 把 WHandle 转换成 hWnd

EXTERNAL PROCEDURE

-

WOnTop

&& 获得当前用户窗口的 WHandle

EXTERNAL PROCEDURE MainHWND

为表单添加自定义方法函数 domci,该函数接收 MCI 命令字符串作为参数,执行该命令并 返回可能的出错信息,代码如下: LPARAMETERS cMCIcmd cRetString = SPACE(80)

&& 接受 MCI 命令字符串作为参数 && 保存返回信息

nRetValue = mciSendString(cMCIcmd,@cRetString,; LEN(cRetString), 0) cErr = THIS. getMCIerror(nRetValue) IF nRetValue > 0 RETURN cErr ENDIF RETURN TRIM(STRTRAN(cRetString,CHR(0),"")) 为表单添加自定义方法函数 getmcierror,把错误号转换成相应的错误信息,编写代码如下: LPARAMETERS cError && 接受错误号作为参数  以下把接受参数转换成数值型 LOCAL lcErrorString,nError nError = 0 IF TYPE("cError")= "C" IF LEFT(cError, 7)= " ERROR" nError = VAL(SUBSTR(cError, 8)) ENDIF ENDIF IF TYPE("cError")= "N" nError = cError ENDIF cErrorString = SPACE(256)&& 保存错误描述信息 = mciGetErrorString(nError,@cErrorString,LEN(cErrorString)) THISFORM. MCIerror = nError THISFORM. MCIerrorString = cErrorString RETURN TRIM(CHRTRAN(cErrorString,CHR(0),""))


调用外部库资源

83

为表单添加自定义方法 showmcierror,编写代码如下: MESSAGEBOX(THISFORM. MCIerrorString + "(" + ; STR(THISFORM. MCIerrorString) + ")") 为表单添加自定义方法 disable,编写代码如下: PARAMETERS lDisable IF lDisable = . T. THEN THISFORM. cmdPlay. ENABLED = . F. THISFORM. cmdPause . ENABLED = . F. THISFORM. cmdStop . ENABLED = . F. THISFORM. cmdTop . ENABLED = . F. THISFORM. cmdEnd . ENABLED = . F. THISFORM. Slider1 . ENABLED = . F. THISFORM. Mixer1 . ENABLED = . F. ELSE THISFORM. cmdPlay. ENABLED = . T. THISFORM. cmdPause . ENABLED = . T. THISFORM. cmdStop . ENABLED = . T. THISFORM. cmdTop . ENABLED = . T. THISFORM. cmdEnd . ENABLED = . T. THISFORM. Slider1 . ENABLED = . T. THISFORM. Mixer1 . ENABLED = . T. ENDIF 在表单的 Destroy 事件中关闭所有打开的 MCI 设备,代码如下: cCmd = ("STATUS FoxMedia READY") IF THISFORM. doMCI(cCmd) = "true" THEN cCmd = ("CLOSE FoxMedia WAIT") THISFORM. doMCI(cCmd) ENDIF RELEASE ALL CLEAR EVENTS 2 . cmdOpen 按钮代码 cmdOpen 按钮主要负责打开多媒体设备,并判断该设备是否合法。如果该设备包含视频 的话还需要把它作为子窗口包含到表单中。最后显示一些设备的基本信息,如播放时间等。 这个过程中将反复使用到各种 MCI 命令和 API 函数,代码如下:  调用“打开”文件对话框,供用户选择文件。 cFileName = GETFILE("视频文件( . avi, . dat, . mov, . mpg):; avi,dat,mov,mpg;音频文件( . wav, . mp3, . mid):; wav,mp3,mid","打开文件")


电脑工程师丛书

84

Visual FoxPro 高级应用实例

IF !EMPTY(cFileName)THEN  判断当前是否已经有打开的文件,如果有则首先关闭之。 cCmd = ("STATUS FoxMedia READY") IF THISFORM. doMCI(cCmd) = "true" THEN cCmd = ("CLOSE FoxMedia WAIT") THISFORM. doMCI(cCmd) THISFORM. DISABLE(. T.) ENDIF  下面将对该文件进行处理,这个过程可能会比较长,把光标变成等待形状。 -

SCREEN. MOUSEPOINTER = 11

 获得当前窗口的 hwnd 句柄。 Main - hWnd = MainHWND() cur - window = - WhToHwnd(- WOnTop()) NullPointer = 0  首先假设该设备包含视频,以子窗口形式打开。 cCmd =(' OPEN "' + cFileName + ' " alias FoxMedia' + ; ' style child parent ' + ALLTRIM(STR(cur - window)) + ' WAIT' ) THISFORM. doMCI(cCmd)  如果出错,表明该该设备不包含视频,重新打开。 IF THISFORM. MCIerror > 0 THEN cCmd = (' OPEN "' + cFileName + ' " alias FoxMedia WAIT' ) THISFORM. doMCI(cCmd)  再次出错,表明该设备不是合法的 MCI 设备,给出错误信息并退出。 IF THISFORM. MCIerror > 0 THEN MESSAGEBOX(THISFORM. MCIerrorString) -

SCREEN. MOUSEPOINTER = 0

RETURN ENDIF ELSE  获得视频播放窗口的句柄。 cCmd = "status FoxMedia window handle wait" hWin = INT(VAL(THISFORM. doMCI(cCmd))) x1Pos = THISFORM. Container1 . LEFT y1Pos = THISFORM. Container1 . TOP x2Pos = THISFORM. Container1 . WIDTH y2pos = THISFORM. Container1 . HEIGHT  调用 Win32API 函数将其定位在 Container1 容器位置内。 setWindowPos(hWin, 0,x1Pos,y1Pos,x2Pos,y2pos, 0)  显示视频播放窗口(第一帧)。


调用外部库资源

85

cCmd = ("WINDOW FoxMedia state show") THISFORM. doMCI(cCmd) ENDIF  获得多媒体设备的播放时间并显示。 THISFORM. doMCI("SET FoxMedia time format milliseconds") cCmd = ("STATUS FoxMedia length") THISFORM. nLength = INT(VAL(THISFORM. doMCI(cCmd)))&& 单位毫秒 nLength = THISFORM. nLength /1000 nHour = INT(nLength /3600) nMinute = INT((nLength%3600)/60) nSecond = INT(nLength%60) IF nHour > 0 THISFORM. Container2 . lblTime . CAPTION = ; STR(nHour, 2)+ ":" + STR(nMinute, 2)+ ":" + STR(nSecond, 2) ELSE THISFORM. Container2 . lblTime . CAPTION = ; STR(nMinute, 2)+ ":" + STR(nSecond, 2) ENDIF THISFORM. Container2 . lblFile . CAPTION = cFileName THISFORM. DISABLE(. F.)&& 播放控制按钮使能 -

SCREEN. MOUSEPOINTER = 0 && 恢复默认光标形状

ENDIF 3 . cmdPlay 按钮代码 cmdPlay 按钮利用 MCI 的 PLAY 命令播放一个被打开的多媒体设备,如果已经播放到结束 位置,则从头开始播放。代码如下: nMediaLength = VAL(THISFORM. doMCI("STATUS FoxMedia length")) nMediaPosition = VAL(THISFORM. doMCI("STATUS FoxMedia position"))  如果已经到达结束位置,则重新定位到文件的起始位置。 IF nMediaPosition > = nMediaLength THEN THISFORM. doMCI("SEEK FoxMedia to start WAIT") ENDIF  播放设备。 THISFORM. doMCI("PLAY FoxMedia") IF THISFORM. MCIerror > 0 THEN THISFORM. showMCIerror ENDIF


电脑工程师丛书

86

Visual FoxPro 高级应用实例

4 . cmdPause 按钮代码 首先利用 STATUS 命令判断当前的播放状态,如果是正在播放则使用 PAUSE 命令暂停播 放,否则继续播放。代码如下: IF THISFORM. doMCI("STATUS FoxMedia mode") = "playing" THEN THISFORM. doMCI("PAUSE FoxMedia") IF THISFORM. MCIerror > 0 THEN THISFORM. showMCIerror ENDIF ELSE THISFORM. cmdPlay. CLICK ENDIF 5 . cmdClose 按钮代码 用“STOP”命令关闭当前播放的多媒体设备,代码如下: cCmd = "CLOSE FoxMedia" THISFORM. doMCI(cCmd) IF THISFORM. MCIerror > 0 THEN THISFORM. showMCIerror ENDIF THISFORM. DISABLE(. T.) 6 . cmdTop 按钮和 cmdEnd 按钮代码 利用“SEEK”命令把当前设备的播放位置定位到起始或终止位置,代码如下:  cmdTop 按钮 Click 事件代码 IF THISFORM. doMCI("STATUS FoxMedia READY") = "true" THEN THISFORM. doMCI("SEEK FoxMedia to start") ENDIF  cmdEnd 按钮 Click 事件代码 IF THISFORM. doMCI("STATUS FoxMedia READY") = "true" THEN THISFORM. doMCI("SEEK FoxMedia to end") ENDIF 7 . Mixer 控件代码 在前面“控件设计”的介绍中,只是实现了 Mixer 控件的外观效果并允许用户拖动滑块,但 是尚未加入音量控制的代码,下面来实现它。重新在类设计器中(而不是在表单设计器中)打 开 Mixer 控件,在滑块的 MouseMove 事件的最后一个“ENDIF”之前添加如下代码: cCmd = "SETAUDIO FoxMedia VOLUME to " + STR((THIS. LEFT - 10)20, 3) THISFORM. doMCI(cCmd)


调用外部库资源

87

用 SETAUDIO 设置 VOLUME 的值可以从 0 到 1000,而滑块的横向坐标从 10 到 60,进行简 单的转换即可实现音量控制功能了。 8 . Slider 控件代码 同样的,Slider 控件的显示和控制播放进度的功能也需要进一步完善。显示播放进度的基 本方法是在定时器中根据当前的播放进度更改滑块的位置,控制播放进度则需要在滑块的 MounseDown 事件中暂时关闭定时器,然后在 MounseUp 事件中执行“SEEK”命令并恢复定时器 的正常工作。 (1)定时器 Timer 事件代码 IF THIS. PARENT. ENABLED = . T. cCmd = "STATUS FoxMedia position" nPos = VAL(THIS. PARENT. PARENT. domci(cCmd))&& 获得当前的播放进度 THIS. PARENT. Container2 . LEFT = INT((nPos /THISFORM. nlength)310)+ 10  修改显示时间 nPos = nPos /1000 nHour = INT(nPos /3600) nMinute = INT(nPos%3600 /60) nSecond = INT(nPos%60) IF nHour > 0 THISFORM. Container2 . lblNow. CAPTION = STR(nHour, 2)+ ; ":" + STR(nMinute, 2)+ ":" + STR(nSecond, 2)+ " /" ELSE THISFORM. Container2 . lblNow. CAPTION = STR(nMinute, 2)+ ; ":" + STR(nSecond, 2)+ " /" ENDIF ENDIF (2)滑块的 MouseDown 事件代码 LPARAMETERS nButton,nShift,nXCoord,nYCoord THIS. PARENT. MDOWN = . T. THIS. PARENT. lx = nXCoord THIS. PARENT. timer1 . INTERVAL = 0 && 关闭定时器 (3)滑块的 MouseUp 事件代码 THIS. PARENT. MDOWN = . F. IF THIS. PARENT. ENABLED cCmd = "SEEK FoxMedia TO " + ; STR((THIS. LEFT - 10)/310 THISFORM. nLength) THISFORM. domci(cCmd)&& 定位播放进度 THISFORM. cmdPlay. CLICK


电脑工程师丛书

88

Visual FoxPro 高级应用实例

nNow=(THIS. LEFT - 10)/310 THISFORM. nLength /1000 nHour = INT(nNow/3600) nMinute = INT(nNow%3600 /60) nSecond = INT(nNow%60) IF nHour > 0 THISFORM. Container2 . lblNow. CAPTION = STR(nHour, 2)+ ; ":" + STR(nMinute, 2)+ ":" + STR(nSecond, 2)+ " /" ELSE THISFORM. Container2 . lblNow. CAPTION = STR(nMinute, 2)+ ; ":" + STR(nSecond, 2)+ " /" ENDIF THIS. PARENT. timer1 . INTERVAL = 360 && 恢复定时器 ENDIF

3 . 3 . 4 制作菜单 新建下拉式菜单 mainmenu . mnx,在菜单设计器中设计其结构(见表 3 . 5)。 表 3.5 菜单栏

子菜单

文件

打开( h < O) ( h < F) 退出( h < Q) 播放( h < G) 暂停( h < P) 停止( h < S)

播放 ( h < P)

h开始( h < T) 结束( h < E)

帮助

主菜单设计

命令 -

选项

SCREEN. ActiveForm. cmdOpen. Click

(过程) -

SCREEN. ActiveForm. cmdPlay. Click

!- SCREEN. ActiveForm. cmdPlay. Enabled

-

SCREEN.ActiveForm. cmdPause. Click

!- SCREEN.ActiveForm. cmdPause. Enabled

-

SCREEN. ActiveForm. cmdStop. Click

!- SCREEN. ActiveForm. cmdStop. Enabled

-

SCREEN. ActiveForm. cmdTop . Click

!- SCREEN. ActiveForm. cmdTop . Enabled

-

SCREEN. ActiveForm. cmdEnd. Click

!- SCREEN. ActiveForm. cmdEnd. Enabled

MessageBox("多媒体播放器 V1 . 0",0 +

关于( h < A) ( h < H) 48 + 256, "关于")

其中“退出”菜单项的过程代码如下: -

VFP. ACTIVEFORM. RELEASE

RELEASE ALL CLEAR EVENTS 设置菜单在顶层表单中运行,保存菜单定义并生成菜单程序 mainmenu . mpr。在 mplayer 表 单的 Init 事件中加入如下命令: DO mainmenu . mpr WITH THIS,. T.


调用外部库资源

89

3 . 3 . 5 联编程序 最后,编写程序文件 main . prg 代码如下: SET DEFAULT TO SYS(5)+ SYS(2003)+ " h " DO FORM mplayer -

SCREEN. HIDE

READ EVENTS 在项目管理器中设置该文件为主文件,并联编应用程序,至此完成了本实例的全部制作过 程。

3.4

本章小结

本章主要介绍的是各种外部函数在 Visual FoxPro 中的使用方法,也许是全书最枯燥的一 章,但本章制作的实例却是本书最生动的一个实例,至少作者是一边用这个实例程序播放音乐 一边完成这章的内容的。正是那些枯燥乏味的函数使得 Visual FoxPro 开发的应用程序有机会 使用到系统和底层的一些功能,从而变得“无所不能”。


ActiveX 控件的使用(兼谈共享数据访问) ———时尚个人通讯录系统

4

本章技术要点:  ActiveX 技术概述。  ActiveX 控件使用。  TreeView 控件详解。  数据的共享访问及缓冲机制。

4.1

本例提要 上一章中曾提到,ActiveX 控件也是 Visual FoxPro 可用的外部资源一部分,但是它和其他的

外部库相比有很大的区别:它可以在设计时加入表单中,以所见即所得的方式进行可视化设 计;可以很好地融入表单中成为 GUI 的一部分……简单地说:你可以“看”着使用它。 但这并不意味着它在运行时一定是可见的,它也可以提供一些不需要可视界面的功能扩 展, 就像表单控件中的定时器那样。如果有人经常抱怨 Visual FoxPro 开发的应用程序界面千 篇一律,或者表单控件不如 Delphi 的 VCL 丰富,那么可以尝试用 ActiveX 控件来美化界面和扩 展功能。 在第 2 章的实例中采用了表单向导制作数据的增、删、改模块,因为当时强调的是快速开 发,因此,在制作过程中忽略了对数据共享访问及缓冲机制的讨论,表单向导提供的基类自动 完成了相关的处理。在本章的实例中为了最大限度地保证灵活性,将不再使用向导,而是完全 采用定制方式制作相应的表单。因此,数据共享访问及缓冲机制也是本章将要讨论的一个重 点内容。 本章实例的运行效果如图 4 . 1 所示。


ActiveX 控件的使用(兼谈共享数据访问)

图 4.1

4.2

91

时尚个人通讯录运行效果图

技术分析

ActiveX 技术是微软公司根据 COM(Component Object Model)模型的规范开发的,实现该技 术的初衷是为了能够让应用程序嵌入到浏览器中运行,从而使开发人员方便地发布基于局域 网或广域网的应用程序。作为微软 COM 战略的重要组成部分,ActiveX 技术的应用范围不仅 仅局限于 Internet,大量的前端(非浏览器)应用程序正在使用这项技术,形成了目前 ActiveX 技 术最主要的应用市场。

4.2.1

ActiveX 技术概述

ActiveX 技术的本质就是对 COM 对象的封装。在某些情况下使用 COM 对象并不是一件很 容易的事情,因此在对 COM 对象的简化封装的基础上,ActiveX 控件支持可视化开发工具所使 用的协议,从而使我们可以很容易地在可视化开发过程中使用它。 COM 技术的应用将延续到 后续的几个章节,在此先按下不表。 ActiveX 控件是 VBA 的后续产品,它通常包含在一个扩展名为 ocx 的动态库中,你也可以 认为 OCX(OLE Custom Control)组件就是 ActiveX 控件的集合。在 Visual FoxPro 的发行光盘中包 含了一些常用的 ActiveX 控件,可以在安装过程中把它们安装到系统目录中。表 4 . 1 列出了随 同 Visual FoxPro 6 . 0 发布的 ocx 文件及其包含的 ActiveX 控件。


电脑工程师丛书

92

Visual FoxPro 高级应用实例

表 4.1

随同 Visual FoxPro 6 . 0 发布的 ActiveX 控件

OCX 文件

ActiveX 控件

Comctl232 . ocx

Animation 控件、Datetimepicker 控件、Monthview 控件、Updown 控件

Foxhwnd. ocx

Visual FoxPro HWND 图像显示控件

Foxtlib. ocx

Visual FoxPro Foxtlib 类型库控件

MCI32 . ocx

Multimedia MCI 控件

MSChrt20 . ocx

MsChart 控件 ImageCombo 控件、ImageList 控件、ListView 控件、ProgressBar 控件、Slider 控

MSComctl. ocx

件、StatusBar 控件、TabStrip 控件、Toolbar 控件、TreeView 控件

MSComm32 . ocx

MSComm 控件

MSInet. ocx

Microsoft Internet Transfer 控件

MSMapi32 . ocx

MAPI Message 控件、MAPI Session 控件

MSMask32 . ocx

Masked Edit 控件

MSWinsck. ocx

Winsock 控件 CheckBox 控件、ComboBox 控件、Commandbutton 控件、Frame 控件、HscrollBar

MSWless. ocx

控件、VScrollBar 控件、ListBox 控件、OptionButton 控件、TextBox 控件(以上 控件均为轻量级)

Picclp32 . ocx

PicClip 控件

Richtx32 . ocx

Rich Textbox 控件

Sysinfo. ocx

SysInfo 控件

这些不是 ActiveX 控件的全部,还可以从其他的途径(如互联网上)获得 ActiveX 控件并在 程序中使用它,甚至可以用第三方开发工具(如 Visual Basic、Visual C + + 等)编写满足自己特 定要求的 ActiveX 控件。由于 ActiveX 控件与开发平台无关,因此,在一种编程语言上开发的 ActiveX 控件无需任何修改,即可在另一种编程语言中使用。由此可见,通过使用 ActiveX 控件 即可实现快速小型的组件重用和代码共享,从而提高编程效率。不过,Visual FoxPro 自身不支 持 ActiveX 控件开发。

4 . 2 . 2 使用 ActiveX 控件 正确使用 ActiveX 控件通常包括以下几个步骤:获得 ocx 文件的拷贝、在 Windows 中注册该 组件、把它添加到控件容器(如表单或表单集)中。 1 . 注册 ActiveX 控件 未注册的 ActiveX 控件是不能在程序中使用的,注册 ActiveX 控件通常有以下三种途径。 (1)使用 Regsvr32 . exe 程序对 ActiveX 控件进行注册 该执行文件位于 Windows 目录的 system 子目录下,在“运行”对话框中使用如下命令即可 实现控件的注册与注销: Regsvr32[/s][/u] < ocx 文件名 >


ActiveX 控件的使用(兼谈共享数据访问)

93

/s 指定使用安静模式,即不显示任何消息对话框,/u 用于注销 ActiveX 控件。 (2)使用安装程序制作软件 InstallShield 使用 Regsvr32 . exe 来注册 ActiveX 控件虽然简单,但需要用户手工注册,在不用时还得手 工解除注册,因此,这对一个应用程序来说并不是一个好的解决方案。大型应用软件一般都有 一个安装程序,在安装程序中解决 ActiveX 控件注册是较为理想的一种方案。使用 InstallShield 可以制作出专业级的 Setup 程序,还可注册其中的 ActiveX 控件;而且,在以后卸载软件时,还可 以自动注销掉以前注册的 ActiveX 控件。 在 InstallShield 中新建一个项目(Project),并在项目中新建一个文件组(File Group),把需要 注册的 ActiveX 控件文件加入到文件组中,并把文件组的 Self - Registered 属性设置为“Yes”,即 可实现在安装程序中自动注册 ActiveX 控件。关于 InstallShield 的使用方法参见本书的最后一 章。 (3)利用程序代码注册 对于小型的应用程序不宜采取第二种方法,如果不希望手工注册 ActiveX 控件,较好的方 法是在程序中嵌入注册代码,实现应用程序自注册。 Visual FoxPro 的 RUN 命令提供了执行外部命令或程序的功能,命令格式如下: RUN[/N]MS - DOS Command | ProgramName 其中 /N 参数表明不需要等待,通常用于连续执行多个外部命令。在程序中注册和注销 ActiveX 控件的代码为: RUN /N Regsvr32 /s OCX文件名 RUN /N /U Regsvr32 /s OCX文件名 要确保以上命令的正确执行,必须把 Command . com 系统文件放置在当前目录或 MS - DOS 搜索路径中。 2 . 插入 ActiveX 控件 在一个 Visual FoxPro 应用程序中插入 ActiveX 控件有以下三种方法。 (1)创建 OLE 容器控件 可以把 ActiveX 控件看成是一种特殊的可视类库,只是它的基类不是 Visual FoxPro 提供的 默认基类,而是从相应的 ocx 文件中派生的。创建这样一个 ActiveX 控件的方法和创建一个可 视类库的方法基本相同。 在“新建”对话框中选择“文件类型”为“类”,在弹出的“新建类”对话框中选择“派生于”下 拉列表框的内容为“OleControl”,并填好类名及保存的类库文件位置,如图 4 . 2 所示。

图 4.2

新建 OLE 容器控件


电脑工程师丛书

94

Visual FoxPro 高级应用实例

单击“确定”按钮后将弹出“插入对象”对话框,选择“插入控件”单选框,在“控件类型”列表 框中将会列出系统中已经注册的 COM 组件和 ActiveX 控件的名称。选中某个控件名称后,对 话框底部还将显示该控件在注册表中保存的键值及文件路径,如图 4 . 3 所示。

图 4.3

插入 ActiveX 控件

如果需要使用的 ActiveX 控件尚未注册,可以单击“添加控件”按钮,并在对话框中找到包 含该控件的 ocx 文件即可。 ActiveX 控件被添加到 OLE 容器空间后,就可以像设计可视类库一样设置属性和编写事件 代码了。 (2)从表单上直接添加 ActiveX 控件 在表单设计器中,单击表单控件工具栏中的“查看类”按钮,并选择“ActiveX 控件”项(见图 4 . 4),表单控件工具栏中将列出 ActiveX 控件的图标,然后可以像添加表单控件一样把 ActiveX 控件添加到表单中。

图 4.4

从控件工具栏添加 ActiveX 控件

(3)利用程序代码动态创建 ActiveX 控件 可以使用 ADDOBJECT()函数在运行时刻动态添加 ActiveX 控件,其基本的语法如下: ) AddObject(cName,cClass [,cOLEClass][,aInit1,aInit2 . . . ] 其中 cName 为新建对象的名称,cClass 为新建对象的类型,在这里选择为“OLEControl”, cOLEClass 为 ActiveX 控件的类型 ID,如果不知道某个控件的类型 ID,可以把它添加到表单上, 在“属性“窗口中查看它的 OleClass 属性即可。例如,需要动态创建一个富文本编辑框(RichEdit)控件,可以先在某个表单上静态的添加该控件,然后在“属性”窗口中查看其 OleClass 属性为 “RICHTEXT. RichtextCtrl . 1”,如图 4 . 5 所示。 因此可以用下面的代码动态创建一个标准 rtf 格式的文本编辑框控件: THISFORM. ADDOBJECT("myrtf","OLEControl","RICHTEXT. RichTextCtrl . 1") 通过 THISFORM. myrtf 即可获得对该 ActiveX 控件的引用,并动态设置其属性或调用方法 函数。


ActiveX 控件的使用(兼谈共享数据访问)

图 4.5

95

查看 ActiveX 控件的类型 ID

3 . ActiveX 控件的属性、事件和方法 ActiveX 控件和表单控件一样,也具有自身的属性、事件和方法,在“属性”窗口中可以快速 地查看这些属性、事件和方法。但一般的情况下,每个 ActiveX 控件还会提供一个专门的属性 设置对话框,在该对话框中会详细地描述这些属性的用途及可能的取值。 例如,在 上 文 提 到 的 RichText 控 件 中 右 击 鼠 标,在 快 捷 菜 单 中 有“RichTextCtrl Properties . . .”菜单项,单击该菜单项将弹出一个 RichText 控件的属性对话框,如图 4 . 6 所示。

图 4.6

RichText 控件属性设置对话框

在该对话框中可以分门别类地设置该控件的各个属性。有些属性在“属性”窗口中是无法 直接设置的,它将在该属性旁提供一个“.”按钮,单击该按钮会自动弹出这个属性设置对话框。 绝大多数情况下,ActiveX 控件并不是 Visual FoxPro 的一部分,也不是专门为 Visual FoxPro 设计的,它既可以使用在 Visual FoxPro 中,也可以使用在 Visual Basic 中,如果需要查看该控件 的使用方法,可以选中该控件,然后按“F1”键激活联机帮助。请注意,这个联机帮助是由控件 的开发人员提供的。 在 Visual FoxPro 中,ActiveX 控件还可以分为绑定型和未绑定型,前者支持简单的数据绑 定。对于一个绑定型的 ActiveX 控件,Visual FoxPro 会公开该控件的 ControlSource 属性,只要把 ControlSource 属性设置为一个表的字段(通常是一个通用型字段),在 ActiveX 控件中就会自动


电脑工程师丛书

96

Visual FoxPro 高级应用实例

显示该字段的值(通常是一个 OLE 对象),并且对控件内容的修改将会自动保存到记录的字段 中。例如,可以把 RichText 控件和一个 Memo 型字段绑定起来,这样就可以在备注字段中使用 富文本格式的文字了。 如果在应用程序中使用了 ActiveX 控件,为了确保所有用户事件都能被正确处理, 需要将 Visual FoxPro 应用程序对象的 AutoYield 属性设置为“假” (. F.),否则,如果在程 序命令执行过程中触发 ActiveX 控件事件,将会取消原有的程序命令,从而导致出错或 者不可预期的执行结果。

4.2.3

TreeView 控件

如果用户在设计程序时,需要用一种树形结构形象生动地显示具有不同层次的数据,那么 TreeView 控件将是最合适的选择。 TreeView 控件可以将用户选定的数据或者从数据库中检索 出来的数据以一定的层次结构展示出来,供用户自由地选择、展开或折叠。 TreeView 控件的特 点包括以下几个方面: ① 将相互间有联系的数据用图形与文字方式以树形结构描绘,以树形节点(Node)对象的 形式展开或收起数据。 ② 每一个节点可以用图像和文本标签来描述。 ③ 标签可以设置为是否允许修改的属性。 ④ 层次深度和节点数目只受系统资源的限制。 另外,使用 TreeView 控件对管理信息量很大的数据源来说,也是一个很好的方式,因为用 户能从中简单快速地选择到所需要的数据。 一个 TreeView 控件由若干节点组成,节点也就是 Node 对象。一个 TreeView 控件只能有一 个根节点(Root),每个节点可以有若干个子节点(Child),除根节点以外每个节点有且只有一个 父节点(Parent),这和数据结构中的“树”是完全一致的。创建 TreeView 控件之后,即可在程序 中动态地控制各个节点,包括添加、删除、展开、折叠等。 对于 5 . 0 版本的 Visual FoxPro,TreeView 控件包含在 COMCTRL32 . OCX 文件中,而 6 . 0 版本 则更新为 Mscomctl . ocx。Visual FoxPro 6 . 0 依然带有这些老的文件,因此可以继续使用 5 . 0 版 本的控件。但是为了更好地利用这些新版本控件最新的和增强的功能,建议把表单和相关的 类进行必要的更新。在微软主页上发布的 actxconv. exe 更新程序(http:/ /premium. microsoft . com/download /vfoxpro6 /actxconv. exe)能够使你非常容易地做到这点,该程序还能自动地将 TreeView 控件更新为最新版本。本实例中采用的 TreeView 控件版本为 6 . 0 SP4(Service Pack 4),该 版本拥有 5 . 0 版所不具备的一些特性,如对图像文件格式更广泛的支持,而且消除了 6 . 0 版本 中一些已知的 Bug,功能则更加完善。 1 . TreeView 控件的基本属性、方法和事件 TreeView 控件的属性设置对话框如图 4 . 7 所示,表 4 . 2 说明了其中主要属性的含义。


ActiveX 控件的使用(兼谈共享数据访问)

图 4.7

TreeView 控件属性设置对话框

表 4.2

TreeView 控件的主要属性说明

Style

用于确定树形结构的显示样式,包括图像、文本、+ / - 号、直线等

MousePointer

鼠标在控件区域的形状

LineStyle

根节点是否显示直线

LabelEdit

能否通过双击事件编辑节点标签

ImageList

使用哪个 ImageList 对象的图像资源

Indentation

树形结构中的横向直线长度,单位是像素

PathSeperator

如果装入一个目录文件系统的话,用于指定路径的分隔符

HideSelection

在未获得焦点时选定的文本是否仍然显示为选定状态

Sort

节点是否按照字母顺序排序

FullRowSelect

是否高亮显示选中的行

CheckBoxed

是否在每个节点前加入复选框,允许用户选中或取消

SingleSel

是否只能展开同级节点中的一个

Scroll

是否自动提供滚动条

Font

控件上显示的字体的大小、样式等

Picture

光标资源文件,支持常用图像文件格式

TreeView 控件主要有以下事件和方法: ① Collapse 事件:折叠一个节点时发生。 ② Expand 事件:展开一个节点时发生。 ③ NodeClick 事件:单击一个节点时发生。 ④ NodeCheck 事件:选中或取消一个节点时发生,当 CheckBoxed 属性为 . F. 时失效。

97


电脑工程师丛书

98

Visual FoxPro 高级应用实例

⑤ BeforeLabelEdit 事件:在试图编辑当前选中的节点标签时发生,在 LabelEdit = 1 时该事 件失效。 ⑥ AfterLabelEdit 事件:在编辑当前选中的 Node 对象标签后发生,在 LabelEdit = 1 时该事件 失效。 ⑦ Nodes 方法:功能:返回一个节点的引用。 语法:Object . Nodes(Index)或 Object . Nodes(Key)。 Object:TreeView 对象的名称。 Key:节点的键值。 Index:节点的索引。 ⑧ GetVisibleCount 方法。 功能:返回固定在 TreeView 控件的内部区域的 Node 对象的个数。 语法:Object . GetVisibleCount 说明:Node 对象的个数取决于在一个窗口中能固定多少行(含列表底部未完整显示的 行)。总的行数取决于控件的高度和 Font 对象的 Size 属性。可以使用 GetVisibleCount 属性确 保可视的最小行数,这样可以精确地访问树形结构的某一个层次。如果最小行数是不可视的, 可以用 Height 属性重新设置 TreeView 控件的大小。 2 . 节点及节点集合(Nodes)对象 一旦在程序中为 TreeView 控件添加了节点后,通常需要对单个节点或节点集合进行判断 和操作。节点或节点集合对象有以下常用的属性和方法: ① Key、Index 属性:键值与索引,每个节点的惟一标识。 ② Image 属性:节点所显示的与 ImageList 控件关联的图像索引或键值。 ③ Text 属性:节点显示的文本内容。 ④ Parent 属性:节点的父节点引用。 根节点没有父节点,因此访问根节点的 Parent 属性将导致程序出错。大多情况下,可 以采用 ISNULL(Node. Parent)来判断某个节点是否为根节点。

⑤ Children 属性:节点所包含的子节点个数。 ⑥ Previous 属性:前一个相邻节点。 ⑦ Next 属性:后一个相邻节点。 ⑧ Expanded 属性:节点是否展开。 ⑨ ExpandedImage 属性:节点展开后的图像索引。 ⑩ Selected 属性:节点是否被选中。 瑡 SelectedImage 属性:节点选中后的图像索引。 瑏  瑢 Add 方法。 瑏  功能:添加一个节点对象。 语法:Object . Add(Relative,Relationship,Key,Text,Image,Selectedimage)。 返回值:一个 Node 对象的引用。 Object:TreeView 对象的名称。


ActiveX 控件的使用(兼谈共享数据访问)

99

Relative:可选参数,参照节点的索引或键值,加入根节点时省略。 Relationship:可选参数,新增加的节点和参照节点的相对关系,Relationship 值的设置如表 4 . 3 所示。 表 4.3 常

Relationship 参数的含义

TvwFirst

0

第一个。新增节点放在参照节点的所有同级节点的前面

TvwLast

1

最后一个。新增节点放在参照节点的所有同级节点的最后。

TvwNext

2

TvwPrevious

3

前一个。新增节点放在参照节点之前

TvwChild

4

子节点。新增节点是参照节点的子节点

(缺省)下一个。新增节点放在参照节点之后

Key:新增节点的键值,必须是字符串。 Text:新增节点的标签,必须是字符串。 Image:可选参数,与 ImageList 控件关联的图像索引或键值,仅在 Style 属性等于 2、 3、 5、 7时 有效。 Selectedimage:可选参数,当节点被选中时所显示的与 ImageList 控件关联的图像索引或键 值,仅在 Style 属性等于 2、 3、 5、 7 时有效。  Remove 方法。 瑣 瑏 说明:删除一个节点。 语法:Object . Nodes . Remove(Key| Index)。 Object:TreeView 对象的名称。 Key| Index:节点的键值或索引。

节点对象没有事件,对用户及系统消息的响应通过它所在的 TreeView 控件的相应事 件来完成,如 NodeClick、NodeCheck 等。

4.2.4

ImageList 控件

ImageList 控件和 TreeView 控件一样也是包含在 Mscomctl . ocx 文件中,但是它是一个运行时 不可见的 ActiveX 控件,并且一般情况下不单独使用。 ImageList 控件的作用相当于图像的储藏 室,它需要另一个能显示图像(Picture)对象的控件来显示所储存的图像,或者是特别设计的、 用于绑定 ImageList 控件的 Windows 通用控件,包括 ListView、ToolBar、TabStrip、Header、ImageCombo 和 TreeView 控件。为了与这些控件一同使用 ImageList,必须通过一个适当的属性将特定的 ImageList 控件绑定到第二个控件。对于 ListView 控件,必须设置其 Icons 和 SmallIcons 属性为 ImageList 控件;对于 TreeView、TabStrip、ImageCombo 和 Toolbar 控件,必须设置 ImageList 属性为 ImageList 控件。 Windows 的资源管理器就是 TreeView 控件与 ImageList 控件配合应用的一个典 型例子。


电脑工程师丛书

100

Visual FoxPro 高级应用实例

可以在设计时用 ImageList 控件“属性”对话框中的“图像”选项卡来添加图像,也可以在运 行时用 Add 方法向 ImageList 对象中添加图像。对于 Windows 通用控件来说,设计时可以用 “自定义属性”对话框来指定一个 ImageList,也可以在运行时用 ImageList 属性和某个 ImageList 控件建立关联。 Visual Studio 6 . 0 版本中包含的 TreeView 控件有一个 Bug,其 ImageList 属性无法在设 计时指定为某一个 ImageList 控件,而需要在程序初始化时利用代码建立两者的关联(参 见本例中 Form1 表单的 Init 事件代码)。

ImageList 的属性窗口包含 General、Images 和 Color 三个选项卡,其中第一个选项卡主要用 于指定图像的大小,不同大小的图像都可以被添加到控件中,但是在关联的 Windows 通用控件 中显示的图像大小将受到添加到控件中的第一个图像大小的约束。例如,添加一个 16 × 16 像 素的图像到 ImageList 控件中,然后将 ImageList 绑定到 TreeView 控件(用节点对象显示),所有 存储于 ImageList 控件中的图像将以 16 × 16 像素显示。因此,通常在添加图像文件之前设定图 像的大小。 Images 选项卡用于装入图像文件,目前支持的图像格式包括 BMP、DIB、JPG、GIF、 ICO 和 CUR,对于一些透明的图像格式(如 GIF),还可以在 Color 选项卡中指定背景色。

4.2.5

CSCommand 控件

除了使用 Windows 系统或开发工具中包含的 ActiveX 控件之外,还可以使用大量的第三方 ActiveX 控件,这些 ActiveX 控件大多采用其他的开发工具开发,通常情况下不需要进行任何修 改即可在 Visual FoxPro 中使用。本例中将使用到随想软件制作的随想命令按钮控件 CSCommand,使用该控件可以一改 Visual FoxPro 按钮控件单调呆板的外观,使得表单上的命令按钮具 有 Windows XP 风 格。该 控 件 属 于 免 费 共 享 软 件,可 以 从 随 想 软 件 的 主 页(http:/ /capricciososoft . com)上免费下载和使用。遵照该软件的授权协议,它也被包含在本书的配套光盘中。 该控件的属性对话框如图 4 . 8 所示,表 4 . 4 列出了该控件主要属性的含义及说明。

图 4.8

CSCommand 控件属性对话框


ActiveX 控件的使用(兼谈共享数据访问)

表 4.4 属

101

CSCommand 控件主要属性说明

Appearance

按钮文本和图标运行时是否有三维的按下效果 CSCMD— Flat = 0 平面效果 CSCMD— Solid = 1 鼠标按下时图标和文字向右下角移动 CSCMD— SolidB = 2 鼠标移动到按钮区域时图标向左上角移动,并绘制图 标阴影。当鼠标按下时图标向右下角移动,与阴影重 合

BackStyle

指定按钮的背景样式是否将屏蔽色区域设置为透明 CSCMD— Opaque = 0 CSCMD— Transparent = 1

DisabledNewStyle

指定按钮不可用时是否使用新的全灰度变换样式。该属性将决定带有 图标的按钮是适合传统标准图标还是真彩色图标

DisabledNewStyleB

一种新的“不可用图标处理样式”,是否在对图标灰度变换的基础上同时 进行色调变换

DisabledFontColor

按钮不可用时文字的颜色

Icon

按钮的图标

IconAlign

是否显示图标和显示的位置 CSCMD— None = 0 不显示图标 CSCMD— Left = 1 在按钮左侧显示图标 CSCMD— Right = 2 在按钮右侧显示图标

Picture、FR、PR

设置按钮皮肤,详见该控件说明文档

PICMaskColor

皮肤图片的屏蔽色

该控件还具有 MouseOut 事件,当鼠标指针移出按钮控件时触发该事件,利用该事件可以 实现一些动态的显示效果。

4 . 2 . 6 数据共享访问及缓冲机制 数据库系统的特点之一就是数据的共享,因此,在数据库应用系统中需要考虑到数据如何 在程序的多个实例之间进行共享访问,这些程序的实例可能同时在本地运行,也可能是在某个 局域网计算机上运行。共享访问意味着不仅要为多个用户使用和共享数据提供更有效的方 法,并且在必要时要对访问进行限制。 1 . 数据的独占和共享 在 Visual FoxPro 中,可以有两种方式访问数据:独占方式和共享方式。前者使得数据源在 某一时刻只能被单个用户使用,是最严格的一种限制方式;而后者允许数据源被多个用户同时 访问,这对在网络环境下共享数据具有重要的意义。一般情况下应该尽量避免以独占方式访 问数据源。 默认情况下,使用 USE 命令将以独占方式打开一个表,也可以通过设置环境变量 SET EXCLUSIVE ON/OFF 来修改打开表的方式,如果设置为 ON 则以独占方式打开,反之则以共享方


电脑工程师丛书

102

Visual FoxPro 高级应用实例

式打开。另外,还可以使用如下命令指定表的打开方式: USE 数据库表名 EXCLUSIVE

(以独占方式打开)

(以共享方式打开) USE 数据库表名 SHARED 执行以下命令时,必须以独占方式打开数据库,否则将返回出错信息。 ① ALTER TABLE:在程序中动态修改表结构。 ② INDEX:创建、添加或删除一个复合索引标识。 ③ INSERT[BLANK]:用 INSERT 命令插入一条[空白]记录,相比之下,APPEND[BLANK] 命令可以在共享方式下追加[空白]记录。 ④ MODIFY STRUCTURE:以交互方式修改一个表结构,如果当前以共享方式打开这个表, 则只能以只读方式显示表结构。 ⑤ PACK:物理删除表记录。 ⑥ REINDEX:重建索引。 ⑦ ZAP:物理删除所有表记录。 2 . 数据锁定 如果以共享方式打开表,则该表有可能被多个用户同时使用,此时必须避免这样一种情况 的发生:多个用户同时对表中的数据进行修改。为了避免它的发生,通常情况下需要对数据进 行锁定。 数据锁定按照其作用范围可分为记录锁定和表锁定。记录锁定只针对特定的记录,表中 别的记录仍然可以被正常修改或删除。表锁定又可以分为表头锁定和整表锁定,前者仅锁定 表头,也就是说用户不能添加或删除记录,但对于记录内的数据仍然可以修改;而整表锁定将 拒绝一切用户修改表中的数据。 按照其工作方式又可分为自动锁定和人工锁定,下面分别加以介绍。 (1)自动锁定 Visual FoxPro 的很多命令会在执行前自动对记录或表进行锁定,如果锁定成功则继续执行 该命令,执行完毕后自动解除锁定。不同的命令,锁定的范围也不同(见表 4 . 5)。 表 4.5 命

自动锁定数据的命令及其锁定范围

锁定范围

ALTER TABLE

整个表

APPEND

表头

APPEND BLANK

表头

APPEND FROM

表头

APPEND FROM ARRAY

表头

APPEND MEMO

当前记录

BLANK

当前记录

BROWSE、CHANGE 和 EDIT

当前记录和相关表中别名字段的所有记录

CURSORSETPROP()

取决于参数

DELETE

当前记录


ActiveX 控件的使用(兼谈共享数据访问)

103

(续表) 命

锁定范围

DELETE NEXT 1

当前记录

DELETE RECORD n

记录 n

删除多个记录的 DELETE

整个表

DELETE - SQL

当前记录

GATHER

当前记录

INSERT

整个表

INSERT - SQL

表头

MODIFY MEMO

编辑开始时,锁定当前记录

READ

当前记录和别名字段的所有记录

RECALL

当前记录

RECALL NEXT 1

当前记录

RECALL RECORD n

记录 n

恢复多个记录的 RECALL

整个表

REPLACE

当前记录和别名字段的所有记录

REPLACE NEXT 1

当前记录和别名字段的所有记录

REPLACE RECORD n

记录 n 和别名字段的所有记录

REPLACE 替换多个记录

整个表和别名字段的所有记录

SHOW GETS

当前记录和别名字段引用的所有记录

TABLEUPDATE()

取决于缓冲

UPDATE

整个表

UPDATE - SQL

整个表

(2)人工锁定与解锁 在某些情况下需要人工锁定记录或表,可以使用以下命令: ① RLOCK()。 功能:锁定一条或多条记录。 语法:RLOCK([记录号列表 , ][工作区 | 表别名 ] )。 记录号列表:可以是单个的记录号,也可以是多条记录号并用“, ”分隔开,如果需要同时锁 定多条记录,必须设置环境变量 MULTILOCKS 为 ON:SET MULTILOCKS ON;如果不指定记录 号,则锁定当前记录,如果把 0 作为参数传递给 RLOCK 命令,则锁定表头。 返回值:如果该命令执行成功则返回 . T. ,否则返回 . F. 。 LOCK 命令的用法与 RLOCK 相同。 ② FLOCK()。 功能:锁定整个表。 语法:FLOCK([工作区 | 表别名 ] )。 返回值:如果该命令执行成功则返回 . T. ,否则返回 . F. 。 如果以上人工锁定命令执行失败,则系统将根据环境变量 REPROCESS 的设置决定下一步 的操作,命令格式如下:


电脑工程师丛书

104

Visual FoxPro 高级应用实例

SETREPROCESSTO 重试次数[秒数 ]| TO AUTOMATIC 重试次数默认为 0,即不进行重试,最大可设置为 32 000 次;如果还指定了秒数则一直重 试到规定的时间为止;如果设置为 AUTOMATIC 将一直尝试直到锁定成功或用户按下 ESC 键 时终止。 在共享环境下锁定了记录或表后,应该尽快地解除锁定。如果是自动锁定,只要简单地移 动表记录指针即可解除锁定;如果是人工锁定则需要执行解锁命令 UNLOCK[ALL],该命令默 认情况下解除当前工作区中所有被锁定的数据,如果加上 ALL 参数则解除当前数据工作期内 所有工作区中被锁定的数据。 3 . 数据缓冲 缓冲是对数据的一种保护机制,这在多用户共享数据的情况下尤其具有重要的意义。例 如, 情况一:用户 A 对数据进行修改和更新期间,此时的数据往往不是最终的结果,如果该数 据同时被用户 B 访问,他获得的将是“脏数据”;情况二:用户 A 读取数据后,用户 B 对原始数 据进行了修改,此时用户 A 把更新结果写入数据库中将导致用户 B 的修改结果丢失。为了避 免出现这些情况,应该在用户的修改和数据库中的数据之间建立起有效的缓冲机制,所有的临 时性数据被放置在缓冲区中,当用户的修改和更新完成后再存储到数据库中。 Visual FoxPro 中提供两种数据缓冲方式:记录缓冲和表缓冲。若一次只对一个记录进行访 问、修改或写操作,那么请选择记录缓冲。在一个多用户环境中,记录缓冲能够提供适当的有 效性检查机制,对其他用户所进行的数据更新操作影响最小。若要对多个记录的更新使用缓 冲,请选择表缓冲。如果想在一个表中处理多个记录或在一对多关系中处理子表记录,表缓冲 提供了最有效的工作方式。 实现缓冲实际上分为两个步骤:锁定与更新。在数据读入缓冲区中,首先对原始数据加 锁,在所有的修改完成后,再把修改的数据进行更新。根据锁定的方式不同,又可以分为保守 式缓冲和开放式缓冲:前者在读取完原始数据后将禁止其他用户读取数据,从而可以有效避免 上文中提到的第一种情况,也是一种更为安全的缓冲方式;后者仅在执行写操作时锁定数据, 但是在更新前会判断数据是否在读取后被修改,如果未被修改则直接更新数据,否则给出错误 信息,从而可以有效避免上文中提到的第二种情况。两种缓冲方式各有各的优点,保守式缓冲 独占数据的时间较长,但数据的更新更加安全可靠;后者独占数据的时间较短,但可能会导致 更新失败。在实际的应用中,应该根据具体的场合和要求选择不同的缓冲方式。 把缓冲的作用范围和锁定的方式结合起来,缓冲共有四种方式:保守式表缓冲、保守式记 录缓冲、开放式表缓冲和开放式记录缓冲,其严格性依次递减。通过设置数据表或临时表的 Buffering 属性可以选择具体的缓冲方式。该属性的取值及含义如表 4 . 6 所示。 表 4.6 Buffering 属性

Buffering 属性与缓冲方式 缓冲方式

1

无缓冲,数据库表的默认值

2

保守式记录缓冲

3

开放式记录缓冲,视图的默认值

4

保守式表缓冲

5

开放式表缓冲


ActiveX 控件的使用(兼谈共享数据访问)

105

例如,在表单的数据环境设计器中,设置某个数据库表或临时表的 BufferModeOverride 属性 为 2, 将启用保守式记录缓冲,或者在表单的初始化事件中执行 CURSORSETPROP("Buffering",5) 命令将启用开放式表缓冲模式。对于远程数据来说,只能使用开放式缓冲,即 Buffering 属性只 能取 3 或 5。 4 . 数据更新与恢复 启用了数据 缓 冲 后,可 以 用 TABLEUPDATE 命 令 把 修 改 的 结 果 更 新 到 表 中,也 可 以 用 TABLEREVERT 命令取消更新,具体语法如下: (1)TABLEUPDATE 功能:把修改的数据更新到数据库表或临时表中。 语法:TABLEUPDATE([nRows [,lForce]][,数据库表别名 | 工作区 ] ) nRows:指定更新的方式,如果取值为 0,只更新被修改的记录;如果取值为 1 将更新所有的 数据,出现错误时终止更新;如果取值为 2 将更新所有的数据并且忽略所有出现的错误,默认 取值为 0。 lForce:决定是否强制更新,如果取值为 . F. 则当数据在缓冲期间被其他用户修改将导致出 错并忽略更新,否则将强制进行更新。 (2)TABLEREVERT 功能:取消缓冲期间对数据的修改。 语法:TABLEREVERT([lAllRows [,数据库表别名 | 工作区 ] )。 lAllRows:如果为 . T. 则所有被修改的数据均被取消,否则仅取消当前记录的修改。 对于记录缓冲而言,移动表记录指针也将导致数据更新。 5 . 事务处理 事务处理是对数据完整性的一种更为高级的保护机制。以银行的转账业务为例,假设客 户要把账户 A 中一定数量的金额转移到账户 B 中,账户 A 和账户 B 分别保存在两个不同的数 据库表中,这两个数据库表可能在网络上的两个不同主机上。这个操作分为两个步骤:从账户 A 中减去相应数量的金额,然后在账户 B 中增加相应数量的金额。假如由于某种原因(如突然 停电或网络中断),在账户 A 中减去一定数量的金额后,在账户 B 中增加相应数量金额的操作 未能正确执行,这将导致用户的财产损失,也就是说数据的完整性被破坏了。利用事务处理可 以避免这种情况的出现。 简单地说,所谓事务就是指一组相关联的数据操作,这些操作要么被完整地正确执行,要 么都不执行(回滚)。 Visual FoxPro 通过以下命令来控制事务处理: ① BEGIN TRANSACTION:初始化一个事务处理。 ② ROLLBACK:回滚事务,取消当前事务的所有操作。 ③ END TRANSACTION 在所有的操作被正确执行后结束事务。 可以用事务处理缓冲对以下各项的修改:表、结构化的 . cdx 文件以及与数据库内表相关 的备注文件。涉及内存变量和其他对象的操作不属于事务处理,因此不能回滚或提交这些操 作。 在事务处理中遵循以下基本规则:


电脑工程师丛书

106

Visual FoxPro 高级应用实例

① 一个事务处理起始于 BEGIN TRANSACTION 命令,以 END TRANSACTION 或 ROLLBACK 命令终止。只有 END TRANSACTION 语句,而前面没有 BEGIN TRANSACTION 与之匹配则会出 错。 ② ROLLBACK 语句前没有 BEGIN TRANSACTION 将出错。 ③ 除非应 用 程 序 中 止(这 将 导 致 回 滚 操 作),事 务 处 理 一 旦 开 始,在 遇 到 相 应 的 END TRANSACTION 语句(或回滚语句)这一期间,始终保持有效,在多个程序、函数之间切换的情况 下也是如此。 ④ 对于涉及事务处理数据的查询,Visual FoxPro 在使用磁盘数据前,首先使用在事务处理 缓冲区内的高速缓冲数据,确保使用的是最新的数据。 ⑤ 如果在事务处理过程中应用程序中止,则所有操作回滚。 ⑥ 事务处理只在数据库容器内进行。 ⑦ 如果 INDEX 命令改写了一个已有的索引文件,或者有任一索引文件已打开,则不能使 用 INDEX 命令。 ⑧ 事务处理只在数据工作期中起作用。 一个典型的事务处理代码如下: BEGIN TRANSACTION  更新数据 && 出错

IF lSuccess = . F. ROLLBACK

&& 执行修改

ELSE  确认数据 IF

&& 出错

ROLLBACK ELSE END TRANSACTION ENDIF ENDIF 使用远程表中存储的数据时,事务处理命令控件只更新视图临时表的本地副本中 的数据;对 远 程 基 表 的 更 新 不 起 作 用。若 要 对 远 程 表 启 用 人 工 事 务 处 理,请 使 用 SQLSETPROP(),然后用 SQLCOMMIT( )和 SQLROLLBACK( )控制事务处理,详见第 6 章。

4.3

实例制作

本实例将要设计一个功能较为完整的个人通讯录系统,允许用户通过表单添加、删除和修 改记录。该系统的特色在于设计了一个树形结构的浏览界面,把所有的联系人按照所在城市 进行归类,可通过该界面迅速地定位到某个具体的联系人。此外,对传统的表单界面进行了一 定的美化。


107

ActiveX 控件的使用(兼谈共享数据访问)

本实例将要制作的文件包括: ① 项目文件:ex4 . pjx。 ② 数据库文件:addrbook . dbc 及表文件 addrbook . dbf。 ③ 表单文件:ex4 . scx。 ④ 快捷菜单:pmenu . mnx 并生成 pmenu . mpr。 ⑤ 主程序:main . mpr(项目主文件)。 ⑥ 图像文件:addrbook1. bmp、addrbook2. bmp、city. bmp、head. bmp、close. gif、delete. gif、edit . gif、 new. gif、save . gif、bg. jpg、closebtn . jpg、minbtn . jpg、T1 . jpg 和 T2 . jpg。 这些文件位于本书配套光盘的 h ex4 目录下。

4 . 3 . 1 建立数据库 新建一个项目,保存为 ex4 . pjx。在项目管理器中新建一个数据库 addrbook,并添加一个 addrbook . dbf 表,该表结构如表 4 . 7 所示。 表 4.7 字段

字段名

类型

1

姓名

2

addrbook 的表结构 宽度

索引

字符型

10

城市

字符型

10

升序

3

地址

字符型

150

4

邮编

字符型

10

5

生日

日期型

8

6

电话

字符型

20

7

传真

字符型

20

8

EMAIL

字符型

30

9

备注

备注型

4

合计

排序

NULL 是

PINYIN

264(262 + 2)

其中城市和姓名字段是分类显示的依据,因此不允许为空值(NULL)。为了保证相同城市 的记录便于在树形结构中显示,还需要对该字段建立结构化复合索引,按照字母顺序和汉语拼 音升序排列,索引标识为 CITY。

4 . 3 . 2 制作表单 在项目管理器中新建一个表单 ex4 . scx,按照如下步骤在表单设计器中修改表单并添加相 应的控件: ① 设置表单的 Picture 属性为 bg. jpg(即背景图片),该图片模拟了表单窗口的标题栏、状 态栏及 3D 的表单效果。 ② 调整表单的大小与背景图片大小吻合。 ③ 设置表单的 TitleBar 属性为“0 - 关闭”,去掉默认的表单标题栏,设置 BorderStyle 属性为


电脑工程师丛书

108

Visual FoxPro 高级应用实例

“0 - 无边框”。 分别设置其 Picture 属性为 Minbtn. jpg 和 Closebtn. jpg, ④ 在表单右上方添加两个 Image 控件, 用来模拟表单标题栏的最小化和关闭按钮。 ⑤ 在表单正上方添加一个 Label 控件,设置其 Caption 为“实例四 时尚个人通讯录”,并设 置 BackStyle 属性为“0 - 透明”,模拟表单的标题栏文字。 ⑥ 按照上文所述的方法在表单上添加以下 ActiveX 控件:Treeview 控件、ImageList 控件和 五个 CSCommand 命令按钮控件。 ImageList 控件运行时不可见,因此其放置位置对表单运行效 果没有影响。 ⑦ 在 CSCommand 命令按钮控 件的属性设置对话框中设置其 ICON 属性为相应的按钮图标文件, ICconAlign 属性为:CSCMD— Left = 1,ShowFocus 属性为 . T. 。 ⑧ 在 ImageList 控件的属性设 置对话框中添加 TreeView 控件将 要用 到 的 图 标 文 件,如 图 4 . 9 所 示。 ⑨ 打开数据环境设计 器,把 数据表 addrbook . dbf 加入到数据环

图 4.9

ImageList 控件属性设置

境中。 ⑩ 添加基本表单控件,完成后的表单设计器中的表单外观如图 4 . 10 所示。

图 4 . 10

表单设计时的外观

按照表 4 . 8 所示设置各个表单控件的属性。


ActiveX 控件的使用(兼谈共享数据访问)

表 4.8 控件名称

Form1

Lbl 姓名、Lbl 地址、 Lbl 城市、Lbl 邮编、 Lbl 生日、Lbl 电话、 Lbl 传真、LblEmail、 Lbl 备注

Txt 姓名、Txt 城市、 Txt 地址、Txt 传真、 Txt 电话、Txt 邮编、 Txt 生日、TxtEmail

表单控件主要属性设置

控件类型

属性

Form

Label

AutoCenter

. T.

ShowWindow

2 - 私有数据工作期 顶层表单

WindowType

1 - 模式

Caption

姓名、地址、城市、邮编、生日、电 话、传真、备注

AutoSize

. T. - 真

BackStyle

0 - 透明

ControlSource Text DisabledBackColor DisabledForeColor

0, 0, 0

Enabled DisabledBackColor DisabledForeColor Style Indentation LineStyle

OleTree

OleImages Command1

addrbook. 姓名、addrbook. 城市、 addrbook. 地址、addrbook. 传真、 addrbook. 电话、addrbook. 邮编、 addrbook. 生日、addrbook. Email . F. - 假 255, 255, 255

Enabled

Edit

DataSession

ControlSource Edt 备注

109

TreeView

ImageList CSCommand

MousePoint

addrbook. 备注 . F. - 假 255, 255, 255 0, 0, 0 7 20 . 00 1 - RootLine 0 - Default

OLEDragMode

0

OLEDropMode

0

FontSize

11

General

16 × 16 编辑

Caption Enabled Caption

. F. - 假 保存

Command2

CSCommand

Command3

CSCommand

Command4

CSCommand

Caption

. F. - 假 新增

Command5

CSCommand

Caption

退出

Enabled Caption Enabled

. F. - 假 删除

4 . 3 . 3 快捷菜单设计 在 Visual FoxPro 表单的文本框、编辑框中,系统的编辑菜单(如“剪切”、 “复制”、 “粘贴”等) 不可用,为此需要设计一个快捷菜单,为用户提供这些最基本的快速编辑功能。在项目中新建 一个快捷菜单并保存为 pmenu . mnx,插入三个系统菜单项:剪切、复制和粘贴,菜单设计器如图 4 . 11 所示,完成后生成菜单程序 pmenu . mpr。


电脑工程师丛书

110

Visual FoxPro 高级应用实例

图 4 . 11

快捷菜单设计器

4 . 3 . 4 编写代码 1 . 表单代码 表单的 Init 事件主要用来完成表单及其控件的初始化,包括判断 ActiveX 控件是否被正确 加载、数据缓冲方式的设置以及 TreeView 控件的节点显示等。在本例中,采用开放式记录缓冲 模式,另外 TreeView 控件的节点显示是一个经常要调用的方法,因此把它的程序代码放在表单 的一个自定义方法 ListNodes 中。 TreeView 控件节点显示的关键是采用一种合理的方式命名各个节点,要求能够很容易地 根据节点的键值或索引得到当前节点的位置、层次及对应的记录号。索引是由系统自动分配 的整数,一般用于树型结构的遍历。在表记录数不确定的条件下,由索引来确定节点位置效率 比较低,且较为繁琐。更为简便的方法是采用有规则的键值命名方式。在本例中,将根节点的 键值命名为“—”,根节点下的城市节点依次命名为: “— 0"、 “— 1”、 “— 2”……,第 N 个城市节点的 子节点(即联系人节点)命名为“— N— 1”、 “— N— 2”、 “— N— 3”……最后一个“—”后面的数值为表 的 RECNO()返回值。因为使用了索引进行排序,不能用简单递增的方式。这样命名的好处是 可以很容易地判断当前节点的层次及对应的记录号,从而快速地定位到相应的记录。 (1)表单的 Init 事件代码  检查 ocx 控件是否被正确装载 IF TYPE("THIS. OleTree") # "O" OR ISNULL(THIS. OleTree) RETURN . F. ENDIF IF TYPE("THIS. OleImages") # "O" OR ISNULL(THIS. OleImages) RETURN . F. ENDIF IF TYPE("THIS. Command1") # "O" OR ISNULL(THIS. Command1) RETURN . F. ENDIF  把 OleImages 绑定到 OleTree 的 ImageList 属性 THIS. OleTree . ImageList = THIS. OleImages


ActiveX 控件的使用(兼谈共享数据访问)

RELEASE ALL = CURSORSETPROP("Buffering",3)&& 开放式记录缓冲 0 - 默认, 1 - 编辑, 2 - 新增  全局变量 rid 保存当前记录号,op 保存当前操作: PUBLIC rid,op rid = 0 op = 0 THIS. ListNodes (2)表单的自定义方法 ListNodes 代码 THISFORM. OleTree . nodes . CLEAR  添加根节点并展开 ) rnode = THISFORM. OleTree . nodes . ADD(, 1,"— ","我的通讯录", 2, rnode . Expanded = . T. i = 0 && 城市节点号 mcity = "" && 当前城市节点的文本标签  按照城市名称排序 SET ORDER TO TAG city SCAN SCATTER MEMO MEMVAR IF mcity! = m. 城市  新建一个城市节点并添加联系人节点 i=i+1 THISFORM. OleTree . nodes . ADD("— ", 4,"— " + ALLTRIM(STR(i)),; ALLTRIM(城市), 3, 3) THISFORM. OleTree . nodes . ADD("— " + ALLTRIM(STR(i)), 4,; "— " + ALLTRIM(STR(i))+ "— " + ; ALLTRIM(STR(RECNO())),; ALLTRIM(姓名), 4, 4) ELSE  在当前城市节点下新增联系人节点 THISFORM. OleTree . nodes . ADD("— " + ALLTRIM(STR(i)), 4,; "— " + ALLTRIM(STR(i))+ "— " + ; ALLTRIM(STR(RECNO())),; ALLTRIM(姓名), 4, 4) ENDIF mcity = m. 城市 ENDSCAN

111


电脑工程师丛书

112

Visual FoxPro 高级应用实例

2 . TreeView 控件代码 TreeView 控件主要响应用户展开、折叠和单击节点事件,当用户展开或折叠根节点时更换 相应的节点图标。单击节点时,首先判断当前节点的位置,如果是根节点或城市节点。则不作 任何处理;如果单击联系人节点,则移动记录指针,根据它的父节点键值的字符串长度来获得 最后一个“—”符号出现的位置,再利用 SUBSTR()函数获得最后一个“—”符号后面的数值,即该 节点所对应记录号。 此外,单击联系人节点时还需要判断当前的操作状态,如果处于编辑或新增记录的状态, 说明缓冲区中可能有被修改的数据,直接移动指针将导致数据更新,因此需要给用户相应的提 示:更新或取消修改的数据。 (1)OleTree 控件的 Click 事件代码  ActiveX 控件事件  LPARAMETERS NODE  判断是否为联系人节点。 IF ISNULL(NODE. PARENT)&& 根节点 RETURN . F. ELSE IF NODE. PARENT. KEY = = "— " && 城市节点。 RETURN . F. ELSE i = TXTWIDTH(NODE. PARENT. KEY) rid = VAL(SUBSTR(NODE. KEY,i + 2)) IF op > 0 yn = MESSAGEBOX("是否保存当前的修改", 1 + 48 + 256,"提示") IF yn = 1 TABLEUPDATE(0)&& 更新数据 ELSE TABLEREVERT(. T.)&& 取消更新 ENDIF op = 0 THISFORM. command1 . CAPTION = " 编辑" THISFORM. command4 . CAPTION = " 新增" ENDIF IF rid > 0 GO rid THISFORM. Commandgroup1 . Command1 . ENABLED = . T. THISFORM. Commandgroup1 . Command3 . ENABLED = . T. THISFORM. REFRESH ENDIF ENDIF ENDIF


ActiveX 控件的使用(兼谈共享数据访问)

113

(2)OleTree 控件的 Collapse 事件代码  ActiveX 控件事件   为根节点的折叠状态更换图标。 LPARAMETERS NODE IF NODE. KEY = = "— " NODE. IMAGE = 1 ENDIF (3)OleTree 控件的 Expand 事件代码  ActiveX 控件事件   为根节点的展开状态更换图标。 LPARAMETERS NODE IF NODE. KEY = = "— " NODE. IMAGE = 2 ENDIF 3 . “编辑”按钮代码 “编辑”按钮实际上有两种状态:编辑和取消。处于编辑状态时,单击该按钮将打开各个表 单文本框控件和编辑框控件的 Enabled 属性,允许用户修改数据库中的数据。不过,由于当前 采用了数据缓冲,这些数据并未真正更新到数据库中,而是保存在缓冲区里。用户在完成修改 后可以单击“保存”按钮执行数据更新,否则,可以再次单击该按钮取消本次对数据的修改,此 时按钮即处于“取消”状态。 Command1 控件的 Click 事件代码: IF THIS. CAPTION = = "编辑"  进入编辑状态。 THISFORM. Txt 姓名 . ENABLED = . T. THISFORM. Txt 城市 . ENABLED = . T. THISFORM. Txt 地址 . ENABLED = . T. THISFORM. Txt 邮编 . ENABLED = . T. THISFORM. Txt 生日 . ENABLED = . T. THISFORM. Txt 电话 . ENABLED = . T. THISFORM. Txt 传真 . ENABLED = . T. THISFORM. TxtEmail . ENABLED = . T. THISFORM. Edt 备注 . ENABLED = . T. THIS. CAPTION = "取消" THISFORM. CommandGroup1 . Command2 . ENABLED = . T. THISFORM. CommandGroup1 . Command3 . ENABLED = . F. THISFORM. CommandGroup1 . Command4 . ENABLED = . F. op = 1 && 修改当前操作标识 THISFORM. Txt 姓名 . SETFOCUS


电脑工程师丛书

114

Visual FoxPro 高级应用实例

ELSE yn = MESSAGEBOX("确认取消这次操作吗?", 1 + 48 + 256,"确认取消") IF yn = 1  取消用户编辑。 THISFORM. Txt 姓名 . ENABLED = . F. THISFORM. Txt 城市 . ENABLED = . F. THISFORM. Txt 地址 . ENABLED = . F. THISFORM. Txt 邮编 . ENABLED = . F. THISFORM. Txt 生日 . ENABLED = . F. THISFORM. Txt 电话 . ENABLED = . F. THISFORM. Txt 传真 . ENABLED = . F. THISFORM. TxtEmail . ENABLED = . F. THISFORM. Edt 备注 . ENABLED = . F. THIS. CAPTION = "编辑" THISFORM. CommandGroup1 . Command2 . ENABLED = . F. THISFORM. CommandGroup1 . Command3 . ENABLED = . T. THISFORM. CommandGroup1 . Command4 . ENABLED = . T. op = 0 TABLEREVERT(. F.) THISFORM. REFRESH ENDIF ENDIF 4 . “保存”按钮代码 “保存”按钮的功能是完成数据更新,其 Click 事件代码如下: IF ALLTRIM(THISFORM. Txt 城市 . TEXT)= = "" OR ; ALLTRIM(THISFORM. Txt 姓名 . TEXT)= = "" MESSAGEBOX("姓名和城市不能为空!", 0 + 48 + 256,"警告") RETURN ENDIF yn = MESSAGEBOX("确认要保存这次操作吗?", 1 + 48 + 256,"确认保存") IF yn = 1 TABLEUPDATE(1)&& 保存编辑结果 THIS. ENABLED = . F. THISFORM. Txt 姓名 . ENABLED = . F. THISFORM. Txt 城市 . ENABLED = . F. THISFORM. Txt 地址 . ENABLED = . F. THISFORM. Txt 邮编 . ENABLED = . F. THISFORM. Txt 电话 . ENABLED = . F.


ActiveX 控件的使用(兼谈共享数据访问)

115

THISFORM. Txt 生日 . ENABLED = . F. THISFORM. Txt 传真 . ENABLED = . F. THISFORM. TxtEmail . ENABLED = . F. THISFORM. Edt 备注 . ENABLED = . F. rid = RECNO() THISFORM. Commandgroup1 . Command1 . ENABLED = . T. THISFORM. Commandgroup1 . Command3 . ENABLED = . T. op = 0 DO CASE CASE op = 1 THISFORM. Commandgroup1 . Command1 . CAPTION = "编辑" CASE op = 2 THISFORM. Commandgroup1 . Command4 . CAPTION = "新增" ENDCASE THISFORM. REFRESH ENDIF 5 . “删除”按钮代码 “删除”按钮的功能是删除当前的记录。由于本实例为小型的个人通讯录系统,因此删除 操作为直接物理删除,为了使用 PACK 命令,需要先关闭当前数据环境中打开的表,并以独占 方式重新打开,待命令执行完毕后再恢复共享的数据环境。 Command3 按钮的 Click 事件代码如下: yn = MESSAGEBOX("确认要删除该记录吗?", 1 + 48 + 256,"确认删除") IF yn = 1 AND rid > 0  关闭数据环境中的表,参见程序说明(3)。 THISFORM. DATAENVIRONMENT. CLOSETABLES()  以独占方式打开表。 USE addrbook EXCLUSIVE GO rid DELETE PACK USE  重新打开数据环境中的表。 THISFORM. DATAENVIRONMENT. OPENTABLES() THISFORM. ListNodes THIS. ENABLED = . F. THISFORM. REFRESH ENDIF


电脑工程师丛书

116

Visual FoxPro 高级应用实例

6 . “新增”按钮代码 “新增”按钮和“编辑”按钮一样也包括两种工作状态:新增和取消,其工作原理和“编辑”按 钮类似。 Command4 按钮的 Click 事件代码如下: IF THIS. CAPTION = = "新增"  清空和字段关联的内存变量作为用户输入的默认值 APPEND BLANK THISFORM. Txt 姓名 . ENABLED = . T. THISFORM. Txt 城市 . ENABLED = . T. THISFORM. Txt 地址 . ENABLED = . T. THISFORM. Txt 邮编 . ENABLED = . T. THISFORM. Txt 生日 . ENABLED = . T. THISFORM. Txt 电话 . ENABLED = . T. THISFORM. Txt 传真 . ENABLED = . T. THISFORM. TxtEmail . ENABLED = . T. THISFORM. Edt 备注 . ENABLED = . T. THIS. CAPTION = "取消" THISFORM. CommandGroup1 . Command2 . ENABLED = . T. THISFORM. CommandGroup1 . Command1 . ENABLED = . F. THISFORM. CommandGroup1 . Command3 . ENABLED = . F. op = 2 THISFORM. Txt 姓名 . SETFOCUS THISFORM. REFRESH ELSE yn = MESSAGEBOX("确认取消这次操作吗?", 1 + 48 + 256,"确认取消") IF yn = 1  取消用户输入。 THISFORM. Txt 姓名 . ENABLED = . F. THISFORM. Txt 城市 . ENABLED = . F. THISFORM. Txt 地址 . ENABLED = . F. THISFORM. Txt 邮编 . ENABLED = . F. THISFORM. Txt 生日 . ENABLED = . F. THISFORM. Txt 电话 . ENABLED = . F. THISFORM. Txt 传真 . ENABLED = . F. THISFORM. TxtEmail . ENABLED = . F. THISFORM. Edt 备注 . ENABLED = . F. THIS. CAPTION = "新增" THISFORM. CommandGroup1 . Command2 . ENABLED = . F.


ActiveX 控件的使用(兼谈共享数据访问)

117

THISFORM. CommandGroup1 . Command1 . ENABLED = . T. THISFORM. CommandGroup1 . Command3 . ENABLED = . T. TABLEREVERT(. T.) op = 0 THISFORM. REFRESH ENDIF ENDIF 7 . “退出”按钮代码 RELEASE THISFORM CLEAR EVENTS 8 . 快捷菜单的运行 表单中的文本框、编辑框在编辑状态下应该允许弹出自定义的快捷编辑菜单,为 Txt 姓 名、Txt 城市、Txt 地址、Txt 传真、Txt 电话、Txt 邮编、TxtEmail 等控件和 Edt 备注控件的 RightClick 事件添加如下代码: DO pmenu . MPR 9 . 主程序代码 本例中的主程序除了和其他实例中的主程序一样进行必要的系统设置之外,还特别需要 设置 APPLICATION 对象的 AUTOYIELD 属性,理由在上文中已讨论过。 main . prg 文件的代码如下: SET DEFAULT TO SYS(5)+ SYS(2003)+ " h " SET TALK OFF — SCREEN. HIDE

DO FORM ex4 READ EVENTS APPLICATION. AUTOYIELD = . F. 在项目管理器中设置该文件为主文件,并联编应用程序。至此,完成了本实例的全部制作 过程。

4.4

本章小结

Visual FoxPro 是一个快速、高效的数据库管理与开发工具,它的优势在于组织信息、运行查 询、创建集成的关系型数据库系统,以及开发功能全面的数据管理应用程序。相对而言,复杂 的界面设计和系统底层功能的开发不是它的强项。借助于 ActiveX 控件强大的功能,可以弥补 Visual FoxPro 在这方面的不足,本实例就很好地说明了这一点。另外,还可以利用 MSComm 控 件快速地开发出具备串口数据通信能力的数据库应用程序,或利用 MAPI Message 控件和 MAPI


电脑工程师丛书

118

Visual FoxPro 高级应用实例

Session 控件方便地为数据库应用程序添加收发 E-mail 的功能等。充分利用好 Visual FoxPro 面 向对象的特性以及对 ActiveX 控件良好的支持,可以让你在最短时间内开发出专业的应用程 序。 TreeView 控件是比较受程序员欢迎的常用 ActiveX 控件之一,在 Visual FoxPro 中的应用也 很广,但关于它的说明文档较少,通过本实例中对该控件的详细说明以及实际的代码,相信读 者可以很快地把它应用到自己的程序中。 TreeView 和 ImageList 控件作为 Windows 的通用组件 成员,本实例中探讨的方法及设计思想也同样适用于别的一些 Windows 开发环境,所要做的只 是把 Visual FoxPro 命令程序换成相应的编程语言而已。 最后,要提醒广大 Visual FoxPro 程序员,ActiveX 控件是一把“双刃剑”,使用得恰到好处可 以增强应用程序的功能与可用性,但滥用 ActiveX 控件会带来不必要的麻烦和程序性能的下 降。


OLE 与自动化 ———应用程序间的相互通信

5

本章技术要点:  OLE 的基本概念。  OLE 对象的嵌入、链接及现场编辑。  OLE 自动化。  OLE 拖放。

5.1

本例提要

在 Visual FoxPro 的表单或通用型字段中,可以包含从其他应用程序中获得的数据(如文本 数据、声音数据、图片数据或视频数据),可以使用创建这些数据的应用程序,以可视的方式查 看或操作这些数据,或者以自动化(Automation)的形式控制应用程序;同样的,也可以在别的应 用程序中使用 Visual FoxPro 的数据和功能。实现这些功能的途径是在应用程序中使用 OLE 与 自动化技术。 在本章的学习过程中将会制作多个 OLE 和自动化技术的小示例,并在最后的实例制作中 使用 Office 自动化功能制作高性能的报表。

5.2

技术分析 5 . 2 . 1 什么是 OLE

在学习本章之前,首先要搞明白什么是 OLE?也许你会不假思索地回答:OLE 就是对象的 链接与嵌入(Object Linking and Embedding)。回答正确。但这只是从字面上解释了 OLE 这个术 语,而没有任何实际的意义。“对象的链接与嵌入”到底指的是什么?让我们首先从 OLE 的发 展历史开始介绍。 1 . OLE 的历史 计算机图形用户界面的出现,使得人们开始习惯于用剪贴板进行各种操作:剪切、复制和 粘贴。这样可以很容易地创建一个复合文档:同时包含了文本、图像及其他格式的数据内容。 而在此之前,可能需要分别在不同的应用程序中把它们打印出来,然后用剪刀和胶水把它们粘


电脑工程师丛书

120

Visual FoxPro 高级应用实例

贴在一起。 但是剪贴板也存在一定的局限性:数据在应用程序之间是静止的。如果需要修改复合文 档中的图片,必须先把它还原成原始的格式并且在图像编辑软件中修改它,然后再重新复制到 复合文档中。为此,微软开发了动态数据交换(Dynamic Data Exchange,DDE)技术,它实际上是 一组复杂的通信协议,可以简化数据在应用程序之间传递的过程和步骤。 在动态数据交换协议的基础上出现了 OLE 1 . 0,它大大提高了创建和管理复合文档的能 力:原始数据以“嵌入”或“链接”的形式包含在复合文档中,并且包含了格式等信息。如果需要 修改其中的图片,只需要简单地双击鼠标,就会自动激活图像编辑软件进行修改。 在 OLE 1 . 0 中,复合文档对象实际上就是一个小型的可重用组件,它可以以插件的形式插 入到别的应用程序中从而获得功能的扩展,更为重要的是无需对使用这些插件的应用程序或 者称之为“容器”作任何修改,即可插入不同的复合文档对象:图表、声音、视频、图像等。而且 这种工作方式和当时的动态链接库(DLL)、Visual Basic 控件(VBX)比起来具有更强大的功能和 更灵活的特性。在这个思想的指导下发布了 OLE 2 . 0,不仅仅提高了对复合文档的处理能力, 而且加强了对组件对象的支持。 OLE 2 . 0 的核心就是强大的、可扩展的组件对象模型(Component Object Model,COM),通过 COM 可以找到很多传统技术难题的解决方案,如可扩展的服务器架构体系,和语言无关的、独 立于应用程序之外的可重用对象。 2 . OLE 的定义 从 OLE 的发展历史看,它应该是一个以面向对象为基础的统一服务环境,在这个环境中 可以定制或扩展自己的组件,也可以使用别人的组件,这些组件之间遵循着共同的规范,相互 之间可以很容易地集成在一起。一个组件可以封装一个或多个对象,一个或多个组件提供一 项 OLE 服务(OLE services),这就是 OLE 的本质。 组件和 OLE 服务通常是联系在一起的,统称为 OLE 服务器(OLE Server);另外还有使用组 件或 OLE 服务的应用程序(称为 OLE 客户端,OLE Client),这两者结合在一起就构成了 OLE 的 C/S 模型。 OLE 客户端通过调用这些方法或函数即可获得 OLE 服务器的功能,如图 5 . 1 所示。

图 5.1

OLE C/S 模型


OLE 与自动化

121

3 . OLE 的主要技术特性及服务 虽然 OLE 技术起源于结构化存储和复合文档,但是早已突破了这个技术范畴。现在的 OLE 技术涵盖面是相当广的,大约包含几百个 32 位 API 函数和接口实现,但是从应用的角度 来看,它大致可以分为以下三类: ① 访问 OLE 组件。 ② 定制 OLE 组件。 ③ 通过自定义的 OLE 组件扩展系统的对象环境。 本章将要讨论的大部分 OLE 技术细节包括可连接的 OLE 对象、OLE 文档、OLE 自动化、定 制的 OLE 组件及 COM、远程 COM(DCOM)、统一数据传输等。这些还不是 OLE 的全部,上一个 实例中讨论的 ActiveX 控件技术实际上也属于 OLE 技术的一部分。有意思的是,结构化存储 和复合文档等内容偏偏不在本章的讨论内容之列,因为一个数据库系统本身就是结构化存储 和复合文档的特定实现,所以,如果在 Visual FoxPro 应用程序中需要一个复合文档的话,自由 表应该是最佳的选择。

5 . 2 . 2 对象的链接和嵌入 很多人通过对象的链接和嵌入获得了对 OLE 技术的最初认识,因此我们选择它作为介绍 OLE 技术的起点。 1 . 嵌入和链接数据 一个可插入的 OLE 对象来自于支持 OLE 的应用程序,例如,当你的系统中安装了 MS Office 办公软件之后,一个 Office 格式的文档(如 Word 文档、Excel 电子表格)就成为可插入到其 他应用程序中的 OLE 对象。插入的方式分为两种:嵌入和链接,这两种插入方式的不同在于 数据的存储位置不同。当使用嵌入时,数据被存储到应用程序的内部,但链接却不是如此,如 把一个 Excel 电子表格嵌入到 Visual FoxPro 数据库表的通用型字段中时,该电子表格被复制到 数据库表中(保存在 FPT 文件内),然而对于链接而言,数据库表只保存一个对该文件的引用, 而不是电子文档本身。图 5 . 2 说明了这两者的区别。

图 5.2

嵌入与链接的区别

要在一个 Visual FoxPro 的数据库表中插入 OLE 对象的步骤如下: ① 修改表结构,添加通用型字段。 ② 浏览该表并双击通用型字段,打开通用型字段的窗口。


电脑工程师丛书

122

Visual FoxPro 高级应用实例

③ 从“编辑”菜单中选择“插入对象”命令,弹出对话框如图 5 . 3 所示。此时可以选择“新 建”或“由文件创建”选项创建一个 OLE 对象,如果选择“由文件创建”选项的话,还可以选择插 入的方式,默认情况下为嵌入,除非选中“链接”复选框。

图 5.3

插入 OLE 对象

此外,也可以使用命令在程序中向数据库表添加 OLE 对象,下面的代码将把用户选择的 Excel 文档作为 OLE 对象嵌入到 OLETable 表的 OLEField 字段中。 cFile = GETFILE("打开 Excel 电子表格( . xls):xls","打开") IF ! EMPTY(cFile) APPEND BLANK APPEND GENERAL OLEField FROM cFile ENDIF 如果需要链接而不是嵌入时,可以在 APPEND GENERAL 语句的末尾加上 LINK,如果需要 指定服务程序,可以使用 CLASS 子句,如 CLASS "Excel . WorkBook",至于服务程序的命名规则稍 后将会谈到。 嵌入与链接有各自的优缺点,应该根据应用的场合和要求选择其中的一种,如在以下场合 通常选择对象的嵌入: ① 希望应用程序在其他计算机上也能正常运行。 ② 希望源文件不在时也能正常地编辑。 ③ 源文件可能不可用,如在网络服务器上。 ④ 应用程序不需要源文件的最新版本。 ⑤ 源文件不需要被多个应用程序使用。 相应的,在以下场合通常选择对象的链接: ① 源文件较大,如多媒体视频文件等。 ② 可能被外部应用程序修改。 ③ 源文件可以在计算机上或通过网络进行更新。 ④ 源文件必须与其他应用程序共享。 当使用链接方式插入对象时,还可以选择更新的方式,默认情况下,对源文件的修改将直 接反映在应用程序中,即自动更新,也可以设置为手动更新。在浏览数据库表的状态下双击打 开通用型字段,在“编辑”菜单中选择“链接”命令,打开如图 5 . 4 所示的“链接”对话框,修改其 更新方式为“手动”,这样就在源文件和应用程序之间建立了一个缓冲,可以只在需要更新时单


OLE 与自动化

123

击“立即更新”按钮进行更新操作。如果选择“断开链接”,则数据库表中的数据将成为静态数 据。

图 5.4

链接对象的更新方式

2 . OLE 对象的现场激活和编辑 在表单或报表中,可以显示表中通用型字段内链接或嵌入的 OLE 对象。在表单设计器中 添加“OLE 绑定控件(OleBoundControl)”到表单中,并设置其 ControlSource 属性为数据库表的相 应通用型字段即可。如果要在报表上显示通用字段中的数据,需要在该报表上添加通用型字 段。对于图片格式的文件,可以使用图像容器直接将图片或图标从外部文件添加到报表中,对 于其他类型的数据(如 Word 文档或 Microsoft Excel 电子表格),只有当它们在 Visual FoxPro 表 的通用字段中事先链接或嵌入后才能被包含进来。在报表设计器中选择“图片 /ActiveX 绑定 控件”,在窗口中单击鼠标弹出如图 5 . 5 所示的对话框,在“图片来源”中输入图片文件的位置 或者数据环境中的通用型字段名。默认情况下,图片或 OLE 对象将显示为默认大小并针对图 文框的大小进行裁减,也可以选择“缩放图片”。 有些 OLE 对象支持现场激活(In - Place Activation)与可视编辑(Visual Editing),也就是说可 以直接在应用程序的窗口中激活 OLE 对象的服务程序,并以可视化的方式对对象进行编辑。 需要注意的是,只能现场编辑嵌入的对象,而不能编辑链接的对象。 Microsoft Graph 是一种用于图形化显示数据的图表 OLE 对象,下面将通过它来体验 OLE 对象的现场编辑。在数据库表中的通用型字段插入“Microsoft Graph 图表”,并在表单中添加 “OLE 绑定控件”与该字段绑定,运行时表单界面如图 5 . 6 所示。 在 Graph 对象上双击,该 OLE 对象就被激活,如图 5 . 7 所示。此时可以在数据表中修改数 据,也可以设置图表的外观和格式。对比图 5 . 6 和图 5 . 7 可以发现,OLE 对象在激活时并没有 脱离原先的运行环境,但是 Visual FoxPro 的菜单和工具栏已经被换成自动服务程序的菜单和 工具栏了。当然,也可以在另一个窗口中打开自动服务程序,然后编辑数据或显示特性,返回 到应用程序时新值将反映到应用程序中。


电脑工程师丛书

124

Visual FoxPro 高级应用实例

图 5 . 5 “报表图片”对话框

图 5.6

5.2.3

运行嵌入 Graph 对象的表单

OLE 自动化

自动化(Automation)的好处是显而易见的,它可以极大地提高人们的工作效率,如一台联 合收割机可以在相同时间内完成大约 500 个使用镰刀的农民的工作量;同时自动化也使得一 些复杂的操作可以被普通用户所接受,如果不是自动测光、自动对焦技术的逐渐成熟,35mm 相 机也不会这么快走进千家万户。计算机本身就是一个自动化工具,操作系统自动地完成计算 机的软硬件资源管理,开发工具自动地把高级程序语言转换成二进制的机器码,现在流行的计


OLE 与自动化

125

图 5 . 7 “报表图片”对话框

算机建模工具还能直接把我们所需要完成的任务转换成程序代码。 实现自动化有多种途径,早先是通过“宏(Macro)”来实现的。所谓宏就是命令的集合,每 个命令完成一个相对独立的操作,把它们组合在一起就构成了一个宏。使用宏可以实现一些 简单的自动化,但是随着应用程序越来越多,功能越来越复杂,宏就显得有点力不从心了。 OLE 通过创建一个统一的对象环境使之成为自动化技术的一个较为成熟的解决方案。 “主”是客户端程序,或者称之为控制器, OLE 的 C/S 模型已经明确地定义了主从关系, “从”是 OLE 组件,或者称之为自动化服务器,OLE 自动化就是指自动地控制 OLE 服务程序。 Visual FoxPro 既可以扮演控制器角色又可以扮演 OLE 服务器角色。例如,可以在 Visual FoxPro 中调用 Word 应用程序,或者被 Visual Basic 调用。 1 . 访问 Word 应用程序的 OLE 对象 从 Visual FoxPro 的命令窗口中执行如下命令,将会打开一个 Word 窗口: oWord = CREATEOBJECT("Word . Application") oWord . Visible = . T. 命令虽然很简单,但却可以在 Visual FoxPro 中得到一个 Word 服务程序的 OLE 对象,并且 可以通过控制这个 OLE 对象的 Visible 属性控制该服务程序的行为。 试着继续执行如下命令: oDoc = oWord . Documents . Add 一个新的 Word 文档被创建了,并且返回了该文档对象的引用。可见 oWord 对象实际上是 一个对象集合,不同的功能被包含在不同的对象里。 oWord 对象的层次关系是非常复杂的,要 查阅它所有的对象以及包含的属性和方法,可以在 Word 中选择“工具”菜单 - >“宏”子菜 单 - >“Visual Basic 编辑器”命令,运行 Visual Basic 编辑器来查阅。单击 Visual Basic 编辑器 “视图”菜单中的“对象浏览器”命令,在对象浏览器中选择库为“Word”,即可看到 Word OLE 对 象的完整模型,如图 5 . 9 所示。


电脑工程师丛书

126

Visual FoxPro 高级应用实例

图 5.8

Word 对象浏览器

继续执行如下命令,向新建的 Word 文档中传递一些数据: oWord . Selection . Font . Bold = 9999998 oWord . Selection . Font . Size = 18 oWord . Selection . Font . Underline = 1 oWord . Selection . Font . Name = "黑体" oWord . Selection . TypeText("OLE 自动化") Selection 对象代表窗口或窗格中的当前所选内容,即文档中被选定(或突出显示的)的区 域, 若文档中没有所选内容,则代表插入点。每个文档窗格只能有一个活动的 Selection 对象, 并且整个应用程序中只能有一个活动的 Selection 对象。运行以上命令后,将得到如下一行文 字:

OLE 自动化 由此可见,在 Word 中进行的文字处理工作完全可以通过 OLE 对象来完成,而且不需要复 杂的编程,只需要合理地设置对象的属性,调用适当的方法即可。如果需要了解某个对象、属 性和方法的有关信息,可以查看 Word 的联机帮助。 可能会有很多人对命令中的 9999998 感到困惑,实际上它是 Word 内置的一个常数,在查 阅《Microsoft Office Visual Basic 参考》 (Office 联机帮助的一部分)时将会看到类似于下面这样的 代码: Selection . Font . Bold = wdToggle Selection . Font . Underline = wdUnderlineSingle wdToggle、wdUnderlineSingle 是这些内置常数的宏替代,但是它们不能在 Visual FoxPro 中被 识别并转换成相应的常数,这给编写代码带来了不便。鉴于《Microsoft Office Visual Basic 参考》


OLE 与自动化

127

是我们在使用 Office 自动化服务时最主要的参考手册,所以有必要把这些宏替代字符串转换 成相应的常数,以便于在 Visual FoxPro 中使用。这些常数都放置在相应的 olb 类型库文件中, 如 Word 的类型库文件为 Office 安装目录下的 MSWORD. olb 文件,可以用程序读出这些常数, 还可以用“ # DEFINE 宏字符串 常数 ”的形式保存为文本文件,这样只需要在程序中 Include 该 文件即可。具体的程序代码参见实例过程。 2 . 从 Word 中调用 Visual FoxPro 实际上,可以从任何一种 OLE 控制器中调用 Visual FoxPro 自动服务程序,此处之所以选择 Word 应用程序,仅仅是由于它最常见且支持 VBA 语法。单击 Word 的“工具”菜单 - >“宏”子 菜单 - >“Visual Basic 编辑器”菜单项,将出现一个类似于 Visual Basic 的开发环境,如图 5 . 9 所 示。

图 5.9

Visual Basic 编辑器

插入一个模块并编写如下代码: Sub vfpole() Dim vfp As Object Set vfp = CreateObject("VisualFoxPro. Application") vfp . Visible = True End Sub 运行该模块,将会运行一个 Visual FoxPro 实例程序。 Application 对象提供了很多有用的属 性和方法设置,通过它们可以实现对 Visual FoxPro 的自动化控制,上面的例子中已经用到了 Visible 属性,表 5 . 1 列出了 Application 对象的主要属性。


电脑工程师丛书

128

Visual FoxPro 高级应用实例

表 5.1

Application 对象的主要属性及其含义

属性

含义

ActiveForm

当前活动窗体的引用

ActiveProject

当前项目管理器中项目对象的引用

Caption

标题栏文字

DefaultFilePath

默认路径

FullName

Visual FoxPro 的运行路径

Height

Visual FoxPro 实例窗体的高度

Left

Visual FoxPro 实例窗体距屏幕左侧的距离

OLERequestPendingTimeout

OLE 自动化请求超时设定

OLEServerBusyRaiseError

OLE 自动化请求被拒绝时是否显示错误信息

OLEServerBusyTimeout

OLE 自动化请求重试时间设定

Parent

Visual FoxPro 实例窗体的父成员或者容器

StartMode

Visual FoxPro 实例的启动模式

StatusBar

状态栏信息

Top

Visual FoxPro 实例窗体距屏幕顶边的距离

Version

Visual FoxPro 实例的版本信息

Visible

显示或隐藏 Visual FoxPro 实例窗体

Width

Visual FoxPro 实例窗体的宽度

Visual FoxPro 的 Application 对象还提供了一些很有用的方法,利用这些方法可以快速地传 递数据或执行 Visual FoxPro 的命令和表单。 ① DataToClip。 功能:把一组记录以文本的方式复制到剪贴板。 语法:DataToClip([工作区 | 表别名 ] [,nRecords][,nClipFormat ] ) : 指定需要复制的记录数, 默认情况下复制从当前记录开始的所有记录。 nRecords nClipFormat:指定字段的分界符,可能的取值为:1 - 以空格分界,3 - 以制表符(Tab)分 界。 ② DoCmd。 功能:执行一个 Visual FoxPro 命令。 语法:DoCmd(cCommand)。 cCommand:一条合法的 Visual FoxPro 命令字符串。 ③ Eval。 功能:计算一个表达式的值。 语法:Eval(cExpression)。 cExpression:可以是字符串、Visual FoxPro 表达式、变量、数组元素和任何类型的字段变量。 返回值:表达式的计算结果。 ④ Help。 功能:打开帮助窗口。


OLE 与自动化

129

⑤ Quit。 功能:结束 Visual FoxPro 实例的运行。 ⑥ RequestData。 功能:创建一个包含当前打开的表中数据的数组。 语法:RequestData([工作区 | 表别名 ][,nRecords] )。 nRecords:指定数组中存储的记录数,默认情况下将保存从当前记录开始的所有记录。 返回值:数组。 Visual FoxPro 还提供了一个动态链接库 Fpole . dll,通过它可以从允许调用 API 但不支持自 动化的应用程序中运行 Visual FoxPro 命令或计算表达式。该动态链接库包含以下几个函数: ⑦ FoxDoCmd()。 功能:运行 Visual FoxPro 命令。 语法:FoxDoCmd(cFoxCommand ,cOptions)。 cFoxCommand:指定要运行的 Visual FoxPro 命令。 “” (空格)- 运行指定命令但不激活 Visual cOptions:运行命令时的选项,可能的取值包括: “a”- 激活 Visual FoxPro 主窗口并运行指定命令、 “i”- 如果未运行 Visual FoxFoxPro 主窗口、 “t”- 如果正在 Visual FoxPro 中运行另一个程序则不执行命令 Pro 则激活之但不显示主窗口、 并且显示错误信息。 返回值:如果命令正确执行返回 0,否则返回 - 1。 ⑧ FoxEval( )。 功能:计算 Visual FoxPro 表达式。 语法:FoxEval(cExpression ,cBuffer ,nLen)。 cExpression:要计算的 Visual FoxPro 表达式。 cBuffer:存储 Visual FoxPro 表达式值的位置,通常为一个引用参数。 nLen:cBuffer 的长度。 返回值:如果表达式计算成功返回 cBuffer 的长度,否则返回 - 1。 ⑨ SetErrMode()。 功能:指定是否在消息框中显示 Fpole . dll 的错误信息。 ⑩ SetOleObject( )。 功能:指定调用 FoxDoCmd()和 FoxEval( )时创建的 OLE 类。  CloseIt( )。 瑡 瑏 功能:关闭使用 FoxDoCmd()或 FoxEval( )时所创建的 OLE 对象。  GetLastErr( )。 瑢 瑏 功能:检查最近发生的错误。 语法:GetLastErr(cBuffer ,nLen)。

5 . 2 . 4 定制的自动化服务 通过前面对 OLE 的介绍,大家应该对 OLE 自动化有了一个较为准确的认识:一个自动化 服务实际上就是把应用程序的数据和功能封装到对象中,从别的应用程序中生成该对象的一 个实例并访问其属性和方法即实现了 OLE 自动化。除了利用 Visual FoxPro 自身提供的 OLE 自


电脑工程师丛书

130

Visual FoxPro 高级应用实例

动化服务程序之外,还可以定制具有特定功能的 OLE 自动化服务程序。 1 . 第一个自动化服务 编写一个自动化服务程序也许并不是一件很复杂的事情,至少在 Visual FoxPro 中是这样 的, 甚至和编写一个普通的 Visual FoxPro 应用程序没有太大的区别。下面从一个最简单的自 动服务程序开始介绍。该程序仅仅是弹出一个消息窗口,但却能很好地说明利用 Visual FoxPro 创建自动服务程序的关键。 新建一个项目命名为 olefirst,新建一个程序文件 hello. prg,并编写如下代码: DEFINE CLASS HelloOle AS custom OLEPUBLIC PROCEDURE INIT MessageBox("Hello OLE!","Visual FoxPro OLE 自动服务程序实例") ENDDEFINE 这段代码定义了一个名为 HelloOle 的 custom 类,它惟一的动作就是在初始化时弹出一个 消息对话框。其中的关键字是“OLEPUBLIC”,它的作用体现在联编该项目的时候。在项目管 理中设置 hello. prg 为主文件(实际上它是项目中惟一的文件),联编项目时将会在状态栏中显 示一条信息:正在创建类型库并注册 COM 组件(该信息稍纵即逝,请细心观察)。这个过程除 了生成 olefirst . exe 文件之外,还生成了 olefirst . tlb(OLE 类库文件)和 olefirst . VBR(注册表信息入 口文本文件)。更重要的是,联编过程在系统中注册了一个名为 olefirst 的类库,其中包含了 HelloOle 对象,这是一个 OLE 自动服务对象,可以通过 Visual Studio 中包含的 OLEViewer 工具查 看。OLEViewer 包含的主要功能有: ① 查看系统中注册的 COM 对象,并可以修改它们的设置。 ② 查看类型库信息,通过它可以查看一个 ActiveX 控件的事件、属性和方法。 ③ 查看每个类的注册入口。 从“开始菜单”中找到“OLE View”并运行,展开 Type Libraries 节点,将会看到当前系统中所 有已注册的类型库,其中包含这样一条信息:olefirst type library(Ver 1 . 0)。选中该节点,右边的 窗格中明确给出了可执行文件的路径就是刚才生成的 olefirst . exe 文件,如图 5 . 10 所示。

图 5 . 10

用 OLE Viwer 查看类库信息

在类库名上双击,还能查看类库中提供的接口,也就是 OLE 对象(如果你还不明白为什么


OLE 与自动化

131

接口就是对象,请重温图 5 . 1),以及对象中封装的方法,如图 5 . 11 所示。

图 5 . 11

类型库查看器

下一步,就可以从任何一个 OLE 控制器中访问这个自定义的 OLE 服务了,仍然以 Word 中 的 Visual Basic 编辑器为例,创建一个模块并添加如下代码: Sub testole() Dim o As Object Set o = CreateObject("olefirst . helloole") End Sub 图 5 . 12 定制的 OLE 服务运行结果 运行该模块,由于 OLE 对象在创建时会 自动调用 Init 方法,因此在 HelloOle 对象的初始化事件中定义的消息窗体将自动弹出,如图 5 .

12 所示。 需要注意的是,通常情况下 OLE 自动服务对象是包含在类库中的,因此在用 CREATEOB“类库名 . 对象名”,其中的类库名就是可执行文件的文件 JECT 创建对象时,需要包括两部分: 名(如 olefirst),对象名是在 Visual FoxPro 中用 DEFINE CLASS 指定的对象名,而不是声明该对象 的程序文件名。一个类库可以包含多个 OLE 对象,就像一个 Visual FoxPro 项目中可以定义多 个 CLASS 一样。 2 . 在 OLE 自动化服务程序中访问数据库 上一个例子说明了利用 Visual FoxPro 构造一个 OLE 自动化服务最基本的步骤:定义一个 对象 - > 把功能封装到对象的方法中 - > 设置对象的“OLEPUBLIC”关键字 - > 联编项目 - > 自动完成类库及 OLE 自动化服务的注册 - > 从 OLE 控制器中注册 OLE 自动服务对象的实 例 - > 调用对象的方法。理论上说,可以在自动服务程序中做任何 Visual FoxPro 应用程序可 以做到的事情,如访问 Visual FoxPro 数据库。不过,在此之前还有一些细节问题需要进一步讨 论。 (1)进程外和进程内自动服务程序 一个自动服务程序可以以两种形式存在:exe 文件或 dll 文件。两者的区别在于服务程序 的运行方式不同:进程外或者进程内。这里所说的进程是指 OLE 控制器的进程,如果是 exe 文 件, 则有它自己独立的进程地址空间,如果是 dll 文件则包含在 OLE 控制器进程地址空间内。 前者将有更多的系统资源开销,但是相对稳定;后者的运行速度会快一些,但是服务程序中的


电脑工程师丛书

132

Visual FoxPro 高级应用实例

错误将导致整个应用程序的崩溃。在 Visual FoxPro 中联编项目时,可以选择服务程序的形式, 如图 5 . 13 所示。如果选择 dll 文件形式,还可以选择单线程或者多线程,前者只能被一个 OLE 控制器访问,后者能够同时被多个 OLE 控制器访问。

图 5 . 13

自动服务程序联编选项

exe 文件和 dll 文件的自动服务程序还有一个很重要的区别是图形交互界面。对于 dll 文 件形式的进程内服务程序来说,不允许包含图形用户界面,确切地说,是不允许在运行时显示 图形用户界面。因此,在程序中使用“WAIT WINDOW”、 “MESSAGEBOX”这样的命令将导致出 错,但如果仅仅把表单当成是容器使用而不显示出来则是被允许的。上面的例子中如果选择 联编类型为 dll 文件,再次在 Visual Basic 编辑器中运行 testole 模块,将会出现如图 5 . 14 所示的 错误。

图 5 . 14

进程内服务程序调用图形用户界面的错误信息

(2)自动服务程序的运行目录 假如要在自动服务程序中访问数据库,那么数据库应该放在什么位置呢?如果不打算在 代码中使用绝对路径的话,需要搞清楚自动服务程序的运行目录。 在通常的 Visual FoxPro 中,利用 HOME()系统函数或者 SYS(5)+ SYS(2003)可以获得程序 的运行路径,然后在程序中 SET DEFAULT 到这个路径即可,但是在 OLE 自动服务程序中情况 将有所变化。把第一个 OLE 服务程序中的程序代码修改如下: DEFINE CLASS HelloOle AS custom OLEPUBLIC PROCEDURE INIT MessageBox(HOME(),SYS(5)+ SYS(2003)) ENDDEFINE 再次运行 Visual Basic 编辑器中的 vfpole 模块,将会得到如图 5 . 15 所示的运行结果(视系


OLE 与自动化

133

统安装路径不同而会有所不同)。

图 5 . 15

修改后的 OLE 自动服务程序运行结果

可见,HOME()函数及 SYS(5)+ SYS(2003)已经不再返回程序文件的所在路径,而是指向 系统目录 SYSTEM32,这意味着 OLE 自动服务程序是从一个特定的系统目录启动的,因此,如 果在程序中使用相对路径的数据库,将会在系统目录 SYSTEM32 下寻找数据库。 当然,把系统目录塞满应用程序的 dbf 文件显然是不可取的,因此找到自动服务程序所在 的主目录才是解决问题的正确途径,有两个 Win32 API 函数能够做到这一点。对于独立进程 的自动服务程序(exe 文件),可以调用 GetModuleFileName()函数获得当前进程的可执行文件 所在路径;如果是进程内的自动服务程序(dll 文件),则需要先调用 GetModuleHandle()函数来 获得服务程序的句柄,然后把该句柄作为参数传递给 GetModuleFileName()函数。下面的代码 将非常有用,因为不论在何种情况下,它总能正确地找到自动服务程序所在的主目录: DEFINE CLASS HelloOle AS CUSTOM OLEPUBLIC PROCEDURE INIT LOCAL buf,nlen buf = SPACE(400) DECLARE INTEGER GetModuleFileName IN win32api ; INTEGER,STRING @,INTEGER IF

-

VFP. STARTMODE = 3 && inproc dll

DECLARE INTEGER GetModuleHandle IN win32api STRING nlen = GetModuleFileName(GetModuleHandle(THIS. srvname + ". dll"),; @buf,LEN(buf)) ELSE nlen = GetModuleFileName(0,@buf,LEN(buf)) ENDIF buf = LEFTC(buf,nlen) MESSAGEBOX(buf ,LEFTC(buf,RATC(’h ’,buf)- 1)) ENDDEFINE 重新联编项目后,从 Visual Basic 编辑器中运行 vfpole 模块,将得到如图 5 . 16 所示的运行 结果,其中的路径信息与图 5 . 11 中的可执行文件路径相同。

图 5 . 16

返回自动服务程序主目录


电脑工程师丛书

134

Visual FoxPro 高级应用实例

(3)访问数据源的自动服务程序 下面将创建一个进程内的自动服务程序,该程序中使用的 Visual FoxPro 自由表包括两个 字符型字段:input 和 output。前者顺序包含了 26 个英文字母和 10 个阿拉伯数字,后者包含同 样的内容,但是是乱序的,两个字段之间建立了一对一的字符映射。自动服务程序的功能就是 对接收到的字符串根据自由表中的字符映射关系进行变换或者反变换。这个例子能够很好地 说明如何在自动服务程序中访问 Visual FoxPro 数据库,如何封装数据和功能,如何与 OLE 控制 程序间通信等问题。 新建项目并命名为 oledbf,按照上述要求设计自由表的结构并录入必要的数据(见表 5 . 2), 并保存为 ole . dbf。 表 5.2

ole. dbf 中保存的字符映射关系

input 字段

a b c d e f g h i j k l mn o p q r s t u v wx y z0 1 2 3 4 5 6 7 8 9

output 字段

f c p j g q w4 v r 5 z 0 6 x 9 y m u 2 3 1 b 8 s 7 e a h n t i l d o k

新建一个 encode 类,基类为 custom,并保存在 code . vcx 文件中。为该类新建自定义 err 属 性和 encoding、decoding 方法。 err 属性用于记录错误信息,默认值为 0,encoding 和 decoding 方法 分别完成字符串的变换或者反变换,即从 input 字段映射到 output 字段,或者从 output 映射到 input 字段。 该类的 Init 事件主要完成系统变量的设置并以共享方式打开数据库,代码如下: SET TALK OFF SET EXCLUSIVE OFF LOCAL buf,nlen buf = SPACE(400) DECLARE INTEGER GetModuleFileName IN win32api INTEGER,; STRING @,INTEGER IF

-

VFP. STARTMODE = 3 && inproc dll

DECLARE INTEGER GetModuleHandle IN win32api STRING nlen = GetModuleFileName(GetModuleHandle(THIS. srvname + ". dll"),; @buf,LEN(buf)) ELSE nlen = GetModuleFileName(0,@buf,LEN(buf)) ENDIF buf = LEFTC(buf,nlen) buf = LEFTC(buf,RATC(’h ’,buf)- 1) SET DEFAULT TO buf + " h " USE ole . DBF 该类的 encoding 方法主要接收字符串并完成字符串的变换,最后返回结果,代码如下: LPARAMETER inputstr LOCAL nLen,outputstr,inputstr


OLE 与自动化

135

nLen = LEN(inputstr) outputstr = "" IF nLen = 0 THIS. err = 1 && 输入字符串为空 RETURN ENDIF SET ORDER TO INPUT FOR inputstr = 1 TO nLen SEEK SUBSTR(inputstr,inputstr, 1) IF ! FOUND() THIS. err = 2 && 输入的字符超出变换范围 RETURN ELSE outputstr = outputstr + OUTPUT ENDIF ENDFOR THIS. err = 0 RETURN outputstr decoding 方法与 encoding 方法类似,仅仅需要修改主控索引为 output 并输出 input 字段,故 不再列出详细代码。 不要忘了告诉系统这是一个 OLE 对象,前面的例子中是通过“OLEPUBLIC”关键字指定的, 也可以在类设计器中指定。打开类设计器,单击“类”菜单 - >“类信息”菜单项,在“类信息”对 话框中选中“OLE 公共”复选框,如图 5 . 17 所示。

图 5 . 17

设置类的 OLEPUBLIC 属性


电脑工程师丛书

136

Visual FoxPro 高级应用实例

现在,oledbf 项目中仅包含了自由表和类库文件,它们都不能作为主文件,为了正确地联 编项目,可以向项目中添加一个空的程序文件并把它设置为主文件,然后联编程序。用 OLEViewer 查看系统的类库信息,可以看到 oledbf 类库及其中的 encode 对象已经被注册,并且包含 了自定义的 err 属性和 encoding、decoding 方法(见图 5 . 18),其中 err 属性是可读写的,因此在 Methods 中将出现两次,分别为 propput 和 propget。

图 5 . 18

查看自定义的 oledbf 类库信息

下一步试着在 OLE 控制器中调用这个自动服务程序。再次打开 Visual Basic 编辑器并插 入一个用户窗体,从工具箱中添加窗体控件到这个用户窗体中,如图 5 . 19 所示。

图 5 . 19

Visual Basic 编辑器中的用户窗体

为“Encode”按钮添加如下 VBA 代码: Private Sub CommandButton1 - Click() Dim o As Object Dim strout As String Set o = CreateObject("oledbf. encode") strout = o. Encoding(TextBox1 . Text) If o. Err = 1 Then MsgBox("空字符串!") Else If o. Err = 2 Then MsgBox("包含非法字符!") Else MsgBox(strout) End If


OLE 与自动化

137

End If End Sub “Decode”按钮的代码与此相仿,只是调用的是 oledbf. encode 对象的 decoding 方法。运行这 个用户窗体,在文本框中输入由英文字母和数字组成的字符串,单击 Encode 和 Decode 按钮即 可看到经过自动服务程序映射后的字符串。 定义自动服务程序的意义是:可以发布一个用 Visual FoxPro 实现的、包含了特定功能的 文件而不需要考虑用户所使用的开发工具和编程语言, 只要对方支持 OLE 控制器,就可 DLL 以像在 Visual Basic 编辑器中那样用 CreateObject()创建对象并访问其属性和方法,惟一的要求 是在使用前用如下命令注册这个 DLL 文件:REGSVR32 oledbf. DLL。 3 . 远程自动化 本章讨论的内容到目前为止还仅限于 OLE 控制器和 OLE 自动服务程序都运行在同一台 计算机上的情况,实际上,OLE 控制器和 OLE 自动服务程序完全可以运行在通过网络连接的 两台不同计算机上,这种情况称为远程自动化。如果说本地自动化是基于 COM 的话,远程自 动化就是基于分布式 COM(DCOM)。我们把运行着 OLE 自动服务程序的计算机称为 OLE 组件 服务器,而把运行着 OLE 控制器的计算机称为 OLE 组件客户端,请注意它们和 OLE 的 C/S 模 型(见图 5 . 1)中的服务器、客户端的区别与联系。要把组件服务器和客户端连接在一起并且 实现远程自动化,除了网络的物理连接之外,还需要特定的网络协议及相应的配置。 远程自动化的好处是:假如发布了一个自动化服务程序后又对其进行了升级,使用本地自 动化服务必须重新发布该程序,并且所有的用户必须重新注册新版本的自动服务程序才能使 用,而远程自动化只需要更新服务器端的自动服务程序即可。下面以上文中的 oledbf 自动服 务程序为例来介绍建立远程自动化的必要步骤。不过有一点,远程自动化只能使用进程外服 务程序,因此需要在 Visual FoxPro 中重新联编项目并选择“Win32 可执行程序 / COM 服务程序 (exe)”,生成 oledbf. exe 文件。 (1)组件服务器配置 ① 拷贝自动服务程序的可执行文件(exe 文件)并注册。 通常情况下开发组件的计算机不一定就是提供自动服务的组件服务器,因此需要把自动 服务程序复制到服务器上并注册。 Visual FoxPro 编译的自动服务程序是自动注册的,也就是 说,只要以 REGSERVER 为选项运行一次该程序即可,命令如下: oledbf. exe /REGSERVER 运行过程没有任何可视的界面。如果组件中使用了数据库或其他相关文件,也需要把它 们复制到相应的目录下。 ② 运行并设置“远程自动化连接管理器”。 单击“开始”菜单 - >“运行”,输入“RACMGR32 . EXE”,将启动远程自动化连接管理器,如 图 5 . 20 所示。 在 COM 类中找到自定义的自动服务对象 oledbf. encode,在“客户访问”选项卡中选中“允许 远程激活”复选框, “系统安全策略”选择“允许通过关键字进行远程创建”;在“服务器连接”选 项卡中选择“远程传输”为“远程自动化”, “网络地址”填本机的 IP 地址(广域网 / 局域网)或者 机器名(局域网), “网络协议”通常为“TCP /IP”;单击“注册表”菜单 - >“本地”菜单项,表明对 于服务器而言这是一个本地自动服务。


电脑工程师丛书

138

Visual FoxPro 高级应用实例

图 5 . 20

服务器端远程自动化连接管理器

③ 运行自动化管理器。 单击“开始”菜单 - >“运行”,在“运行”对话框中输入“AUTMGR32 . EXE”,运行后将显示如 图 5 . 21 所示的窗口,窗口中显示的是当前的连接数和对象数。保持该窗口,否则将关闭远程 自动服务。

图 5 . 21

自动化管理器

(2)客户端配置 ① 注册自动服务对象。 上文中曾提到在 Visual FoxPro 中联编自动服务程序时会自动生成一个注册入口信息文件 vbr,用记事本打开该文件将会看到如下一些注册表信息: VB5SERVERINFO VERSION = 1 . 0 . 0 HKEY - CLASSES - ROOT h oledbf. encode = encode HKEY - CLASSES - ROOT h oledbf. encode h NotInsertable HKEY - CLASSES - ROOT h oledbf. encode h CLSID = {8A4CC011 - 4CB3 - 4F26 - 933B - 56C53F927DF6} HKEY - CLASSES - ROOT h CLSID h{8A4CC011 - 4CB3 - 4F26 - 933B - 56C53F927DF6} = encode HKEY - CLASSES - ROOT h CLSID h{8A4CC011 - 4CB3 - 4F26 - 933B - 56C53F927DF6} h ProgId = oledbf. encode


OLE 与自动化

139

…… 对于 Visual FoxPro 6 . 0 生成的 vbr 文件需要手动添加其中的“encode”,即对象名。把该 vbr 文件复制到客户端计算机上,运行如下命令; CLIREG32 . exe oledbf. vbr 其中 CLIREG32 是注册客户端自动服务组件的工具,被包含在 Visual Studio 工具中,运行 该命令后,将出现如图 5 . 22 所示的设置窗口。

图 5 . 22

CLIREG32 程序设置界面

在“网络地址”中填写服务器的 IP 地址或者机器名,协议选择“ncacn - ip - tcp”,即使用 TCP /IP 协议。 ② 修改远程自动化设置。 如果需要修改从 vbr 文件中导入的注册信息,还可以运行远程自动化连接管理器程序 RACMGR32 . EXE,其中和服务器端设置最大的不同在于自动服务程序具有远程属性。“网络地 址”、 “网络协议”和“身份验证”等选项的内容必须和服务器的设置相对应,如图 5 . 23 所示。

图 5 . 23

客户端远程自动化连接管理器

③ 运行客户程序。 至此,一切准备就绪,在客户端的任意一个 OLE 控制器程序中都可调用服务器上的远程


电脑工程师丛书

140

Visual FoxPro 高级应用实例

自动服务了。试着在客户端的 Visual Basic 编辑器中运行上文中的用户自定义窗体,运行结果 应该和本地自动化的结果完全相同,由于网络的延时,可能运行速度会有所下降。一旦建立起 远程自动化连接后,服务器端的自动化管理器上将会显示出连接数和对象数的变化。

5.2.5

OLE 拖放

拖放是一种常见的用户操作,如在“资源管理器”中把一个文件拖放到另一个目录下,或者 在 Word 文档中把一段文字拖放到另一个位置,这种拖放称为本地拖放,也就是在应用程序内 部传递数据。还有另一种情况,如常用的下载工具网际快车(FlashGet)可以显示一个半透明的 悬浮窗口,可以在浏览器中把包含链接地址的对象拖放到这个悬浮窗口中,从而快速获得链接 地址进行下载,这种拖放实际上是在不同的应用程序之间传递数据,称为 OLE 拖放。 Visual FoxPro 6 . 0 的新特性之一就是对 OLE 拖放的支持,而在此之前的低版本中只能支持本地拖放。 1 . 和 OLE 拖放有关的属性、事件和方法 一个拖放操作包括两部分: “拖”和“放”, “拖”是指把数据从应用程序或控件中提取出来, “放”是指把数据放置到目标应用程序或控件中。从中获得数据的应用程序或控件称为拖动 源,接收数据的应用程序称为放落目标。 (1)和拖动源有关的属性、事件和方法 ① OLEDragPicture 属性:指 定 在 OLE 拖 放 操 作 过 程 中 鼠 标 指 针 下 显 示 的 图 片,可 以 是 bmp、dib、jpg、gif、ani、cur 和 ico 等图片文件类型,主要用于对用户的提示。 0 - 手动(默认值) ② OLEDragMode 属性:指定拖动源管理 OLE 拖动操作的方式,可以是: 或 1 - 自动。前一种情况需要在程序中调用 OLEDrag 方法才能使用拖放,主要是为了提供对 低版本应用程序的兼容,后一种情况由 Visual FoxPro 自动管理拖放操作。 ③ OLECompleteDrag 事件:在数据放落到目标上或取消 OLE 拖放操作时发生,在触发该事 件时,系统还将返回一个 nEffect 参数,其含义如表 5 . 3 所示。 表 5.3 nEffect 参数

nEffect 参数取值及含义

宏替换

含义

0

DROPEFFECT - NONE

OLE 拖放被取消或数据不被放落目标接收

1

DROPEFFECT - COPY

数据从拖动源复制到放落目标中

2

DROPEFFECT - MOVE

数据从拖动源移动到放落目标中

4

DROPEFFECT - LINK

数据从拖动源链接到放落目标中

④ OLEGiveFeedBack 事件:在每次 OLEDragOver 事件之后发生,允许拖动源指定 OLE 拖放 操作和可视化反馈的类型。其返回的 nEffect 参数及含义与 OLECompleteDrag 事件相同。 ⑤ OLESetData 事件:在放落目标调用 GetData 方法程序而 DataObject 中没有指定格式的数 据时发生。该事件使得只在必要的时候才设置数据的格式。 ⑥ OLEStartDrag 事件:在调用 OLEDrag 的时候发生。 ⑦ OLEDrag 方法:开始一次 OLE 拖放操作。


OLE 与自动化

141

(2)和放落目标有关的属性、事件和方法 ① OLEDropEffects 属性:指定 OLE 放落目标支持的放落操作类型,其可能的取值如表 5 . 3 所示。 ② OLEDropHasData 属性:指定如何管理放落操作,其可能的取值为:- 1(默认值) - 由 Visual FoxPro 自动决定数据能否被放落到当前目标中,并通过鼠标指针的形状给出提示;0 数据不能被放落到当前目标中;1 - 数据可以被放落到当前目标中。 ③ OLEDropMode 属性:指定放落目标管理 OLE 放落操作的方式,其可能的取值为:0(默认 值)- 放落操作不可用;1 - 放落操作可用;2 - 把数据传递给容器,容器控件的 OLEDropMode 属性必须设置为 1 或 2。 ④ OLEDragDrop 事件:当数据放落到目标,并且放落目标的 OLEDropMode 属性设置为 1 时 发生。 ⑤ OLEDragOver 事件:当数据拖动到放落目标,并且放落目标的 OLEDropMode 属性设置为 1 时发生。 2 . 一个简单的 OLE 拖放示例 最简单的 OLE 拖放莫过于在表单的两个文本框控件中拖放文本数据了,利用 Visual FoxPro 自动拖放的功能,不需要写任何代码即可实现这个功能。在表单上添加两个文本框控件, 设置其中一个 OLEDragMode 为“1 - 自动”作为拖动源,另一个文本框的 OLEDropMode 属性为 “1 - 自动”作为放置目标,运行该表单,在第一个文本框中输入一些文字,用鼠标选中后即可 把这段文字拖放到第二个文本框中,如图 5 . 24 所示。

图 5 . 24

简单的 OLE 拖放示例

如果在拖放的同时按住键盘的 < Ctrl > 键,则是把文字复制到放置目标中,此时的鼠标形 状包含一个“ + ”号,如图 5 . 25 所示。对这些拖放操作的处理完全由 Visual FoxPro 自动控制, 不需要在程序中进行干预。

图 5 . 25

简单的 OLE 拖放示例(复制)


电脑工程师丛书

142

Visual FoxPro 高级应用实例

3 . 包含格式文本的拖放实例 文本框编辑器只能使用纯文本,也就是说不能包含格式信息。在第 4 章中曾介绍过 RichText 控件,它可以包含标准的 RTF 格式信息,同时该控件也支持 OLE 的自动拖放。在表单上 添加 RichText 控件,并设置其 OLEDragMode 属性为 1,OLEDropMode 为 2,运行该表单,即可与写 字板等文字处理软件之间进行格式文本的拖放,效果如图 5 . 26 所示。

(a)拖放前

(b)拖放后

图 5 . 26

格式文本的 OLE 拖放实例

4 . 控制拖放对象 很多时候拖放对象的数据格式是未知的,可能是文本、图片、磁盘文件或者其他的数据,在 这种情况下需要对拖放对象的数据格式进行判断或控制。在拖放操作开始时将生成一个 DataObject 对象,拖放操作完成后该对象自动清除,DataObject 对象可以包含多个数据,每个数 据可以有不同的格式,因此一个 DataObject 对象就是当前拖放操作的数据容器。 DataObject 对象包含五种方法,通过这些方法可以修改和读取数据的内容与格式。


OLE 与自动化

143

(1)SetData 方法 功能:向 DataObject 对象中添加数据。 语法:oDataObject . SetData(eData [,nFormat | cFormat ] )。 eData:要向 DataObject 对象中添加的数据,可以是常量、变量或数组,但不能是对象的引用 和通用型字段,或者包含这两种类型数据的数组,否则将产生错误。 nFormat | cFormat:指定 eData 的数据格式,常用的数据格式如表 5 . 4 所示。 表 5.4 数据格式

eData 的数据格式 说明

nFormat | cFormat

CF - TEXT

1

文本格式

CF - OEMTEXT

7

包含 OEM 字符集的文本格式

CF - UNICODETEXT

13

Unicode 编码的字符格式(Windows NT 平台)

CF - FILES or CF - HDROP

15

一组文件的句柄

CFSTR - OLEVARIANTARRAY CFSTR - OLEVARIANT CFSTR - VFPSOURCEOBJECT

"OLE Variant Array"

数组

"OLE Variant"

变量

"VFP Source Object"

Visual FoxPro 拖动源对象的引用

除了以上 常 用 格 式 之 外,还 可 以 使 用 自 定 义 的 数 据 格 式 字 符 串。 SetData 方 法 只 能 在 OLEStartDrag 事件和 OLESetData 事件中调用。 (2)SetFormat 方法 功能:设置 DataObject 对象中数据的格式。 语法:oDataObject . SetFormat(nFormat | cFormat)。 nFormat | cFormat 的取值和含义与 SetData 方法中完全相同。 可以在向 DataObject 对象添加相关数据之前设置其格式,如果在设置格式的同时没有添 加相应的数据,在 OLEDragDrop 事件中使用 GetData 方法将会触发 OLESetData 事件,在该事件 中调用 SetData 方法即可向 DataObject 对象中添加数据。 SetFormat 方法只能在 OLEStartDrag 事 件和 OLESetData 事件中调用。 (3)GetData 方法 功能:接收 DataObject 对象中的数据。 语法:oDataObject . GetData(nFormat | cFormat [,@ArrayName] )。 @ArrayName:当 DataObject 对象中包含多个数据时可以把它们存储到数组中,此时的数据 格式只能是 CF - FILES、CF - HDROP 或 CFSTR - OLEVARIANTARRAY。该数组在调用 GetData 方 法之前必须已经申明,如果数组不够大,Visual FoxPro 会自动增加数组元素的个数,反之会自动 减少数组元素的个数。 (4)GetFormat 方法 功能:判断 DataObject 对象中包含的数据格式是否可用。 语法:oDataObject . GetFormat(nFormat | cFormat)。 返回值:如果与 nFormat 或 cFormat 指定的格式相符则返回 . T. ,否则返回 . F. 。 (5)ClearData 方法 功能:清除 DataObject 对象中的所有数据和方法。


电脑工程师丛书

144

Visual FoxPro 高级应用实例

语法:oDataObject . ClearData。 只能在 OLEStartDrag 事件中调用该方法。 5 . 混合格式拖放示例 利用对 DataObject 对象的数据和格式控制,可以在 OLE 拖放过程中对混合格式的数据进 行拖放,下面这个示例能够很好地说明如何利用 OLE 拖放的相关属性和事件,以及 DataObject 对象的方法进行混合格式拖放控制。 在表单中添加三个控件:图像控件 Image1、容器控件 Container1 和文本框控件 Text1,如图 5 . 27 所示。

图 5 . 27

混合格式拖放表单

基本的想法是,允许拖放图片控件中的图片,如果放置到容器控件上则直接在控件上显示 图片,如果放置到文本框中则显示图片的文字说明。 首先需要设置 Image1 控件的 OLEDragMode 属性为“1 - 自动”,并且在 OLEStartDrag 事件 中设置数据的格式,代码如下: LPARAMETERS oDataObject,nEffect  清除 DataObject 对象的所有数据和格式 oDataObject . ClearData  指定以 Copy 的方式拖放数据 nEffect = 1  添加文本格式 oDataObject . SetFormat( 1 )  添加自定义格式 PICTURE oDataObject . SetFormat( "PICTURE" ) 在 OLEStartDrag 事件中仅设置了格式而没有添加任何数据,因为现在还无法确切地得知 放置目标能够接收什么样的数据。这个工作将要留到 Image1 控件的 OLESetData 事件中完成。 OLESetData 事件的代码如下: LPARAMETERS oDataObject,eFormat DO CASE CASE trans( eFormat ) = = "1" && 文本格式  如果放置目标接收文本数据  把说明信息“Visual FoxPro”添加到数据对象中


OLE 与自动化

145

oDataObject . SetData( "Visual FoxPro",1 ) CASE trans( eFormat ) = = “PICTURE” && 自定义的 PICTURE 格式  如果放置目标接收"PICTURE"格式数据  把 Image1 控件中的图片文件名添加到数据对象中 oDataObject . SetData( This . Picture,"Private Format" ) ENDCASE 现在已经可以把图片拖放到文本框中了,拖放完成后,文本框中将显示“Visual FoxPro”,如 图 5 . 28 所示。

(a)拖放中

(b)拖放后

图 5 . 28

把图片拖放到文本框中

要把图片拖放到容器控件中还需要进行一定的处理,当对象被拖动到容器控件上时,判断 DataObject 对象中是否包含“PICTURE”格式的数据,如果有,则允许以 COPY 方式放置。当对象 被放置到容器控件中时,得到“PICTURE”格式的数据(实际上是图片的文件名)并显示相应的 图片。 在 Container1 控件的 OLEDragOver 事件中添加如下代码: LPARAMETERS oDataObject,nEffect,nButton,nShift,; nXCoord,nYCoord,nState IF nState = = 0 AND oDataObject . GetFormat( "PICTURE" ) This . OLEDropHasData = 1 && 允许放落 This . OLEDropEffects = 1 && 以 COPY 方式拖放 ENDIF 在 Container1 控件的 OLEDragDrop 事件中添加如下代码:


电脑工程师丛书

146

Visual FoxPro 高级应用实例

LPARAMETERS oDataObject,nEffect,nButton,nShift,; nXCoord,nYCoord IF oDataObject . GetFormat( "PICTURE" ) This . Picture = oDataObject . GetData( "PICTURE" ) nEffect = 1 THIS. Draw NODEFAULT && 停止默认的拖放动作 ENDIF 运行该表单,可以把图片拖放到容器控件或文本框控件中,运行效果如图 5 . 29 所示。

(a)拖放中

(b)拖放后

图 5 . 29

5.3

把图片拖放到容器控件中

实例制作

OLE 及自动化技术覆盖面广,概念抽象,技术细节多,因此在上文的讨论过程中已经制作 了很多小例子,帮助读者对相关内容的理解。下面将完整介绍如何在 Visual FoxPro 中利用 OLE 自动化技术制作 Word 格式的报表,它的实际应用价值在于:虽然 Visual FoxPro 提供了报 表设计器,但是让非专业人员使用报表设计器修改报表格式显然是一件不现实的事情,而以 Word 文档格式制作报表,一方面可以利用字处理软件强大的功能,另一方面也便于软件使用 人员按照自己的要求修改报表。


OLE 与自动化

147

5 . 3 . 1 获得 Word 常数 在开始使用 Word 制作报表之前,首先需要获得 Word 类型库中的内置常数,方法是使用 ( TLI TypeLib Information)对象库。这个库包含在系统文件夹 SYSTEM32 下的 TlbInf32 . dll 文件 中,它是一组 COM 对象的集合,通过它可以快速地获取或浏览类型库中的信息,关于 TLI 的详 细介绍请参考 TLI 的联机手册。在这里主要用到的是 TypeLibInfo 对象,该对象是 TLI 中的核 心成员,在 Visual FoxPro 中可以用 CreateObject("TLI . TypeLibInfo")创建该自动服务对象。 使用 TypeLibInfo 对象获取类型库中包含的常数主要使用到以下的属性: ① ContainingFile 属性:设置 ContainingFile 属性用于装载类型库文件(扩展名为 OLB),Word 的类型库文件通常位于 Office 安装目录下的 MSWORD. OLB 文件。 ② Constants 属性:Constants 属性是一个集合,包含了类型库中的所有常数信息。每个常数 实际上包含许多成员,它们都保存在 Constants 属性的 Members 子属性中,每个 Member 又包含 许多项(Item),其中值得关心的是 Name 和 Value,即常数的名称与值。 下面将制作一个表单,从表单中可以选择需要加载 OLB 文件,并利用 TLI 提取出其中所有 的常数,并用“ # DEFINE 常数名 常数值”的形式显示在表单的编辑框中,还可以把它保存在一 个 . H 文件中,以备下一步使用。表单的外观如图 5 . 30 所示。

图 5 . 30

获取 Office 内置常数表单

该表单的 Text1 控件用于输入和显示 OLB 文件的文件名(包含路径),也可以通过“. . .”按 钮在“打开”对话框中浏览和查找文件,并把结果返回到 Text1 控件中。“. . .”按钮的 Click 事件 代码如下: OLBFile = GETFILE("OLB") IF ALLTRIM(OLBFile)# "" THISFORM. text1 . VALUE = OLBFile ENDIF 在“获取常数”按钮中将创建 TypeLibInfo 对象并根据 Text1 中的文件名加载相应的类型库 文件,并把读取出来的结果返回到 Edit1 控件中显示,其 Click 事件代码如下:


电脑工程师丛书

148

Visual FoxPro 高级应用实例

IF ! FILE(THISFORM. Text1 . VALUE) MESSAGEBOX("类型库文件不存在!", 0 + 48 + 256,"错误提示") RETURN ENDIF tli = CREATEOBJECT("tli . typelibinfo") tli . Containingfile = THISFORM. Text1 . VALUE constr = "" FOR EACH Constant IN tli . Constants FOR EACH Member IN Constant . Members constr = constr + " # DEFINE " + Member . NAME + " " + ; TRANSFORM(Member . VALUE) + CHR(13)+ CHR(10) NEXT Member NEXT Constant THISFORM. Edit1 . VALUE = constr 以上代码中的 CHR(13)+ CHR(10)为 Windows 中的换行符。运行该表单,并从“. . .”按钮 中选中 MSWORD. OLB 文件,单击“获取常数”按钮后,Edit1 控件中将列出 Word 中包含的所有 常数,如图 5 . 31 所示。在其中找到 wdToggle,显示为:# DEFINE wdToggle 9999998,这就是在上 文的例子中常数 9999998 的由来。

图 5 . 31

运行表单后得到的 Word 内置常数

为了在程序中直接使用常数的名称而不是 9999998 这样的数值,可以把这些常数保存在 一个 C 语言格式的 . H 头文件中,在需要的时候可以用“INCLUDE”命令把它包含到程序中。 在“保存常数”按钮的 Click 事件中添加如下代码: [. h] ) hFile = PUTFILE("头文件",JUSTSTEM(THISFORM. text1 . VALUE) + [. h], IF hFile # "" STRTOFILE(THISFORM. edit1 . VALUE,hFile) ENDIF


OLE 与自动化

149

5 . 3 . 2 制作 Word 格式报表 在第 2 章中曾制作了一个企业设备清单报表,下面将利用 OLE 自动化制作 Word 格式的 报表,所有的预览和打印功能将在 Word 中完成。 新建程序文件 OLE - WordReport . PRG,代码如下: # INCLUDE MSWORD. H && 包含 Word 常数定义头文件 SET EXCLUSIVE OFF SET DELETE ON  创建 OLE 自动化服务对象 oWord = CREATEOBJECT("Word . Application") oWord . VISIBLE = . T. oDoc = oWord . Documents . ADD  居中对齐 oDoc . Paragraphs(1). ALIGNMENT = wdAlignParagraphCenter  用 24 号加粗黑体字显示报表标题 oWord . SELECTION. FONT. Bold = wdToggle oWord . SELECTION. FONT. SIZE = 24 oWord . SELECTION. FONT. NAME = "黑体" oWord . SELECTION. TypeText(" × × × × 企业设备清单") oWord . SELECTION. TypeText(CHR(13)) oDoc . Paragraphs(2). ALIGNMENT = wdAlignParagraphLeft  以 12 号宋体字加下划线显示报表打印时间 oWord . SELECTION. FONT. Bold = wdToggle oWord . SELECTION. FONT. SIZE = 12 oWord . SELECTION. FONT. NAME = "宋体" oWord . SELECTION. TypeText("时间:") oWord . SELECTION. FONT. Underline = wdUnderlineSingle oWord . SELECTION. TypeText(DTOC(DATE())) oWord . SELECTION. FONT. Underline = wdUnderlineNone oWord . SELECTION. TypeText(CHR(13))  以上完成报表标题,下面开始输出详细设备清单 USE registry i=0 SCAN oDoc . Paragraphs(3 + 30 i). ALIGNMENT = wdAlignParagraphRight oWord . SELECTION. TypeText("设备编号:") oWord . SELECTION. TypeText(ALLTRIM(设备编号)) oWord . SELECTION. TypeText(CHR(13))


电脑工程师丛书

150

Visual FoxPro 高级应用实例

oDoc . Paragraphs(4 + 30 i). ALIGNMENT = wdAlignParagraphCenter  插入表格 loTable = oDoc . TABLES. ADD(oWord . SELECTION. RANGE, 5, 6) loTable . BORDERS. InsideLineStyle = wdLineStyleSingle loTable . BORDERS. OutsideLineStyle = wdLineStyleDouble  向单元格中添加相关信息 loTable . Cell(1, 1). RANGE. TEXT = "设备名称" loTable . Cell(1, 2). RANGE. TEXT = 设备名称 loTable . Cell(1, 3). RANGE. TEXT = "部门" loTable . Cell(1, 4). RANGE. TEXT = 部门 loTable . Cell(1, 5). RANGE. TEXT = "主要设备" loTable . Cell(1, 6). RANGE. TEXT = IIF(主要设备,"是","否") loTable . Cell(2, 1). RANGE. TEXT = "购买时间" loTable . Cell(2, 2). RANGE. TEXT = DTOC(购买时间) loTable . Cell(2, 3). RANGE. TEXT = "登记时间" loTable . Cell(2, 4). RANGE. TEXT = DTOC(登记时间) loTable . Cell(2, 5). RANGE. TEXT = "价格" loTable . Cell(2, 6). RANGE. TEXT = STR(价格, 10, 2) loTable . Cell(3, 1). RANGE. TEXT = "产地" loTable . Cell(3, 2). RANGE. TEXT = 产地 loTable . Cell(3, 3). RANGE. TEXT = "制造商" loTable . Cell(3, 4). RANGE. TEXT = 制造商 loTable . Cell(3, 5). RANGE. TEXT = "设备状况" loTable . Cell(3, 6). RANGE. TEXT = 设备状况 loTable . Cell(4, 1). RANGE. TEXT = "维修情况" loTable . Cell(4, 2). Merge(loTable . Cell(4, 6)) loTable . Cell(4, 2). RANGE. TEXT = 维修情况 loTable . Cell(5, 1). RANGE. TEXT = "备注" loTable . Cell(5, 2). Merge(loTable . Cell(5, 6)) loTable . Cell(5, 2). RANGE. TEXT = 备注 oWord . SELECTION. MoveDown(wdParagraph, 27,wdMove) oDoc . Paragraphs(31 + 30 i). ALIGNMENT = wdAlignParagraphRight oWord . SELECTION. TypeText("登记员:") oWord . SELECTION. TypeText(ALLTRIM(登记员姓名)) oWord . SELECTION. TypeText("(") oWord . SELECTION. TypeText(ALLTRIM(登记员编号)) oWord . SELECTION. TypeText(")") oWord . SELECTION. TypeText(CHR(13)) oWord . SELECTION. TypeText(CHR(13))


OLE 与自动化

151

i=i+1 ENDSCAN USE filename = SYS(5)+ SYS(2003)+ " h report . doc" oDoc . SAVEAS(filename)&& 保存 Word 文档 运行该程序后,即可自动生成 Word 格式的报表文档,如图 5 . 32 所示。

图 5 . 32

5.4

Word 格式报表文档

本章小结

“应用程序间的相互通信”副标题已经很好地说明了本章的主旨,而 OLE 与自动化技术是 实现这一功能的最佳解决方法,它不仅仅局限于 OLE 绑定等简单的应用,而是深入到 Visual FoxPro 程序开发的方方面面。本章关于 COM 技术和自动服务组件内容的讨论还将成为第 7 章中开发 Web 应用程序的基础。


采用两层 C /S 体系架构 ———设备管理系统的 SQL Server 升迁

6

本章技术要点:  C/S 体系综述。  数据库的升迁。  远程视图。  SQL Pass - Through 技术。  C/S 体系的性能优化。

6.1

本例提要

Client - Server Architecture 通常被翻译成“客户端 / 服务器体系”,或被译成“主从结构”,在 本章中简称为 C/S 体系,它是针对多用户访问、海量数据存储、安全保密性要求高等应用场合 提供的一种理想解决方案。本章主要讨论两层 C/S 体系架构,它通常包括后台的数据库服务 和前台的客户端应用,Visual FoxPro 以其功能强大及快速、高效的开发环境,可以充分胜任前台 客户端应用程序开发的角色。 SQL Server 是一个集性能、可伸缩性、可靠性和易用性于一身的 数据库管理系统,它和 Visual FoxPro 构成了 Windows 平台下开发两层 C/S 体系架构数据库应用 系统的最佳拍档。 本章将把第 2 章中制作的设备管理系统以 C/S 方式重新构建,程序运行结果如图 6 . 1 所 示。


采用两层 C/S 体系架构

图 6.1

6.2

153

程序运行结果

技术分析 6.2.1

C/S 体系综述

C/S 模型起源于 20 世纪 80 年代,是伴随着个人电脑(PC)和网络的普及而逐渐流行的。 相对于之前的架构体系而言,C/S 体系以通用的、基于消息传递机制的模块化架构为基础,具 有灵活性、可协作性和易伸缩性等特点而受到青睐。 1 . 常见架构体系对比 (1)集中体系 谈到集中体系,很多人首先想到的是一个星形拓扑结构:一台大型主机,连接了若干个终 端,所有的数据和应用程序都集中在大型主机上,人们通过终端登陆主机完成各种操作。虽然 这种情形确实存在,但却是对集中体系狭义的理解。实际上,这里讨论的主要是软件的架构方 式。众所周知,一个应用程序通常包含数据和功能两部分,如果数据和功能完全集中在应用程 序内部,这样的软件架构方式称为集中体系。基于集中体系的软件并不一定运行在大中型主 机上,也可能运行在 PC 机或工作站上。集中体系是产业界惯用的一种软件架构模式,它具有 系统稳定性好、安全、快速等优点,因此,至今仍在很多场合得到应用,到目前为止,本书开发的 所有实例都属于集中体系。但是集中体系的缺点也很明显,主要表现在数据和代码交织在一 起,两者相互依附,结构复杂、可伸缩性差和不易维护,而且数据不便于在多个用户之间共享。 (2)文件共享体系 在 PC 机组成的局域网中,文件共享是一种常用的网络服务,通常会有专门的计算机存储 公共的文件,这样的计算机称为文件服务器(File Server)。把数据库文件放到文件服务器上, 就可以在局域网的多台计算机中简单地实现数据共享,这种体系通常也被称作 F /S 体系。 F /S 体系最大的特点是:程序可以运行在每一台单独的计算机上,数据的获取和更新是通过局域网


电脑工程师丛书

154

Visual FoxPro 高级应用实例

的文件共享协议完成的。也就是说,每台运行程序的计算机上都保存了一个数据文件的副本。 由于个人电脑的价格低廉,因此 F /S 系统的投资小,成本低,深受中小型企业的欢迎。但是 F / S 体系最大的缺点是网络负荷量大,从一个极端的情况来看:假如需要在一个包含 100 万条记 录的表中查询数据,符合条件的记录只有一条,但是这个表中的所有数据都会通过网络传输到 本地的计算机上,网络状况将会随着用户的增加而急剧恶化。因此,F /S 体系一般用于用户数 较少的小型网络中。 (3)C/S 体系 很显然,集中体系和文件共享体系是两个极端:前者高度集中而后者过于分散,因此各自 带来了许多问题。 C/S 架构体系的核心思想是把程序和数据分离,数据由专门的关系数据库 管理系统(RDBMS)进行管理,这样的数据库管理系统通常具有多用户和网络共享的特性,可以 对用户的请求进行智能应答,从而避免了 F /S 体系中网络利用率低下的缺陷。前台的应用程 序可以和数据库管理系统运行在同一台主机上,或者通过网络与数据库服务器相连。 2 . 两层架构与多层架构 C/S 体系又可以分为两层架构和多层架构两种。在两层架构中,客户端应用程序通常运 行在用户的桌面系统上,而数据库则往往位于专门的数据库服务器上。客户端与服务器之间 是直接通信的:客户端发送请求或命令,数据库返回运行结果。两层架构的优点是简单、可靠, 但是缺乏足够的灵活性,而且在用户数量非常大(如上千用户)时性能将十分低下,因此一般用 于局域网络。三层架构甚至更多层架构体系的出现是为了弥补两层架构体系中的一些不足, 三层架构体系在数据层和应用层之间加入了一个中间层,客户端不直接与数据库服务器通信, 而是把请求发送给中间层,中间层通过优先级和队列排序依次把请求发送给数据库,最后从数 据库获得运行结果并返回给客户端。中间层的缓冲机制使得三层架构体系的灵活性大大增 强, 而且适用于数百甚至上千用户的广域网络中,常见的浏览器 / 服务器(Browse /Server,B/S) 体系就是三层架构的典型应用。三层体系的主要缺点是中间层开发大多没有可视化的开发环 境,因此难度较大。 本章主要讨论基于 Visual FoxPro 和 SQL Server 的两层 C/S 体系架构,B/S 体系将在下一章 作详细的介绍。对于 C/S 体系中的数据层其实有很多种 DBMS 可供选择(如 Oracle、Sybase、 SQL Server 等),但对基于 Visual FoxPro 的 C/S 体系开发中,SQL Server 具有得天独厚的优势,因 为两者同属于 Microsoft 家族中数据库系统的重要成员,具有较好的平台兼容性。

6.2.2

SQL Server 简介

在这里不打算完整介绍 SQL Server 的方方面面,那将是另外一本 SQL Server 参考书中的内 容。事实上,对于 Visual FoxPro 程序员来说,并不需要搞清楚 SQL Server 的所有技术细节,专职 的数据库管理员(DBA)才需要如此。即使你对 SQL Server 一无所知,也可以在很短的时间内把 它应用到自己的 C/S 体系数据库系统中,本小节和下一小节的目的就在于此。如果需要更深 入地了解 SQL Server,请参考它的联机丛书及其他参考资料。 1 . SQL Server 的历史 SQL Server 最早是由关系数据库 Sybase 演变而来的。 1988 年由 Sybase、Microsoft 和 Ashton


采用两层 C/S 体系架构

155

- Tate 三家公司共同开发了 Sybase 的第一个 OS/2 版本,在 Windows NT 操作系统推出后,Microsoft 和 Sybase 分道扬镳,前者致力于开发和推广基于 Windows NT 平台系列版本的 SQL Server,其发展过程中的主要事件如下: ① 1995 年发布 SQL Server 6 . 0(代号 SQL 95)。 ② 1996 年发布 SQL Server 6 . 5(代号 Hydra)。 ③ 1998 年发布 SQL Server 7 . 0(代号 Sphinx)。 ④ 2000 年发布 SQL Server 2000(代号 Shiloh)。 ⑤ 2003 年发布 SQL Server 2000 64 位版(代号 Liberty)。 关于 SQL Server 更完整的发展历史及其中的趣闻轶事请访问 Microsoft 关于 SQL Server10 周年(1993 - 2003)庆典的专题站点。 2 . SQL Server 的版本 SQL Server 版本繁多,以 SQL Server 2000 为例,至少包含以下版本: ① SQL Server 2000 64 位企业版。 ② SQL Server 2000 企业版。 ③ SQL Server 2000 标准版。 ④ SQL Server 2000 开发版。 ⑤ SQL Server 2000 评估版。 ⑥ SQL Server 2000 个人版。 ⑦ SQL Server 2000 桌面引擎。 ⑧ SQL Server 2000 Windows CE 版。 其中最重要的两个版本是企业版和标准版,前者几乎包含所有的高级功能,如高级 OLAP 分析特性、全面的分析服务、Web 分析、分布式分区视图、索引视图等;后者不包含这些高级功 能,因而体积和价格更为适中。个人版包含在企业版或标准版中,不单独出售,只具有基本的 功能,如果需要在 Windows 98 等非 NT 平台安装 SQL Server 的服务组件,个人版是惟一选择。 其他的一些版本主要用于特殊的用户,如开发版在企业版的基础上集成了一些开发工具和相 关的授权协议,适用于高级用户。关于 SQL Server 的版本选择,请参考 Microsoft 的《SQL Server 2000 版本选择白皮书》。 3 . SQL Server 的主要特点 概括起来 SQL Server 具有以下特点: ① 丰富的图形化管理工具。 SQL Server 包含 10 余种图形化的管理工具,可以直观地完成各种日常管理工作。 ② 隐含的并发控制能力。 在第 4 章中曾介绍过共享数据访问的内容,主要是数据的锁定和缓冲机制,在 SQL Server 中可以自动地完成相关的控制而不需要用户干预。 ③ 丰富的编程接口。 SQL Server 提供了众多的开发工具(如 Transact - SQL、DB - Library for C 等),同时还支持 ODBC 和 OLE DB 规范,使得其他应用程序可以通过相应的接口访问 SQL Server 数据库。 ④ 多线程体系支持。


电脑工程师丛书

156

Visual FoxPro 高级应用实例

SQL Server 支持多线程操作,这在多用户并发访问时可以有效地提高系统的性能。 ⑤ 良好的伸缩性。 SQl Server 可以运行在多种不同的操作系统上,能够满足从桌面应用到企业级应用各个不 同领域的要求。 4 . SQL Server 的主要组件 下面列出了 SQL Server 2000 四个主要版本(企业版、标准版、个人版和开发版)中包含的主 要数据库服务器组件。 (1)服务器组件 ① SQL Server:SQL Server 关系数据库引擎和其他核心工具。 ② 升级工具:提供低版本数据库的升级向导。 ③ 复制支持:提供快照复制、事务复制、合并复制等功能。 ④ 全文检索(Windows 98 上的个人版不支持该组件):Microsoft 全文检索引擎。 ⑤ 调试符号。 ⑥ 性能计数器(Windows 98 上的个人版不支持该组件)。 (2)管理工具 ① 企业管理器:SQL Server 的主要管理工具,用于执行服务器和企业管理任务。 ② 事件探查器:用于监视、记录和支持 SQL Server 数据库事件,是重要的故障诊断工具。 ③ 查询分析器:用于交互输入 Transact - SQL 语句和过程,还能提供图形查询分析功能。 ④ DTC 客户端支持:用于将数据库事务扩展到多个服务器。 ⑤ 冲突查看器 :用于查看和更改同步冲突的解决方式。 (3)客户端连接 用于客户端和服务器之间的通信,包括 Microsoft 数据访问组件(MDAC)和 DB - Library、 ODBC 以及 OLE DB 的网络库。 (4)开发工具 ① C 语言头文件和库文件:C 语言开发人员开发程序(程序使用 OLE DB、ODBC、DB - Library、开放式数据服务、SQL - DMO、用于 C 语言的嵌入式 SQL 以及 MS DTC)所需的包含( . h)文件和库( . lib)文件。 ② MDAC SDK:MDAC 和 XML 软件开发工具包。 ③ 备份 / 还原 API:安装软件厂商开发自定义应用程序所需的头文件、示例程序和文档, 备份和还原 SQL Server 数据库。 ④ 调试程序界面:用于存储过程调试的界面。 (5)文档 ① 联机丛书。 ② 代码示例。


采用两层 C/S 体系架构

6.2.3

157

SQL Server 的安装与管理

1 . SQL Server 安装指南 (1)安装前的准备 和其 他 很 多 软 件 一 样,SQL Server 对 系 统 的 硬 件 有 一 个 最 基 本 的 要 求,包 括 Pemtium 166MHz 以上的 CPU、 200M 左右(视安装的组件而定)的磁盘空间、64M 以上的内存,对于现在 的 PC 机硬件配置来说这些要求应该都不是问题。此外,还需要考虑下列事项: ① 选择合适的版本。 SQL Server 的不同版本对操作系统也有不同的要求,因此应根据当前所使用的操作系统选 择合适的版本。 Windows 98 /Me:只能安装个人版。 Windows NT 4 . 0 /2000 Server(含 Advanced Server、Datacenter Server):可以安装所有的版本,但 是 Windows NT 4 . 0 必须安装 Service Pack 5(SP5)或更高版本。 Windows NT 4 . 0 /2000 非 Server 版、Windows XP:可以安装个人版或开发版。 Windows 2003 Server:目前只能安装个人版或开发版,或等待 SQL Server 2003。 作者在编写本书时使用的操作系统为 Windows XP,因此对安装过程的介绍将以开发版为 例,它具有企业版的所有特性和功能(《SQL Server 版本选择白皮书》上的原话,实际上有极少量 的功能不具备,通常情况下可以忽略),但是本章后面所讨论的内容适用于 SQL Server 的所有 版本。 ② 创建用户。 SQL Server 有两种用户账户形式:本地系统账户和域用户账户。前者不需要密码,但没有 网络访问权限,同时限制 SQL Server 安装与其他服务器的交互功能;后者使用 Windows 身份验 证,即用于连接到操作系统的用户名和密码也用于连接到 SQL Server。一般情况下都使用域用 户账户,因为许多服务器之间的活动只能使用域用户账户才能执行。在开始安装之前,可以为 操作系统新建一个本地域账户,并用该用户登陆系统。 ③ 备份和关闭其他应用程序。 如果是从低版本的 SQL Server 进行升级,需要对数据做好备份,在开始安装之前最好关闭 所有其他的应用程序,以免安装失败。 ④ Internet Explore 版本。 SQL Server 还有一个特殊的要求,就是在系统中必须安装 IE 5 . 0 以上版本,如果你使用的 是较老的 Windows 版本,可能需要对 IE 进行升级。 (2)基本安装 假设你是第一次安装 SQL Server,和安装别的 Microsoft 软件产品一样,你只需要回答一系 列的问题即可把 SQL Server 顺利地安装到计算机上,当然,也有一些重要的或者 SQL Server 特 有的选择,文中会加以解释说明。把 SQL Server 安装光盘放入光驱中,如果安装程序没有自动 运行,请双击运行光盘根目录下的 AUTORUN. EXE 文件。在启动画面中选择“安装 SQL Server 2000 组件”- >“安装数据库服务器”,进入安装向导。具体的安装步骤如下: ①“计算机名称”对话框可以选择在本地计算机、远程计算机或虚拟服务器上安装 Mi-


电脑工程师丛书

158

Visual FoxPro 高级应用实例

crosoft SQL Server 2000,如图 6 . 2 所示。

图 6 . 2 “计算机名”对话框

默认情况下,编辑框中的名称为本地计算机(即正在运行安装程序的计算机)名称。对于 本地安装,接受默认值并单击“下一步”按钮即可,如果进行远程安装可以填入远程计算机名, 或通过单击“浏览”按钮定位到远程计算机,如果检测到 Microsoft 群集服务(MSCS),还可以进 行虚拟服务器安装。 ② 单击“下一步”按钮,弹出“安装选择”对话框,在该对话框中可以选择以何种方式运行 安装程序,如图 6 . 3 所示。

图 6 . 3 “安装选择”对话框

可以选择“创建新的 SQL Server 实例或安装客户端工具”,或者“对现有的 SQL Server 实例 进行升级、删除或添加组件”,现有实例包括早期版本以及 SQL Server 2000 实例的安装,如果需 要使用群集维护、无值守安装和注册表重建等功能,还可以选择“高级选项”,高级安装内容将 在稍后介绍。


采用两层 C/S 体系架构

159

③ 单击“下一步”按钮,在弹出的“安装定义”对话框中选择要包含在 SQL Server 2000 安装 中的组件,如图 6 . 4 所示。

图 6 . 4 “安装定义”对话框

可以选择安装“服务器和客户端工具”以创建具有管理能力的关系数据库服务器,或者选 择“仅客户端工具”,此选项仅包含管理 SQL Server 的客户端工具和客户端连接组件, “仅连接” 选项用于安装关系数据库客户端连接组件,包括连接 SQL Server 命名实例所需的 MDAC 2 . 6 (Microsoft 数据访问组件)。通常情况下需要选择“服务器和客户端工具”来创建一个完整的 SQL Server 安装。 ④ 单击“下一步”按钮,弹出“实例名”对话框。对话框中有一个“默认”复选框,用于安装 默认的实例,清除此复选框的选择可以安装和维护命名实例,如图 6 . 5 所示。

图 6 . 5 “实例名”对话框

“最小”和“自定义”三 ⑤ 单击“下一步”按钮,在弹出的“安装类型”对话框中选择“典型”、


电脑工程师丛书

160

Visual FoxPro 高级应用实例

种安装类型,或者修改程序文件和数据文件的安装路径,如图 6 . 6 所示。

图 6 . 6 “安装类型”对话框

使用默认安装选项将安装整个 SQL Server,建议用户采用此安装,最小安装仅安装必要的 组件和工具,而自定义可以选择组件和子组件,更改排序规则、服务账户、身份验证或网络库的 设置。 ⑥ 如果上一步骤选择了“自定义”安装,将出现“选择组件”对话框,可以选择要安装的或 者重新安装(如果没有进行初始安装)的组件和子组件,如图 6 . 7 所示。

图 6 . 7 “选择组件”对话框

不能通过清除该屏幕上的复选框将组件删除,删除已安装的组件的惟一方法是将,SQL Server 整个删除。 ⑦ 服务账户:这是 SQL Server 安装过程中的一个重要步骤。在“服务帐户”对话框中可以 将登录账户指派给两个 SQL Server 服务:SQL Server 和 SQL Server 代理程序,可以使用本地系统 账户或域用户账户,并且,两个服务可使用同一账户,默认设置是使用同一账户,且自动启动每 个服务。通常情况下使用域用户账户,但最好不要使用管理员账户,在文本框中输入本地域用 户的用户名、密码和域,如图 6 . 8 所示。


采用两层 C/S 体系架构

161

图 6 . 8 “服务账户”对话框

若要创建或维护 SQL Server 2000 故障转移群集,则必须登录到拥有管理员特权的计算机 上,成为该计算机或域“管理员”本地组的成员,对于群集则意味着必须是所有群集节点的管理 员。 ⑧ 单击“下一步”按钮,在弹出的“身份验证模式”对话框中设置系统安全模式:Windows 身 份验证模式和混合模式,后者主要是为兼容性而设。如果选择混合模式则需要添加 sa 登录密 码(见图 6 . 9),空密码选项不推荐使用。

图 6 . 9 “身份验证模式”对话框

⑨ 在“排序规则设置”对话框中可以修改默认的排序规则设置,如图 6 . 10 所示。在“排序 规则指示器”单选框下面的列表框中选择“Chinese— PRC”,如果需要兼容 SQL Server 的早期版 本,还应选中“SQL 排序规则”单选框。排序次序是一个很容易被忽视的重要选项,它有可能对 系统产生重要的影响,如二进制排序的速度最快,但是排序的结果往往让人无法理解,而“区分 大小写”选项会对查询的结果产生影响。


电脑工程师丛书

162

Visual FoxPro 高级应用实例

图 6 . 10 “排序规则设置”对话框

⑩ 在“网络库”对话框中选择网络间传输数据所用的网络协议,系统默认采用命名管道和 TCP /IP 协议,如图 6 . 11 所示。

图 6 . 11 “网络库”对话框

至此,安装向导已经收集了足够的信息,可以开始安装程序了,如果需要查看或修改任何 设置,可以单击“上一步”按钮返回,所有设置确认无误后单击“下一步”按钮开始复制文件。 (3)高级安装 在图 6 . 3 所示的“安装选项”对话框中选中“高级”选项后, “高级选项”对话框将提供三种 选择: ① 记录无值守 . ISS 文件。


采用两层 C/S 体系架构

163

该选项将创建可用于 SQL Server 无值守安装的安装文件,特别适用于在不同计算机上执 行几个相同配置的 SQL Server 安装。 ② 注册表重建。 如果 SQL Server 注册表区受损可以用此选项修复。 ③ 维护故障转移群集的虚拟服务器。 更改现有群集,如修改名称、添加和删除群集的节点等。 2 . SQL Server 的管理 SQL Server 安装完毕后,还需要对其进行必要的管理和维护,主要包括服务的启动和停止、 创建和维护数据库等。 (1)启动、暂停和停止 SQL Server 在登陆 SQL Server 并访问其中的数据之前,首先需要启动 SQL Server。单击 Windows 系统 的“开始”菜单 - >“Microsoft SQL Server”子菜单 - >“服务管理器”,打开如图 6 . 12 所示的“SQL Server 服务管理器”窗口,即启动了 SQL Server。 服务管理器窗口中的“服务”下拉列表框中列出了当前已安装的服务,其中“SQL Server”为 数据库引擎,在计算机上运行的每个 SQL Server 实例都对应一个 SQL Server 服务。“ “

”和“

”、

”三个按钮分别用于启动、暂停和停止服务。还可以在服务管理器中选择是否在

操作系统启动时自动运行 SQL Server 服务。 SQL Server 服务管理器还包含一个系统托盘程序,双击托盘图标可以弹出服务管理器对话 框,右击则弹出一个快捷菜单(见图 6 . 13),在该快捷菜单中可以实现相同的服务管理功能。

图 6 . 12 “SQL Server 服务管理器”窗口

图 6 . 13

服务管理托盘程序快捷菜单

(2)SQL Server 的管理 企业管理器是 SQL Server 的管理中心,利用它可以配置数据库系统、增加或删除数据对 象、 管理用户访问许可等。单击 Windows 系统的“开始”菜单 - >“Microsoft SQL Server”子菜单 - >“企业管理器”,将运行企业管理器并自动加载 Microsoft Management Console(微软控制台, MMC),如图 6 . 14 所示。 企业管理器的功能非常强大,几乎涉及到 SQL Server 的方方面面,在这里仅择要加以介 绍。 ① 注册服务器。 企业管理器可以同时管理多个 SQL Server 服务器,这些 SQL Server 服务器可以位于本地, 也可以运行在远程的计算机上,通过 SQL Server 注册向导可以把它们添加到企业管理器的 SQL


电脑工程师丛书

164

Visual FoxPro 高级应用实例

图 6 . 14

SQL Server 企业管理器

Server 组中。单击“工具”菜单 - >“向导”菜单项,选择“注册服务器向导”运行注册向导,在向 导的提示下即可完成 SQL Server 服务器的注册。对于本地安装的 SQL Server 服务器系统会自 动完成注册,因此在 SQL Server 组中应该已经包含了一个 SQL Server 服务器。 ② SQL Server 服务器属性设置。 在已经注册的 SQL Server 服务器上右击鼠标,选择快捷菜单中的“属性”菜单项,打开 SQL Server 服务器的属性配置对话框,如图 6 . 15 所示。 该属性配置对话框包括九个选 项卡,其中“常规”选项卡主要显示一 些常规信息及启动参数和网络配置, “内存”选项卡可以指定内存的动态 调整或固定不变, “处理器”选项卡用 于指定多处理器环境下的处理器工 作方式, “安全性”选项卡可以指定相 同的认证模式和启动时的服务账户, “连接”选项卡可以指定服务器的最 大连 接 数、连 接 选 项 和 远 程 连 接 设 置, “复制”选项卡主要涉及发布服务 器和分发服务器的配置, “Active Directory”选项卡用于添加、刷新和删除 Active Directory 中的 SQL Server 服务 器, “服务器设置”选项卡用于设置系 统默认的语言、服务器行为和 SQL 邮 件, “数据库”选项卡主要针对该服务 器上所有数据库的选项设置。

图 6 . 15

SQL Server 属性配置对话框

③ 新建、设置和删除数据库。 一个 SQL Server 服务器在创建时会自动添加一些系统数据库(如 master 数据库、tempdb 数 据库、model 数据库、msdb 数据库等),主要用于记录 SQL Server 服务器的所有系统级别信息、保


采用两层 C/S 体系架构

165

存临时表和临时存储过程、提供所有数据库的模板或供 SQL Server 代理程序调度警报、作业以 及记录操作员时使用。展开“数据库”节点,将可以看到这些系统数据库。用户数据不应该保 存在这些系统数据库中而应新建数据库。 在 SQL Server 中新建数据库有多种方式,常用的有向导方式和对话框方式。单击“工具” 菜单 - >“向导”菜单项,展开“数据库”节点并选择“创建数据库向导”,在该向导中主要完成对 数据库的命名、构建数据库文件和指定数据库文件增长信息等内容。也可以单击“操作”菜单 - >“新建数据库”菜单项,通过对话框新建数据库。在新建数据库的对话框中需要填写的内 容与新建数据库向导中设定的内容基本相同。 新建数据库后还能对其进行设置,右击数据库对应的节点,在快捷菜单中选择“属性”菜单 项,将弹出如图 6 . 16 所示的“数据库属性”设置对话框。

图 6 . 16

数据库属性设置对话框

该对话框主要包含六个选项卡,其中“常规”选项卡显示数据库的一些基本状态信息, “数 据文件”和“事务日志”选项卡分别用于设置数据文件和日志文件的路径和文件属性, “文件组” 选项卡可以查看或指定文件组的名称、文件数和文件组的状态, “选项”选项卡可以设置数据库 的访问方式、故障还原模型及其他一些相关选项, “权限”选项卡可以设置用户 / 角色的名称以 及对数据库不同类型操作的权限控制。 当某个数据库不再使用时需要把它删除,删除操作非常简单,右击数据库对应节点并在快 捷菜单中选择“删除”菜单项即可,在删除前还可以选择是否为数据库删除备份并还原历史纪 录。注意,不能删除系统数据库。 ④ 表结构操作。 右击数据库的“表”节点,在快捷菜单中选择“新建表”菜单项,将弹出一个类似于 Visual FoxPro 表设计器的对话框,如图 6 . 17 所示。 在这个对话框中可以设计表的基本结构,如字段名、数据类型、字段宽度、描述、默认值等。


电脑工程师丛书

166

Visual FoxPro 高级应用实例

图 6 . 17

新建表对话框

在字段上右击鼠标,弹出如图 6 . 18 所示的快捷菜单,利用该快捷菜单可以设置表的主键、索 引 / 键、关系及约束条件等。 完成表的修改后在快捷菜单中选择“保存”选项,即完成了新建表的操作。在某个表节点 上右击鼠标,快捷菜单如图 6 . 19 所示,在该快捷菜单中可以重新打开表的设计窗口对其进行 修改,或者重命名和删除表。

图 6 . 18

表结构设计快捷菜单

图 6 . 19

表操作快捷菜单

⑤ 表数据操作。 SQL Server 中对表数据的操作有两种方式:可视化方式和 SQL。因为 SQL Server 的表中很 可能保存了海量的数据,用可视化的操作方式效率是极其低下的,在 SQL Server 中通常使用 INSERT、UPDATE、DELETE 等 SQL 语句来完成对表数据的增、删、改。由于本章中主要以 SQL Server 作为后台数据库,对数据的操作主要通过 Visual FoxPro 开发前台的应用程序来完成,因 此对这部分内容不再进行讨论。 SQL Server 和 Visual FoxPro 的数据库管理系统相比,既有相同之处,又有很大的差异,需要 通过学习和大量的实践才能领略其精妙之所在。限于篇幅不再对其作深入的讨论,希望上述 介绍的内容可以帮助读者开始后续内容的学习而不会遇到太大的障碍。


采用两层 C/S 体系架构

167

6 . 2 . 4 数据库的升迁 在开发 C/S 体系架构的应用程序时,通常需要先设计一个本地原型,或者通俗地理解为 应用程序的“草稿”。在本地原型中,所有的数据都放置在 Visual FoxPro 的数据库、表和视图 中,这样做的好处是开发效率较高,可以相对快捷地调整应用程序的设计方案和程序结构;还 有一种情况是应用程序设计之初是基于本地数据库的,也就是所谓的“单机版”,出于某种需要 希望把它升级为基于 C/S 体系架构的“网络版”。不论是哪种情况,都需要把 Visual FoxPro 的 本地数据升迁到 SQL Server 服务器中。 Visual FoxPro 提供的升迁向导,可以很方便地实现这一 要求。 1 . 数据升迁的基本原理 Visual FoxPro 升迁向导的工作过程是完全透明的,理解升迁向导的工作原理有助于更好地 完成数据升迁并对可能出现的问题提前做好准备。 (1)数据导出方式 Visual FoxPro 升迁向导有两种导出数据的方式:第一种方式是创建一个存储过程,同时完 成多行数据的插入;第二种方式是为每条记录创建一条 SQL INSERT 语句,依次执行 SQL 语句 即可完成数据的导出。前一种方式速度较快,但表的字段数不能超过 250 个,而且不能把数据 存储在 SQL Server 的 Text 或 Image 类型的字段中,Visual FoxPro 的升迁向导将根据情况自动选 择数据的导出方式。 (2)数据对象的映射关系 升迁向导的任务就是尽可能把本地数据库中的数据对象(包括数据库、表、索引、关系与数 据规则等内容)转换成 SQL Server 服务器中相应的数据对象。在这个转换过程中,有些数据对 象是简单的一对一映射关系(如 Visual FoxPro 的数据库对应 SQL Server 的数据库,Visual FoxPro 的数据库表对应 SQL Server 的表,Visual FoxPro 的索引对应 SQL Server 的索引等),但有些数据 对象必须进行一定的转换,甚至有少数数据对象无法进行升迁。表 6 . 1 列出了 Visual FoxPro 与 SQL Server 中数据对象的映射关系。 表 6.1

Visual FoxPro 与 SQL Server 数据对象的映射关系

Visual FoxPro 数据对象

SQL Server 数据对象

数据库

数据库

数据库表

字段

视图

(无法升迁)

记录

记录

索引

索引

默认值

默认值

有效性规则

SQL Server 存储过程,由 UPDATE 和 INSERT 触发器调用

永久性关系 触发器

更新、插入和删除触发器 (无法升迁)


电脑工程师丛书

168

Visual FoxPro 高级应用实例

(3)数据类型的映射关系 Visual FoxPro 中的字段类型有 12 种,而 SQL Server 中的数据类型则更为丰富。在数据升 迁的过程中,Visual FoxPro 的数据类型将被自动转换成 SQL Server 中的数据类型,其映射关系 如表 6 . 2 所示。 表 6.2

Visual FoxPro 与 SQL Server 数据类型映射关系

缩写

Visual FoxPro 数据类型

SQL Server 数据类型

C

字符型

Char

Y

货币型

Money

D

日期型

Datetime

T

日期时间型

Datetime

B

双精度型

Float

F

浮点数型

Float

N

数值型

Float

G

通用型

Image

I

整数型

Int

L

逻辑型

Bit

M

备注型

Text

M(二进制)

二进制备注型

Image

C(二进制)

二进制字符型

Binary

(4)索引的映射关系 Visual FoxPro 的索引与 SQL Server 非常相似,表 6 . 3 列出了它们之间的对应关系。 表 6.3

Visual FoxPro 与 SQL Server 索引类型对应关系

Visual FoxPro 索引类型

SQL Server 索引类型

主索引

惟一聚集索引

候选索引

惟一索引

惟一索引 / 普通索引

非惟一索引

(5)表达式的映射关系 在 Visual FoxPro 数据库的默认值或有效性规则中,可能包含有一些运算符和表达式,升迁 向导会把这些内容也转换成 SQL Server 中相应的运算符和表达式,表 6 . 4 说明了它们之间的 对应关系。


采用两层 C/S 体系架构

表 6.4

Visual FoxPro 与 SQL Server 表达式对应关系

Visual FoxPro 运算符或表达式

SQL Server 运算符或表达式

真(. T.)

1

假(. F.)

0

#

< >

. AND.

AND

. NOT.

NOT

. NULL.

NULL

. OR.

OR

= <

< =

= >

> =

ASC()

ASCII( )

AT()

CHARINDEX()

CDOW()

DATENAME(dw,. . .)

CHR()

CHAR()

CMONTH()

DATENAME(mm,. . .)

CTOD()

CONVERT(datetime,. . .)

CTOT()

CONVERT(datetime,. . .)

DATE()

GETDATE()

DATETIME()

GETDATE()

DAY()

DATEPART(dd,. . .)

DOW()

DATEPART(dw,. . .)

DTOC()

CONVERT(varchar,. . .)

DTOR()

RADIANS()

DTOT()

CONVERT(datetime,. . .)

HOUR()

DATEPART(hh,. . .)

LIKE()

PATINDEX()

MINUTE()

DATEPART(mi,. . .)

MONTH()

DATEPART(mm,. . .)

MTON()

CONVERT(money,. . .)

NTOM()

CONVERT(float,. . .)

RTOD()

DEGREES()

SUBSTR()

SUBSTRING()

TTOC()

CONVERT(char,. . .)

TTOD()

CONVERT(datetime,. . .)

YEAR()

DATEPART(yy,. . .)

另外,还有以下表达式在 Visual FoxPro 和 SQL Server 中是相同的:

169


电脑工程师丛书

170

Visual FoxPro 高级应用实例

CEILING() LOG() LOWER() LTRIM() RIGHT() RTRIM() SOUNDEX() SPACE() STR() STUFF() UPPER() 2 . 升迁前的准备 要确保成功地升迁数据库,还需要做一些准备工作,主要包括客户端和服务器端两方面的 准备。 (1)服务器端准备 ① 检查磁盘剩余空间。 保证服务器端有足够的磁盘剩余空间是非常重要的,因为升迁向导并不对此进行判断,如 果在升迁到一半的过程中出现剩余空间不足的情况,升迁向导将会停止工作,而且已经完成升 迁的部分数据会残留在 SQL Server 服务器的数据库中,这些数据通常需要手工清除。 服务器端需要的磁盘剩余空间很难用公式精确地计算出来,实际上也没有这个必要。由 于 SQL Server 的数据库除了数据文件之外还有日志文件,因此,一个 Visual FoxPro 数据库升迁 到 SQL Server 后体积大约为原来的 1 . 2 倍左右。实际当中通常应当保证服务器端磁盘剩余空 间为当前本地数据库占用空间的 1 . 5 倍以上。 如果 SQL Server 服务器上有多个硬盘,默认情况下新建的数据库会保存在 SQL Server 所安 装的硬盘上,如果希望把升迁后的数据库保存在其他的硬盘上,则不能够使用 Visual FoxPro 升 迁向导所提供的新建数据库功能,而应该利用 SQL Server 企业管理器在其他硬盘上事先新建 一个数据库,然后在 Visual FoxPro 升迁向导中指定使用该数据库。 ② 必要的权限。 要把数据升迁到 SQL Server 中还需要有必要的权限,最基本的是一个登陆账号和密码。 另外,针对不同的升迁类型,该用户还需要有不同的权限。 如果把 Visual FoxPro 中的数据升迁到 SQL Server 服务器中一个已经存在的数据库中,需要 该用户有该数据库的 CREATE TABLE、CREATE DEFAULT、CREATE RULE 和 CREATE PROCEDURE 等 命 令 的 执 行 权 限。 如 果 在 升 迁 过 程 中 需 要 新 建 数 据 库,则 还 需 要 有 CREATE DATABASE 命令的执行权限以及对 MASTER 表的 SELECT 权限。 (2)客户端准备 ① 备份即将升迁的数据库。 在升迁的过程中,向导不会修改表内的数据,也就是说,DBF、FPT、CDX 等文件将以只读的 形式打开。但是对于数据库则不同,升迁向导会以表的形式打开 DBC、DCT 和 DCX 三个文件, 并有可能修改其中的内容,如添加新的视图、修改表的名称等。为了防止在升迁过程中发生意 外,或者希望能恢复到升迁之前的数据库状态,强烈推荐备份所有的数据库文件。 ② 关闭即将升迁的表。 Visual FoxPro 升迁向导会试图以独占方式打开需要升迁的表,如果该表已经以独占方式打 开则升迁向导将无法继续;如果该表以共享方式被打开,升迁向导会关闭它并重新以独占方式 打开。这样做的目的是为了防止在升迁过程中数据被修改,因此比较好的做法是在升迁之前 关闭所有的表和使用这些表的应用程序。 ③ 建立数据访问途径。 升迁向导通过 ODBC 数据源或者 Visuao FoxPro 数据库内包含的连接来访问 SQL Server 服 务器,这两种方式没有本质的区别,最便捷的方式是升迁之前在客户端建立好相应的 ODBC 数


采用两层 C/S 体系架构

171

据源。 双击“控制面板”窗口中的“数据源(ODBC)”图标,打开“ODBC 数据源管理器”对话框,如图 6 . 20 所示。

图 6 . 20

ODBC 数据源管理器

ODBC 数据源管理器中提供了三种 DSN(Data Source Name,数据源名称):用户 DSN、系统 DSN 和文件 DSN。在 Visual FoxPro 中可以使用前两种类型的 DSN。用户 DSM 和系统 DSN 的区 别在于:前者只能被创建该连接的用户使用,而后者可以被所有的用户使用,本例中采用系统 DSN。 单击“系统 DSN”选项卡上的“添加”按钮,首先会弹出“创建新数据源”对话框,如图 6 . 21 所示。在该对话框中需要选择数据源的驱动程序,也就是数据库引擎,在列表中找到“SQL Server”,选中后单击“完成”按钮。

图 6 . 21 “创建新数据源”对话框

下一步将创建一个连接到 SQL Server 数据库的新数据源,如图 6 . 22 所示。可以在该对话 框中为即将新建的数据源命名和添加相应的描述,并选择 SQL Server 服务器所在的计算机名。


电脑工程师丛书

172

Visual FoxPro 高级应用实例

此处所填写的名称就是将要在 Visual FoxPro 升迁向导中使用的 ODBC 数据源名称,同时系统会 自动搜索当前局域网络中正在运行的 SQL Server 服务器,并列在下拉列表框中,如果手工填写 请确保服务器名称正确无误。

图 6 . 22

创建到 SQL Server 的新数据源

单击“下一步”按钮,在弹出的对话框中选择 SQL Server 账户登陆及身份验证的方式,这是 创建连接到 SQL Server 数据源关键的环节,需要根据 SQL Server 服务器的设置来进行选择,如 图 6 . 23 所示。

图 6 . 23

SQL Server 登陆设置

如果选择“使用网络登录 ID 的 Windows NT 验证”,该数据源将与 SQL Server 服务器建立安 全连接,同时需要 SQL Server 的管理员把服务器上的用户账号和本地的系统账号建立关联。 如果选择“使用用户输入登录 ID 和密码的 SQL Server 验证”,需要输入“登录 ID”和“密码”。在 这种情况下数据源与 SQL Server 服务器之间不需要建立安全连接,但需要在所有的连接中指 定登录 ID 和密码。对于 SQL Server 服务器安装在本地的情况,通常可以选择第一种方式。 如果选中“连接 SQL Server 以获得其他配置选项的默认设置”复选框,该向导将连接图 6 . 22中输入的 SQL Server 服 务 器,并 从 中 获 得 初 始 的 设 置,否 则 在 后 面 的 对 话 框 中 将 使 用


采用两层 C/S 体系架构

173

ODBC 数据源的标准设置作为默认值。 在下一个对话框中,主要修改默认使用的数据库,即连接至 SQL Server 服务器后首先作用 的数据库,默认值是 master 数据库。如果已经在 SQL Server 服务器上为升迁准备新建了数据 库,也可以按照需要进行修改,如图 6 . 24 所示。

图 6 . 24

修改默认数据库

最后一个步骤是设置一些附加的信息(如语言、字符集以及日志文件等),如图 6 . 25 所示。

图 6 . 25

附加信息设置

单击“完成”按钮,将显示一个对话框并列出主要的设置信息。如果这些信息正确无误,单 击“确定”按钮即完成了 ODBC 数据源的创建,单击“取消”按钮可以返回向导重新修改设置,还 可以单击“测试数据源”按钮以验证设置的正确性,如图 6 . 26 所示。 3 . 完成数据升迁 下面将以第 2 章中的企业设备管理系统的 equipment . dbc 为例,来说明数据库升迁的过程 及注意事项,同时也是为本章最后的实例做准备。 单击 Visual FoxPro 的“工具”菜单 - >“向导”子菜单 - >“升迁”菜单项,Visual FoxPro 提供


电脑工程师丛书

174

Visual FoxPro 高级应用实例

图 6 . 26

完成数据源创建

了两种升迁向导:SQL Server 升迁向导和 Oracle 升迁向导,在这里仅介绍前者。 SQL Server 升迁 向导共需要九个步骤完成升迁,下面分别加以说明。 (1)选择本地数据库 第一个步骤非常简单,打开要升迁的数据库,然后单击“下一步”按钮,如图 6 . 27 所示。如 果希望升迁自由表,需要先把它加入到一个数据库中,因为 SQL Server 中不存在自由表。

图 6 . 27

选择本地数据库

(2)选择数据源 数据源可以是 ODBC 数据源或数据库中包含的连接。如果已经按照上文中的步骤完成了 ODBC 数据源的创建,在“可用数据源”列表框中应该包含“equipment(SQL Server)”的数据源,如 图 6 . 28 所示。由于 SQL Server 升迁向导仅适用于 SQL Server 服务器,因此在列表框中只显示 连接至 SQL Server 的数据源,而对于其他的数据源将不予显示。


采用两层 C/S 体系架构

图 6 . 28

175

选择数据源

(3)选择表 这个步骤用来选择需要把哪些表升迁到 SQL Server 上, “可用表”列表框中将列出本地数 据库中所有的表,在这里把其中所有的表通过“ ”按钮移动到“选定表”中,如图 6 . 29 所示。

图 6 . 29

选择需要升迁的表

(4)匹配字段数据类型 Visual FoxPro 与 SQL Server 的字段数据类型不太相同,在表 6 . 2 中已经列出了默认的对应 关系,可以在这个步骤中对其进行适当地修改,如字符型字段可以升迁为 SQL Server 的 char 型、varchar 型、image 型和 text 型。但有的类型无法修改,如逻辑型字段只能对应 SQL Server 的 bit 型。在某个字段的“服务器类型”上单击会给出当前允许的 SQL Server 数据类型,可以按照 需要进行修改,如图 6 . 30 所示。 如果希望在 SQL Server 数据库中增加一个时间戳字段(即字段类型为 timestamp),还可以 选中“时间戳列”复选框,该字段主要用于快速地检查更新冲突,可以提高 SQL Server 数据库的 性能,是典型的空间换时间的做法。该选项在默认情况下是选中的。


电脑工程师丛书

176

Visual FoxPro 高级应用实例

图 6 . 30

匹配字段数据类型

(5)选择目的数据库 在这里可以选择升迁到 SQL Server 服务器的哪个数据库中,在下拉列表中提供了当前服 务器上的所有数据库,可以从中选择一个,也可以选择新建数据库,并填写数据库的名称。请 确保当前连接的登陆账号有相应的权限。在这里采用新建数据库的方法,并把数据库命名为 equipment,如图 6 . 31 所示。

图 6 . 31

选择目的数据库

(6)设置升迁选项 这是整个升迁向导中最重要的一个步骤,如图 6 . 32 所示。 对话框中的每个复选框的含义如下: ① 索引:是否升迁本地索引到 SQL Server 的表索引,其中索引的映射关系已在表 6 . 3 中介 绍了。还需要注意的一点是,SQL Server 的索引没有升序或降序排列。 ② 默认值:是否升迁字段中存储的默认值。 ③ 关系:是否升迁数据库中表与表之间的关系。


采用两层 C/S 体系架构

图 6 . 32

177

设置升迁选项

④ 有效性规则:是否升迁本地数据中的有效性规则。 ⑤ 仅结构,不包含数据:如果选中该选项将不升迁表中包含的记录。 ⑥ 使用声明的参照完整性:SQL Server 有两种形式的参照完整性:触发器式和声明式,默 认采用触发器式。 ⑦ 创建升迁报表:选中该选项会在升迁过程结束后生成一个升迁报表,用于分析本地数 据对象是否被正确地升迁到 SQL Server 服务器上。 ⑧ 重定向视图到远程数据:如果 Visual FoxPro 数据库中包含有本地视图,升迁之前它的数 据来源是本地表,选中该选项则视图的数据来源将成为升迁后 SQL Server 中对应的远程表。 如果是从本地原型开始的应用程序务必选中该选项。 ⑨ 在表上创建远程视图:如果选中该选项会把数据库中的本地表加上后缀“— local”,同时 用原名新建对应的远程视图。直接在向导中创建远程视图是一种看似简便其实效率很低下的 做法,因为远程数据库中的内容会全部通过网络传输到本地,势必增加网络流量。 ⑩ 与视图一起存储口令:如果数据源的登陆设置不是网络 ID 的 Windows NT 验证,同时在 步骤(2)中使用数据源而不是连接,在每次访问远程数据时都会要求输入登陆账号和密码,在 这种情况下可以选中该选项以把口令和视图一同存储,但是这样做可能会带来安全隐患。 此外,该对话框还包含一个“空映射”下拉列表框,可以选择希望在升迁过程中忽略的字段 类型,通常选择默认设置。 (7)完成 至此,升迁向导已经收集了所有必要的信息,可以开始进行数据升迁操作。选择“升迁”将 立即开始升迁操作,选择“保存生成的 SQL”将把这个升迁向导即将使用的 SQL 命令代码保存 下来但是不执行,选择“升迁并保存生成的 SQL”将同时完成以上两个步骤,如图 6 . 33 所示。 在这里选择“升迁并保存生成的 SQL”单选框并单击“完成”按钮。在图 6 . 34 所示的对话框弹 出后,整个升迁过程最终完成了。由于升迁使用的是 ODBC 数据访问方式,所以当本地数据库 中数据量较大时,这个升迁过程可能会需要较长时间。


电脑工程师丛书

178

Visual FoxPro 高级应用实例

图 6 . 33

完成升迁的准备工作

图 6 . 34

升迁完成

完成升迁后,可以查看升迁报表中是否包含有错误信息,也可以在 SQL Server 企业管理器 中查看从 Visual FoxPro 中升迁来的数据,在有些情况下还需要在企业管理器中对升迁的结果 进行手工修改。在这里,主要查看 equipment 数据库是否被创建,本地数据库中的 registry 表、 user 表和 dict 表及其中的数据是否被正确升迁,如图 6 . 35 所示。

图 6 . 35

升迁的 SQL Server 数据库

从图中可以看到,所有的表和数据都被正确地升迁,惟一产生意外的是“user”表被重命名


采用两层 C/S 体系架构

179

为“user—”,这是因为该表名违反了 SQL Server 的命名规则:SQL Server 的表名不能超过 30 个字 符,不能包含空格而且不能与 SQL Server 的保留关键字相同,user 就是 SQL Server 的保留关键 字之一,因此升迁向导会在表名上自动添加“—”。

6 . 2 . 5 远程视图 视图对于 Visual FoxPro 程序员来说应该不陌生,它就是一种可更新的查询,或者理解为 “逻辑表”、 “虚拟表”。其中远程视图是访问后台数据库服务器的有效途径,它和本地视图的区 别在于:本地视图的数据来源通常是本地的 Visual FoxPro 表,而远程视图的数据来源是通过 ODBC 数据连接的远程数据。在这里需要对“远程”的概念需要澄清一下:远程并不一定意味 着就是网络上其他的计算机,也可以是指本机,使用远程这个词只是强调它与当前的应用程序 之间具有独立性而且具备远程访问能力。 简单地说,对远程数据的访问包括两方面:读和写。下面将对远程视图这两种最基本的操 作进行简要的介绍。 1 . 获取远程数据 使用远程视图访问 SQL Server 之前,通常需要在数据库中建立一个连接,它将为视图提供 一个通向远程数据的管道。对于 SQL Server 服务器来说,连接中包含了 SQL Server 的登陆信息 及相关设定,使用连接设计器可以很轻松地完成这些工作。 如果数据库已经加入到项目中,可以打开项目管理器,在“数据”选项卡上展开相应数据库 的节点,选中“连接”并单击“新建”按钮即可运行连接设计器。如果数据库未加入到项目中,可 以直接打开该数据库并在快捷菜单中选择“连接”菜单项,此时将会列出该数据库中的所有连 接,可以选择新建或者修改现有的连接。 如图 6 . 36 所示的连接设计器,其中最重要的设定是指定数据源。在“数据源”列表框中会

图 6 . 36

连接设计器


电脑工程师丛书

180

Visual FoxPro 高级应用实例

列出当前所有的系统 DSN 和用户 DSN,从中选择已经创建好的 equipment 数据源,由于在该数 据源中已经设置为使用网络登录 ID 的 Windows NT 验证,因此用户标识和密码可以省略, “数 据库”文本框中填写已经升迁好的 SQL Server 数据库 equipment。 此外也可以选择“连接串”单选框,分别用“DSN”、 “UID”、 “PWD”和“DATABASE”指定数据 源、用户账号、密码和数据库(见图 6 . 37),类似格式的连接字符串在后面的 SPT 技术中还将使 用到。单击“验证连接”按钮,如果返回“连接成功”的信息则表明所有的输入正确无误。

图 6 . 37

用连接串指定数据源

连接设计器中还提供了其他一些设置选项,其中“显示 OBDC 登录提示”可以有三种选择: 未指定登录信息时显示、总显示和从不显示。默认使用第一种,但是如果该连接将被进程内 COM 组件或远程自动化服务访问,最好设置为从不显示,因为任何可视界面都将导致出错。 其他一些设置除非你确切地知道自己在干什么,否则使用默认值就可以了。所有设置完成后 单击“确定”按钮,并保存该连接为“local”,表明它是连接到本地 SQL Server 服务器的数据源。 连接创建完毕后,就可以在数据库中新建远程视图了。远程视图的设计过程与本地视图 基本相同,最主要的不同在于新建远程视图前必须指定一个连接或数据源,如图 6 . 38 所示。 通常情况下选择“连接”单选项,因为在连接设计器中可以对其进行完整的定义,包括使用的数 据库是否提示登陆信息等,具有更大的灵活性。

图 6 . 38

指定远程视图的连接

选定“local”作为远程视图的连接,单击“确定”按钮,在弹出的“打开”对话框中将给出 SQL Server 上 equipment 数据库中所包含的所有表,此处选择“user—”表,在视图设计器中把它的四 个字段全部添加到“选定字段”列表框中(见图 6 . 39),最后把该视图保存为 user。 从某种意义上讲,可以把远程视图可以看作是升迁向导的逆过程:升迁向导是把本地的 Visual FoxPro 数据库映射到 SQL Server 服务器中,而远程视图正好相反,它把 SQL Server 服务器 上的数据映射到视图中,因此在这个过程中也存在各种数据类型转换的问题。如果需要修改


采用两层 C/S 体系架构

图 6 . 39

181

设计远程视图

默认的转换规则,可以选中某一“选定字段”,并单击“属性”按钮,在“视图字段属性”对话框中 修改“数据匹配”规则或其他设置,如 SQL Server 中只有 datatime(日期时间型)字段而无 data(日 期型)字段,如果在程序中使用日期型字段更为合适的话,可以在该对话框中修改相应的数据 类型。当然,远程视图和升迁还是有很多不同的地方,如升迁是一个单向的操作,而远程视图 是双工的,不仅可以获取数据,还可以把修改的结果更新到数据源中。 2 . 更新远程数据 浏览上文中创建的 user 远程视图,结果如图 6 . 40 所示,可见 SQL Server 中的数据已经被映 射到该视图中,如果需要,可以像本地的数据那样进行各种操作,如筛选、排序、分组等。也就 是说,获取数据的工作已经全部完成。但是,假如在浏览窗口中修改其中的数据,通过 SQL Server 的企业管理器观察可以发现,所作的修改并未反映到远程数据源中,这是因为还有一项 很重要的工作没做:设置更新条件。

图 6 . 40

远程视图 user 的浏览结果

视图与查询最根本的区别就在于视图是可更新的,利用远程视图的更新功能可以像操纵 本地数据一样对 SQL Server 中的数据进行添加、删除和修改等操作,而不需要使用任何 SQL Server 数据库自身的命令,由此可以看出远程视图功能强大而又方便快捷的一面。 打开视图设计器,并切换到“更新条件”选项卡,其中标注出了实现可更新远程视图的几个 基本条件,如图 6 . 41 所示。


电脑工程师丛书

182

Visual FoxPro 高级应用实例

图 6 . 41

远程视图更新条件

(1)设定键值 键值是对记录惟一性的保证,没有键值的视图是无法更新的,因为在更新过程中需要能够 惟一标识表中的数据记录。假如修改了某个系统管理员(管理权限为 . T.)的密码,在更新的 时候导致所有的系统管理员密码均被修改,这显然是不能容忍的。在这里使用“用户名”字段 作为视图的键值,单击字段名,在钥匙图标对应的列上打上“√”标记,在这种情况下要求“用户 名”字段中的值不能有重复。 但是键值和主索引或候选索引中使用的关键字又有所不同,键值可以是由多个字段共同 组成,也就是说如果可以由某几个字段共同确定一条惟一的记录,那么用它们作为键值也是允 许的。例如,在 user 视图中,如果用户名有重复,但是同一个用户名的密码是不同的,在这种情 况下可以用“用户名”和“密码”字段共同作为视图的键值。 (2)指定可更新字段 只有当一个字段被设置为可更新字段,这样当记录被修改后该字段的值才会更新到远程 数据库中。选中某个字段,在铅笔图标对应的列上打上“√”标记,即可把该字段设置为可更新 字段,或者单击“全部更新”按钮可以设置所有非键值字段为可更新字段。默认情况下,键值字 段都是不可更新的,如果需要更新键值字段可以用手工指定。在这里选择更新包括键值在内 的所有字段。 (3)检查更新冲突 在第 4 章中已经对共享访问的环境下各种类型的冲突问题进行了详细的介绍,由于 SQL Server 数据库支持多用户网络访问,冲突问题更是在所难免,因此在远程视图中还需要设定更 新冲突的检查条件。在“SQL WHERE 子句包括”中给出了四种更新冲突的检查条件: ① 关键字段:如果选择该选项,远程视图在更新时只检查键值字段在编辑期间是否修改。 ② 关键字和可更新字段:如果选择该选项,远程视图在更新时只检查键值字段和可更新 字段在编辑期间是否修改。 ③ 关键字和已修改字段:如果选择该选项,远程视图在更新时只检查键值字段和已修改 字段在编辑期间是否修改。 ④ 关键字和时间戳:如果选择该选项,远程视图在更新时只检查键值字段和可更新字段 在编辑期间是否修改,使用该选项的条件是在升迁时添加了“时间戳列” (见图 6 . 30)或者在 SQL Server 及远程视图中都包含了时间戳字段。 (4)打开更新开关 最后,确保选中“发送 SQL 更新”选项,否则所有的更新操作将被忽略。 完成以上设定后,重新浏览远程视图,并试着在其中添加、修改或删除记录,通过 SQL Server 企业管理器可以发现,在远程视图和 SQL Server 数据库之间已经实现了数据的同步。


采用两层 C/S 体系架构

183

3 . 条件视图 一切看起来都是那么顺理成章:把远程视图加入到表单的数据环境中,把表单控件和视图 的字段绑定,添加导航条和增、删、改按钮……一个以 SQL Server 服务器为后台数据库的 C/S 体系应用程序完成了。但回过头来阅读本章开头关于文件共享体系和 C/S 体系之间区别的 论述,会发现其中很重要的一点就是利用 C/S 体系中后台数据库智能应答的特性有效降低网 络的数据传输量,也就是说,只从远程数据中选择需要的数据下载到本地,操作完毕后再把更 新结果返回到远程数据库中。而到目前为止,SQL Server 的智能应答还没有被利用上,刚才创 建的远程视图只是把所有的数据都映射到本地,这样做的恶劣后果是可以预见的:如果表中包 含有十万条数据,执行一条 GO BOTTOM 命令可能需要等待一分钟甚至更长的时间。为此,需 要对远程视图中包含的数据进行筛选,这样的视图称为条件视图。 在视图设计器中的“筛选”选项卡中可以对视图的筛选条件进行设定,如图 6 . 42 所示。

图 6 . 42

视图筛选条件

设置筛选条件为“User— . 用户名 = admin”,保存视图定义并浏览该视图,视图中只显示“用 户名”字段为“admin”的记录。当然,不可能为每条记录都设计一个视图,因此在筛选条件中可 以使用变量,方法是在“实例”中填写变量名并在前面添加一个“? ”符号,如图 6 . 43 所示。

图 6 . 43

在筛选条件中使用变量

设置筛选条件为“User— . 用户名 = ? cUserName”,保存视图定义并浏览该视图,系统会提 示要求输入参数,如图 6 . 44 所示。 输入“admin”作为变量 cUserName 的值,效果与在筛选条件中直接设置“User— . 用户名 = admin”相同。


电脑工程师丛书

184

Visual FoxPro 高级应用实例

图 6 . 44

6.2.6

输入视图参数

SQL Pass - Through 技术

远程视图具有方便快捷的特性,Viusal FoxPro 程序员不需要学习太多的新知识就可以很好 地掌握 C/S 体系架构的要领,并在 SQL Server 后台数据库的基础上开发出优秀的应用程序。 但是在某些情况下,远程视图还不能够完全地发挥出 C/S 体系的优越性。对于以下几种情况 远程视图就无能为力: ① 通过客户端应用程序新建一个 SQL Server 数据库。 ② 执行 SQL Server 上的存储过程。 ③ 调用 SQL Server 的特殊函数。 …… SQL Pass - Through(SPT)技术弥补了远程视图在这方面的不足,通过 SPT 可以直接调用 SQL Server 服务器的功能,执行 SQL 命令,实现系统管理等。当然 SPT 也有自身的一些缺陷,主 要表现在缺乏可视化设计界面,很多基本的操作都需要手工干预。表 6 . 5 列出了远程视图与 SPT 技术的比较,从中不难看出两者的优劣所在。在大多数情况下需要把远程视图和 SPT 相 结合才能体会到 Visual foxpro 对开发 C/S 体系应用程序的强大支持。 表 6.5

远程视图与 SPT 技术的比较

远程视图

SPT 技术

基于 SQL - SELECT 命令

可以执行数据库服务器支持的所有 SQL 命令

可加载至表单数据环境并与表单控件绑定

无法与表单及其控件直接关联

不能执行后台数据库的 DLL 命令

提供了对后台数据库执行 DLL 命令的方法

每次只能获得一个结果集

一次可获得一个或多个结果集

提供自动联接管理

需要手工进行联接管理

为数据增、删、改提供内部的默认更新信息

不提供默认更新信息

隐式地执行 SQL 命令并获取数据

显式地执行 SQL 命令并需要控制结果的输出

不提供事务处理

提供明确的事务处理

数据具有长期性

数据基于工作期

SPT 技术的核心其实就是一系列的函数,通过这些函数可以完成建立远程连接、执行命 令、获取数据、实现控制等功能,通常称之为 SPT 函数。表 6 . 6 列出了 SPT 函数并简要地介绍 了它们的功能。


采用两层 C/S 体系架构

表 6.6 SPT 函数分类

SPT 函数总览与简介

SPT 函数名

SPT 函数简介

SQLCONNECT()

为 SPT 操作连接一个数据源

SQLSTRINGCONNECT() SQLDISCONNECT()

使用 ODBC 联接字符串语法连接数据源

SQLCANCEL() SQLEXEC()

取消一个异步执行的 SQL 查询 执行 SQL 命令或数据库服务器上的存储过程

SQLMORERESULTS() SQLPREPARE()

传回后续的查询结果

SQLCOMMIT() SQLROLLBACK()

申请提交一个事务

数据源信息

SQLCOLUMNS() SQLTABLES()

将表中的字段信息存入一个临时表

其他控制

SQLGETPROP() SQLSETPROP()

获得活动连接的属性

连接管理

SQL 命令执行

185

断开一个对 ODBC 数据源的连接

预编 SQL 命令以提高执行速度 申请回滚一个事务 将数据源中的表名存入一个临时表 设置活动连接的属性

下面将择要介绍这些 SPT 函数的使用方法。 1 . 建立数据源连接 使用 SPT 函数访问数据源的第一个步骤是建立连接,使用 SQLCONNECT( )函数和 SQLSTRINGCONNECT()函数都可以完成这一功能,从最终的执行结果上来讲,这两个函数的作用 是完全相同的,只是类似于在连接设计器中使用“数据源、用户标识、密码”和“字符串”两种方 式指定数据源的区别,请自行对比图 6 . 36 和图 6 . 37。 (1)SQLCONNECT()函数 语法:SQLCONNECT([DataSourceName,cUserID,cPassword | cConnectionName] ) DataSourceName:DSN 名称,可以是用户 DSN 或系统 DSN。 cUserID,cPassword:数据源的登陆账号和密码。 cConnectionName:本地 Visual FoxPro 数据库中已经建立好的连接名。 返回值:如果连接成功返回一个大于零的连接句柄,通常需要保存在一个内存变量中备 用,如果连接失败返回负值。 例如,可以使用如下两种方法连接 SQL Server 服务器: nHandle = SQLCONNECT("equipment") 或 OPEN DATABASE equipment nHandle = SQLCONNECT("local") 第一条语句中的 equipment 取决于上文中已经创建好的系统 DSN 的名字,而不是远程数据 库的名字,第二条语句中的 local 是本地 Visual FoxPro 数据库 equipment . dbc 中已经创建好的连 接,由于在连接中可以指定使用某个具体的数据库,因此第二种方法比第一种方法更有效。 (2)SQLSTRINGCONNECT()函数 语法:SQLSTRINGCONNECT([cConnectString ] ) cConnectString:连接字符串,格式同图 6 . 37 中所示的连接串格式,如果不使用参数会弹出


电脑工程师丛书

186

Visual FoxPro 高级应用实例

“选择数据源”对话框。 返回值:和 SQLCONNECT()函数的返回值含义相同。 举例: SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") 使用 SQLSTRINGCONNECT()函数建立连接的好处是直接了当,不像 SQLCONNECT()函数 那样要么不能指定使用的数据库,要么必须先在本地数据库中创建连接,因此在程序代码中通 常使用这种方法。 2 . 执行 SQL 命令 执行 SQL 命令需要通过 SQLEXEC()函数来完成,因此它也是 SPT 函数中最核心的一个。 语法:SQLEXEC(nHandle,[cSQLCommand ,[CursorName]] ) nHandle:通过执行 SQLCONNECT()函数或 SQLSTRINGCONNECT()函数得到的连接句柄。 ”分隔。 cSQLCommand:SQL 命令字符串,可以同时包含多个 SQL 命令,以“; CursorName:临时表名称,如果缺省该参数 Visual FoxPro 会把 SQL 命令的执行结果保存在 名为 SQLRESULT 的临时表中。 返回值:如果返回值为 0 表示 SQL 命令正在执行;如果返回值小于 0 表示 SQL 命令执行失 败;如果返回值为 1 表示 SQL 命令已经正确执行完毕。由于执行一次 SQLEXEC()函数可能有 多个结果集,在这种情况下将返回结果集的个数。 下面将通过一系列的代码片段来说明 SQLEXEC()函数的使用方法。 ① 简单查询。 试执行以下命令: n = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") p = sqlexec(n,"Select  from user— ","cur1") SELECT cur1 BROWSE 这段代码首先建立一个数据源连接并使用 SQL Server 服务器上的“equipment”数据库,然 后执行一条 SQL 命令:Select  from user— ,该命令从“user—”表中选出所有的记录并把它们保 存在临时表 cur1 中。选择该临时表即可用 BROWSE 命令查看到其中的内容,还可以在程序中 动态地把临时表 cur1 与表单控件进行绑定: THISFORM. Text1 . ControlSource = "cur1 . 用户名" ② 条件查询。 试执行以下命令: n = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") p = sqlexec(n,"Select  from user— where 管理权限 = 1","cur1") SELECT cur1 BROWSE 这段代码将从“user—”表中取出所有具有管理权限的用户资料,方法是在 SQL 命令中使用 WHERE 子句。和条件视图一样,在 SQLEXEC()函数中执行的 SQL 命令中也可以使用变量作 为 WHERE 子句的参数,试执行如下命令: n = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment")


采用两层 C/S 体系架构

187

cUserName = "admin" p = sqlexec(n,"Select  from user— where 用户名 = ? cUserName","cur1") SELECT cur1 BROWSE 这和执行 sqlexec(n,"Select  from user— where 用户名 = ' admin' ","cur1")的效果是完全相同 的。 ③ 多重查询 试执行如下命令: n = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") p = sqlexec(n,"Select  from user— ;Select  from dict") m = AUSED(AryCursor) FOR i = 1 TO m ) SELECT(AryCursor[i, 2] BROWSE NOWAIT ENDFOR 在这个例子中同时执行了两条 SQL 查询,需要用“; ”进行分割,执行的结果放在 SQLRESULT、SQLRESULT1……中,也可以通过 AUSED()函数把它们添加到数组中以便于调用。 ④ 创建可更新的临时表。 在以上示例中的临时表是可读写的,但是修改后的结果不会更新到后台数据库中,必须使 用 CURSORSETPROP()函数设置临时表的属性,步骤与设置远程视图的更新条件类似。 CURSORSETPROP()函数的基本语法如下: ) CURSORSETPROP(cProperty [,eExpression ][,cTableAlias | nWorkArea ] cProperty:欲设置的临时表属性。 eExpression:为该属性设置的值或表达式。 cTableAlias | nWorkArea:临时表的别名或工作区。 表 6 . 7 给出了可以通过 CURSORSETPROP()函数设置的主要属性及含义。 表 6.7

CURSORSETPROP()函数设置的临时表属性及含义

属性 BatchUpdateCount

含义 把多少条 SQL 语句作为一个单独的数据包同时发送,默认值为 1,如果在程序 中经常使用大批的 SQL 命令,增加该值可以降低网络流量,提高程序性能 1 - 关闭缓冲; 2 - 保守式行缓冲; 3 - 开放式行缓冲; 4 - 保守式表缓冲; 5

Buffering

- 开放式表缓冲。这些缓冲模式第 4 章已有论述,对于远程数据(如远程视 图或远程 SQL 的临时表),只能使用 3 或 5

CompareMemo

是否更新 MEMO 型字段

FetchAsNeeded

是否仅获取所需的数据,对于远程数据还受 MaxRecords 属性的约束

FetchMemo

是否获取 MEMO 型字段


电脑工程师丛书

188

Visual FoxPro 高级应用实例

(续表) 属性

含义 每次获取远程数据源上的记录数,默认为 100。也就是说在获取了前 100 条记

FetchSize

录后程序将把控制权交出,然后再以 100 为单位获取下一批数据。设为 - 1 可以获取所有的数据,实际获取的记录数还受 MaxRecords 属性的约束

KeyFieldList MaxRecords

关键字段列表,无默认值,必须手工设定 结果集中包含记录数的最大值,默认为 - 1,表示不加限制,设置为 0 表示不获 取任何数据

Prepared

是否准备好执行 REQUERY()函数重新查询

SendUpdates

是否发送更新

Tables

远程表的名字列表,无默认值,必须手工设定

UpdatableFieldList

可更新字段列表

UpdateNameList

指定临时表与远程表的字段对应关系

UpdateType

更新类型: 1 - 使用 UPDATE 更新; 2 - 使用 DELETE 和 INSERT 更新 指定把字段转换成 MEMO 类型字段的最小宽度,可以从 1 ~ 255,默认值为

UseMemoSize

255。例如,某字符型字段如果宽度超过 UseMemoSize 将会被转换成 MEMO 型 字段

WhereType

指定更新冲突判断方式: 1 - 关键字段; 2 - 关键字与可更新字段; 3 - 关键 字与被修改字段; 4 - 关键字与时间戳列

相信细心的读者已经发现,远程视图其实就是 SQL - SELECT 命令与 CURSORSETPROP() 函数相结合的产物,只是在此基础上添加了自动连接管理和图形化的设计界面而已。 试执行以下命令: n = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") p = sqlexec(n,"Select  from user— ","cur1") SELECT cur1  设置更新目标:后台的表名 CURSORSETPROP("tables","user— ")  设置关键字段 CURSORSETPROP("keyfieldlist","用户名")  设置可更新字段 CURSORSETPROP("UpdatableFieldList","用户名,密码,用户描述,管理权限")  设置临时表字段与远程数据表字段的对应关系 CURSORSETPROP("updatenamelist","用户名 user— . 用户名," + ; "密码 user— . 密码," + ; "用户描述 user— . 用户描述," + ; "管理权限 user— . 管理权限")  设置允许发送更新 CURSORSETPROP("SendUpdates",. T.)


采用两层 C/S 体系架构

189

BROWSE 在 BROWSE 窗口修改临时表中记录的值,通过 SQL Server 企业管理器可以发现所进行的 修改已经体现在后台数据库中。在这段代码给出了设置可更新临时表的五个基本步骤,缺一 不可。在第四个步设置临时表字段与远程数据表字段的对应关系时,远程表的字段前缀“user— .”不能省略。 ⑤ 创建存储过程。 对于有些需要经常性执行的 SQL 命令可以把它放在 SQL Server 的存储过程中,使用存储 过程至少有以下好处:代码简洁、通过网络传输的数据量小、可以预编译从而加快执行的速度。 SQL Server 存储过程就是由 SQL 语句和流程控制语句组成的预编译集合,具有单元处理 的特性,是 SQL Server 数据库对象之一。 SQL Server 存储过程分系统存储过程和用户存储过 程,前者具有“sp—”的前缀并保存在 master 数据库中;命名用户存储过程也需要遵循 SQL Server 的命名规则,而且不能与系统存储过程同名,否则用户存储过程永远也得不到执行。 创建 SQL Server 存储过程使用 Transact - SQL 语法,下面仅介绍其基本语法: CREATE PROCEDURE procedure— name [{ @parameter data — type }] [ WITH{ RECOMPILE | ENCRYPTION | RECOMPILE ,ENCRYPTION }] AS sql — statement procedure— name:创建的存储过程名称。 @parameter data— type:参数及类型。 {RECOMPILE | ENCRYPTION | RECOMPILE,ENCRYPTION}:RECOMPILE 表示每次执行该存 储过程都重新编译,ENCRYPTION 表示对存储过程的文本进行加密,可以同时使用这两个选 项。 sql— statement:存储过程中包含的 Transact - SQL 代码。 另外可以用 DROP PROCEDURE procedure— name 删除一个存储过程。试执行如下命令: N = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") = sqlexec(N,"drop procedure my— proc") = sqlexec(N,"create procedure my— proc as select  from user— ") 该命令的含义是:如果 equipment 数据库中存在名为 my— proc 的存储过程则首先把它删 除,然后创建一个名为 my— proc 的存储过程,该存储过程包含的 SQL 语句为“select  from user—”。命令执行完毕后,打开 SQL Server 企业管理器,在其中可以找到名为 my— proc 的存储过 程,如图 6 . 45 所示。 ⑥ 执行存储过程。 试执行如下命令: n = SQLSTRINGCONNECT("dsn = equipment;uid = ;pwd = ;database = equipment") p = sqlexec(n,"my— proc","cur1") SELECT cur1 BROWSE 执行结果与第一个例子完全相同,但是如果需要多次执行相同的 SQL 命令,而这些命令 又较为复杂(SQL Server 可以创建最大为 128M 的存储过程),可以明显的提高效率。 通过 SQLEXEC()函数可以对后台数据库进行各种复杂的操作,通过以上示例已经可以窥


电脑工程师丛书

190

Visual FoxPro 高级应用实例

图 6 . 45

通过 SPT 创建的存储过程

得一斑,更多的功能需要对 SQL Server 做进一步的学习,这已经超出了本书讨论的范围,因此 在这里就不一一列举了。 3 . 关闭数据源连接 在创建数据源连接后,这个连接会一直有效,直到通过 SPT 函数关闭连接为止。通常情况 下,如果一个连接不再使用,应该尽快把它关闭。虽然 SQL Server 可以支持很多客户端同时连 接,但是每个连接毕竟要占用一定的系统资源,而且如果只创建连接而不关闭连接的话,最终 会导致客户端连接数超过 SQL Server 的上限而被拒绝访问。 使用 SQLDISCONNECT()函数可以关闭数据源连接,语法如下: SQLDISCONNECT( nConnectionHandle ) nConnectionHandle:已创建的数据源连接句柄。 返回值:成功终止连接返回 1,由于网络连接故障而失败返回 - 1,由于系统环境故障而失 败返回 - 2。 如果希望关闭所有的数据源连接,可以使用 SQLDISCONNECT(0)。 在这里仅以 SQL Server 为例来说明 SPT 函数对后台数据库的访问方法,实际上它适 用于所有的通过 ODBC 接口连接的数据源,包括 Oracle、Sysbase 甚至 Visual FoxPro 自身的 数据库。

6.3

实例制作

下面将以第 2 章的企业设备管理系统为例来说明如何用 C/S 体系重新架构一个数据库应 用系统,在这个过程中会同时使用到远程视图和 SPT 技术,更重要的是 C/S 体系中不同的程序 设计理念。

6 . 3 . 1 准备工作 实际上,所有的准备工作在前文中都有论述,现归纳如下: ① 安装 SQL Server 服务器组件,可以是个人版、标准版、企业版或开发版。


采用两层 C/S 体系架构

191

② 启动 SQL Server 服务。 ③ 正确创建 ODBC 数据源连接(用户 / 系统 DSN),在本例中连接名为 equipment。 ④ 使用 SQL Server 升迁向导把实例二的 equipment 数据库升迁到 SQL Server 的 equipment 数据库中。 以上步骤全部成功后,就可以开始后续的内容了。本实例将要制作的文件包括: ① 项目文件:ex6 . pjx。 ② 数据库文件及表文件:equipment . dbc、config. dbf。 ③ 表单文件:config. scx、manager . scx。 ④ 主程序:main . mpr(项目主文件)。 这些文件位于本书配套光盘的 h ex6 目录下。

6 . 3 . 2 参数设置模块 在 C/S 体系中,为应用程序制作参数设置模块的意义是显而易见的:在开发过程中使用 的数据源连接参数往往和用户在实际使用中的参数是不同的,因此在把应用程序交付给用户 使用后,应该允许用户根据自己的实际情况修改参数的设置,而且,还应该以尽可能友好的形 式提供该功能,不能寄希望于用户能够自行修改类似于“dsn = equipment;uid = ;pwd = ; ”这样的 连接字符串。 一旦参数设置完成后,还应该能够保存这个参数设置,因为通常不会经常性地修改这些参 数,每次都让用户输入相同的一大堆参数也是程序缺乏友好性的表现。为了实现这一点,可以 把数据源、用户名、密码和使用的数据库等信息保存在一个本地表中,在程序开始运行时首先 判断利用这些连接信息能否成功地连接到数据源,如果成功就直接开始后面的操作;如果不成 功则运行参数设置对话框,提示用户修改连接参数设置,或者确保 SQL Server 服务器和网络连 接均正常工作。 由于本例中还使用到基于连接的远程视图,它能否正常工作取决于本地数据库中连接的 设置是否正确,如果用户修改了数据源的相关参数,还应该在程序中修改连接的设置。 1 . 创建本地数据库和表 本地数据库中主要保存远程视图、连接以及参数设置表。新建项目 ex6 . pjx,并在项目管 理器新建数据库 equipment . dbc,添加一个本地表 config. dbf,其结构如表 6 . 8 所示。 表 6.8

config. dbf 表结构

字段名

类型

宽度

说明

DSN

字符型

30

ODBC 数据源名称,可以是用户 DSN 或系统 DSN

UID

字符型

30

数据源的登录账号

PWD

字符型

30

数据源的登录密码

DB

字符型

30

使用的数据库名

在表中根据当前的数据源连接情况填入相关的数据。此处 DSN 字段填“equipment”,DB 字 段填“equipment”,其余字段留空。


电脑工程师丛书

192

Visual FoxPro 高级应用实例

紧接着需要在数据库中新建一个连接 local,具体的设计方法前文已有论述,需要强调的 是:如果 config. dbf 中的设置被用户修改的话,连接将会被重新创建以确保其能够正常工作。 最后还需要创建一个远程视图,该视图使用 local 连接,并加入远程数据库中的 dict 表及 其所有字段。由于该表中保存的是用户的字典数据,数据量一般不大而且很少更新,所以在这 里省略了筛选条件和更新条件的设置,只是简单地把所有数据下载到本地。最后保存该视图 为 dict。浏览该视图,确保其工作正常。 2 . 连接参数设置表单 在该表单中允许用户修改数据源连接参数,修改完成并且工作正常的话可以把这些参数 保存到 config. dbf 中,并且相应地修改 local 连接的属性。在项目管理器中新建表单并保存为 config. scx,设计其外观如图 6 . 46 所示。

图 6 . 46

参数设置表单

把 config. dbf 表加入到该表单的数据环境中,并把表单上的四个文本框 txtDSN、txtUID、 txtPWD、txtDB 分别和 config. dbf 中的 DSN、UID、PWD 和 DB 字段绑定,然后开启数据缓冲模式: 设置数据环境中的 Cursor1 的 BufferModeOverride 为“3 - 开放式行缓冲”。 “测试连接”按钮主要提供对当前输入参数的正确性进行判断的功能,使用简单的 SQLSTRINGCONNECT()函数即可实现,其 Click 事件代码如下: LOCAL tmpstr tmpstr = "dsn = " + ALLTRIM(THISFORM. txtDSN. VALUE)+ ; ";UID = " + ALLTRIM(THISFORM. txtUID. VALUE)+ ; ";PWD = " + ALLTRIM(THISFORM. txtPWD. VALUE)+ ; ";DATABASE = " + ALLTRIM(THISFORM. txtDB. VALUE) nHandle = SQLSTRINGCONNECT(tmpstr) IF nHandle > 0 MESSAGEBOX("连接成功", 0 + 48 + 256,"测试连接") sqldisconnect(0) ELSE MESSAGEBOX("连接失败,检查参数是否正确,SQL Server 服务器" + ; "是否工作正常,或者网络是否通畅!", 0 + 48 + 256,"测试连接") ENDIF


采用两层 C/S 体系架构

193

如果连接失败,原因可能有多方面:参数设置错误、SQL Server 服务器没有开启或者网络故 障,这个就交给用户去判断了。 “确定”按钮仍然要对参数的可靠性进行判断,如果连接成功而且“保存此次设定”的复选 框被选中,则把相关的信息保存到 config. dbf 中,并修改数据库中 local 连接的设置。为了便于 后面使用 SPT 函数访问数据源,还可以把连接字符串保存在一个全局变量中。该按钮的 Click 事件代码如下: LOCAL tmpstr tmpstr = "dsn = " + ALLTRIM(THISFORM. txtDSN. VALUE)+ ; ";UID = " + ALLTRIM(THISFORM. txtUID. VALUE)+ ; ";PWD = " + ALLTRIM(THISFORM. txtPWD. VALUE)+ ; ";DATABASE = " + ALLTRIM(THISFORM. txtDB. VALUE) nHandle = SQLSTRINGCONNECT(tmpstr) IF nHandle > 0 PUBLIC constr constr = tmpstr IF THISFORM. check1 . VALUE = 1 TABLEUPDATE(1)&& 更新 config. dbf 中的数据 ELSE TABLEREVERT(. T.) ENDIF OPEN DATABASE equipment DELETE CONNECTION LOCAL  更新 local 连接设置 CREATE CONNECTION LOCAL ; DATASOURCE ALLTRIM(THISFORM. txtDSN. VALUE); USERID ALLTRIM(THISFORM. txtUID. VALUE); PASSWORD ALLTRIM(THISFORM. txtPWD. VALUE); DATABASE ALLTRIM(THISFORM. txtDB. VALUE) THISFORM. RELEASE sqldisconnect(0) ELSE MESSAGEBOX("参数设置有误,请重新设置!", 0 + 48 + 256,"错误") ENDIF 对于 local 连接的属性设置,采用的是先删除再重新创建的方法,因为 MODIFY CONNECTION 命令将运行连接设计器,这是用户不希望看到的。 还有两种情况:用户单击“退出”按钮或者直接关闭窗口,这两种情况下对用户输入的参数 正确性均未判断,因此不应该把结果保存到 config. dbf 中,由于数据缓冲模式已经开启,所以只 需要在“退出”按钮的 CLick 事件以及表单的 Destory 事件中加入 TABLEREVERT(. T.)命令即 可。


电脑工程师丛书

194

Visual FoxPro 高级应用实例

3 . 主程序设计 主程序中需要对当前 config. dbf 中保存的连接参数进行判断,如果连接正确可以跳过参数 设置表单,直接运行后面的设备数据管理表单,否则弹出参数设置对话框提示设置正确的连接 参数。在项目管理器中添加程序文件 main . prg,代码如下: SET EXCLUSIVE OFF SET TALK OFF SET DEFAULT TO SYS(5)+ SYS(2003)+ " h " USE config. DBF tmpstr = "dsn = " + ALLTRIM(dsn)+ ";uid = " + ALLTRIM(uid)+ ; ";pwd = " + ALLTRIM(pwd)+ ";database = " + ALLTRIM(db) nHandle = SQLSTRINGCONNECT(tmpstr) IF !nHandle > 0 do form config. scx READ EVENTS ELSE sqldisconnect(0) PUBLIC constr constr = tmpstr DO FORM manager . scx READ EVENTS ENDIF

6 . 3 . 3 设备数据维护模块 在设备数据维护模块中,既要考虑到 C/S 体系架构的特点,又要给用户尽可能方便的操 作环境,在此要避免走两个极端:一种极端是像操作本地数据那样不加选择地从服务器下载所 有的数据,另一种极端是仅根据用户输入的查询条件来下载数据。比较好的做法是从服务器 中下载一些关键信息,用户可以从这些关键信息中选择想要操作的记录,然后再下载相应的数 据。在这里选择设备编号作为关键字段,用户可以浏览所有的设备编号,并针对该设备进行相 应的数据操作。在实际应用中可以根据具体情况选择相应的关键字段,总的指导思想就是在 数据量和易操作性上找到一个最佳的平衡点。 在 C/S 体系中,数据流程往往比较复杂,因为牵涉到远程数据和远程视图或临时表之间 的数据传递,因此在开始具体的设计之前最好能花点时间设计数据流程图,它能让你在设计过 程中不至于迷失方向。图 6 . 47 说明了本例中各个模块的数据走向,从中可以发现,除了删除 设备是直接使用 SPT 函数与 SQL - DELETE 命令和 SQL Server 服务器打交道外,其余模块均只 通过表单控件和本地的临时表进行数据传递,并通过设置临时表的可更新条件把本地数据更 新到 SQL Server 的数据库中。在这个过程中,对关键字段“设备编号”的切换控制着本地临时 表对应的记录。 在项目管理中新建表单并保存为 manager . scx,设计其外观,如图 6 . 48 所示。


采用两层 C/S 体系架构

图 6 . 47

195

设备数据维护模块数据流程图

图 6 . 48

设备数据维护表单

如果说图 6 . 47 以数据为中心说明了设备管理模块的数据流程,那么图 6 . 48 则是以用户 为中心说明了这个模块的操作流程。在编写代码的过程中,可能需要经常在这两个中心之间 进行切换。 1 . 表单初始化 表单初始化时首先需要利用主程序文件 main . prg 或参数设置表单传递过来的连接字符串 和 SQL Server 数据库建立连接,并把连接句柄保存在表单自定义属性 nHandle 中。然后从远程 数据库中获取关键字段“设备编号”的所有值保存在“cu 编号”临时表中(即图 4 . 27 中的设备编 号临时表),并把它作为“编号列表”下拉列表框的 ROWSOURCE。 紧接着应该取出编号列表中的第一条设备信息作为表单控件默认显示的内容,方法是使 用一个条件查询,并把临时表中的数据和表单控件绑定。由于临时表是在运行时生成的,因此 不能在设计时进行绑定(实际上仅绑定字段名也是允许的,然后在运行时选择相应的工作区,


电脑工程师丛书

196

Visual FoxPro 高级应用实例

但是为了可靠起见,这里没有采取这种做法),为此需要在表单中设计一个自定义方法 bind,主 要完成表单控件的动态绑定。为了便于后面进行修改操作,还需要设置“cu 细节”临时表为可 更新,代码放置在表单的自定义方法“enableupdate”中。 (1)动态绑定表单控件的 bind 方法代码 THIS. 设备编号 . CONTROLSOURCE = "cu 细节 . 设备编号" THIS. 设备名称 . CONTROLSOURCE = "cu 细节 . 设备名称" THIS. 部门 . CONTROLSOURCE = "cu 细节 . 部门" THIS. 主要设备 . CONTROLSOURCE = "cu 细节 . 主要设备" THIS. 购买时间 . CONTROLSOURCE = "cu 细节 . 购买时间" THIS. 登记时间 . CONTROLSOURCE = "cu 细节 . 登记时间" THIS. 价格 . CONTROLSOURCE = "cu 细节 . 价格" THIS. 产地 . CONTROLSOURCE = "cu 细节 . 产地" THIS. 制造商 . CONTROLSOURCE = "cu 细节 . 制造商" THIS. 设备状况 . CONTROLSOURCE = "cu 细节 . 设备状况" THIS. 维修情况 . CONTROLSOURCE = "cu 细节 . 维修情况" THIS. 备注 . CONTROLSOURCE = "cu 细节 . 备注" THIS. 登记员编号 . CONTROLSOURCE = "cu 细节 . 登记员编号" THIS. 登记员姓名 . CONTROLSOURCE = "cu 细节 . 登记员姓名" (2)设置临时表可更新条件的 enableupdate 方法代码  设置更新目标:后台的表名 CURSORSETPROP("tables","registry","cu 细节")  设置关键字段 CURSORSETPROP("keyfieldlist","设备编号","cu 细节")  设置可更新字段 CURSORSETPROP("UpdatableFieldList","设备编号,设备名称,部门,主要设备,购买时间,登 记时间,价格,产地,制造商,设备状况,维修情况,备注,登记员编号,登记员姓名 ","cu 细节")  设置临时表字段与远程数据表字段的对应关系 CURSORSETPROP("UpdateNameList","设备编号 registry. 设备编号," + ; "设备名称 registry. 设备名称," + ; "部门 registry. 部门," + ; "主要设备 registry. 主要设备," + ; "购买时间 registry. 购买时间," + ; "登记时间 registry. 登记时间," + ; "价格 registry. 价格," + ; "产地 registry. 产地," + ; "制造商 registry. 制造商," + ; "设备状况 registry. 设备状况," + ; "维修情况 registry. 维修情况," + ; "备注 registry. 备注," + ;


采用两层 C/S 体系架构

197

"登记员编号 registry. 登记员编号," + ; "登记员姓名 registry. 登记员姓名","cu 细节")  设置允许发送更新 CURSORSETPROP("SendUpdates",. T. ,"cu 细节")  采用开放式表缓冲 CURSORSETPROP("Buffering", 5,"cu 细节") 以上代码中大部分内容在前文已有详细论述,惟独启用开放式表缓冲模式的理由未曾讨 论过,在后面的新增记录模块中对此作进一步的说明。 (3)表单初始化事件(Init)代码 SELECT DIST 部门 FROM dict WHERE ! ISBLANK(部门)INTO CURSOR cu 部门 THIS. 部门 . ROWSOURCE = "cu 部门" SELECT DIST 设备状况 FROM dict WHERE ! ISBLANK(设备状况); INTO CURSOR cu 设备状况 THIS. 设备状况 . ROWSOURCE = "cu 设备状况" THISFORM. nHandle = SQLSTRINGCONNECT(constr) = sqlexec(THISFORM. nHandle,"select 设备编号 from registry",; "cu 编号") SELECT cu 编号 THIS. 编号列表 . CLEAR THIS. 编号列表 . ROWSOURCE = "cu 编号" THIS. 编号列表 . VALUE = THIS. 编号列表 . LIST(1) str1 = ALLTRIM(THIS. 编号列表 . VALUE) = sqlexec(THISFORM. nHandle,; "select  from registry where 设备编号 = ? str1","cu 细节") THISFORM. bind THISFORM. enableupdate THIS. REFRESH 2 . 切换记录模块 在表单中有一个重要的控件就是“编号列表”下拉列表框控件,该控件在表单初始化时填 充了所有的设备编号,用户通过该控件可以在不同的设备记录之间进行切换。“编号列表”控 件所要做的就是根据相应的设备编号更新本地的临时表“cu 细节”。其 InteractiveChange 事件 的代码如下: str1 = THIS. VALUE = sqlexec(THISFORM. nHandle,; "select  from registry where 设备编号 = ? str1","cu 细节") THISFORM. enableupdate THISFORM. REFRESH


电脑工程师丛书

198

Visual FoxPro 高级应用实例

3 . 修改、添加与删除数据模块 增、删、改永远是数据库应用系统的核心功能,在本例中也不例外。在这里,增、删、改功能 由“修改”、 “保存”、 “添加”和“删除”四个按钮共同完成。这些按钮中经常需要批量操纵表单控 件的 ReadOnly 属性或者 Enabled 属性,所以把这些代码放在表单的自定义方法 enableedit 和 disableedit 中,前者使绑定型表单控件处于可编辑状态,后者则正好相反。 单击“修改”按钮将打开绑定型表单控件的编辑模式,并使“添加”、 “删除”、 “退出”按钮和 “编号列表”控件不可用,由于临时表已经开启了缓冲模式,所以必须显示的更新修改或取消修 改,否则将会导致程序运行出错。再次单击“修改”按钮(此时显示为“取消”)将取消这次修改, 单击“保存”按钮可以把这次修改的结果保存到临时表中,至于临时表到 SQL Server 数据库的 远程更新由我们设置好的更新条件自动完成。 单击“添加”按钮将在临时表中添加一条空白记录,并等待用户输入数据,再次单击该按钮 可以取消这次操作。和“修改”按钮一样,在这个过程中必须使其他控制按钮不可用。单击“保 存”按钮可以把新增的记录保存到临时表中,在这种情况下,必须使用表缓冲模式,因为 APPEND 命令会修改表头,只有表缓冲能够锁定整个表头,否则临时表中新增的记录不会自动到 远程数据源中。需要说明的是,在这里采用表缓冲并不会加大更新冲突的机会,因为临时表中 的数据实际上只对应 SQL Server 数据库中的一条记录,因此对本地数据采用表缓冲并不影响 远程数据的记录缓冲模式。 “删除”按钮只是简单地把一条 SQL - DELETE 命令发送到 SQL Server 服务器上去执行。 (1)表单 enableedit 方法代码 THIS. 设备编号 . READONLY = . F. THIS. 设备名称 . READONLY = . F. THIS. 部门 . ENABLED = . T. THIS. 主要设备 . READONLY = . F. THIS. 购买时间 . READONLY = . F. THIS. 登记时间 . READONLY = . F. THIS. 价格 . READONLY = . F. THIS. 产地 . READONLY = . F. THIS. 制造商 . READONLY = . F. THIS. 设备状况 . ENABLED = . T. THIS. 维修情况 . READONLY = . F. THIS. 备注 . READONLY = . F. THIS. 登记员编号 . READONLY = . F. THIS. 登记员姓名 . READONLY = . F. (2)表单 disableedit 方法代码 THIS. 设备编号 . READONLY = . T. THIS. 设备名称 . READONLY = . T. THIS. 部门 . ENABLED = . F. THIS. 主要设备 . READONLY = . T.


采用两层 C/S 体系架构

THIS. 购买时间 . READONLY = . T. THIS. 登记时间 . READONLY = . T. THIS. 价格 . READONLY = . T. THIS. 产地 . READONLY = . T. THIS. 制造商 . READONLY = . T. THIS. 设备状况 . ENABLED = . F. THIS. 维修情况 . READONLY = . T. THIS. 备注 . READONLY = . T. THIS. 登记员编号 . READONLY = . T. THIS. 登记员姓名 . READONLY = . T. (3)“修改”按钮 Click 事件代码 IF THIS. CAPTION = "修改" THIS. CAPTION = "取消" THISFORM. enableedit THISFORM. 保存 . ENABLED = . T. THISFORM. 编号列表 . ENABLED = . F. THISFORM. 添加 . ENABLED = . F. THISFORM. 删除 . ENABLED = . F. THISFORM. CLOSABLE = . F. THISFORM. 退出 . ENABLED = . F. ELSE THIS. CAPTION = "修改" THISFORM. 保存 . ENABLED = . F. THISFORM. 编号列表 . ENABLED = . T. THISFORM. disableedit TABLEREVERT(. T. ,"cu 细节") THISFORM. 添加 . ENABLED = . T. THISFORM. 删除 . ENABLED = . T. THISFORM. CLOSABLE = . T. THISFORM. 退出 . ENABLED = . T. THISFORM. REFRESH ENDIF (4)“添加”按钮 Click 事件代码 IF THIS. CAPTION = "添加" THIS. CAPTION = "取消" SELECT cu 细节 APPEND BLANK THISFORM. enableedit THISFORM. 编号列表 . ENABLED = . F.

199


电脑工程师丛书

200

Visual FoxPro 高级应用实例

THISFORM. 修改 . ENABLED = . F. THISFORM. 保存 . ENABLED = . T. THISFORM. 删除 . ENABLED = . F. THISFORM. 退出 . ENABLED = . F. THISFORM. CLOSABLE = . F. THISFORM. REFRESH ELSE TABLEREVERT(. T. ,"cu 细节") THIS. CAPTION = "添加" THISFORM. 修改 . ENABLED = . T. THISFORM. 保存 . ENABLED = . F. THISFORM. 删除 . ENABLED = . T. THISFORM. 退出 . ENABLED = . T. THISFORM. CLOSABLE = . T. THISFORM. 编号列表 . ENABLED = . T. THISFORM. REFRESH THISFORM. 编号列表 . VALUE = THISFORM. 设备编号 . VALUE ENDIF (5)“保存”按钮 Click 事件代码 lSuccess = TABLEUPDATE(. T. ,. F. ,"cu 细节") IF lSuccess MESSAGEBOX("更新成功") ELSE MESSAGEBOX("更新失败") ENDIF THISFORM. 编号列表 . ENABLED = . T. THIS. ENABLED = . F. THISFORM. 修改 . CAPTION = "修改" THISFORM. 添加 . CAPTION = "添加" THISFORM. 修改 . ENABLED = . T. = sqlexec(THISFORM. nHandle,; "select 设备编号 from registry","cu 编号") SELECT cu 编号 THISFORM. 编号列表 . CLEAR THISFORM. 编号列表 . ROWSOURCE = "cu 编号" THISFORM. 编号列表 . VALUE = THISFORM. 设备编号 . VALUE THISFORM. CLOSABLE = . T. THISFORM. 退出 . ENABLED = . T. THISFORM. 添加 . ENABLED = . T. THISFORM. 删除 . ENABLED = . T. THISFORM. disableedit THISFORM. REFRESH


采用两层 C/S 体系架构

201

(6)“删除”按钮 Click 事件代码 yn = MESSAGEBOX("是否删除该记录?", 1 + 48 + 256,"确认删除") IF yn = 1 str1 = THISFORM. 编号列表 . VALUE lSuccess = sqlexec(THISFORM. nHandle,; "delete from registry where 设备编号 = ? str1") IF lSuccess = 1 MESSAGEBOX("删除成功") = sqlexec(THISFORM. nHandle,; "select 设备编号 from registry","cu 编号") SELECT cu 编号 THISFORM. 编号列表 . CLEAR THISFORM. 编号列表 . ROWSOURCE = "cu 编号" THISFORM. 编号列表 . VALUE = THISFORM. 设备编号 . VALUE THISFORM. 编号列表 . VALUE = THISFORM. 编号列表 . LIST(1) str1 = THISFORM. 编号列表 . VALUE = sqlexec(THISFORM. nHandle,; "select  from registry where 设备编号 = ? str1","cu 细节") SELECT cu 细节 THISFORM. bind THISFORM. enableupdate THISFORM. REFRESH ELSE MESSAGEBOX("删除失败") ENDIF ELSE RETURN ENDIF 至此,设备数据维护模块的主要功能就完成了,别忘了在表单的关闭事件中断开与 SQL Server 的连接:SQLDISCONNECT(0)。在实例二中还有一些字典管理和报表打印等功能,可以 直接使用远程视图而无需修改本地的代码,故不再赘述。

6.4

本章小结

Visual FoxPro 一向被定位在桌面数据库开发工具这一层次,其实,通过 C/S 架构模式以及 同 SQL Server 的组合,Visual FoxPro 完全可以胜任大型数据库应用系统开发的要求,C/S 体系将 完全改变 Visual FoxPro 传统的设计模式。套用一句时髦的话来说,数据库应用系统的开发将 从“粗放型”走向“集约型”。


基于 Web 的数据库应用系统开发 ———全国邮编、区号在线查询系统

7

本章技术要点:  活动文档。  Web 发布向导。  FoxISAPI 的使用。  三层架构与中间件开发。

7.1

本章提要

互联网和 WWW 的流行使得开发基于 Web 的应用程序成为现在最热门的话题之一。利 用 Visual FoxPro 对 Internet 的支持可以很容易地创建与 Internet 一起使用的应用程序,如把应用 程序嵌入到浏览器中执行,在网上快速地发布数据库,或者开发多层架构体系的中间件,它们 能够有效地与 IIS 服务器、Aitive Server Page 等协调工作。 本章将结合 Visual FoxPro 和 ASP 开发一个邮政编码、电话区号的在线查询系统,该应用程 序在浏览器中的执行结果如图 7 . 1 所示。

图 7.1

实例 7 运行效果


基于 Web 的数据库应用系统开发

7.2

203

技术分析 为什么要开发基于 Web 的应用程序呢?它至少具有以下优点:

① 统一的浏览器界面。 Web 浏览器(如 Internet Explorer 浏览器,简称 IE 浏览器),为用户提供了一个简单易用的 操作界面。通过浏览器的导航按钮以及 Web 页面上提供的超级链接,可以快速地在页面之间 进行切换,从而方便地获得想要的信息。 ② 无需安装特别的客户端程序。 除了简单易用之外,浏览器还有一个重要特征就是它的普遍性,在绝大多数的系统中均安 装有浏览器,在 Windows 系统中的 IE 浏览器更是一个必须安装的核心组件。 ③ 开放的协议和标准。 Internet 上主要使用 TCP /IP 协议,Web 页面使用标准的 HTML 语法,这使得应用程序具有 高度的适应性:一个基于 Web 的应用程序无需考虑客户端的系统信息和运行环境,它可以是 Windows,也可以是 Mac OS、Unix 或者 Linux。

7 . 2 . 1 准备工作 在使用 Visual FoxPro 开发基于 Web 的 数据库应用系统之前,还需要准备如下一 些工具或组件。 1 . TCP /IP 网络协议 要开发 Internet 网络应用程序,并不一 定要求接入 Internet,甚至连网卡或调制解 调器等网络设备也不是必须的,但是 TCP / IP 协议作为 Internet 上使用的最主要网络 协议是必须安装的重要组件之一。在 Windows 的发行光盘中包含了该协议,查看操 作系统的网络连接属性(见图 7 . 2),如果没 有包 含 该 项 目,则 单 击“安 装”按 钮,并 从 “协议”中选择“Internet 协议(TCP /IP)”并安 装。 2 . Web 服务器及扩展

图 7.2

安装 TCP /IP 协议

安装 Web 服务器是在 Internet 上发布 信息的一种主要途径,它实际上是一个特殊的应用程序,可以获得客户端的请求并返回相应的 结果。Web 服务器软件有很多种,常见的有 Apache 和微软的 IIS(Internet Information Server)。为 了更好地与 Visual FoxPro 集成,本书以后者为例。安装 IIS 信息服务器的操作系统平台最好为


电脑工程师丛书

204

Visual FoxPro 高级应用实例

Windows NT 系列,如 Windows 2000、Windows XP 或 Windows Server 2003。如果仅仅是开发调试 的话,也可以在 Windows 95 /98 平台下安装 PWS(Personal Web Server)Web 服务器。 IIS 是 Windows NT 系列操作系统的一个服务组件,可以在安装系统时就安装它,或者单独 安装。在“控制面板”窗口中运行“添加 / 删除程序”程序,并选择“添加 / 删除 Windows 组件”,在 “Windows 组件向导”对话框选择“Internet 信息服务(IIS)”一项,如图 7 . 3 所示。

图 7.3

安装 Internet 信息服务

IIS 实际上由很多种服务组件组成,单击“详细信息”按钮,将会看到其中包含了诸如 Web 服务与管理、FTP(文件传输协议)服务和 SMTP(电子邮件传输协议)服务等内容(见图 7 . 4),其 中只有 Web 服务和管理是必须的,确保选中了“万维网服务”和“Internet 信息服务管理单元”复 选框,别的组件可以根据需要进行选择。

图 7.4

IIS 服务组件

安装完毕后从“开始”菜单中运行“Internet 信息服务”命令,在打开的窗口中可以对 Web 服 务器进行管理和配置。展开左边的“Internet 信息服务”节点,选中默认网站,可以启动、停止或 暂停 Web 服 务。启 动 IIS 服 务 后,打 开 浏 览 器,在 地 址 栏 中 输 入“http:/ /127 . 0 . 0 . 1 /“或 者


基于 Web 的数据库应用系统开发

205

“http:/ /localhost /”,将会看到 IIS 为我们准备的默认网站首页。“127 . 0 . 0 . 1”是一个保留的 IP 地址,它实际上用于标识一个回环(LoopBack)设备,我们只需要知道它和“Localhost”一样可以 用来表示本机就可以了。至此,Web 服务器已经安装成功了,PWS 的安装与配置方法与此类 似,不再赘述。

图 7.5

Internet 信息管理单元

3 . IE 浏览器 浏览器是访问基于 Web 应用程序的客户端工具,一般情况下选择 IE 浏览器,它对 OLE 文 档和包含 ActiveX 控件的网页有较好的支持,请确保它工作正常。

7 . 2 . 2 活动文档 活动文档(Active Document)是 Visual FoxPro 6 . 0 中引入的一个新特性,通过它可以快速地 把一个 Visual FoxPro 应用程序嵌入到浏览器中执行。活动文档实际上是一种 OLE 嵌入文档, Web 浏览器则是它的包容器。由于活动文档几乎可以做 Visual FoxPro 应用程序可以做的所有 事情(如执行表单、预览报表、打印标签等),因此活动文档技术是把现有的应用程序发布到 Web 上的最便捷途径。活动文档包含以下一些主要特性: ① 活动文档总在现场激活,关于现场激活请参阅 5 . 2 . 2 小节的内容。 ② 通过活动文档菜单和工具栏命令,可以使用到活动文档宿主程序(如 IE 浏览器)的一 些功能。 ③ 使用 IE 浏览器查看时,活动文档可以实现同其他网页的无缝集成,也就是说可以把一 个活动文档嵌入到一个标准的 HTML 页面中。 ④ 对于将纯 Visual FoxPro 客户应用程序迁移到使用基于 HTML 客户界面的 Active Platform 应用程序,活动文档提供了一种快速的解决方案。 1 . 创建活动文档 创建一个活动文档和创建普通的 Visual FoxPro 应用程序并没有什么太大的区别,只是活 动文档必须基于“ActiveDoc”对象。创建活动文档通常遵循以下步骤:


电脑工程师丛书

206

Visual FoxPro 高级应用实例

① 新建并保存项目。 ② 创建一个以 ActiveDoc 为基类的自定义类,并把它保存在类库(vcx)文件中。 ③ 把该类作为项目的主文件。 这样,一个活动文档就制作完成了,可以像设计普通的 Visual FoxPro 应用程序一样添加数 据库、表单、代码、对象等。 2 . ActiveDoc 对象的属性、事件 根据前面的编程经验可知,在项目中通常有一个主程序文件负责环境变量的设置、表单的 显示和建立事件循环等操作,现在这个任务在活动文档中将由基于 ActiveDoc 对象的自定义类 来完成,因为它现在才是项目的主文件。 在类设计器中打开 ActiveDoc 对象,查看它的属性和事件,其中大部分都是常见的属性和 事件,但是也有少量独特的属性和事件,它们是操纵基于活动文档应用程序的主要工具和手 段。 (1)ShowDoc 事件 当一个活动文档对象完成自身的初始化以后,首先触发的事件就是 ShowDoc 事件。在该 事件中,活动文档将完成自身被浏览器包容。 (2)Run 事件 ShowDoc 事件完成后,紧接着触发的是 Run 事件。 Run 事件是应用程序开始执行的真正起 点,一般在该事件中加入显示表单、菜单等功能的程序代码,并建立事件循环进程。可见,在 Run 事件中编写的代码类似于普通应用程序中主程序文件的功能。 (3)HideDoc 事件 当活动文档离开时,将触发 HideDoc 事件。返回时,将重新触发 ShowDoc 事件。 (4)ContainerRelease 事件 如果关闭活动文档的容器(即 Web 浏览器),将首先触发一个 HideDoc 事件,然后触发 ContainerRelease 事件,在这个事件中可以添加释放内存、清除事件循环等功能的代码。 (5)ContainerReleaseType 属性 在浏览器关闭时,它实际上是向当前的活动文档发出 QUIT 命令关闭。通过设置 ContainerReleaseType 属性,可以选择返回的环境,如果设为 0(默认),则返回 Visual FoxPro 的主窗口,从 而可以继续运行,设置为 1 则退出 Visual FoxPro 返回操作系统。 ActiveDoc 对象实际上就是一个应用程序的引导器,通过它可以把应用程序嵌入到浏览器 中执行。编写一个基于活动文档的应用程序与普通应用程序的主要不同就在于把控制程序运 行流程的代码从主程序中添加到 ActiveDoc 对象的相应事件中。以第 1 章中制作的涂鸦板程 序为例,要把该程序嵌入到浏览器中执行除了按照上文所述创建一个活动文档对象之外,只需 要在活动文档对象的 RUN 事件中添加运行表单的代码:DO FORM EX1 . SCX 即可。由于整个 应用程序被嵌入到浏览器中,因此表单中的一些属性设置可能会无效,如表单的“WindowType” 属性、 “Desktop”属性和“ShowWindow”等。 3 . 运行活动文档 在完成了活动文档应用程序的设计后,下面来讨论如何运行它。在此之前,首先得把这个 以活动文档类为主的文件项目编译成可执行的文件形式,如一个 app 文件。


基于 Web 的数据库应用系统开发

207

(1)通过“工具”菜单运行 在 Visual FoxPro 的开发环境中,可以通过“工具”菜单中的“运行 Active Document”命令来运 行活动文档, 对话框如图 7.6 所示。选择该命令与在“命令”窗口中运行 DO < Active Doc Name > 命令相同。

图 7.6

在 Visual FoxPro 中运行活动文档

在该对话框中选择即将运行的活动文档,并选择运行的方式,每种运行方式的特点如表 7 . 1 所示。 表 7.1

活动文档的运行方式

运行方式

说明

在浏览器中(默认)

使用运行时刻的 Visual FoxPro,在 Internet Explorer 中运行 Active Document

单独执行

使用运行时刻的 Visual FoxPro,将 Active Document 作为独立的应用程 序运行

在浏览器中(调试)

使用可执行的 Visual FoxPro (Vfp6 . exe),在 Internet Explorer 中运行 “命令”窗口和 Visual FoxPro 开发环境的 Active Document。调试能力、 所有功能都可用

单独执行(调试)

使用可执行的 Visual FoxPro(Vfp6 . exe),将 Active Document 作为一个 独立的应用程序运行。调试能力、命令窗口和 Visual FoxPro 开发环 境的所有功能都可用

(2)在浏览器中直接运行 在 IE 浏览器的“打开文件”对话框中打开编译好的活动文档,或者从其他带有该活动文档 的超级链接的网页中,都可以直接运行该活动文档。甚至还可以把它放到 IIS 的主目录(如: C:h Inetpub h wwwroot)下,然后在浏览器的地址栏中输入: http:/ /127 . 0 . 0 . 1 / 活动文档文件名 仍然以实例一制作的涂鸦板程序为例,把该项目编译成 app 文件后放置在 IIS 的主目录 下,在浏览器中的运行效果如图 7 . 7 所示。 应用程序已经被包含在浏览器中执行了,如果是已经接入 Internet 的计算机,实际上它已 经被发布到了 Internet 上,网络上别的用户也可以通过浏览器来执行这个应用程序,惟一需要 修改的就是把浏览器中的“127 . 0 . 0 . 1”换成该计算机的实际机器名或 IP 地址。 4 . 活动文档的主要局限性 虽然利用活动文档可以很方便地把应用程序移植到浏览器中执行,并且可以发布到 Internet 上,但是它并不是真正意义上的基于 Web 的应用。当我们把一个基于活动文档的应用程 序通过 Web 服务器发布到 Internet 上,通过网络上的另一个客户端执行该程序,其工作原理如


电脑工程师丛书

208

Visual FoxPro 高级应用实例

图 7.7

在浏览器中执行实例一

图 7 . 8 所示。

图 7.8

通过网络执行的活动文档

图 7 . 8 说明了这么一个事实:活动文档实际上是在客户端被执行的,客户端必须安装 Visual FoxPro 的运行环境才能正常运行活动文档应用程序。应用程序的所有文件,包括活动文 档本身、数据库文件和其他支持文件必须被下载到客户端,在这个过程中,Web 服务器仅仅扮 演了一个网络文件传输服务器的角色。对于客户端来说,由于活动文档可以对本地的文件系 统进行 操 作,这 还 有 可 能 带 来 安 全 隐 患。 归 根 结 底,活 动 文 档 依 然 属 于 胖 客 户 端(FAT CLIENT)开发技术,只不过它能够很方便地借助于网络传输,如果你需要的是真正的主从结构 的服务器端的开发,那么请忘掉活动文档。

7.2.3

Web 发布向导

如果需要把数据库或视图中的数据发布到 Web 上,可以使用 Visual FoxPro 提供的 Web 发 布向导,它将生成标准 HTML 格式或 DHTML 的页面,可以在网络上进行共享。 单击“工具”菜单 - >“向导”子菜单 - >“Web 发布”菜单项,将运行“Web 发布向导”,通过


基于 Web 的数据库应用系统开发

209

以下五个步骤即可完成数据库的 Web 发布。 ① 字段选取。 首先选择需要发布的数据库表或自由表,在“数据库和表”列表框中选择表,查看可以添加 的字段,然后选择要在 Web 页面上发布的一个或多个字段。这里以全国邮政编码和电话区号 数据库为例,把所有的字段添加到“选定字段”列表框中,如图 7 . 9 所示。

图 7.9

Web 发布向导步骤一

② 排序记录。 选择用来排序每组记录的字段,可以按照不同优先级的多个字段进行排序,还可以指定按 升序还是降序排列。这里以“省份”字段作为第一级排序条件,以“城市”字段作为第二级排序 字段,并且按照升序排列,如图 7 . 10 所示。

图 7 . 10

Web 发布向导步骤二

③ 选择样式。 在这个步骤中,主要选择数据布局和可视化的外观样式。 Web 发布向导共提供了五种数 据布局和数十种可视化样式(见图 7 . 11),在快捷菜单中还能查看其说明。对话框左上角给出


电脑工程师丛书

210

Visual FoxPro 高级应用实例

了当前数据布局的一个缩略图,如果这些布局和样式不能满足要求,还可以单击“选项”和“高 级”按钮,在弹出的对话框中指定外观的细节,如字体、颜色、边框、图像、样式表等。还可以单 击“预览”按钮进行预览。

图 7 . 11

Web 发布向导步骤三

④ 生成 HTML 页面。 根据以上步骤的选择生成相应的 HTML 页面,并且可以指定页面的标题及保存后的动作 (如浏览或编辑等),如图 7 . 12 所示。

图 7 . 12

Web 发布向导步骤四

⑤ 共享 HTML 页面。 最后,可以把生成的 HTML 页面用网页编辑器(如 FrontPage、Dreamweaver 甚至记事本)进行 修改,修改完毕后放置到 IIS 的共享目录下,或者把当前目录映射为虚拟目录。最终的结果如 图 7 . 13 所示。


基于 Web 的数据库应用系统开发

图 7 . 13

211

Web 发布结果

除了利用 Web 发布向导把数据库转换成 HTML 形式的页面之外,还可以把表单或报表另 存为 HTML 形式的页面:打开表单或报表设计器,从“文件”菜单中选择“另存为 HTML”命令即 可。报表控件中的标签控件和域控件可以直接转换,但表单控件只有少数(如标签控件、文本 框控件、编辑框控件和按钮控件)可以直接转换,因此对于表单来说实用价值不大。

7 . 2 . 4 创建基于 FoxISAPI 的 Web 应用 活动文档和数据库的 Web 发布向导只是本章的“开胃小菜”,下面的内容才是“大餐”。 Visual FoxPro 中包含了一个名为 FoxISAPI . dll 的 ISAPI 扩展程序,通过它可以创建真正基于 Web 的应用程序,它具有以下特性: ① 真正的服务器端 Web 工具。 FoxISAPI 是运行在 Web 服务器的后台应用程序,在客户端不需要安装任何 Visual FoxPro 的组件(包括运行库),这是它和活动文档的最大区别。另外,应用程序和数据库也只保存在服 务器端,网络上传输的仅仅是运行结果,因此可以很方便地进行升级而不需要重新分发应用程 序,在这一点上有点类似于分布式 COM 和远程自动服务。 ② 高性能的 Web 服务器接口。 对 Web 服务器的每个请求将转换成对 FoxISAPI 的调用,并把输出的结果直接返回给客户 端,Visual FoxPro 6 . 0 中改进了 FoxISAPI 内置的缓冲管理等功能,因此整个过程的效率非常高。 ③ ISAPI 的工作方式。 ISAPI 特指以 Internet 服务器应用程序接口(Internet Server API)方式工作的扩展程序,它与 操作系统的底层接口大多通过 C + + 语言实现,具有高效、灵活、多线程支持等特点,能够为


电脑工程师丛书

212

Visual FoxPro 高级应用实例

IIS 服务器和 Visual FoxPro 应用程序之间建立起有效的通信途径。 ④ 基于 COM 组件的通信机制。 FoxISAPI 通过 COM 接口和 Visual FoxPro 程序进行通信,因此需要把 Visual FoxPro 应用程 序封装在 COM 对象中。在这种情况下,FoxISAPI 充当着 OLE 自动服务模型中的客户端角色。 ⑤ 标准的 HTML 输出。 Web 服务器负责把 HTTP 请求传递给 FoxISAPI,并把输出转换成标准的 HTML 形式,这为 Web 应用程序提供了最大限度的兼容性。 1 . B/S 体系与多层架构 在第 6 章中详细介绍了两层 C/S 体系的基本结构和实现原理,并提出了多层架构的概念, 而 B/S(浏览器 / 服务器)体系就是多层架构的典型应用。和 C/S 体系最大的不同在于,B/S 体 系不需要开发专门的客户端,取而代之的是 Web 浏览器。也许你会产生这样的疑惑:浏览器 如何按照我们的要求去访问远程的数据库呢?这个任务就要交给中间层去完成了。中间层可 以接收从客户端的浏览器中传递来的信息,并完成对数据库的操作,最终把结果返回给客户 端。 对网络服务有一定认识的朋友应该知道,和浏览器直接打交道的是 Web 服务器(如 IIS 服 务器),则开发的中间层还要和 IIS 进行数据交互,但这个工作不需要我们手工完成,FoxISAPI 就是专门为 Visual FoxPro 开发中间层而提供的一个有力工具,它能够很好地协调 IIS 和中间层 之间的信息传递。 那么,Visual FoxPro 到底以什么形式开发中间层呢?答案是 COM 组件。在第 5 章中介绍 了很多关于自动服务对象的开发实例,实际上它们都是 COM 组件,所以在那里使用到的技术 可以原封不动地搬到 Web 应用程序开发中来。图 7 . 14 说明了 FoxISAPI、COM 组件和 IIS 服务 器一起构成的多层架构体系的结构和工作原理。

图 7 . 14

多层架构体系工作原理

从图中可见,FoxISAPI 扮演着 Web 服务器和 Visual FoxPro 应用程序之间的连接器角色。 首先由客户端向 Web 服务器发送类似于下面 URL 的 HTTP 请求: http:/ / ServerName / scripts / foxisapi . dll /自动服务名 .COM对象名 . 方法名 其中 ServerName 是 Web 服务器在网络上的地址,scripts 目录是具有执行权限的虚拟目录, foxisapi. dll 文件必须放置在这样的目录下。 foxisapi . dll 后面就是要求执行的 Visual FoxPro 应用 程序。 IIS 服务器在接收到这样的请求后,会把 foxisapi . dll 装入内存中,并把 URL 和页面中包 含的参数传递给 foxisapi . dll。 foxisapi 将根据 URL 中的参数创建自动服务对象并调用方法,类 似于执行如下命令:


基于 Web 的数据库应用系统开发

213

o = CREATEOBJECT("自动服务 . COM对象 ") o.方法名(相关参数) 最后 FoxISAPI 会把运行结果以 HTML 的形式返回给 Web 服务器,并最终传送到客户端的 浏览器页面上。 FoxISAPI 以及 Visual FoxPro 的自动服务程序在接收到第一次请求时可能会需要较长的时 间, 因为这个过程需要在内存中装载 Visual FoxPro 的运行库和建立对象实例,此后将常驻内 存,因此对后续的请求速度将大大加快。 2 . Web 服务器设置 要正确地运行基于 FoxISAPI 的应用程序还需要对 Web 服务器进行一定的配置。这包括 以下几个步骤: ① 虚拟目录权限设置。 为能够从一个 HTTP 请求中执行 FoxISAPI,需要把它放置在具有执行权限的目录中。默认 情况下,IIS 服务器会包含一个名为 Scripts 的虚拟目录,该目录具有执行的权限,也可以在 Internet 信息服务管理器中利用新建虚拟目录向导新建一个虚拟目录,在向导设置过程中指定目 录的访问权限。后面的叙述均以 Scripts 目录为例,该目录最终的属性如图 7 . 15 所示。

图 7 . 15

IIS 虚拟目录权限设置

② 安装和配置 FoxISAPI 组件。 把 Visual FoxPro 提供的 foxisapi . dll 及其配置文件 foxisapi . ini 复制到 Scripts 目录下,如果需 要调试程序,还可以把 Oldebug. prg 程序文件复制到 Visual FoxPro 的安装目录下。 ③ 启动浏览器测试。 为了保证各个环节均已准备就绪,可以打开浏览器,在地址栏中输入如下 URL: http:/ /127 . 0 . 0 . 1 /scripts /foxisapi . dll /status 如果能够看到图 7 . 16 所示的页面就说明 FoxISAPI 组件已经正常工作了。 该页显示了 FoxISAPI 组件及其创建的对象实例的工作状态,因为这是第一次运行 FoxIS-


电脑工程师丛书

214

Visual FoxPro 高级应用实例

图 7 . 16

FoxISAPI 组件状态页

API 程序,因此没有显示任何实例。 3 . 第一个 FoxISAPI 应用 如果还是觉得开发 Web 应用程序遥不可及的话,那么完成下面这个小例子能帮助你拨开 所有的迷雾。在 Visual FoxPro 中新建项目并命名为 IsapiFirst,在其中添加程序文件 hello. prg, 代码如下: # DEFINE CR CHR(13)+ CHR(10) DEFINE CLASS HelloFoxISAPI AS Custom OLEPUBLIC FUNCTION Helloworld LPARAMETER cVars,cIniFile,nPersistInstance LOCAL cOutput cOutput = "HTTP /1 . 1 200 OK" + CR + "Content - type:text /html;" + CR + CR cOutput = cOutput + " < script language = JavaScript > " + CR + ; ’)" + CR + " < /script > " "alert(’Hello FoxISAPI! RETURN cOutput ENDDEFINE 请暂时放下对上述代码的困惑,在项目管理器中设置该程序文件为主文件,并联编该项 目。根据第 5 章学过的内容可以知道,一个名为 IsapiFirst 的 COM 组件制作完成了。打开 IE 浏览器,并在地址栏输入如下 URL: http:/ /127 . 0 . 0 . 1 /scripts /foxisapi . dll /isapifirst . hellofoxisapi . helloworld 如果没有意外的话,IE 浏览器中显示的结果将如图 7 . 17 所示。 回过头对比 hello. prg 和 5 . 2 . 5 小节中第一个 OLE 自动服务程序 hello. prg 的代码,可以发 现既有相同又有不同,相同之处在于:都定义了一个 OLE 类并且把功能封装在该类中,这是因 为它们都是基于 COM 对象的调用。不同之处则很好地说明了开发一个基于 FoxISAPI 应用的 关键,具体有以下三点:


基于 Web 的数据库应用系统开发

图 7 . 17

215

第一个 FoxISAPI 应用

(1)cVars、cIniFile 和 nPersistInstance 参数 当 FoxISAPI 调用一个 OLE 对象时,它将自动地向该对象传递三个参数,在这个程序中虽 然没有用到这些参数,但是它们却是必须申明的,否则将导致程序出错。 cVars:包含了 Web 服务器向 FoxISAPI 传递的所有参数,这些参数有两种形式,一种是直接 包含在 URL 中的,它们与 URL 之间用“? ”号分隔,多个参数之间用“&”符号分隔,如 isapifirst . hellofoxisapi. helloworld? para1 = 10&para2 = 20 将向 FoxISAPI 传递两个参数 para1 和 para2,值分 别为 10 和 20。此外还可以在页面的表单中提交参数,注意这里所说的表单不同于 Visual FoxPro 中的表单,而是指由 < FORM> 和 < /FORM> 标签分隔的一段 HTML 代码,在表单中允许使 用 HTML 的表单元素,通过这些表单元素可以向 Web 服务器传送必要的信息。例如,下面一 段 HTML 表单代码在用户单击其中的“提交”按钮后,cVars 的内容将为“textfield = test&Submit = % CC% E1% BD% BB”。 < form action = foxisapi . dll /isapifirst . hellofoxisapi . helloworld method = "post" > < input type = "text" name = "textfield" value = "test" > < input type = "submit" name = "Submit" value = "提交" > < /form > 包含这段 HTML 代码的页面必须放在和 FoxISAPI . dll 同一目录下。其中的汉字“提交”属 于扩展字符,所有的扩展字符都会被转换成 16 进制的溢出字符,空格则会被转换成“ + ”号。 cIniFile:每次 FoxISAPI 被调用时都会自动创建一个 ini 文件,其中包含了此次 Web 请求的 一些基本信息,如请求的方式是 POST 还是 GET,主机的地址及端口,客户端域名及 IP 地址等。 FoxISAPI 把这个 ini 文件的文件名及路径作为 cIniFile 参数传递给 Visual FoxPro 的自动服务程 序, 可以在自动服务程序内访问该文件以获得 Web 服务器上的一些信息。每个 ini 文件都存 放在 Scripts 文件夹中,具有以“FOX”为前缀的惟一文件名。 nPersistInstance:指定 FoxISAPI 组件创建的实例在执行完方法程序后是否保持活动。 nPersistInstance 通过 FoxISAPI . dll 对方法程序的引用来传递。如果在 Web 应用程序中将 nPersistInstance 设置为 0,那么当方法程序执行完成后自动服务程序的实例仍然是活动的;如果 nPersistInstance 设置为非 0 的数,则当方法程序执行完成后将释放自动服务程序的实例。将 nPer-


电脑工程师丛书

216

Visual FoxPro 高级应用实例

sistInstance 设置为 0 可获得更好的执行性能,否则下一次调用自动服务程序时还需要重新启动 它。 (2)"HTTP /1 . 1 200 OK" + CR + "Content - type:text /html;" + CR + CR 这是一个标准的 HTTP 头信息,第一行中的 HTTP /1 . 0 表示服务器采用 HTTP 1 . 1 版本的 协议, 200 OK 则是一个应答代码,表示请求完成。 CR 是一个换行符,Windows 平台下的换行为 “ h r h n”,转换成相应的 ANSI 码就是 CHR(13)+ CHR(10)。第二行中的 Content - type:text / html 表示输出的内容为文本或 HTML 页面。最关键的是连续的两个 CR,表示 HTTP 头信息的 结束,后面将开始正文内容。如果没有这两个连续的 CR,客户端将会把后面的内容也当成 HTTP 头信息,从而一直处于等待状态,在浏览器上将没有任何显示。 (3)cOutput 的内容 cOutput 除了头信息之外,还包含了一小段 JavaScripts,它们可以嵌入在 HTML 页面中,以 < SCRIPT > 和 < /SCRIPT > 两个 HTML 标签作为标识。这段 JavaScripts 实现的功能和 MessageBox()基本相同,但是它是通过浏览器执行的。如果在代码中直接使用 MessageBox()将会导 致出错,这是因为基于 FoxISAPI 组件的应用中不能包含可视的界面,所有的输出都应该采用纯 文本或 HTML 的形式。 4 . FoxISAPI 组件的优化和配置 由于 FoxISAPI . dll 支持多线程,因此它可以缓冲多个 COM 对象以便为网络应用程序提供 更好的可扩充性。为了充分利用 FoxISAPI 组件缓冲的优点,需在 Web 应用程序中将 nPersistInstance 设置为 0 以使 FoxISAPI 的实例保持活动。 一个 Web 应用程序可能会同时接受到多个 HTTP 请求,FoxISAPI 可以通过加载多个实例 来同时响应多个请求,这种技术称为线程池缓冲。对某个服务创建的实例数目取决于 FoxISAPI. ini 初始化文件中的设置。要创建多个 FoxISAPI 自动服务程序的缓冲,可在方括号中加入 一项,该项带有要创建缓冲的 FoxISAPI 自动服务程序的名字。该项后应跟有组成缓冲的所有 FoxISAPI 自动服务程序的列表,以及用来指定每个 FoxISAPI 自动服务程序可创建的最多实例 数的数值。 例如,将下列几行文字放置在 Foxisap . ini 文件中,可创建一个由四个 ISAPI 自动服务程序 组成的缓冲,它可为向 isapifirst . hellofoxisapi 发出的请求提供服务。 FoxISAPI . dll 最多可创建 isapifirst. hellofoxisapi 自动服务程序的四个实例和 isapifirst2 . hellofoxisapi 自动服务程序的三个实 例来为请求提供服务。 [isapifirst . hellofoxisapi] isapifirst . hellofoxisapi = 4 isapifirst2 . hellofoxisapi = 3 FoxISAPI. ini 可指定在收到某一服务请求之前将 ISAPI 自动服务程序实例化。在实例数 目之后加上逗号及星号即可做到这一点,如下面的文字所示: [isapifirst . hellofoxisapi] isapifirst . hellofoxisapi = 4, isapifirst2 . hellofoxisapi = 3, 这样将会导致 FoxISAPI . dll 的首次加载时间变长,但以后对每次请求的响应速度将大大加 快。总之,充分利用好 FoxISAPI 的缓冲技术,可以有效地提高 Web 服务器及 FoxISAPI 组件的


基于 Web 的数据库应用系统开发

217

负载能力,对流量较大的 Web 站点有着重要的实用价值。

7 . 2 . 5 和 ASP 配合开发三层架构应用程序 FoxISAPI 组件虽然可以提供快速、高效的 Visual FoxPro 程序的 Web 访问接口,但是也存在 一些不够灵活的地方,如需要手工进行参数的处理,没有内嵌 HTML 的功能,如果页面的输出 很复杂,在程序代码中必将包含大量的 HTML 标签,这实际上加重了服务器的负担,因为一个 静态 HTML 页面可以直接被 Web 服务器读取并输出。为此,可以把 Visual FoxPro 与 ASP 相结 合,从而在速度与灵活性之间获得一个较好的平衡。 1 . ASP 简介 很多人把 ASP(Active Server Page)当成是一种专门的编程语言,这其实是把 ASP 与默认使 用的 VBScript 脚本语言等同了起来。实际上,ASP 只是 Windows 平台下的一种服务器端脚本编 写环境,使用它可以创建和运行动态、交互的 Web 服务器应用程序。 ASP 的主要特点包括:可 以无缝地嵌入到 HTML 页面中,支持多种脚本引擎和 ActiveX 组件,简单、易维护。严格意义上 的 ASP 只能部署在 Windows 平台的 IIS 或 PWS 服务器上,虽然在别的一些 Web 服务器软件(如 Apache)上也可以通过加载模块来支持类似于 ASP 语法规则的脚本,但是不能很好地处理 ASP 的一些内置对象,对 ActiveX 和 COM 组件技术也缺乏必要的支持。 通常情况下,服务器端的脚本语言可以分为 CGI(通用网关接口)或 ISAPI 两种不同的工作 模式,前者通过一个独立的外部进程对脚本进行解释和执行,如常见的 Perl 脚本;后者则通过 Web 服务器加载的 ISAPI 模块对脚本进行解释和执行,ASP 就属于这种情况。此外还有一些脚 本语言同时支持这两种工作模式,如 PHP 等。当客户端向 IIS 服务器请求一个以 ASP 为扩展 名的 Web 页面时,IIS 服务器将把这个页面转交给 ASP. DLL(一个 ISAPI 扩展程序)来进行处 理,这个过程是由服务器的脚本映射完成的。 ASP. DLL 中包含了 VBScript 和 JScript 脚本引擎 以及 HTML 标签解析器,负责完成对 ASP 页面的解析并把结果返回给 Web 服务器,图 7 . 18 说 明了整个 ASP 脚本执行环境。

图 7 . 18

ASP 脚本执行环境

ASP 提供的脚本引擎最重要的特性就是对 COM 对象的支持,首先它包含了很多内置的对


电脑工程师丛书

218

Visual FoxPro 高级应用实例

象,除此之外,也可以定制 COM 对象来扩展 ASP 的功能。可以毫不夸张地说,ASP 中 COM 对 象无所不在。和 FoxISAPI 不同的是,ASP. DLL 及其内置对象在 IIS 服务器启动时就会被自动 加载并常驻内存,因此在 ASP 脚本中无需显式地创建它们。 2 . ASP 的基本语法 一个 ASP 页面实际上就是以 ASP 为扩展名的纯文本文件,其中包含了 HTML 标签和 ASP 脚本,默认情况下 ASP 使用 VBScript 脚本语言。 ASP 标签可以有以下三种嵌入方法: ① 表达式嵌入。 < % = 表达式 % > ② 代码段嵌入。 <%

需要执行的脚本语言或命令行 %> ③ 混合嵌入。 < % IF条件表达式 % > Text / HTML文本 < % ELSE % > Text / HTML文本 < % END IF % > 下面这段 ASP 脚本同时使用了这三种嵌入方式,把它保存为 hello. asp,并放置到 IIS 的主 目录下,从浏览器的地址栏输入 http:/ /127 . 0 . 0 . 1 /hello. asp,观察页面的输出效果。 < HTML > 当前服务器时间:< % = Now % > < br > 客户端地址:< % = Request . ServerVariables("remote— addr") % > < br > <% For x = 1 To 10 Response . Write(" < p align = center > ") Response . Write(x) Response . Write(" < /p > ") Next %> < % For x = 10 To 1 Step - 1 % > < p align = center > < % = x % > < /p > < % Next % > < /HTML > 3 . ASP 的内建对象 利用 ASP 提供的内建对象可以容易地收集通过浏览器请求发送的信息、响应浏览器以及 存储用户信息(如用户首选项),下面简要介绍这些内建对象的用途。 ① Application 对象:可以使用 Application 对象使给定应用程序的所有用户共享信息。


基于 Web 的数据库应用系统开发

219

② Request 对象:可以使用 Request 对象访问任何用 HTTP 请求传递的信息,包括从 HTML 表格用 POST 方法或 GET 方法传递的参数、cookie 和用户认证。 Request 对象使用户能够访问 发送给服务器的二进制数据,如上载的文件。 ③ Response 对象:可以使用 Response 对象控制发送给用户的信息,包括直接发送信息给浏 览器、重定向浏览器到另一个 URL 或设置 cookie 的值。 ④ Server 对象:Server 对象提供对服务器上的方法和属性进行的访问。最常用的方法是创 建 ActiveX 组件的实例(Server . CreateObject),其他方法用于将 URL 或 HTML 编码成字符串,将 虚拟路径映射到物理路径以及设置脚本的超时期限。 ⑤ Session 对象:可以使用 Session 对象存储特定的用户会话所需的信息。当用户在应用程 序的页之间跳转时,存储在 Session 对象中的变量不会清除;而用户在应用程序中访问页时,这 些变量始终存在。也可以使用 Session 方法显式地结束一个会话和设置空闲会话的超时期限。 ⑥ ObjectContext 对象:可以使用 ObjectContext 对象提交或撤消由 ASP 脚本初始化的事务。 对 ASP 技术进行深入地介绍超出了本书的讨论范围,下面着重介绍如何在 ASP 中调用 Visual FoxPro 开发的 COM 组件。 4 . 利用 ASP 调用外部 COM 对象 还是以上文的 HelloWorld 为例,但是需要对代码进行小小的改动,因为在 ASP 中可以直接 向 COM 对象传递参数,而不需要像 FoxISAPI 那样进行转换。新建项目并保存为 aspfirst . pjx,添 加程序文件 hello. prg,并修改代码如下: # DEFINE CR CHR(13)+ CHR(10) DEFINE CLASS Hello AS Custom OLEPUBLIC FUNCTION Helloworld LOCAL cOutput cOutput = " < script language = JavaScript > " + CR + ; ’)" + CR + " < /script > " "alert(’Hello World! RETURN cOutput ENDDEFINE 可见,固定的参数格式不需要了,输出中的 HTTP 头信息也不需要了,和编写普通 COM 组 件的方法完全相同。设置 hello. prg 文件为项目主文件并编译该项目,下面只需要在 ASP 中创 建这个对象即可。修改 hello. asp,它只需要包含下面几行: <% set o = Server . CreateObject("aspfirst . hello") Response . write o. HelloWorld() %> 打开浏览器并在地址栏中输入 http:/ /127 . 0 . 0 . 1 /hello. asp,图 7 . 17 将会重现在你的眼前。 5 . 几点说明 在图 7 . 18 中列出了 ADO 对象,它是 ASP 访问数据库的另一个途径,也就是说,不需要自 行开发 COM 组件也能在 ASP 中访问各种类型的数据库,其中包括 Visual FoxPro 数据库,那么 用 Visual FoxPro 自行开发数据访问组件有什么意义呢?


电脑工程师丛书

220

Visual FoxPro 高级应用实例

① 更高效的数据访问引擎。 ADO 对象是通过 ODBC 接口调用数据库的驱动程序,进而获得对数据库的访问,理论上说 和 Visual FoxPro 内建的数据库引擎比起来速度应该较慢。对此作者未作任何测试,因为在小 数据量的情况下两者的差别是微乎其微的,所以也无法用实测数据来说明用 Visual FoxPro 定 制的 COM 组件具体比 ADO 对象快多少,但有一点是肯定的,定制的 COM 组件访问数据库的 途径要比 ADO 对象更直接,因此效率会更高。 ② 更合理的架构模式。 数据访问模块和功能模块的分离可以使得应用程序更易于维护。例如,COM 组件完全可 以在 Visual FoxPro 内调试完毕确保其正常工作后再加入到 ASP 页面中,如果对数据访问的方 式或途径发生了变化,只需要修改 COM 组件而不需要重新修改 ASP 页面的代码。 ③ 利用 COM+ 实现负载均衡。 COM + 是 Windows 2000 中引入的新技术,可以有效地实现分布式处理和负载均衡,而 ADO 对象只能借助于数据库自身的分布式特性实现这一点,从这个意义上说,COM 组件可以把不 支持分布式处理的数据库进行优化。 ④ 充分发挥 Visual FoxPro 程序员的技术优势。这一点是不言而喻的。 此外在利用 ASP 和 Visual FoxPro 相结合开发 Web 应用程序时,可能会遇到无法重新编译 COM 组件的情况,这是因为 COM 组件被 IIS 进程加载到内存中。这个问题有两种解决方法: 如果该组件以进程外的方式(exe 文件)存在,只要在 Windows 的任务管理器中找到该进程并杀 死进程即可,但是这有可能导致 IIS 服务崩溃,不推荐采用;另一种方法是重启整个 IIS 进程, 只需要在 DOS 命令提示符状态下执行 iisreset 即可,该方法对进程内组件(dll)和进程外组件均 有效。 最后再强调一点,如果希望 ASP 脚本正确执行,请务必通过 IIS 服务器来执行它,也就是 说在 IE 浏览器中输入的是网络地址(如 http:/ /127 . 0 . 0 . 1 /hello. asp),而不是本机的绝对路径 (C:h Inetpub h wwwroot h hello. asp)。

7.3

实例制作 从使用的数据来看,这是本书中最简单的一个实例,所使用的是一个只有四个字段的自由

表 info. dbf,这个表在 Web 发布向导中已经使用过了,它包含了全国所有的省份、城市及其对应 的邮政编码和电话区号信息。但是和 Web 发布向导生成的静态 HTML 页面不同,此处是把它 变成动态的、在服务器端执行的查询程序,其中包含两部分内容:实现数据查询的 COM 组件和 提供给用户访问的 ASP 页面。用户通过浏览器打开查询页面后,可以输入查询的内容和方 式,通过 Web 服务器提交这些参数给 ASP 页面,并由它调用相应的 COM 组件完成查询操作, 最后返回查询结果。 本实例将要制作的文件包括: ① 项目文件:ex7 . pjx。 ② 主程序:main . mpr(项目主文件)。 ③ 类库文件:query. vcx。 ④ Web 页面:default . htm、query. asp。


基于 Web 的数据库应用系统开发

221

这些文件位于本书配套光盘的 h ex7 目录下。

7 . 3 . 1 实现查询的 COM 组件 新建项目并保存为 ex7 . pjx,在项目管理器中新建一个类,类名为 Query,基类为 Custom,保 存在 query. vcx 中。该类中将要封装四种查询功能:根据城市查询邮编、根据城市查询区号、根 据区号查询城市、根据邮编查询城市,城市、邮编和区号字段已经建好相应的索引。 1 . Query 类的 Init 事件代码 以下代码完成系统环境变量设置及初始化: SET RESOURCE OFF SET EXCLUSIVE OFF SET CPDIALOG OFF SET DELETED ON SET EXACT OFF SET SAFETY OFF SET REPROCESS TO 2 SECONDS SYS(2335, 0)  重要!获得程序的实际执行路径 SET DEFAULT TO AddBS(JustPath(Application . ServerName)) 2 . 实现城市 - > 邮编查询 接收城市名作为参数,并返回相应的邮编。添加自定义方法 cs2yb,代码如下: LPARAMETER cCS USE INFO SHARED SET ORDER TO cs SEEK cCS IF FOUND() RETURN 邮编 ELSE RETURN "未找到相关记录" ENDIF USE 3 . 实现城市 - > 区号查询 接收城市名作为参数,并返回相应的区号。添加自定义方法 cs2qh,代码如下: LPARAMETER cCS USE INFO SHARED SET ORDER TO cs


电脑工程师丛书

222

Visual FoxPro 高级应用实例

SEEK cCS IF FOUND() RETURN 区号 ELSE RETURN "未找到相关记录" ENDIF USE 4 . 实现邮编 - > 城市查询 接收邮编作为参数,并返回相应的城市。添加自定义方法 yb2cs,代码如下: LPARAMETER cYB USE INFO SHARED SET ORDER TO yb SEEK cYB IF FOUND() RETURN 省份 + " " + 城市 ELSE RETURN "未找到相关记录" ENDIF USE 5 . 实现区号 - > 城市查询 接收区号作为参数,并返回相应的城市。添加自定义方法 qh2cs,代码如下: LPARAMETER cQH USE INFO SHARED SET ORDER TO qh SEEK cQH IF FOUND() RETURN 省份 + " " + 城市 ELSE RETURN "未找到相关记录" ENDIF USE 不要忘了单击“类”菜单 - >“类信息”菜单项,在“类信息”对话框中指定该类的“OLE 公 共”属性。在项目管理器中新增一个空的程序文件 main . prg,设置该文件为项目主文件并联编 该项目。 为了测试 COM 组件是否工作正常,可以在 Visual FoxPro 的命令窗口中执行如下命令:


基于 Web 的数据库应用系统开发

223

o = CREATEOBJECT("ex7 . query") ? o. cs2yb("北京") ? o. cs2qh("北京") ? o. yb2cs("100000") ? o. qh2cs("010") 如果输出的结果正确,COM 组件就制作完成了。

7 . 3 . 2 编写 Web 页面 本实例制作的 Web 页面包括静态的纯 HTML 页面和动态的 ASP 脚本,前者可供用户输入 查询的参数,当用户提交 FORM 后把参数传递给 ASP 脚本;而后者接收 FORM 参数并调用 COM 组件完成查询,并把查询结果返回给客户端。 1 . 查询信息输入页面 对于 静 态 的 HTML 页 面 的 制 作 可 以 使 用 很 多 种 专 门 的 网 页 制 作 工 具 来 完 成,如 Dreamweaver、FrontPage 等。如果对 HTML 标签很熟悉而页面又比较简单的话,甚至可以在记事 本程序中直接编写 HTML 代码。 把下面这段 HTML 代码以纯文本的形式保存到文件中,并命名为 default . htm,使用这个名 字是因为它通常是 IIS 服务器的默认页。 < html > < head > < title > 全国城市邮编、区号查询系统 < /title > < meta content = "text /html;charset = gb2312" > < /head > < body bgcolor = " # FFFFFF" text = " # 000000" > < div align = "center" > < p > 城市邮编、区号查询 < /p > < form name = "form1" method = "post" action = "query. asp" > < table width = "75% " border = "1" > < tr > < td > 查询内容:< /td > < td > < input type = "text" name = "input" > < /td > < /tr > < tr > < td height = "19" > &nbsp;< /td > < td height = "19" > < input type = "radio" name = "qtype" value = "1" checked > 根据城市查询邮编 < br >


电脑工程师丛书

224

Visual FoxPro 高级应用实例

< input type = "radio" name = "qtype" value = "2" > 根据城市查询区号 < br > < input type = "radio" name = "qtype" value = "3" > 根据邮编查询城市 < br > < input type = "radio" name = "qtype" value = "4" > 根据区号查询城市 < /td > < /tr > < tr > < td colspan = "2" > < div align = "center" > < input type = "submit" name = "Submit" value = " 确 认 " > < /div > < /td > < /tr > < /table > < /form > < /div > < /body > < /html > 以上这段 HTML 代码中最关键的部分是 < FORM> 和 < /FORM> 两个标签中间的部分,在 FORM 标签中指定了 action 为 query. asp,也就是说在提交这个页面后将转向 query. asp 文件,并 把其中的参数(包含在 input 和 qtype 两个页面元素中)传递给该文件。 2 . 实现查询的 ASP 脚本 实际上真正的查询工作是交给 COM 组件完成的,这个 ASP 脚本完成的操作有:获取页面 参数,创建 COM 对象,根据参数调用不同的方法并传递参数,获得查询结果并返回给客户端。 在任何一种文本编辑器中输入以下代码并保存为 query. asp 文件。 <% dim input,qtype  获取 default . htm 页面通过 FORM 标签传递来的参数 input = request . form("input") qtype = request . form("qtype") if input = "" then ’查询内容为空 response . write " < div align = center > < font color = red > 请输入查询的内容! < /font > < /div > " else set o = Server . createobject("ex7 . query") select case qtype case "1" ’城市 - > 邮编查询 response . write " < div align = center > < font color = red > " + input + "


基于 Web 的数据库应用系统开发

225

的邮编为:" response . write o. cs2yb(input) response . write " < /font > < /div > " case "2" ’城市 - > 区号查询 response . write " < div align = center > < font color = red > " + input + " 的区号为:" response . write o. cs2qh(input) response . write " < /font > < /div > " case "3" ’邮编 - > 城市查询 response . write " < div align = center > < font color = red > 邮编为" + input + " 的城市是:" response . write o. yb2cs(input) response . write " < /font > < /div > " case "4" ’区号 - > 城市查询 response . write " < div align = center > < font color = red > 区号为" + input + " 的城市是:" response . write o. qh2cs(input) response . write " < /font > < /div > " end select set o = Nothing end if %> 把 query. htm 和 query. asp 文件放置到同一目录下,并且设置该目录的 Web 共享属性,或者 放置在 IIS 服务器根目录下,在浏览器的地址栏输入:http:/ /127 . 0 . 0 . 1 /default . htm,在该页面 中输入查询的内容及查询方式,单击“确认”按钮,运行效果如图 7 . 1 所示。如果条件允许,还 可以把它发布到 Internet 上为所有的互联网用户提供这项查询服务了。

7.4

本章小结

用 Visual FoxPro 开发 Web 应用程序仍然是一个前沿的课题,很多技术尚处于开发完善状 态,新技术也层出不穷。 Visual FoxPro 7 . 0 中引入了 Web Service 技术,此外还有很多第三方的 开发工具,如 Foxweb、West - Wind Web Connection 等,利用它们可以开发出功能更强大、可伸缩 性更强的 Web 应用程序。可以预见,基于网络的 Visual FoxPro 数据库应用系统的开发将扮演 更加重要的角色。


开发产品级企业应用 ———企业人事劳资 MIS 系统

8

本章技术要点:  数据模型与 ERWin 数据建模工具。  系统架构。  模板与统一风格设计。  动态报表设计。  数据备份与恢复。  Rushmore 技术。  团队开发与源码版本控制。  帮助文档制作。  发布应用程序。

8.1

本章提要 耐心看到这里的读者也许已经踌躇满志了,但在真正地投入到一个实际项目之前,还是需

要思考以下问题: ① 如何应对企业应用中庞大的数据和复杂的关系? ② 如何设计风格统一而又友好的用户界面? ③ 如何保证系统的灵活性和可靠性? ④ 如何融入到开发团队中? ⑤ 如何对系统进行优化? ⑥ 如何发布应用程序? 这些问题并没有一个标准的答案,根据实际应用场合的不同可以有不同的选择,作为全书 的最后一章,本章将以一个企业人事劳资 MIS 系统为例来说明产品级企业应用程序从设计到 开发直至最后完成的全过程,希望它能够帮助你更好地思考和回答以上问题。 实例运行效果如图 8 . 1 所示。由于本章内容覆盖面较广,故不再遵循书中一贯的“技术分 析 - > 实例制作”的编写模式,而是把理论和实践有机地融合在一起,希望这样能够更好地帮 助读者领会本章的要旨。


开发产品级企业应用

图 8.1

8.2

227

人事劳资 MIS 系统运行效果

MIS 系统概述

MIS 为管理信息系统(Management Information System)的简称,它是一个跨行业、跨领域的研 究方向,覆盖了管理科学、系统工程、运筹学、统计学及信息技术等多门学科。 MIS 的创始人 Gordon B. Davis 为它下了一个较完整的定义:MIS 是一个利用计算机软硬件资源,通过手工作 业,包含分析、计划、控制、决策模型及数据库的人机系统。它能提供信息以支持企业或组织的 运行、管理和决策。 MIS 的命名已经很好地说明了它的核心组成:管理(M)、信息(I)和系统(S),这三者构成了 MIS 系统的金字塔结构。 ① 管理:包括计划、判断、控制、预测和领导等,管理的核心是决策,可见 MIS 系统必须为 决策提供依据。管理处于 MIS 金字塔的最高层。 ② 信息:现代管理区别于传统管理的关键就是量化,而对信息的处理是量化管理的前提。 大量信息的收集、存储、传递、加工和分析构成了 MIS 金字塔的中间层。 ③ 系统:系统是用户、信息和计算机技术的有机结合,它构成了 MIS 金字塔的最低层。在 这样一个人机系统中,人和计算机分别扮演着不同的角色,本章中将侧重于讨论计算机技术, 确切地说是数据库技术及软件工程思想在 MIS 中的应用,但同时还需要知道哪些工作应该交 给计算机处理,而哪些工作应该由人完成。 从应用层次上分,MIS 包括统计系统、数据更新系统、状态报告系统和决策支持系统;从功 能和对象上分,MIS 包括企业管理系统、办公自动化系统和专家系统等,本章将要介绍的企业 人事劳资管理系统即属于其中的一种。在这个系统中,主要面向企业的各个管理部门,通过对


电脑工程师丛书

228

Visual FoxPro 高级应用实例

人事劳资信息的自动化管理实现其自身的价值。 MIS 系统的开发有多种模式,如自顶向下式、自底向上式、面向对象式、原型 - 变迁式等, 本例采用自顶向下的设计思想,在设计时按照功能对其进行模块划分,并最终通过数据建立起 有机的联系,这个设计思想在本书前面的章节中已多次用到。

8.3

数据建模

从使用 Visual FoxPro 或者其他数据库开发工具的程序员角度来看,MIS 系统也许可以等同 为一个数据库应用系统(DBAS),这种看法未必准确,却是简化思路的有效途径。但和普通的 数据库应用程序不同的是,MIS 系统的数据量大,关系错综复杂,仍旧在 Visual FoxPro 的设计器 中设计数据库、表、字段和关系已经不适应开发 MIS 系统的要求,为此,必须采用一种更高层次 的方式来对数据进行抽象、组织和结构设计,这就是数据建模。

8 . 3 . 1 数据模型 数据模型是对数据和数据之间关系的描述。数据模型可以简单地分为两类:逻辑模型和 物理模型。勿庸置疑,任何一种数据库系统都必须遵循一定的数据模型,如 Visual FoxPro 采用 的是关系型数据库(RDB),因此二维表是它的基本逻辑模型,而字段的类型定义、表的结构、表 与表之间的关系则是其物理模型的一部分。下面着重讨论数据的逻辑模型。 数据的逻辑模型又可分为基于记录的逻辑模型和基于对象的逻辑模型,刚才所说的关系 数据库实际上就是基于记录逻辑模型中的一种,类似的还有层次数据库、网状数据库等。基于 对象的逻辑模型在更高的层次上描述数据,其中最常用的是实体 - 关系(Entity - Relationship, ER)模型,它基于对现实世界的这样一种认识:现实世界是由一组称作实体的基本对象以及这 些对象之间的关系组成的。实体是可以区别于其他对象的一个“事物”,如每个人是一个实体, 每份档案也是一个实体。实体最重要的特征是它的属性,如档案中包含的不同信息就是这个 实体的属性。关系是实体之间的关联,如每个人和他的档案之间就存在着一一对应的关系,这 种关系具有自明性。 描述一个实体 - 关系模型最常用的方法是实体 - 关系图,简称 ER 图。在 ER 图中不仅能 描述实体和关系,还能描述特定的约束条件,如每份档案必须要有且只有一个人和它对应,这 就是一个典型的一对一约束条件。在 ER 图中通常遵循以下的表示方法: ① 矩形:实体或实体集。 ② 椭圆:属性。 ③ 菱形:关系或关系集。 ④ 线段:联系实体、属性和关系。 ⑤ 双椭圆:多值属性。 ⑥ 虚椭圆:派生属性。 对于关键属性,还可以用下划线标识,图 8 . 2 给出一个简单的 ER 图实例。 在这个 ER 图中定义了“员工”和“档案”两个实体,它们有着相同或不同的属性,人事档案 管理部门负责建立起它们两者之间的联系。


开发产品级企业应用

图 8.2

229

ER 图实例

不要把实体 - 关系模型想象得过于深奥,其实只要稍加揣摩就可以领会到实体 - 关系模 型的实质:对于一个数据库系统而言,实体通常对应一个表,实体的属性就是表的字段,而关键 属性就是表的键值。再进一步推广,实体间的关系就是表的关联,对实体和关系的约束条件就 是表的记录规则、字段规则以及参照完整性,至此,实体 - 关系模型和数据库系统的数据对象 建立起了一个很容易理解的对应关系。当然,把这样一个抽象的概念具体化最大的坏处就是 无法保证其理论严密性,但是本书不是数据库系统的教科书而更接近于工程技术手册,因此通 俗易懂比严密性要重要得多,至少作者是这么认为的。 下一个问题是,为什么要先用实体 - 关系模型来描述数据而不是直接进行数据库的物理 设计呢?它至少具有以下优点: ① 概念设计。所谓概念设计是指把注意力放在对数据的描述及其相互关系上,而不是专 著于定义物理存储的细节,设计过程中可以直观地检查所有数据需求是否满足,是否存在数据 冲突,还能有效地控制数据冗余度。 ② 高度的灵活性。利用实体 - 关系模型可以把数据作为对象进行处理,遵循从整体到局 部,逐步深入和细化的方式进行设计。例如,在 Visual FoxPro 中不允许创建多对多的关系,在 设计过程中再拆分表的结构显然是很痛苦的一件事,在实体 - 关系模型中允许使用各种关系 描述,并最终设计成满足所有要求的形式。 ③ 逻辑模型和物理模型的便捷转换。如果把实体 - 关系模型比作设计图纸,物理模型就 是最终盖好的房子,两者之间可以进行便捷的转换。如果你是建筑设计师,你也许希望能把设 计好的图纸自动地“变”成房子,或者能根据房子快速地获得设计图纸,修改图纸后再转换成房 子,从而完成二次设计,这对建筑行业来说只是可望而不可及的梦想,但对于数据库系统设计 来说已经成为现实,完成这一任务的就是下面要介绍的数据建模工具。

8 . 3 . 2 强大的数据建模工具——— ERWin 1 . ERWin 简介 简单的数据模型可以直接把 ER 图画在纸上,设计完成后再手工创建数据库的物理模型, 更多的情况是利用专门的数据建模工具完成这一过程,类似的工具有 Sybase 公司的 PowerDesigner、Quest Software 公司的 QDesigner 、Embarcadero 公司的 ER /Studio 等,在这里介绍的是 Computer Associates 公司的 AllFusion Erwin 数据建模工具,下面简称 ERWin。 ERWin 提供了业界领先的数据建模解决方法,可以帮助用户创建和维护数据库、数据仓库


电脑工程师丛书

230

Visual FoxPro 高级应用实例

和企业数据模型,能够形象化地描述数据的结构和关键元素,从而获得最佳的数据库设计。 ERWin 主要具有以下特点: ① 独立于 DBMS 系统之外。 ERWin 不依赖于任何数据库管理系统,各种不同的数据库管 理系统可以使用相同的数据模型。 ② 提供清晰的参照完整性描述。在关系数据库模型中参照完整性具有重要的地位,ERWin 对此提供了良好的支持。 ③ 逻辑模型 / 物理模型对照。针对某种特定的数据库系统,ERWin 可以快速直观地给出 逻辑模型和物理模型图。 ④ 支持多种数据库的物理模型转换。这是最重要的一点,ERWin 支持多种数据库系统的 物理模型导入和导出。 关于 ERWin 的技术资料不多见,中文资料更是少得可怜,但是它有一个很不错的联机文 档和帮助手册。下面将对 ERWin 的基本使用方法进行简单的介绍,从而帮助读者顺利地完成 后续内容的学习。 截止到目前为止,ERWin 的最新版本是 ERwin 4 . 1 Service Pack 2b Build 2773(国际 版),而在此之前的低版本 ERWin 对中文字符的显示存在一定的问题(虽然不影响实质 性使用),如何修补这个问题不在本书讨论范围之列,后面对 ERWin 的讨论也只针对这 个版本。可以从 CA 公司的 FTP 站点免费获得此版本的评估版(ftp:/ /ftp. ca. com/pub / erwin /IntrntlCrtfctn /)。

2 . ER 图 前面已经简要地介绍了 ER 图,但是这里说的是 ERWin 所用的 ER 图,它更直接地描述了 数据库结构中的各种关键元素。 (1)定义实体和属性 假设要定义一个“员工档案”实体,其中包含了“工号”、 “姓名”、 “性别”、 “出生年月”、 “部 门”、 “职务”和“主要社会关系”属性,其中“工号”属性是关键属性,相应的 ER 图如图 8 . 3 所 示。

图 8.3

实体和属性 ER 图


开发产品级企业应用

231

可见,ERWin 用一个方框来表征实体,方框的标题就是实体的名称,方框的顶部是实体的 关键属性,方框中下部罗列出所有的一般属性。那么,如何在 ERWin 中创建这样一个实体呢? 运行 ERWin,并单击“File”菜单 - >“New”菜单项,打开如图 8 . 4 所示的对话框。

图 8.4

新建数据模型

在该对话框中可以选择数据模型的类型和模板,在这里暂时选择逻辑模型“Logical”,确定 后的窗口如图 8 . 5 所示。

图 8.5

ERWin 主窗口

单击工具栏上的“Entity”按钮(见图 8 . 6),然后在绘图区域单击,即在当前的模型中添加一 个实体,如图 8 . 7 所示。 修改实体的名称为“员工档案”,如图 8 . 8 所示。单击 < Tab > 键继续输入关键属性和一般 属性,如果属性不止一个,可以用 < Enter > 键输入多个属性,并随时用 < Tab > 键在实体名称、


电脑工程师丛书

232

Visual FoxPro 高级应用实例

关键属性和一般属性之间切换。

图 8.6

图 8.7

实体快捷按钮

新建的实体

图 8.8

修改实体名称

由 ER 图的性质及逻辑模型和物理模型的对应关系可知,方框的标题将成为表的名称,方 框中包含了表中所有的字段,至于表的存储位置、字段的属性暂时不用去关心它。用类似的方 法再创建一个“员工工资”实体,包含“基本工资”、 “奖金”、 “补贴”、 “房租”、 “水电”、 “应发”和 “实发”属性,最终完成后的实体如图 8 . 9 所示。

图 8.9

员工档案和员工工资实体图

在某个实体上双击,能够显示实体的属性对话框,如图 8 . 10 所示。在该属性对话框中可 以重新修改实体的各个属性,如重命名、排序、描述、注释等。

图 8 . 10

实体属性设置对话框

(2)创建关系 在 ERWin 中允许创建三种类型的关系:认证一对多关系、非认证一对多关系和多对多关 系。一对多关系是最常用的关系,也很好理解,如每个员工可以对应多个工资记录,但是每个 工资记录只能对应一个员工,这就是典型的一对多关系。在这个关系中,员工是一方,或者称 为父实体,而工资记录是多方,或者称为子实体。认证一对多关系和非认证一对多关系的区别 主要表现在父实体的主键是否对应为子实体的主键,类似于 Visual FoxPro 中的永久一对多关 系和临时一对多关系。多对多关系主要用于设计初期,最终必须细化为若干个一对多关系。 图 8 . 11 是三种关系对应的工具栏按钮。


开发产品级企业应用

图 8 . 11

233

关系工具栏

图 8 . 12 给出了在两个实体之间建立关系的三个基本步骤: ① 在工具栏单击非验证一对多关系对应的快捷按钮。 ② 单击父实体。 ③ 单击子实体。

图 8 . 12

创建关系的步骤

关系建立好后,会在实体之间显示一条连接线, “ ”的一端表示父实体, “

”一端表示子

实体,虚线代表非验证关系。同时,父实体中的关键属性会自动添加到子实体中成为外部属性 并以 FK 标识,表示这是一个外部键(Foreign Key)。 如果需要建立验证一对多关系,步骤与建立非验证一对多关系相仿,但是验证关系是用实 线来表示的,并把父实体的关键属性添加到子实体中成为关键属性,同时子实体将成为圆角矩 形,试对比图 8 . 13 与图 8 . 12 的区别。

图 8 . 13

验证一对多关系

双击关系对应的线条,将运行关系设置对话框,如图 8 . 14 所示。


电脑工程师丛书

234

Visual FoxPro 高级应用实例

图 8 . 14

关系设置对话框

在关系设置对话框中,可以进一步设置约束条件和参照完整性,在“Cardinality”组合框中 可以设置多方与一方的映射关系为:零个、一个、多个(“Zero,One,More”);一个或多个(“One or More”);零个或一个(“Zero or One”)和确切的个数(“Exactly”)。例如,选择“One or More”则意 味着每个父实体必须至少有一个子实体和它对应。在“RI Action”选项卡中还可以设定插入、 更新和删除规则,可以有以下选项: ① 限制(Restrict):在进行相应操作时,如果父实体或子实体中存在关联的记录,则不允许 修改。 ② 级联(Cascade):对父实体的修改将同时作用到关联的子实体上。 ③ 忽略(No Action):忽略父实体与子实体之间的关联。 ④ 无(None):无参照完整性约束。 这些设置和 Visual FoxPro 参照完整性生成器中的设置相仿,实际上绝大多数关系数据库 都允许设置这些基本的规则。 3 . 逻辑 / 物理模型 在物理模型设计中主要关心的是数据存储的细节,如字段的属性、索引等。在 ERWin 中 把逻辑模型转换成物理模型是非常方便的,而且可以通过自定义的属性和模板来完成相关的 设置。 新建一个模型,并设置其类型为逻辑 / 物理(Logical /Physical)模型,此时可以设置目标数据 库的类型,在这里我们选择 FoxPro,如图 8 . 15 所示。 可以通过剪贴板把刚才创建的逻辑模型复制过来,更一般的情况是从一开始就创建一个 逻辑 / 物理模型。在如图 8 . 16 所示的模型切换工具栏中选择“Physical”,可以看到当前逻辑模 型所对应的物理模型,如图 8 . 17 所示。


开发产品级企业应用

图 8 . 15

235

创建逻辑 / 物理模型

图 8 . 16

图 8 . 17

模型切换工具栏

物理模型示例

(1)字段属性设置 默认情况下,物理模型的所有字段都将采用宽度为 18 的字符型数据,对此可以进行修改。 一方面可以修改模型默认采用的数据类型,单击“Model”菜单 - >“Model Properties”菜单项,在 弹出的对话框中的“Default”选项卡中可以设置默认的 Null 属性和字段属性,如图 8 . 18 所示。 如果要单独修鼓某一字段的属性,可以在绘图区中双击该字段,打开字段设置对话框,如 图 8 . 19 所示。 在这个对话框中可以修改针对当前目标数据库所使用字段的名称、类型、Null 属性、有效 性规则、默认值等内容。如果物理模型中字段很多,单独地定义每个字段的类型效率是很低 的,ERWin 允许创建自己的数据类型模板(称为域(Domain))。在模型浏览器中选择“Domain” 选项卡,其中包含了五种基本类型:< default > 、Blob、Datetime、Number 和 String,可以在这些基 本类型中添加自定义的类型,如员工工资表中经常要使用到货币型数据,可以在 Number 类中 新建一个名为 Money 型的域,并设置其数据类型为 Numeric(6, 2),表示宽度为 6,小数位数为 2, 如图 8 . 20 所示,然后在物理模型中把员工工资表中相应的字段类型赋为 Money 型即可。如果 将来发现宽度不够,可以重新修改 Money 的设置,这样所有 Money 型的字段属性也会随之被修


电脑工程师丛书

236

Visual FoxPro 高级应用实例

图 8 . 18

图 8 . 19

修改模型的默认值

字段属性设置对话框

改,如果需要为所有的货币型字段设置非空(Not Null)属性、有效性规则和默认值,都可以在域 设置中完成而不需要针对每个字段一一进行处理。可见域的使用不仅仅是定义自己的数据类 型,更重要的是把类型相似的字段放在一起设计其属性,从而便于同时对多个字段进行设置和 修改。 (2)创建索引 在 ERWin 中允许创建四种类型的索引: ① 主索引(Primary Key Index,PK 索引):表的主键,不允许有重复值,而且每个表只能有一 个 PK 索引,但其中可以同时包含多个字段。 ERWin 会自动把逻辑模型中的关键属性转换成 PK 索引。 ② 外部键索引(Foreign Key Index,FK 索引):FK 索引是基于外部属性的索引,它是通过一 对多关系从父表传递到子表的。 ERWin 会自动把逻辑模型中的外部属性转换成 FK 索引。 ③ 候选索引(Alternate Key Indexes,AK 索引):没有重复值的索引。 ④ 普通索引(Inversion Entry Indexes,IE 索引):允许重复值的索引。


开发产品级企业应用

图 8 . 20

237

自定义域

单击“Model”菜单 - >“Indexes”菜单项,或者双击模型浏览器中某个表的“Indexes”节点将 会运行 ERWin 的索引编辑器,如图 8 . 21 所示。

图 8 . 21

索引编辑器

索引编辑器中列出了当前表中的所有索引,在“Member”选项卡中可以指定索引包含的字 段成员,在“FoxPro”选项卡中可以指定索引文件的类型、升降序,对于非 PK 和 FK 索引还能指 定是否为惟一索引。 ERWin 的功能远不止以上这些,但本书不准备一一对它们进行演示,相信大多数读者已经 意识到数据建模在大型数据库系统开发过程中的强大威力了。学习 ERWin 的最好途径就是 把它应用到自己的项目中,下面将利用 ERWin 创建本章人事劳资 MIS 系统的数据模型,并最 终把它转换成 Visual FoxPro 的数据库。


电脑工程师丛书

238

Visual FoxPro 高级应用实例

8 . 3 . 3 创建人事劳资系统数据模型 在一个 MIS 系统的设计过程中,首先要清楚地描述将要处理的数据及它们之间的关系,请 暂时忘掉自己的程序员身份,从业务流程的角度来看待这些数据。以人事劳资 MIS 系统为例, 它包含四个基本的模块:人事档案管理、考勤管理、劳动量管理和工资管理,每个模块会和不同 的数据打交道,但是这些模块之间的数据又不是孤立的,实体 - 关系模型就能够排上用场了。 很显然,这个人事劳资 MIS 系统将处理四个实体:所有员工的人事档案、每天的考勤记录、 劳动量统计和月工资报表,因此第一步是在 ERWin 中描述这些实体。在这个过程中不需要考 虑任何技术上的细节,只需要从收集到的资料中提取出能够充分反映其特征的属性。图 8 . 22 是在 ERWin 中对这四个实体的描述。

图 8 . 22

人事劳资 MIS 系统实体原始描述

“人事档案”实体以员工的“工号”作为键值属性,这是因为管理是以人为本的。对于一个 以月薪为计薪单位的企业来说,工资管理显然以月份作为统计的时间依据。下面再来看数据 的冗余和惟一性保证, “工号”属性在“人事档案”实体中是可以保证其惟一性的,但在其他实体 中都无法保证,而且“人事档案”实体与其他实体间存在非验证一对多关系,因此“工号”属性可 以通过外部属性插入到这些实体中。“月工资”实体的“月份”属性无法保证其惟一性,因为同 一个月可能有多个员工的工资记录,因此需要把月份信息从“月工资”实体中提取出来成为一 个单独的实体。该实体只有一个属性“月份”,而且是该实体的键值属性。经过以上实体属性 的组合与拆分,最终每个实体的描述如图 8 . 23 所示。本例其实对数据原型进行了化简,如果 实际应用中数据量很大,那么这个过程可能需要经过多次反复,最终得到一个合理的描述形 式。 下面来考虑这些实体之间的关系。“人事档案”实体需要和其他几个实体建立一对多关 系, “月份”实体和“月工资”实体也需要建立一对多关系。考勤记录和劳动量记录也是按月统 计的,因此“月份”实体和“考勤记录”、 “劳动量记录”实体之间也存在一对多关系。“月份”实体 成了联系“月工资”实体和“考勤记录”、 “劳动量记录”实体之间的桥梁。图 8 . 24 说明了这几个 实体之间的关系。另外添加了一个独立的“用户”实体,它是对这个 MIS 系统操作员的描述。


开发产品级企业应用

图 8 . 23

人事劳资 MIS 系统实体细化描述

图 8 . 24

人事劳资 MIS 系统实体 - 关系图

239

确认以上的 ER 图能够准确无误地描述数据的结构和关系后,就可以开始设置物理模型 了,主要包括各个字段的属性、需要哪些索引、索引的类型等,这个过程和在 Visual FoxPro 的数 据库设计器中进行设计的思路是基本相同的,相信读者能够根据上文所述的内容自行完成。

8 . 3 . 4 导出 Visual FoxPro 数据库 数据模型建立完毕后,需要生成程序中使用的 Visual FoxPro 数据库,这个过程可以由 ERWin 自动完成。单击“Tools”菜单 - >“Forward Engineer /Schema Generation”菜单项,将运行前向 工程对话框,如图 8 . 25 所示。


电脑工程师丛书

240

Visual FoxPro 高级应用实例

图 8 . 25

前向工程对话框

在这个对话框中可以选择转换的选项,并能够预览或保存转换过程使用的 SQL 命令,单 击“Generate”按钮并选择目标文件的路径,即可完成转换。对于不同的数据库管理系统,可以 转换的内容会有所不同,对于 Visual FoxPro 数据库,ERWin 只能转换物理模型中的表、字段属 性及索引,因此表与表之间的关系还需要根据 ER 图在数据库设计器中进行相应的设计,最终 完成后的数据库如图 8 . 26 所示。

图 8 . 26

最终完成的 Visual FoxPro 数据库


开发产品级企业应用

功能设计

8.4

8 . 4 . 1 整体设计 整体设计的主要任务是把 MIS 系统的业务流程进行归纳, 划分出不同的功能模块和子模 块,理 清 各 个 模 块 之 间 数 据 联 系。对每个模块设计统一的界 面风格,充分发挥面向对象程序 设计的优势,自顶向下地完成设 计的全过程。 1 . 系统框图和目录树 图 8 . 27 是 这 个 人 事 劳 资 系统的结构框图。 MIS 在开始任何实际代码的编 写之前,应该创建一个合理的目 录结构,因为随着内容的不断增 加,文件数目也会越来越多,如 果把它们全部放在同一个目录 下必然会给管理带来混乱。因 此,可以把这些文件按照不同类 别放置到不同的子目录下,本系 统采用如图 8 . 28 所示的目录结 构。 创建目录结构后需要注意 的是,在程序中访问任何数据库 文件、动态加载类库文件等都需 要在默认路径的基础上添加相 对路径,而绝对路径的使用是应 该绝对避免的。

图 8 . 27

系统框图

241


电脑工程师丛书

242

Visual FoxPro 高级应用实例

图 8 . 28

系统目录树

2 . 统一风格设计 任何一个设计良好的软件都会有一个统一的界面风格,如 MS Office 中的 Word、PowerPoint、 Excel 等不同组件都有着相似的菜单、工具栏和布局框架,使得用户在熟悉了其中某个组件的 界面后可以很容易地掌握其余的组件。第 1 章中曾经对表单的统一风格设计有过论述,在这 里要讨论的是各个功能模块之间统一的风格设计。 本系统共包含五个模块,用户应该可以很容易地在这五个模块之间进行切换。同时,每个 模块下还包含若干个子模块,这些模块与子模块之间应该有合理的布局,这些都可以通过模板 的设计来完成。在 Visual FoxPro 中的模板是通过可视类库完成的。例如,希望所有的表单上 都包含一个企业的 Logo 图标,可以制作一个仅包含该图标的空表单,并把它保存为可视类库, 以后所有的表单都从该类中继承,如果需要修改图标的图案或者布局,只需要对这个基类进行 修改就可以了。 本例需要制作两个模板,分别是每个功能模块及其子模块的显示模板。子模块显示模板 中包含一个图标和文字说明,在这里可以设置一些公共的属性和布局,如图标的大小,文字说 明的对齐方式,鼠标停留在该模块上时光标的形状等,如图 8 . 29 所示。

图 8 . 29

子模块模板

模块显示模板则应该包含不同的子模块,由于本系统中每个模块最多具有五个子模块,故 在此基础上进行设计和布局,如图 8 . 30 所示。 在模板的基础上就可以进行下一步的设计了,如添加各个子模块的图标和文字说明等。 图 8 . 31 是按照这个模板设计完成的考勤模块,其余的模块可以进行同样的设计。


开发产品级企业应用

图 8 . 30

模块模板

图 8 . 31

243

考勤模块

最后设计系统的总表单,主要提供在各个模块之间进行切换的界面,相信这应该难不到各 位读者,最终的设计效果如图 8 . 32 所示。

图 8 . 32

总表单界面

8 . 4 . 2 功能实现 功能实现其实是本章希望淡化的内容,因为本书前面所有的章节都在从不同的技术层面 上讨论各种功能的实现途径,把它们应用到这个人事劳资 MIS 系统中来足以应付大多数的技 术问题,因此不必要把每个模块的实现方法再重复一遍。如果从数据库应用系统的多样性考 虑,又不可能把每种系统的开发过程中可能遇到的问题一一列举,问题的关键在于活学活用, 举一反三,以不变应万变。同时,要学会充分利用现有的资源和他人的成果,站得高才能看得 远。


电脑工程师丛书

244

Visual FoxPro 高级应用实例

1 . 一对多表单设计 在前面的数据设计过程中用到了很多的一对多关系,因此需要进行一对多表单设计。在 低版本的 Visual FoxPro 中设计一对多表单需要进行复杂的编程,随着表格控件的出现这种情 况有了很大的改观,而利用 Visual FoxPro 的一对多表单向导可以很容易地创建一对多表单。 设计一对多表单真正的难点在于搞清楚数据之间的关联和它的工作流程。 以工资管理子模块为例,在这个模块中将要处理两个一对多关系:员工档案表和工资表的 一对多关系、月份表和工资表的一对多关系。针对这两个关系,又分别有两种操作模式:查看 或修改某个员工所有月份的工资记录,查看或修改某个月份所有员工的工资记录。在前一种 操作模式中,父表为员工档案表,子表为工资表,在后一种操作模式中,父表为月份表,子表为 工资表。 通过一对多表单向导创建员工工资管理表单,父表为 Employee 表,并选取“姓名”、 “性 别”、 “部门”和“工资级别”字段,子表为 Wage 表,并选取所有字段,表之间的关系为共同的“工 号”字段,完成后表单如图 8 . 33 所示。

图 8 . 33

员工工资管理一对多表单

单击导航条,父表的记录指针将发生移动,子表中则通过关键字段筛选出相应的记录。例 如,父表区域中显示的是工号为“95000001”的员工信息,子表中则显示该员工所有的工资记 录,这是一对多关系中的指针联动和记录筛选。 再看添加记录的功能,单击“添加”按钮,将运行如图 8 . 34 所示的对话框,其中有三种添加 方式:仅对父表添加记录、仅对子表添加记录和两者都添加记录,如果选择仅对子表添加记录, “关键值”输入框不可编辑,如果选择两者都添加记录, “关键值”输入框中输入的值将同时成为 父表和子表中“工号”字段的值,这样的设计是为了保证父表与子表之间的参照完整性。 一对多表单还有一个特点是没有删除按钮。因为对父表进行删除操作有可能破坏参照完 整性,这个工作应该交给专门处理父表的表单完成,在本系统中由专门的职工档案管理模块实 现该功能。对子表的删除操作通过表格控件完成,单击“编辑”按钮,表格控件的最左边将显示 一个删除列,在该列中可以为一个或多个子表记录打上删除标记。 可见,一对多表单的记录浏览、增、删、改等操作方式和单表表单有很大的不同,一方面是 充分利用表之间的关系,另一方面又要考虑到参照完整性。以上仅以一对多表单向导生成的 表单为例来说明其设计思想和流程,在此基础上也可以脱离表单向导直接在表单设计器中设


开发产品级企业应用

图 8 . 34

245

在一对多表单中添加记录

计更为灵活的一对多表单,但最重要的是必须先知道自己的一对多表单将以什么样的方式工 作。 2 . 高级报表设计 Visual FoxPro 的报表设计器不能够满足所有报表设计的需求,这是不争的事实,如在程序 中动态地添加或删除字段,调整列宽等,对于这些问题 Visual FoxPro 的报表设计器基本上无能 为力。可是仔细研究过报表文件格式的朋友都知道,报表和通常所使用数据表其实是同一类 型的数据文件,也就是说 frx 文件其实就是一个改了扩展名的 dbf 文件。用 USE 命令可以以表 的形式打开报表,如图 8 . 35 所示。每条记录就是一个报表控件,记录中的字段保存了该报表 控件的属性(表单 SCX 文件、可视类库 VCX 文件也都是如此)。

图 8 . 35

以表的形式打开报表文件

当然,如果不是真地需要,没有谁愿意直接和图 8 . 35 这样的表打交道,有很多第三方的报 表开发工具可供使用,它们都是在此基础上进行了功能封装,如 GenReporX 就是一个面向对象 的报表设计工具,能够动态地在程序中控制报表对象。在这里以思特软件(http:/ /www. chongsheng. com)开发的报表工具类库为例来说明如何更为灵活地设计动态报表,经授权,这个类库 被允许包含在本书的配套光盘中( h ex8 h lib h rtplib . vcx)。 这个报表工具类库的使用非常简单,只需要在程序中创建该类库中的 ReportMain 对象,并 把一个和数据源绑定好的表格控件、一个标题字符串以及预览 / 打印标识作为参数传递给该对 象即可,例如: SET CLASSLIB TO lib h rptlib CREATEOBJECT(’ReportMain’,THISFORM. grid1,"报表标题",. T.)


电脑工程师丛书

246

Visual FoxPro 高级应用实例

这段代码将以预览的形式运行一个动态报表,报表的数据来源与表单的 grid1 控件相关 联,报表的字段和表格控件的字段相同,而且通过控制表格控件的列宽度还能控制表格的列宽 度,这相当于为最终用户提供了一个形象直观的报表设计器。这个报表类库在打印表格形式 的报表时特别方便灵活。 由此可见,设计动态报表就演化成控制一个表格控件的问题,这对于 Visual FoxPro 程序员 来说应该是驾轻就熟的了。下面以设计本系统中的职工清单报表为例来加以说明。 职工清单报表具备以下功能:可以打印全部职工的清单或者是满足一定条件的职工清单; 可以选择清单中包含职工档案中的哪些信息;可以根据页面设置可视地调整列宽。实现的基 本思路是提供一个记录筛选条件和字段选取对话框,根据用户输入的记录筛选条件和选定的 字段生成一个临时表,把该临时表绑定到一个表格控件中供用户调整列宽,最终动态生成报 表。 报表的记录筛选条件和字段选取表单如图 8 . 36 所示。

图 8 . 36

报表记录筛选条件和字段选取表单

“记录筛选条件”框内是 Visual FoxPro 提供的向导基类 SearchClass,它位于 wizbtns . vcx 类库 文件中,这个类库前面已经多次用到。 SearchClass 有一个重要的方法 searchexpr(),执行该方法 可以把用户输入的条件转换成一个符合 Visuao FoxPro 语法的条件表达式,可以直接使用在 SQL - SELECT 命令的条件子句中。 “输出字段”框是一个自定义的可视类,如图 8 . 37 所示。 该类包含两个列表框 lstSource 和 lstSelected,前者显示当前表中所有的字段,后者显示用户 选定的字段,这样的操作界面在各种向导和设计器中经常看到。 lstSource 列表框在初始化时 把表中所有的字段添加到自己的列表中,单击“添加 > ”、 “全部添加 > > ”、 “ < 移除”和“全部 移除”按钮就可以把字段在这两个列表框中进行切换,并最终选定合适的字段。 lstSource 列表 框的 Init 事件代码如下:


开发产品级企业应用

图 8 . 37

输出字段选择可视类

# DEFINE NUM- AFIELDS 16 LOCAL i PUBLIC aWizFList DIMENSION aWizFList[1] = AFIELDS(aWizFList) FOR m. i = FCOUNT()TO 1 STEP - 1 IF INLIST(aWizFList[m. i, 2],"G","M","U") = ADEL(aWizFList,m. i) DIMENSION aWizFList[MAX(1,ALEN(aWizFList, 1)- 1),NUM-AFIELDS] ENDIF ENDFOR FOR i = 1 TO ALEN(aWizFList, 1) ) THIS. ADDITEM(aWizFList[i, 1] THIS. VALUE = THIS. LIST[1] ENDFOR “添加 > ”按钮的 Click 事件如下: THISFORM. LOCKSCREEN = . T. nCnt = 1 DO WHILE nCnt < = THIS. PARENT. lstSource . LISTCOUNT IF THIS. PARENT. lstSource . SELECTED(nCnt) THIS. PARENT. lstSelected . ADDITEM(THIS. PARENT. lstSource . ; LIST(nCnt)) THIS. PARENT. lstSource . REMOVEITEM(nCnt) ELSE nCnt = nCnt + 1 ENDIF ENDDO THISFORM. LOCKSCREEN = . F. “全部添加 > > ”按钮的 Click 事件如下: THISFORM. LOCKSCREEN = . T. FOR i = 1 TO THIS. PARENT. lstSource . LISTCOUNT

247


电脑工程师丛书

248

Visual FoxPro 高级应用实例

THIS. PARENT. lstSelected . ADDITEM(THIS. PARENT. lstSource . LIST(i)) ENDFOR THIS. PARENT. lstSource . CLEAR THISFORM. LOCKSCREEN = . F. “ < 移除”按钮的 Click 事件如下: THISFORM. LOCKSCREEN = . T. nCnt = 1 DO WHILE nCnt < = THIS. PARENT. lstSelected . LISTCOUNT IF THIS. PARENT. lstSelected . SELECTED(nCnt) THIS. PARENT. lstSource . ADDITEM(THIS. PARENT. lstSelected . ; LIST(nCnt)) THIS. PARENT. lstSelected . REMOVEITEM(nCnt) ELSE nCnt = nCnt + 1 ENDIF ENDDO THISFORM. LOCKSCREEN = . F. “ < < 全部移除”按钮的 Click 事件如下: THISFORM. LOCKSCREEN = . T. FOR i = 1 TO THIS. PARENT. lstSelected . LISTCOUNT THIS. PARENT. lstSource . ADDITEM(THIS. PARENT. lstSelected . ; LIST(i)) ENDFOR THIS. PARENT. lstSelected . CLEAR THISFORM. LOCKSCREEN = . F. 以上内容设计完毕后,在当前表单中添加表单集和另一个表单 Form2,如图 8 . 38 所示。

图 8 . 38

报表列宽调整表单


开发产品级企业应用

249

该表单在默认情况下是隐藏的(Visibale = . F.)。为第一个表单的“下一步”按钮添加如 下代码: Fieldsort = ALLTRIM(THISFORM. fieldsort1 . lstSelected . LIST(1)) IF EMPTY(Fieldsort) ”错误”) MESSAGEBOX(”没有选择需要打印的字段,请重新选择”,0 + 48 + 256, RETURN ENDIF FOR i = 2 TO THISFORM. fieldsort1 . lstSelected . LISTCOUNT ”+ ; Fieldsort = Fieldsort + ”, ALLTRIM(THISFORM. fieldsort1 . lstSelected . LIST(i)) ENDFOR wherestr = THISFORM. searchclass1 . searchexpr() IF ! EMPTY(wherestr) SQL = ”select ”+ Fieldsort + ”from employee where ”+ wherestr + ”into cursor rstemp” ELSE SQL = ”select ”+ Fieldsort + ”from employee into cursor rstemp” ENDIF &SQL THISFORMSET. form2 . grid1 . RECORDSOURCE = ”rstemp” THISFORMSET. form2 . grid1 . COLUMNCOUNT = THISFORM. fieldsort1 . lstSelected . LISTCOUNT THISFORM. HIDE THISFORMSET. form2 . SHOW 这段代码首先判断用户是否选择了必要的字段(至少打印一个字段),如果没有则给出错 误提示信息,如果选择了则根据用户输入的筛选条件和选定字段用 SQL - SELECT 命令输出到 一个临时表 rstemp 中,并把这个表和 Form2 的 Grid1 控件绑定,然后显示 Form2。运行时刻的 Form2 外观如图 8 . 39 所示。

图 8 . 39

运行时刻的报表列宽调整表单


电脑工程师丛书

250

Visual FoxPro 高级应用实例

在这个表单中,用户可以直观地修改各个列的宽度,并进行预览, “预览”按钮的代码如下: SELECT rstemp HEADER = ALLTRIM(THISFORMSET. form1 . text1 . VALUE ) IF RECCOUNT()< 1 MESSAGEBOX("未选定任何记录", 0 + 48 + 256,"错误") RETURN ENDIF SET CLASSLIB TO lib h rptlib CREATEOBJECT(' ReportMain' ,THISFORM. grid1,HEADER,. T.) 图 8 . 40 给出了选定不同字段和列宽调整后的两个报表,可见这种动态报表的设计方法能 够很好地弥补 Visual FoxPro 在动态报表设计方面的不足。

图 8 . 40

实际动态报表输出效果对比

最后向这个动态报表类库的设计者周从胜先生表示感谢,感谢他为广大读者(包括作者) 提供了这么好的报表设计工具。 3 . 数据备份与恢复 对于一个 MIS 系统来说,可靠的数据备份与恢复机制是必不可少的,由于这个问题本书前 面一直未曾涉及,所以有必要在这里探讨一下。 在系统设计期间为整个系统设计了一个合理的目录结构,这为实现数据的备份和恢复提 供了极大的方便:要从包含上百个文件的目录中维护一个数据和备份文件列表显然是一件吃 力不讨好的事情,而建立了目录结构后,所有的数据文件均放在 data 目录下,所有的备份文件 均放在 backup 目录下,后面的编程工作量将会大大减轻。 (1)XCeedZip 压缩 / 解压缩控件 备份和恢复就是把数据文件复制到 backup 目录下,恢复的时候再还原到 data 目录来么?


开发产品级企业应用

251

不是这么简单,一方面,用户可能需要保持系统的多个备份,另一方面还要考虑到磁盘空间的 占用问题,为此,对数据文件进行打包压缩是一个较为理想的解决方案。如果大家曾使用过各 种压缩工具(如 WinZip、WinRAR)的话,对打包压缩过程应该不会陌生,不过 Visual FoxPro 自身 并不提供打包压缩的功能,那么这就需要借助于外部工具了。 XCeedZip 是一个专门用于打包 压缩和解压缩的 ActiveX 控件,利用它可以轻松地把多个文件以 ZIP 格式制作成一个打包文 件,也可以从 ZIP 的压缩文件中解出原文件。 ActiveX 控件的使用在第 4 章已有详尽论述,这里 只讨论 XCeedZip 控件的一些基本使用方法。该控件可以从 XCeedSoft 的网站(http:/ /www. xceedsoft. com/download /ZipCompL/)上免费获得预览版,通过网络下载的实际上是一个完整的 工具集,其中 Xceed Zip Compression Library 5 . 0 才是我们需要使用的组件,其中的 ActiveX 文件 XceedZip. dll 位于安装目录的 Bin 子目录下,虽然它的扩展名是 DLL,但是用法和 OCX 文件完 全相同。 如果只是利用 XCeedZip 控件进行简单的打包压缩和解压缩,只需要知道以下属性和方法 就可以了,实际上本例中也只用到了这些属性和方法。 XCeedZip 控件压缩相关的属性和方法如下: ”或“”通配 ① FILESTOPROCESS 属性:即将进行打包的文件名或文件列表,可以使用“? 符,如想要打包压缩 data 目录下的所有文件,可以为把属性赋值为“D:h mis h data h  . ”。 ② ZIPFILENAME 属性:打包后的压缩文件名。虽然压缩文件是 ZIP 格式的,但是文件扩 展名可以任意。 ③ PreservePaths 属性:在压缩文件中是否保存路径信息。 ④ ZIP 方法:执行压缩。 XCeedZip 控件解压缩相关的属性和方法如下: ① FILESTOPROCESS 属性:即将从压缩文件中解出来的文件名,留空则解出所有文件。 ② ZIPFILENAME 属性:将要进行解压缩的文件名。 ③ UnzipToFolder 属性:解压缩后文件的存放路径,如“D:h mis h data h ”。 ④ UNZIP 方法:执行解压缩。 这些属性和方法的具体用法参见后面相关的程序代码。 (2)设计数据备份和恢复模块 有了 XCeedZip 控件后,剩下的问题就 是如 何 帮 助 用 户 方 便 地 管 理 这 些 备 份 文 档。让用户自己去某个目录下寻找备份文 件显然不是一个好主意,因此可以采用这 样的思路:在备份文档时,以当前的系统日 期作为文件名,考虑到用户每天可能不止 备份一次,可以用当天的备份次数作为扩 展名,如 2003 - 06 - 03 . 1、2003 - 06 - 03 . 2 ……恢 复 数 据 时 首 先 选 择 恢 复 哪 天 的 数 据, 然后把该天所有的备份数据列出来供 用户选择。按照这个思路设计的数据备份 表单如图 8 . 41 所示,数据恢复表单如图 8 . 42 所示。

图 8 . 41

数据备份表单


电脑工程师丛书

252

Visual FoxPro 高级应用实例

图 8 . 42

数据恢复表单

表单的 Load 事件中包含以下代码, 这段代码的作用是设置日期函数的形式为 YYYY - MMDD 的形式,因为它将成为备份文件的命名依据。 SET DATE ANSI SET CENTURY ON SET MARK TO ”- ” 表单中包含一个 ListFile 方法,可以在列表框 List1 控件中列出日期输入框中的日期所对 应的备份文件,当表单初始化或用户修改日期输入框的值时都需要调用这个方法,代码如下: LOCAL i,bdir i=0 bdir = SYS(5)+ SYS(2003)+ " h backup h " THIS. list1 . CLEAR FOR i = 0 TO 9 IF FILE(bdir + ALLTRIM(DTOC(THIS. dinput1 . Text1 . VALUE))+ ". " + ; STR(i, 1)) THIS. list1 . ADDLISTITEM("栏位" + STR(i, 1)+ "(已备份)") ELSE THIS. list1 . ADDLISTITEM("栏位" + STR(i, 1)+ "(空)") ENDIF ENDFOR THISFORM. REFRESH 单击“备份”按钮时,首先判断数据目录、备份目录是否存在,如果备份文件已经存在还需 要用户确认是否覆盖,以上答案都肯定的情况下,设置好 XCeedZip 控件的属性并执行备份操 作,其 Click 事件代码如下: LOCAL sdir,bdir,i,bfile sdir = SYS(5)+ SYS(2003)+ " h data h " bdir = SYS(5)+ SYS(2003)+ " h backup h " IF !DIRECTORY(sdir)


开发产品级企业应用

253

MESSAGEBOX("数据目录" + sdir + "不存在,无法进行数据备份。",; 0 + 48 + 256,"出错") RETURN ENDIF IF !DIRECTORY(bdir) MESSAGEBOX("备份目录" + bdir + "不存在,无法进行数据备份。",; 0 + 48 + 256,"出错") RETURN ENDIF IF EMPTY(ALLTRIM(SUBSTR(THISFORM. list1 . VALUE, 5, 1))) MESSAGEBOX("请先选择一个备份栏位", 0 + 48 + 256,"出错") RETURN ENDIF i = ALLTRIM(SUBSTR(THISFORM. list1 . VALUE, 5, 1)) bfile = bdir + ALLTRIM(DTOC(THISFORM. dinput1 . text1 . VALUE))+ ". " + i IF FILE(bfile) yn = MESSAGEBOX("该栏位已经保存有备份数据,是否覆盖?", 1 + 48 + 256,"提示") IF yn = 0 RETURN ENDIF ENDIF THISFORM. olezip . FILESTOPROCESS = sdir + " . " THISFORM. olezip . ZIPFILENAME = bfile CLOSE DATA ALL zipok = THISFORM. olezip . ZIP IF zipok = 0 MESSAGEBOX("备份成功", 0 + 48 + 256,"成功") ELSE MESSAGEBOX("备份失败", 0 + 48 + 256,"失败") ENDIF THISFORM. listfile “恢复”按钮的 Click 事件代码如下: LOCAL sdir,bdir,i,bfile sdir = SYS(5)+ SYS(2003)+ " h data h " bdir = SYS(5)+ SYS(2003)+ " h backup h " IF !DIRECTORY(sdir) MESSAGEBOX("数据目录" + sdir + "不存在,无法进行数据恢复。",; 0 + 48 + 256,"出错") RETURN


电脑工程师丛书

254

Visual FoxPro 高级应用实例

ENDIF IF !DIRECTORY(bdir) MESSAGEBOX("备份目录" + bdir + "不存在,无法进行数据恢复。",; 0 + 48 + 256,"出错") RETURN ENDIF IF EMPTY(ALLTRIM(SUBSTR(THISFORM. list1 . VALUE, 5, 1))) MESSAGEBOX("请先选择一个备份栏位", 0 + 48 + 256,"出错") RETURN ENDIF i = ALLTRIM(SUBSTR(THISFORM. list1 . VALUE, 5, 1)) bfile = bdir + ALLTRIM(DTOC(THISFORM. dinput1 . text1 . VALUE)); + ". " + i ! IF FILE(bfile) MESSAGEBOX("该栏位没有数据,请重新选择!", 0 + 48 + 256,"出错") RETURN ENDIF yn = MESSAGEBOX("确认从" + ; ALLTRIM(DTOC(THISFORM. dinput1 . text1 . VALUE)); + "这天的栏位 " + i + " 恢复数据吗?", 1 + 48 + 256,"提示") IF yn = 1 THISFORM. olezip . ZIPFILENAME = bfile THISFORM. olezip . UnzipToFolder = sdir USE CLOSE DATABASE ALL unzipok = THISFORM. olezip . unzip IF unzipok = 0 MESSAGEBOX("数据恢复成功", 0 + 48 + 256,"成功") ELSE MESSAGEBOX("数据恢复失败", 0 + 48 + 256,"失败") ENDIF ELSE MESSAGEBOX("取消数据恢复。", 0 + 48 + 256,"取消") RETURN ENDIF THISFORM. listfile


开发产品级企业应用

8.5

255

系统优化 对于程序员来说,不仅要考虑功能如何实现,还要考虑如何让应用程序运行得更快更好,

这就需要对系统进行必要的优化。对于 Visual FoxPro 数据库应用系统来说,充分发挥 Rushmore 技术的优势是其中的关键。

8.5.1

Rushmore 技术

如果一本英汉字典不是按照字母顺序排列而是杂乱无章的话,要在其中找一个英语单词 是十分困难的,这说明合理的排序可以大大加快查询的速度。在数据库系统中也不例外,对一 个创建了索引的字段执行 SEEK 命令要比执行 LOCATE FOR 命令快得多,因为前者是索引查 询而后者是顺序查询。一般情况下都会尽可能地使用索引查询,但在某些情况下也不可避免 地要用到顺序查询的命令,如 COUNT FOR 命令等。 Rushmore 技术就是针对这些情况而提出的 一种优化数据访问的技术,它可以大大加快某些命令的执行速度。 例如,用以下命令创建一个 50 万条记录的表,并创建索引。 create table test(test c(8)) for n = 1 to 500000 insert blank endfor go 1 replace test with "rushmore" go 100000 replace test with "rushmore" go 500000 replace test with "rushmore" index on test to test 再执行以下命令: set optimize off start = second() count for test = "rushmore" ?"关闭 Rushmore 执行时间:" ?? second()- start set optimize on start = second() count for test = "rushmore" ?"开启 Rushmore 执行时间:" ?? second()- start


电脑工程师丛书

256

Visual FoxPro 高级应用实例

相信你已经看到这其中巨大的差别了,前者的执行时间以秒计,而后者的执行时间以毫秒 计。 也许你会想:既然这样,那么一直打开优化选项不就可以了嘛。真得就这么简单么?试着 把上面测试程序中的 COUNT 命令改为: count for not test S| "rushmore" 很遗憾,这回无论是否打开了优化选项,时间都是以秒计的。可见 Rushmore 技术并不是 在所有情况下都能够对命令的执行进行优化,因此在程序设计过程中除了打开优化选项之外, 还应该尽可能地避免出现这种情况。 1 . Rushmore 技术的可优化条件 Rushmore 技术的可优化条件可以简单地归纳为三点:命令、索引、表达式。 (1)可优化命令 在执行表 8 . 1 中的命令时,如果其中包含了 FOR 条件子句都有可能享受到 Rushmore 技术 带来的好处。 表 8.1

可以使用 Rushmore 技术优化的命令

AVERAGE

BLANK

BROWSE

CALCULATE

CHANGE

COPY To

COPY TO ARRAY

COUNT

DELETE

DISPLAY

EDIT

EXPORT TO

INDEX

JOIN WITH

LABEL

LIST

LOCATE

RECALL

REPLACE

REPLACE FROM ARRAY

REPORT

SCAN

ET DELETED

SET FILTER

SORT TO

SUM

TOTAL TO

(2)可优化索引 使用 Rushmore 技术进行优化还必须对相应的字段建立索引,索引的格式可以是单索引、 结构复合索引和非结构复合索引。但是相比较而言,这几种索引在速度上也有一定的区别,复 合索引要略快于单索引。在创建索引时还需要注意,INDEX 命令中不能包含有 FOR 条件子 句,也不能使用 UNIQUE 选项,Rushmore 将会忽略筛选索引和惟一索引。 (3)可优化表达式 在 FOR 条件子句中,并非所有的条件表达式都是可优化的,上面已经给出了一个例子:当 条件表达式中包含“S|”运算符时 Rushmore 技术将不起作用。一个能够优化的表达式通常具有 以下形式: FOR 索引表达式 关系运算符 表达式 其中索引表达式必须和建立索引时所用的表达式相同。例如,在创建索引时使用的是如 下命令: INDEX ON UPPER(test)TAG test 那么在条件表达式中也必须使用相同的表示形式, “FOR UPPER(test)= "Rushmore"”是可


开发产品级企业应用

257

优化的,而“FOR test = "Rushmore"”是不可优化的。此外 ISBLANK()和 EMPTY()函数也不能出 现在索引表达式中。 除了“S|”之外,其余的关系运算符都可以用在可优化条件表达式中。 有些时候还需要用逻辑运算符 NOT、AND 和 OR 把多个条件表达式连接起来成为一个复 合条件表达式,这种情况下复合表达式能否优化一般取决于各个子表达式。例如,把两个可优 化的条件表达式通过逻辑运算符组合,最终的复合表达式将是可优化的;把两个不可优化的条 件表达式通过逻辑运算符组合,最终的复合表达式将是不可优化的,如果两个条件表达式只有 一个可优化,最终的复合表达式是部分优化的。惟一的特例是 OR 运算符,只要有一个条件表 达式不可优化,最终的复合表达式将是不可优化的,因此在使用 OR 运算符时要特别注意。 2 . 使用 Rushmore 技术的注意事项 即使以上所说的所有条件都具备,也并非任何时候都可以进行优化,Rushmore 技术对内存 的需求量要比一般情况下大,如果可用的物理内存不足将出现错误信息,不过这个错误并不影 响命令的正确执行。如果要关闭 Rushmore 优化,可以有两种方式: ① 全局性关闭:SET OPTIMIZE OFF。 ② 暂时性关闭:命令 < NOOPTIMIZE > 。 使用 Rushmore 技术的好处已经是显而易见的了,但物极必反,滥用 Rushmore 反而会导致 性能的下降。索引是优化的前提,如果查询命令中包含很多不同的条件表达式,那就需要对这 些表达式一一创建索引,这虽然会提高查询的速度,但在修改数据时系统却要花费更多的时间 去维护索引,因此需要在查询速度和数据维护速度这两方面寻找一个合适的平衡点。

8 . 5 . 2 其他的优化措施 除了 Rushmore 技术之外,在很多细节上稍作改进也可以有效地提高程序的性能,现择要 列举如下: ① 减少对象引用次数。 当需要对对象的多个属性赋值时,可以使用 WITH 命令。例如: -

VFP. Caption = "title"

-

VFP. Top = 0

-

VFP. Left = 0

-

VFP. Visible = . T.

可以把上面这段代码修改为: WITH

-

VFP

. Caption = "title" . Top = 0 . Left = 0 . Visible = . T. ENDWITH 如果要给朋友送四个苹果,是每次送一个跑四趟呢,还是装在篮子里一次送过去呢?同样


电脑工程师丛书

258

Visual FoxPro 高级应用实例

的道理,前一种情况引用了 - VFP 对象四次,而后一种情况只引用了一次,那么 WITH 命令就相 当于那个装苹果的篮子。 ② 减少文件打开次数。 如果需要在程序中使用不同的表,并且需要在表之间频繁地切换,不应该每次关闭其中一 个再打开另一个,而应该把它们在不同的工作区的打开,并使用 SELECT 命令进行切换。 ③ 尽量使用数据环境。 数据环境中的表会随着表单的打开而自动打开,表单关闭后也会自动关闭,这样做的好处 不仅仅是节省了几行代码,更重要的是在数据环境中打开表的速度要比用 USE 命令打开表的 速度快。 ④ 延迟屏幕刷新。 如果需 要 对 表 单 上 的 多 个 控 件 进 行 修 改,最 好 在 修 改 前 锁 住 表 单 屏 幕(把 表 单 的 LockScreen 属性赋为真),在完成修改后调用表单的 Refresh 方法,最后再解除表单的屏幕锁定, 这样做的好处表单只需要刷新一次。惟一例外的情况是当你想在表单上创造动画效果(如一 个会飞的按钮)的时候,千万不要锁定表单屏幕。 ⑤ 动态加载对象。 在程序运行之初就加载所有的对象将会导致程序启动速度下降,而且这些对象不论是否 起作用都会消耗一定的内存,因此对那些一开始没有用到或不显示的对象可以在需要的时候 动态创建。这个道理和动态链接库的工作原理很相似,如果 Windows 在启动时加载所有的动 态库,它的启动速度一定让人无法忍受。 类似的优化手段还有很多,需要读者在长期的编程实践中进行摸索和总结。一般来说,优 化可以提高程序的执行速度或降低系统的资源占用,但是会增加开发过程的复杂度,所以采用 什么样的优化手段和形式还需要根据具体的应用场合来进行判断。

多人协作开发

8.6

对于大型的 MIS 系统来说,往往需要多个程序员进行合作开发,如果系统的模块划分比较 明确,那么在前期开发团队的成员可以各自独立开发,但是到了后期必须进行系统的联调。联 调期间每个成员可能还需要对自己的模块进行修改,这种情况下如何在开发团队之间共享程 序的源代码并合理地控制其版本成为团队开发模式必须解决的问题。使用协作版本控制系统 (Concurrent Versions System,CVS)是一个较好的解决方案,它除了可以实现项目文件在开发团 队之间共享和同步之外,还可以保证一个人的修改不会被另一个覆盖,或者随时获得项目的最 新版本。由于开放源码项目大多采用协作开发模式,因此 GNU CVS 是最常用的一种 CVS 系 统,此外还有 WinCVS 和 Visual SourceSafe 等版本控制工具,下面以 Visual SourceSafe 6 . 0 为例来 说明。 Visual SourceSafe 可以把任何类型的项目文件存放在自己的数据库中,而且可以在多个项 目中共享某个文件,可以随时向数据库中添加文件、获取最新版本或者覆盖老的版本。 Visual SourceSafe 可以和 Microsoft Access、Visual Basic、Visual C + + 、Visual FoxPro 等开发工具整合,从 而无需运行 Visual SourceSafe 的客户端就可以使用它的版本控制功能。 Visual SourceSafe 是 Visual Studio 中的一员,可以通过 Visual Studio 的安装程序来安装这个


开发产品级企业应用

259

组件。安装完毕后运行“Visual SourceSafe 6 . 0 Admin”,出现一个登陆对话框,第一次运行时可 以用 Admin 登陆,密码为空。在这个管理工具中可以为开发团队的每个成员创建一个账号,并 针对当前的开发项目创建项目数据库,或者使用默认的 Common 数据库也可以。如果把项目 数据库文件放到文件共享服务器上就可以实现局域网内的项目文件共享,通过 TCP /IP 协议的 插件还可以实现广域网共享。

8.6.1

Visual FoxPro 与 Visual SourceSafe 的整合

Visual FoxPro 是通过项目管理器管理项目中的各个文件的,通过整合 Visual FoxPro 与 Visual SourceSafe 可以使得项目管理器同时具有源代码管理的功能。 在 Visual FoxPro 中单击“工具”菜单 - >“选项”菜单项,在“项目”选项卡中有一个“源代码 管理选项”下拉列表,系统会自动检测可整合到 Visual FoxPro 中的源代码管理器,并提供了一 些自动管理的选项,如图 8 . 43 所示。

图 8 . 43

源代码管理选项设置

1 . 把项目添加到源代码管理器中 如果在图 8 . 43 所示的对话框中选中了 “自动 加 入 新 的 项 目 到 源 代 码 管 理 器”复 选 框,则在新建一个项目并在本地保存后系统 将自动运行一个 Visual SourceSafe 的登录对话 框,如图 8 . 44 所示。 输入用户名、密码和项目数据库的名称 (默认为 Common),即可把当前新建的 Visual

图 8 . 44

Visual SourceSafe 登录对话框


电脑工程师丛书

260

Visual FoxPro 高级应用实例

FoxPro 项目添加到 Visual SourceSafe 的项目数据库中,如图 8 . 45 所示。如果有必要还可以向项 目数据库中添加备注信息。

图 8 . 45

添加项目到项目数据库

如果项目文件已经存在,可以在项目管理器中打开该项目,并单击“项目”菜单 - >“把项 目加到源代码管理器中”菜单项,然后重复图 8 . 44 和图 8 . 45 所示的步骤即可。 一个项目加入到源代码管理器后,Visual SourceSafe 的项目数据库中并不保存 PJX 和 PJT 项目文件,而是保存项目的文件列表 PJM,各个用户维护自己本地的项目文件副本。项目数据 库中的文件可能被其他用户修改,而本地副本只能在本地进行修改,源代码管理器实际上就是 在这两者之间进行协调。每次打开一个这样的项目时,Visual FoxPro 会自动提示登录 Visual SourceSafe 从而获取项目文件的最新版本。 2 . 加入现有的项目 如果项目数据库中存在已经创建好的项目,想要加入该项目可以单击“文件”菜单 - >“联 接源代码管理项目”菜单项,输入用户名、密码和项目数据库名称后,将弹出如图 8 . 46 所示的 对话框,指定要加入的项目名称和该项目副本在本地的保存路径。

图 8 . 46 “联接源代码管理项目”对话框

3 . 解除项目的源代码管理 如果不再想控制项目中的文件,可以从源代码管理中解除该项目:单击“项目”菜单 - > “源代码管理”子菜单 - >“从源代码管理中分离项目”菜单项。操作完成后,项目中的文件仍


开发产品级企业应用

261

然保留在源代码管理项目中,因此其他开发者可以继续使用它们,但本地项目文件和源代码管 理项目之间的联系将断开,并且本地文件变为可读写的。在解除项目的源代码管理之后,要确 保建立人工的版本控制过程,否则可能含有不在源代码管理之下进行处理的文件。

8 . 6 . 2 在源代码管理系统下管理 Visual FoxPro 项目 源代码管理和版本控制主要包括以下操作: ① 签出和签入。 用户在修改一个文件之前,需要将该文件从项目数据库保存到本地,这个过程称为签出。 当一个文件被签出后,该文件对其他用户具有只读属性,不能被再次签出或修改。当用户处理 完一个文件时,可以把修改结果更新到项目数据库,这个过程称为签入。 ② 合并。 为了允许多个用户同时处理同一个文件,源代码管理软件允许多个用户同时签出这个文 件(通常只有对文本文件才可以这样做,如程序源代码)。如果其中一个用户对该文件进行了 修改会同时反映到其他用户的文件中。 ③ 项目控制。 用户可以在项目或其他目录中组织文件,也可以在多个项目之间共享同一个文件。 ④ 跟踪。 在签入文件时,源代码管理系统能够对文件所作的修改进行跟踪,这样用户可以重建文件 的旧版本从而恢复以前的工作。 ⑤ 版本比较。 源代码管理软件允许用户比较一个文件的不同版本并查看它们之间的区别。 ⑥ 历史记录。 用户可以检查每个文件的签入历史记录,包括每个用户在签入文件时所作的注释。 在 Visual FoxPro 中,通过“项目管理器”菜单可以很容易地完成这些操作,如图 8 . 47 所示。

图 8 . 47

源代码管理菜单项

当文件被用户签入、签出或合并后,项目管理器中该文件的源代码状态图标会发生相应的 变化。状态图标的含义如表 8 . 2 所示。


电脑工程师丛书

262

Visual FoxPro 高级应用实例

表 8.2

项目管理器源代码状态图标及含义

图标

含义 该文件已经被当前用户签出 该文件已经被多个用户签出 该文件已经被其他用户签出 该文件正在被当前用户签入,尚未签出 该文件已合并 该文件已合并但存在冲突 该文件状态未知

需要特别强调的是数据库和表文件,为了在多个用户之间共享数据信息,这些文件不保存 本地副本,一般情况下也不允许修改数据库的设置和表的结构,对数据库的数据访问遵循通常 的数据共享访问规则。

8.7

后期制作 一部电影拍摄完毕后需要经过剪辑、处理和包装才能最终和观众见面,一个产品级的应用

软件在开发工作基本完成后也需要经历类似的步骤才能最终发布,这个过程称为后期制作。 在这里主要介绍联机帮助文档和安装程序的制作。

8 . 7 . 1 联机说明文档的制作 大多数商业软件都包含一个联机说明文档,该文档不仅介绍软件的使用方法,还提供上下 文敏感的帮助信息。帮助文件有两种不同的样式:WinHelp 样式和 HTML 样式。前者的扩展名 为 HLP,通过 WinHelp32 程序打开;后者的扩展名为 CHM,通过 Windows HTML Help 程序打开。 前者的制作和工作方式较为原始,所以本例仅介绍 HTML 帮助文档的制作及应用。 1 . HTML Help Workshop HTML Help Workshop 是 Microsoft 发布的 HTML 帮助文档的制作工具,它不再使用特定格式 的 RTF 文档,而是以 HTML 页面为基础创建帮助文档,通过内嵌的 IE 组件来浏览文档,可以支 持标准的 HTML 语法、ActiveX 控件和 Java 小程序。 HTML Help Workshop 可以从 Microsoft 的网 站下载,同时它也包含在 Office 软件的工具箱中。 (1)准备工作 在开始设计帮助文档之前需要先对帮助系统进行规划,如包含哪些主题,每个主题下包含 哪些说明等,并且为每个主题和说明创建相应的 HTML 页面。本例的帮助系统根据图 8 . 27 所 示的系统框图创建五个主题及对每个子模块的说明。以人事管理为例,创建 HTML 页面 rs . htm,其中包含了四个子模块说明文件的链接,如图 8 . 48 所示。


开发产品级企业应用

263

人事管理 

     

     

  基本信息录入   职工档案管理 

  职工清单打印 

  职工档案报表



图 8 . 48

帮助系统中人事管理 HTML 页面

其中的每个链接地址分别指向 rs - 1 . htm、rs - 2 . htm……别的模块和子模块与此类似,最后 创建帮助系统的首页 default . htm,包含所有模块的链接,如图 8 . 49 所示。 人事劳资 MIS 系统帮助

          

          

   人事管理 

  考勤管理 

  劳动管理 

  工资管理 



图 8 . 49

 系统管理

帮助系统中首页 HTML 页面

其中每个链接指向 rs . htm、kq . htm……。以上工作完成后,就可以开始创建 HTML 格式的 帮助文件了。 (2)创建内容目录 运行 HTML Help Workshop,单击“File”菜单 - >“New”菜单项,在类型选择对话框中选择 “Project”,运行新建帮助项目向导。在向导中指定帮助项目存放的路径,如果有已经制作好的 内容列表文件(HHC)、索引文件(HHK)和 HTML 页面的话,可以在这个过程中加入到帮助项目 中,从而完成帮助项目(mis . hhp)的创建。创建好后的 HTML Help Workshop 的界面如图 8 . 50 所 示。 单击“Contents”选项卡,如果目前项目中还没有包含 HHC 文件则需要创建它。在该选项 卡中可以添加主题、标题和内容页面,它采用的实际上是一种树形结构,每个主题相当于一个 目录,主题下可以包含子主题和内容页面,最终形成整个帮助系统的目录。 ① 添加一个主题条目。 单击“Insert a heading”按钮,将弹出如图 8 . 51 所示的条目编辑对话框,在该对话框中可以 输入标题文字,可以用“Add”、 “Edit”和“Romove”按钮向其中添加、修改和删除内容页面。 ② 添加一个内容条目。 单击“Insert a page”按钮,同样是在条目编辑对话框中指定标题和页面位置。 ③ 修改条目。 单击“Edit selection”按钮可以重新修改某个条目的信息,单击“Delete selection”按钮可以删


电脑工程师丛书

264

Visual FoxPro 高级应用实例

除某个条目。通过鼠标拖放操作或者单击“ ”和“ ”按钮可以改变条目的排列顺序,单击 “ ”、 “ ”按扭可以改变条目的层次。

图 8 . 50

HTML Help Workshop 主界面

图 8 . 51

条目编辑对话框

把设计好的 HTML 页面作为条目添加到帮助系统中并合理设置其顺序和层次结构,最终 完成后的目录结构如图 8 . 52 所示。 (3)创建索引 帮助文件索引可以根据关键字列出相关的页面,有助于快速查找相关的信息。选择“Index”选项卡,单击“Insert a keyword”按钮可以向其中添加索引关键字,其操作过程和添加条目基 本相同,只是在添加页面时可以指定其“Title”。


开发产品级企业应用

图 8 . 52

265

完成后的帮助系统目录结构

(4)编译 HTML 帮助文件 最后还需要把这些由目录和索引组织起来的 HTML 页面编译成 CHM 格式的帮助文件,单 击“File”菜单 - >“Compile”菜单项,如果编译过程没有错误的话,在帮助项目文件所在的目录 下将会生成一个 mis . chm 文件,双击该文件可以运行该帮助文件查看其效果,如图 8 . 53 所示。

图 8 . 53

运行帮助文件


电脑工程师丛书

266

Visual FoxPro 高级应用实例

2 . 在 Visual FoxPro 应用程序中使用帮助文档 帮助文件 mis . chm 制作完成后,下一步要把它关联到 Visual FoxPro 的应用程序中,一般可 以在项目的主文件中使用如下命令: SET HELP TO 帮助文件 这样,在系统运行过程中用户可以通过按“F1”键运行帮助文件。 如果要针对某个特定的主题添加上下文敏感帮助,可以使用 HELP 命令再加上帮助文件 的索引中已经设计好的主题。例如,在系统登陆界面中,默认的用户名和密码都是“admin”,可 以在登陆表单上添加一个“帮助”按钮,其 Click 事件代码为: HELP 登录 当用户单击该按钮时将运行帮助文件并自动定位到该主题所对应的页面。

8 . 7 . 2 创建安装程序 所有的开发工作完成后,最后一项工作是创建一个安装程序,它能够把必要的文件复制到 用户的系统上并自动完成 DLL 和 ActiveX 控件的注册,如果必要的话还可以在桌面或开始菜 单中添加快捷方式。 Visual FoxPro 自带一个安装向导,可以通过简单的几个步骤完成这些工 作,故不再赘述,在这里要讨论的是更专业的安装程序制作工具 InstallShield。下面将介绍使用 InstallShield 7 . 0 Professional 制作安装程序的基本步骤,在此之前还需要安装好 InstallShield 的语 言包,它能够为安装程序提供合适的语言环境。 1 . 创建安装程序项目 可以从一个空的项目开始制作安装程序,但是更为简便的方法是使用安装程序项目向导。 运行 InstallShield,并单击“File”菜单 - >“New Project”菜单项,在弹出的对话框中选择“Setup Project Wizard”,将项目命名为“人事劳资 MIS 系统”,并选择项目文件的路径,如图 8 . 54 所示。

图 8 . 54

创建安装程序项目


开发产品级企业应用

267

2 . 填写项目基本信息 在这个步骤中主要填写项目的一些基本信息,如产品名称、版本、公司名称及可执行文件 等,如图 8 . 55 所示。

图 8 . 55

填写项目基本信息

3 . 选择安装界面的语言 如果 InstallShield 的中文语言包已经正确安装的话,在这个步骤中可以选择“Chinese (Simplified)”简体中文选项,否则只能使用英语作为安装程序的界面,如图 8 . 56 所示。 4 . 创建可安装组件 从现在开始的几个步骤都非常关键,它决定了最终的安装程序能否正常工作。所谓可安 装组件,其实就是把应用程序分成几个部分,如 Visual FoxPro 安装程序包括主程序、向导及生 成器、运行时刻文件、专业应用程序、ActiveX、图形、ODBC 驱动程序和工具等多个组件,用户可 以选择安装其中的全部或部分组件。 InstallShield 默认情况下把应用程序分成三个组件:主程 序(Main App)、帮助(Tutorial)和示例(Examples),可以根据需要添加、删除或重命名组件,在这 里只需要主程序和帮助两个组件,如图 8 . 57 所示。 5 . 建立文件组 创建了可安装组件后,每个组件应该对应有不同的文件组,并根据文件存放的位置进行归 类,放置在不同目录下的文件应该分别创建不同的文件组,同时需要指定文件组的目标路径。 例如,可执行程序通常放在“ < TARGETDIR > ”目录(该目录即用户通过安装程序指定的实际安 装目录)下,DLL 和 ActiveX 控件文件通常放置在系统目录“ < WINSYSDIR > ”下,如果是共享文 件或者自注册文件,还需要指定“sharing”和“self - registering”属性,共享文件在反安装程序运行 时不会自动删除。在这里需要创建的文件组和目标路径如表 8 . 3 所示。


电脑工程师丛书

268

Visual FoxPro 高级应用实例

图 8 . 56

选择安装界面的语言

图 8 . 57 表 8.3 文件组

创建可安装组件 文件组及目标路径 目标路径

说明

App Executables

< TARGETDIR >

可执行文件

App Self Reg DLLs

< WINSYSDIR >

动态库及 ActiveX 控件

Data Files

< TARGETDIR > h data

数据库文件

Library Files

< TARGETDIR > h lib

外部库文件


开发产品级企业应用

269

(续表) 文件组

目标路径

说明

Backup Files

< TARGETDIR > h backup

备份文件

Tutorial Files

< TARGETDIR > h help

帮助文件

设置完成后的对话框如图 8 . 58 所示。

图 8 . 58

建立文件组

6 . 指定文件组中包含的文件 这一步是至关重要的步骤。把需要发布的文件添加到相应的文件组中(见图 8 . 59)。 本例中需要发布的文件及其对应的文件组如表 8 . 4 所示。 表 8.4 文件组

发布文件列表

文件

说明

Mis. exe

主程序文件

XCeedZip. dll

数据备份和恢复模块用到的 ActiveX 控件

Vfp6r. dll Vfp6t. dll

Visual FoxPro 运行时刻库文件,如果不包含这些文件 程序将无法在未安装 Visual FoxPro 的系统中运行。如 果未使用到多线程 DLL 文件可以不包含 vfp6t. dll 文件

Data Files

. dbc . dbf . . .

所有的数据库文件

Library Files

Vfp6chs. dll. . .

所有非自动注册的外部库文件

App Executables

App Self Reg DLLs

该文件组在安装时无需发布文件,但是这个目录必须 创建,否则需要在程序中自动创建该目录

Backup Files Tutorial Files

Mis. chm

帮助文件

原则上,所有在 Visual FoxPro 联编项目时不包含在执行程序中的文件(如数据库文件、帮 助文件等)都必须和主程序一起加入到相应的文件组中。


电脑工程师丛书

270

Visual FoxPro 高级应用实例

图 8 . 59

指定文件组中包含的文件

7 . 指定可安装组件对应的文件组 下面要把这些文件组添加到可安装组件中,此处把“Tutorial Files”文件组加入到“帮助”组 件中,其余的文件组加入到“主程序”组件中,如图 8 . 60 所示。如果用户只选择安装“主程序” 组件的话, “Tutorial Files”文件组中的文件将不会安装,当然,用户也可以通过再次运行安装程 序添加或删除该组件。

图 8 . 60

指定可安装组件对应的文件组


开发产品级企业应用

271

8 . 添加第三方组件 有些应用程序可能用到了一些第三方组件,如 ODBC 数据库引擎、DirectX 库、. NET 框架 等,可以在这个步骤中添加到安装程序中,在这里不需要选择任何第三方组件,直接单击“下一 步”按钮。 9 . 添加快捷方式 InstallShield 可以添加两种形式的快捷方式:桌面快捷方式和开始菜单快捷方式,如果需要 用户在系统启动时自动执行某个程序,还可以在开始菜单的“启动” (Startup)子菜单中添加快 捷方式。本实例中分别创建桌面快捷方式和开始菜单快捷方式,并指定其目标为“ < TARGETDIR > h mis . exe”,如图 8 . 61 所示。

图 8 . 61

创建快捷方式

10 . 指定安装程序对话框 在安装程序执行过程中会显示一些欢迎画面或其他信息的对话框,可以在这一个步骤中 指定。 InstallShield 提供了欢迎画面(Welcome)、接受授权许可协议(License Agreement)、用户信 息(Customer Information)、安装类型(Setup Type)、目标路径(Destination Location)、组件选择(Select Components)、准备安装(Ready to Install)和安装结束(Wizard Complete)几个对话框,单击选中某 个复选框可以预览该对话框的显示画面,其中目标路径和组件选择两个步骤是必须选中的,其 余则可选,如图 8 . 62 所示。 11 . 指定是否安装多个实例 有时用户可能在同一个系统上多次运行安装程序,可以在这个步骤中指定是覆盖原先的 安装实例还是允许安装多个实例,通常选择“标准” (Standard)。


电脑工程师丛书

272

Visual FoxPro 高级应用实例

图 8 . 62

指定安装程序对话框

12 . 完成 至此,该向导的所有步骤已经全部完成,并给出其中的主要设置信息,如果需要修改其中 的某些设置,可以单击“上一步”按钮返回,确认无误后单击“完成”按钮,系统将根据设置生成 一个安装程序的项目文件。 13 . 定制安装程序项目 在项目向导完成后,还可以在 InstallShield 中进一步设置,如编辑脚本文件或者修改其中 的细节,这个过程请参考 InstallShield 的帮助文档。 14 . 创建可发布磁盘文件 以上只是完成了一个安装程序项目文件的制作,还需要编译该项目以获得最终可发布的 应用程序磁盘文件。单击“Buile”菜单 - >“Build Media”菜单项,如果没有意外的话,在安装程 序项目文件所在目录的 Media 子目录下将会生成最终的安装程序,把它们复制到磁盘上就可 以最终发布了。

8.7

本章小结

本章的初衷并不是要详细解说一个人事劳资 MIS 系统的开发细节,也不在于给出一个放 之四海而皆准的开发模式,实际上 MIS 系统可以形态万千,而这个开发过程也是具有高度弹性 的,本章最重要的目的是引导读者对本章一开始提出的问题进行深入的思考,并从软件工程的 角度重新审视自己的开发项目。


附录

Visual FoxPro 错误信息代码及含义

为便于读者根据错误编号查找错误信息,或者在程序中添加自定义的错误处理功能,特把 Visual FoxPro 的所有错误信息按照编号排列作为本书的附录。 1

文件不存在

3

文件正在使用

4

已到文件尾

5

记录超出范围

6

打开的文件太多

7

文件已存在

9

数据类型不匹配

10

语法错误

11

函数参数的值、类型或数目无效

12

找不到变量“变量” (“变量”为实际的变量名)

13

找不到别名

15

不是一个表

16

不能识别的命令谓词

17

表编号无效

18

行太长

19

索引文件与表不匹配

20

记录不在索引中

21

变量字符串的长度超过了内存容量

22

变量太多

23

索引表达式超出最大长度

24

别名已被使用

26

表没有设置排序索引

27

非数值表达式

30

行或列位置超出屏幕

31

无效的下标引用

34

对备注字段、通用字段或图片字段此操作无效


电脑工程师丛书

274

Visual FoxPro 高级应用实例

36

命令中含有不能识别的短语或关键字

37

FILTER 子句中必须使用逻辑表达式

38

已到文件头

39

数值上溢。数据已丢失

41

备注文件“名称”缺少或无效

42

LOCATE 命令必须在 CONTINUE 命令之前发布

43

无足够内存完成此操作

44

循环关系

45

非字符表达式

46

表达式计算结果为一非法值

47

找不到要处理字段

50

报表文件“名称”无效

52

当前工作区中没有打开的表

54

标签文件无效

55

内存文件无效

56

没有足够的磁盘空间保存“文件”

58

LOG():不能使用 0 或负数作为参数

61

SQRT()参数不能为负数

62

不能访问字符串以外的字符

67

表达式计算失败

78

 或 定义域错误

91

文件没有使用 LOAD 命令装入内存

94

必须指定额外参数

95

在交互方式下不能使用此语句

96

嵌套错误

101

打不开文件

102

不能创建文件“文件” (“文件”为实际文件名)

103

超出允许的 DO 嵌套层次

104

不能识别的功能键

107

操作符 / 操作数类型不匹配

108

其他用户正在使用文件

109

其他用户正在使用记录

110

文件必须以独占方式打开

111

不能更新临时表

112

无效的关键字长度

114

索引与表不匹配。请删除该索引文件然后重建索引

115

. DIF 文件头无效

116

. DIF 矢量无效 - . DBF 字段不匹配


Visual Foxpro 错误信息代码及含义

117

. DIF 类型指示符无效

119

. SYLK 文件头无效

120

. SYLK 文件维数边界无效

121

. SYLK 文件格式无效

124

打印机重定向无效

125

打印机未准备好

127

视图文件无效

130

记录没有锁定

138

未发现要复制字段

152

缺少表达式

164

此菜单标题尚未用 DEFINE PAD 定义

165

菜单没有用 DEFINE POPUP 定义

166

此菜单没有定义菜单项

167

菜单项位置必须为正数

168

菜单没有用 DEFINE MENU 定义

169

不能定义菜单项

170

不能释放菜单项

174

不能重定义正在使用的菜单

175

不能重定义正在使用的弹出式菜单

176

不能清除正在使用的菜单

177

不能清除正在使用的弹出式菜单

178

菜单没有用 ACTIVATE MENU 激活

179

菜单没有用 ACTIVATE POPUP 激活

181

ACTIVATE MENU 指定的菜单正在使用

182

ACTIVATE POPUP 指定的菜单正在使用

202

无效的路径或文件名

214

窗口没有用 DEFINE WINDOW 定义

215

窗口没有用 ACTIVATE WINDOW 激活

216

显示方式不可用

221

含缩进值的左页边距必须小于右页边距

222

行号必须小于页长

223

列号必须在 0 到 255 之间

225

“名称”不是变量

226

“名称”不是文件变量

227

框大小定义无效

228

制表位必须按递增顺序

230

数组维数无效

231

SET 函数中使用了无效参数

275


电脑工程师丛书

276

Visual FoxPro 高级应用实例

232

“名称”不是数组

255

不是一个有效的 RapidFile 数据库

256

不是一个有效的 Framework II 数据库或电子表格

279

菜单没有入栈

287

菜单尺寸太小

291

ASIN()中的表达式超出范围

292

不能使用 0 或负数作为 LOG10()参数

293

ACOS()中的表达式超出范围

297

Lotus 1 - 2 - 3 2 . 0 版文件格式无效

332

DEFINE WINDOW 中指定的窗口使用了无效的坐标

350

必须是备注型字段

355

此宏未定义

356

键盘宏文件格式无效

392

导入文件的记录长度超出允许的最大长度

1000

内部一致性错误

1001

此功能不可用

1002

输入 / 输出操作失败

1097

不允许用户定义的函数

1098

API 函数 - UserError( )被调用

1102

不能创建文件

1103

无效的寻找偏移量

1104

读文件发生错误

1105

写文件发生错误

1106

不能存取文件;正在进行事务处理

1108

图形太大、损坏或格式错误

1111

无效的文件描述符

1112

关闭文件发生错误

1113

文件没有打开

1115

无效的临时表操作

1116

打开的窗口过多

1117

关键字字段长度不匹配

1124

关键字超出允许大小

1126

记录太长

1127

FOR 或 WHILE 子句中必须使用逻辑表达式

1130

找不到“字段”表达式

1131

FROM 子句中指定的文件是空文件

1134

变量必须在选定表中

1138

属性没有默认值


Visual Foxpro 错误信息代码及含义

1140

FILTER 表达式超出允许大小

1141

不能识别索引文件所做的改动,请重建索引

1145

必须是一个字符或数值型的关键字段

1147

目标表已经建立关系

1148

在执行筛选时重新输入了表达式

1149

无足够内存用于缓冲区

1150

无足够内存进行文件映像

1151

无足够内存用于文件名

1152

不能访问选定表

1153

不能重命名文件到另一个设备

1156

字段名重名

1157

不能更新文件

1161

在演示版中不能浏览或编辑太多记录

1162

找不到过程“过程” (“过程”为实际过程名)

1163

浏览表已关闭

1164

浏览结构已更改

1165

“字段”没有与当前工作区建立关系

1166

临时表已损坏或格式错误

1167

图标损坏或格式错误

1168

不能将图标添加到可执行文件

1169

项目文件是只读的

1178

应用程序文件“文件”没有关闭(“文件”为实际文件名)

1181

不能把版本资源加入到可执行文件中

1183

关系已经存在

1184

文件不能关闭因为存在尚未解决的引用

1190

文件“名称”太大

1191

不能保存表单:某个对象具有无效的名称

1193

缺少 . RTT 节

1194

链接命令失败

1195

对象文件“文件”是由其他版本的 FoxPro 编译的(“文件”为实际文件名)

1196

“文件”不是 Visual FoxPro 的 . EXE 文件

1201

使用名称太多

1202

程序太大

1206

递归宏定义

1211

缺少 IF | ELSE | ENDIF 语句

1212

结构嵌套太深

1213

FOR … ENDFOR 或者 DO CASE … ENDCASE 命令中缺少关键字

1214

ENDTEXT 没有对应的 TEXT 语句

277


电脑工程师丛书

278

Visual FoxPro 高级应用实例

1217

GET 语句中的 Picture 子句发生错误

1220

命令中包含无效字符

1221

命令中缺少必需的子句

1223

无效的变量引用

1225

必须是一个变量或数组

1226

必须是文件变量

1229

参数太少

1230

参数太多

1231

缺少操作数

1232

DIMENSION 语句包含变量声明但缺少必需的下标参数

1234

下标超界

1235

结构无效

1236

使用 RESUME 之前须挂起程序

1238

找不到 PARAMETER 语句

1241

在分组表达式中使用了不正确的数据类型

1242

字段表达式中有语法错误

1243

内部错误:报表中字符太多

1245

标签定义文件中使用了无效的表达式

1246

标签宽度超出允许大小

1249

使用了太多的 READ 命令

1250

使用了太多的 PROCEDURE 命令

1252

此命令行编译后所产生的代码太长

1253

不能重命名当前目录

1254

不能嵌套键标记

1255

键标记“标签”无效

1256

键标记中大括号不匹配

1257

键字符串太长

1258

PICTURE 子句出现错误

1294

“名称”不是一个有效的资源文件

1296

读取资源时发生错误

1297

此命令只允许在交互方式下使用

1298

“名称”带区太大不能放入页中

1300

函数名缺少 )

1304

函数名缺少(

1306

缺少逗号(, )

1307

不能被 0 除

1308

堆栈空间不足

1309

“文件”不是一个目标文件


Visual Foxpro 错误信息代码及含义

1310

PICTURE 子句中指定字符太多

1313

取消创建类

1337

不能嵌套 PRINTJOB 命令

1338

文件版本错误

1405

RUN| !命令失败

1410

不能创建临时工作文件

1411

RUN| !命令字符串太长

1412

找不到 COMSPEC 环境变量

1413

页框不能被调整到如此之小

1420

OLE 对象无效或损坏

1421

不能激活 OLE 服务程序

1422

保存 OLE 对象时出现错误

1423

创建 OLE 对象时出现错误

1424

将 OLE 对象复制到剪贴板时发生错误

1426

OLE 错误码 0x“名称”

1427

OLE IDispatch 异常码“名称”

1428

“名称” OLE 从“名称”返回非正常的代码“编号”:

1429

“OLE 错误”

1431

超出了允许的 OLE 参数数目

1434

类“名称”未注册

1436

只有可插入对象允许在通用字段中出现

1437

不能注册 ActiveX 控件

1438

ActiveX 控件模块错误,无法正确注册

1439

不是 ProjectHook 类

1440

OLE 异常错误 ”名称”。OLE 对象可能已损坏

1462

“名称”内部一致性错误

1463

服务器没有返回结果集

1465

SQL 传递内部一致性错误

1466

连接句柄无效

1467

此属性对本地临时表无效

1468

此属性对基于表的临时表无效

1469

属性值超出范围

1470

属性名不正确

1471

列格式不正确

1472

在未预先定义的 SqlExec()函数调用中,需要 SQL 语句参数

1473

环境级属性无效

1474

执行 SQLEXEC()序列时进行了无效的调用

1475

执行 SQLMORERESULTS()序列时进行了无效的调用

279


电脑工程师丛书

280

Visual FoxPro 高级应用实例

1476

执行 SQLTABLES()序列时进行了无效的调用

1477

执行 SQLCOLUMNS()序列时进行了无效的调用

1478

数据环境中关系对象的 ChildOrder 属性不再有效。该关系将被取消

1479

无效的更新列名“名称”

1480

警告:不能设置连接的同步 / 异步方式

1481

警告:不能设置连接超时

1482

警告:不能设置查询超时

1483

警告:不能设置连接包大小

1484

警告:下面“cExpr”个经修改的记录已经进行了远程更新("cExpr"为实际个数)

1485

警告:在记录“编号”处有无效的数据转换

1486

警告:在记录“编号”处有无效的备注字段转换

1487

警告:不能设置连接事务处理方式

1488

没有连接关联此临时表

1489

通用字段不能用在更新语句的 WHERE 条件中。请更改视图的 WhereType 属性

1490

不能将一个已转换表单保存为一个类

1491

未指定要更新的表。请使用临时表的 Tables 属性

1492

没有为要更新的表“名称”指定关键列。请使用临时表的 KeyFieldList 属性

1493

缺少 SQL 参数

1494

视图定义已经更改

1495

警告:表“别名”中 KeyField 属性定义的关键字不惟一

1496

警告:没有可用于检查远程更新冲突的信息

1497

连接名无效

1498

SQL SELECT 语句无效

1499

SQL 参数“名称”无效

1501

输出参数不支持类型转换

1502

记录正在使用,不能写入

1503

文件不能锁定

1504

此函数不支持 SQL 的传递临时表

1507

屏幕代码太大,无足够内存

1508

初始化 OLE 时发生错误

1509

转换已取消。请调整备注块大小

1510

无效的文件格式。如果是 dBASE 文件,则必须首先进行转换。要转换此文件,请键入 MODIFY LABEL < 标签文件名 >

1520

没有打开数据库或没有数据库设置为当前数据库

1521

此操作不支持类成员对象

1522

连接的内部一致性错误

1523

用户取消执行

1524

选定打印机驱动程序不支持直接访问


Visual Foxpro 错误信息代码及含义

1525

在远程表上不支持此函数

1526

连接错误: “名称”

1527

找不到 ODBC 库 — ODBC32 . DLL

1528

未找到 ODBC 入口点“名称”

1529

文件“名称”作为数据库的一部分已经存在

1530

取消数据读取;远程表关闭

1531

由于索引“名称”的索引表达式或筛选表达式无效,因而不能执行对表的更改操作

1532

不支持类型转换

1533

此属性只读

1534

数据库“别名”没有打开

1535

此数据库正由一个项目使用,不能关闭

1536

在本地表上不支持此函数

1537

不能加入这个表:它属于数据库“名称”

1538

存储过程正在执行

1539

触发器失败

1540

工作期编号无效

1541

连接“名称”忙

1542

基表字段已被更改,不再匹配视图字段,因此不能设置视图字段属性

1543

字段“名称”的 DataType 属性所要求的类型转换无效

1544

字段“名称”的 DataType 属性无效

1545

别名“名称”的表缓冲区含有不能予以实现的修改

1546

在执行与表相联系的表达式时不能关闭表

1547

不能从视图中插入一个空行到其基表

1548

表“别名”有一个或多个非结构索引已打开,请关闭这些索引,然后重试 Begin Transaction

1549

不能在释放工作期“ # 编号”后仍然打开着事务处理

1550

. DBC 内部一致性错误

1551

文件“名称”是一个无效的数据库

1552

文件“名称”不是数据库

1553

文件“名称”是一个数据库

1554

实例化临时表时发生错误。表“别名”不能打开。对象将被忽略

1555

关系表达式无效

1556

因为临时表对象不再有效,所以不能浏览表

1557

数据库必须以独占方式打开

1558

文件“名称”不是数据库的一部分

1559

找不到属性

1560

属性值无效

1561

数据库无效,请进行检查

1562

在数据库中不能找到对象“名称”

281


电脑工程师丛书

282

Visual FoxPro 高级应用实例

1563

在当前数据库中找不到视图“名称”

1564

表“名称”在数据库中已存在

1565

文件“名称”是数据库的一部分

1566

数据库的表正在使用时不能发布 PACK 命令

1567

主关键字属性无效;请进行数据库检查

1568

视图字段已经更改,更新和关键字字段属性重新设置为默认值

1569

数据库“名称”不能访问文件

1570

数据库只读

1571

已有对象使用了名称“名称”,请使用其他名称(“名称”为实际对象的名称)

1572

数据库对象在设计器中被打开时不能被删除

1573

不能改变可见表单的样式

1574

初始化应用程序对象时产生错误

1575

对象名无效

1576

在类库中找不到类“名称”

1577

表“名称”在一个关系中被引用

1578

无效的数据库表名

1579

对于临时表处于表缓冲方式下的表,不能发布此命令

1580

此功能对非 . DBC 表不可用

1581

“名称”字段不能为 NULL 值

1582

“名称”字段的有效性规则被破坏

1583

违反了记录有效性规则

1584

从数据库读取属性时发生错误,该属性将被忽略

1585

更新冲突

1586

函数要求使用行缓冲或表缓冲方式

1587

非法嵌套 OLDVAL()或 CURVAL()

1588

在启用行缓冲或表缓冲,或者使用完整性约束时,不能执行 INSERT 操作

1589

表缓冲及行缓冲要求 SET MULTILOCKS 设置为 ON

1590

BEGIN TRANSACTION 命令失败。嵌套层次太多

1591

END TRANSACTION 命令必须与 BEGIN TRANSACTION 命令配对使用

1592

ROLLBACK 必须与 BEGIN TRANSACTION 命令配合使用

1593

在事务处理期间不能发布此命令

1594

非法操作。在一个事务中已有记录锁定的情况下,试图对文件进行锁定从而导致非法

1595

发生更新冲突。当前行批处理的一些修改已经执行。请使用带 lForce 参数的 TABLEUPDATE()函数进行更新,或进行人工事务处理,回滚更新

1596

没有启用表缓冲

1597

视图要求使用 DB - BUFOPTROW 或 DB-BUFOPTTABLE

1598

规则和触发器代码必须平衡事务处理的使用

1599

数据工作期“ # 编号”被迫回滚全部事务处理以避免死锁


Visual Foxpro 错误信息代码及含义

1600

USE 命令没有足够的内存打开一个表

1604

没有定义菜单项

1605

没有定义菜单

1607

超出了允许的菜单项数目(128)

1608

超出了允许的菜单数目(25)

1609

超出了允许的菜单项最大长度(50)

1611

菜单项或标题必须是字符型

1612

没有定义此类菜单或菜单项

1621

此菜单没有定义菜单标题

1632

窗口文件格式无效

1637

文件必须以独占方式打开以转换备注文件

1642

找不到颜色集资源

1643

打印机驱动程序损坏

1644

找不到打印机驱动程序

1645

报表中包含嵌套错误

1646

总计字段类型必须是日期型或数值型

1647

字段表达式包含无效的数据类型

1649

没有 PRINTJOB 命令与此命令相对应

1651

不允许“取消”或“终止”

1652

无效。不能将一个 Visual FoxPro 函数用作数组

1653

标签嵌套错误

1657

列号必须在 0 到右页边距之间

1659

表含有备注型字段,该字段在以只读方式打开时不能转换

1661

Microsoft Excel 文件格式无效

1662

Lotus 1 - 2 - 3 1 . 0 版文件格式无效

1670

Multiplan 4 . 0 版文件格式无效

1671

不能导入有密码保护的文件

1672

不能追加有密码保护的文件

1673

Symphony 1 . 0 版文件格式无效

1674

Symphony 1 . 1 版文件格式无效

1678

Lotus 1 - 2 - 3 3 . 0 版文件格式无效

1679

只导入 Lotus 1 - 2 - 3 3 . 0 版文件的 Worksheet A

1680

Lotus 1 - 2 - 3 3 . 0 版文件的 Worksheet A 被隐藏

1681

PREVIEW 子句不能与 OFF /NOCONSOLE 或 TO PRINT/FILE 联用

1682

非用户自定义窗口

1683

找不到索引标识

1684

必须指定索引标识或文件名

1685

项目文件无效

283


电脑工程师丛书

284

Visual FoxPro 高级应用实例

1686

表单文件“名称”无效

1687

菜单文件无效

1688

Paradox 文件格式无效

1689

没有主程序不能连编

1690

在索引过程中表操作无效

1691

库文件“名称”无效

1692

无法解决的 REGIONAL 名称冲突

1693

找不到菜单生成程序

1694

指定的扩展名太多

1695

COLUMN | FORM | ALIAS | NOOVERWRITE | WIDTH 只允许与 FROM 子句联用

1696

NOWAIT/SAVE /NOENVIRONMENT/IN/WINDOW 子句不允许与 FROM 子句联用

1698

COLUMN | ROW | ALIAS | NOOVERWRITE | SIZE | SCREEN 只允许与 FROM 子句联用

1699

此窗口不支持 IN 子句

1705

不能存取文件

1706

不能按降序对 . IDX 文件排序

1707

找不到结构 . CDX 文件

1708

文件已在另一个工作区打开

1709

数据库对象正被其他人使用

1710

MULTISELECT 或 MOVER 子句不支持 PROMPT 样式的菜单

1711

使用的 API 库版本和 Visual FoxPro 版本不匹配。重建库

1712

字段名称重名或者无效

1713

字段宽度或小数位数目无效

1714

“名称”窗口没有被定义

1715

找不到服务器“服务器” (“服务器”为实际服务器名)

1716

找不到队列“队列” (“队列”为实际队列名)

1717

不能生成打印机驱动程序

1718

“名称”为只读文件

1719

对象文件“名称”正在使用,不能从内存中清除

1720

在 READ 命令对含格式文件进行操作时,不能发出 SET FORMAT 命令

1722

预处理表达式无效

1723

不匹配的 # IF / # ELSIF / # ELSE / # ENDIF

1724

缺少 # ENDIF

1725

常量已经用 # DEFINE 定义

1726

找不到 API 库

1727

需要 . DBF 类型的帮助文件

1728

配色方案已保存

1731

不能修改基类

1732

此属性的数据类型无效


Visual Foxpro 错误信息代码及含义

1733

找不到类定义“名称”

1734

找不到属性“名称”

1735

类定义中发生错误

1736

实例化对象时发生错误

1737

“名称”是一个方法程序、事件或对象

1738

属性“名称”不是方法程序或者事件

1739

属性设置要到数据环境重新加载时才生效

1740

“名称”是只读属性

1741

不能添加“名称”。类定义循环

1742

此对象的数据源必须是一个变量引用

1743

属性“名称”只读

1744

对于此容器,对象类无效

1745

“名称”不是 Visual FoxPro 事件

1746

“名称”的类文件名无效

1747

类文件“名称”无效

1748

此文件与当前版本的 Visual FoxPro 不兼容

1749

表单文件(. SCX)必须包含至少一个表单

1750

此文件是由比当前版本更高的 Visual FoxPro 版本建立

1751

类文件版本高于当前版本

1752

类“名称”被命令 MODIFY CLASS 使用

1753

不能加载 32 位 DLL“名称”

1754

在 DLL 中找不到入口点“名称”

1755

不能在此类中添加对象

1756

成员对象已移去 - 不能完成“保存”操作

1757

属性“名称”受保护

1758

不能在 SetAll 方法程序中更改“名称”属性

1759

表达式无效。请使用有效的表达式设置“名称”属性

1760

成员“名称”是一个类成员

1761

类“名称”已经存在

1762

找不到类“名称”

1763

属性“名称”已经存在

1764

数组不是父对象的成员

1765

方法程序中含语法错误,没有保存

1766

找不到对象“名称”

1767

父对象不允许对“名称”进行此属性设置

1768

不能在表格控件中添加此对象

1769

不能在列中添加此对象

1770

不能清除正在使用的类

285


电脑工程师丛书

286

Visual FoxPro 高级应用实例

1771

同名的成员对象已经存在

1772

不能更改 BROWSE 对象的 RecordSource

1773

数据库对象类型无效

1774

找不到工作表

1775

找不到工作表“名称”

1776

类“名称”正在使用

1777

视图不支持这个命令

1778

此表在追加前必须进行转换

1779

将“名称”添加到对象时发生错误。成员或属性重名

1780

此数组元素已经定义为一个对象,不能在类定义中重新定义

1781

对象的控件源不能设置为它的 Value 属性

1782

此 OLE 属性不能是表达式

1783

@… Class 命令中指定的类无效

1784

这个对象派生于基类因此没有父类

1791

没有找到文本生成程序

1792

文本生成被取消

1793

不支持此网络要求

1794

不能枚举“名称”

1795

不能安排未经输送的表单

1796

在当前平台中没有找到记录

1797

转换时发生错误

1798

找不到转换程序

1799

取消转换

1800

SQL:内部错误

1801

SQL:关联字段时出错

1802

SQL:找不到表

1803

SQL:HAVING 子句无效

1804

SQL:语句无效

1805

SQL:子查询太多

1806

SQL:找不到列“字段 | 变量”

1807

SQL:GROUP BY 子句无效

1808

SQL:ORDER BY 子句无效

1809

SQL:内存不足

1810

SQL:不能使用子查询

1811

SQL:对非数值表达式进行合计操作

1812

SQL:语句太长

1813

SQL:在子查询中使用 UNION 无效

1814

SQL:不支持此类查询


Visual Foxpro 错误信息代码及含义

1815

“临时表”必须由 SELECT . . . INTO TABLE 创建

1818

SQL:需要 FROM 子句

1819

SQL:DISTINCT 无效

1820

SQL:SELECT 包含无效的 

1822

SQL:无效的合计字段

1825

SQL:子查询无效

1826

SQL:SELECT 无效

1828

SQL:子查询中出现非法的 GROUP BY 子句

1830

SQL:找不到索引

1831

SQL:建立临时索引时发生错误

1832

287

“字段 | 变量”不惟一,必须加以限定

1833

SQL:WHERE 子句无效

1834

SQL:UNION 太多

1839

SQL:操作已经取消

1841

SQL:引用的列太多

1842

SQL:子查询嵌套太深

1844

不能嵌套合计函数

1845

SQL 表达式太复杂

1846

不能按合计字段分组

1851

SELECT 与 UNION 不兼容

1860

子查询返回了多条记录

1864

SQL:UPDATE 中字段太多

1865

SQL:UPDATE 中使用了无效的 SET 表达式

1866

SQL:无效的 TOP 说明

1867

SQL:TOP 需要一个 ORDER BY

1870

ALTER TABLE 操作被中断

1871

不能 DROP 全部已有列

1872

列太多

1875

进行要求的索引修改时产生多个警告

1877

没有可 DROP 的规则

1878

没有可 DROP 的默认值

1879

无主关键字

1880

在当前数据库中找不到相关表

1881

加载文件时发生错误 - 记录号 n, “对象” (或者它的一个成员)。“Issue”: “错误” (“Issue”是 实际的属性或方法名)

1882

相关标识找不到,或者相关标识不是主索引或候选索引

1883

主关键字已经存在

1884

索引“名称”不惟一


电脑工程师丛书

288

Visual FoxPro 高级应用实例

1885

只有结构索引标识才能定义为候选关键字

1886

索引不接受 NULL

1887

在规则计算中出现非法递归

1888

标识名太长

1889

多重关系中的表只能包含一个子序列

1890

SQL:无法确定 SQL 列的数据类型

1903

字符串太长,不能容纳

1907

驱动器名无效

1908

长度值或小数点位置的参数无效

1909

无法从项目图元文件中更新项目

1910

非法递归调用打印驱动程序

1911

此环境要求使用本地化产品

1912

对于通用字段此操作无效

1913

此字段必须是通用字段

1914

代码页编号无效

1915

找不到排序序列“名称”

1916

要使用此功能,请在 CONFIG. FPW 文件中设置 CODEPAGE = AUTO 然后重新启动 Visual FoxPro

1918

文件名太长

1922

卷不存在

1923

找不到对象“名称”

1924

“名称”不是一个对象

1925

不能识别的成员“名称”

1926

不能嵌套类定义

1927

在类定义中此语句无效

1928

此语句只在类定义中有效

1929

“名称”只能在方法程序中使用

1930

不能重定义“名称”

1931

语句不在过程中

1932

“名称”在挂起时不能关闭

1933

文件“名称”没有关闭

1934

此语句只在方法程序中有效

1935

当前对象不是由类“名称”继承而来

1936

应用程序对象未被初始化

1937

找不到过程文件“名称”

1938

对象没有包含在“名称”中

1939

WITH /ENDWITH 不匹配

1940

在 WITH /ENDWITH 之外此表达式无效


Visual Foxpro 错误信息代码及含义

289

1941

错误代码无效

1942

不能将对象赋给数组

1943

成员“名称”不是一个对象

1944

找不到生成器程序

1945

当前对象已经释放

1946

项目文件“名称”版本不对

1947

表达式太复杂

1948

不能修改正在使用的类

1949

类名无效

1950

类定义“名称”出现递归

1951

不能清除正在使用的对象

1952

不能对视图自身建立视图

1953

在对象处于设计方式时此功能才可用

1954

生成器程序已经运行

1955

WIN. INI 或注册表损坏

1956

不能访问打印机

1957

脱机打印时发生错误

1958

加载打印驱动程序时发生错误

1959

无效的坐标

1960

非法重定义变量“名称”

1961

子目录或文件“名称”已经存在

1962

目录非空

1963

找不到目录

1964

在 WindowType 属性设置为 READ 或 READ MODAL 的表单集或表单中,找不到页框或页面 对象

1965

此类中的某一成员派生自非可视类,因此不能写入 . VCX 文件

1966

数据环境已经加载

1967

数据环境已经卸载

1968

此表单或表单集的一个成员派生自非可视类,不能写入 . SCX 文件

1969

剪贴板中含有一个或多个不能添加入容器内的对象,这些对象将不粘贴

1970

不能重新打开项目文件“名称”

1971

在当前的 COMPILE 命令完成之后才能进行下一编译

1972

数组“名称”正在使用

1973

创建表“名称”时发生错误

1974

数组不能赋给数组元素

1975

成员对象“名称”没有实例化

1976

不能解决后链

1977

要更新此文件需以独占方式打开。请重新打开此文件


电脑工程师丛书

290

Visual FoxPro 高级应用实例

1978

不能以可视方式修改此种类型的类

1979

不能以可视方式修改派生自非可视类的类

1981

文件“名称”编译时出错

1982

TO 子句只能用于模式表单或表单集

1983

“名称”对象不能返回值

1984

表“名称”中的字段与数据库中的项不匹配

1985

对象已有一个数据环境;不能在 SaveAs 方法程序中使用另一个

1986

GDI 内存不足,请关闭一个或多个窗口,再试一次

1987

不能以编程方式将对象添加到表单设计器的表单集中

1988

货币值超出范围

1989

不能以编程方式从表单设计器中删除对象

1991

表单文件“名称”来自以前版本的 FoxPro,在执行前需转换为当前格式

1992

“名称”不是一个函数、过程或程序

1993

找不到类库文件“名称”

1994

找不到包含文件“名称”

1995

加载数据环境时发生错误。表正在使用

1996

字段“名称”的有效性规则的求值结果不是逻辑型或 NULL 型

1997

字段“名称”的默认值的求值结果不符合该字段的应有类型

1998

索引“名称”的索引表达式包含对备注字段、通用字段或图片字段的无效引用

1999

此函数功能尚未实现

2000

内存不足,因此不能进行流输出更新

2001

“文件”的对象文件的名称已被项目中别的程序使用

2002

连编 DLL 不能缺少 OLE 公共类

2003

改进的 OLE 属性流版本错误。忽略子类属性

2004

表文件“名称”被移动了。请验证数据库“名称”,并重试

2005

“名称” - “属性”:“错误”发生错误

2006

无法定位您的 Web 浏览器

2007

脱机时对游离视图的无效操作

2008

正在联机或管理方式下使用时,对游离视图的操作无效

2009

对象不是一个游离视图

2010

在联机或管理模式下使用时游离视图必须为独占使用

2011

对游离视图的无效操作

2012

不能从 When、Valid、RangeHigh 或 RangeLow 事件中调用 SetFocus

2013

无法直接打开持久表缓冲区

2014

被标识为更新关键字的字段将被清除

2015

对于此命令游离视图必须被联机使用

2016

不允许删除类成员

2017

不能在不同的模式中重复使用游离视图


Visual Foxpro 错误信息代码及含义

2018

重新连接游离视图失败。视图以管理模式打开

2019

不能以当前正被修改的视图生成游离视图

2020

无法找到与此字段相关联的类文件()- 类关联被清除

2021

不能在 OLEDragOver 或 OLEDragDrop 事件中修改 DataObject

2022

未找到指定的文件或路径

2023

没有与所给的文件扩展名相关联的应用程序

2024

发生共享冲突

2026

无效的地区 ID

2027

声明 DLL 调用出现异常

2028

API 调用出现异常

2029

类“名称”不是表单类

2030

无法删除文件“名称”

2031

现在不允许操作用户界面

2032

不明确的日期 / 日期时间常量

2033

CTOD 和 CTOT 会产生不正确的结果

2034

日期 / 日期时间计算为无效值

2035

日期 / 日期时间中包含了非法字符

291


292

从编写本书的第一天起,作者就体会到利用业余时间著书是一件痛苦并快乐的事情:痛苦 的是经常性的彻夜不眠,干涩的双眼,快乐的是“孕育新生儿”的喜悦,与他人分享知识的幸福。 一个人的能力是有限的,能够作出的成就也是有限的,要更好地实现个人价值,必须“站在 巨人的肩膀上”,作者以及这本书也不例外。在本书的编写过程中参考了大量的文档和技术资 料,因此有必要在最后把一些主要参考的资料列举出来,一方面对他人做出的工作表示尊重和 感谢,另一方面给读者指明一条进一步学习有关内容的途径。  MSDN(http:/ /msdn . microsoft . com) MSDN 包含了最权威的 Visual FoxPro 开发文档,也是本书主要的参考资料之一。 MSDN 中 还包含了一些示例程序,它们为书中的部分实例代码提供了借鉴。  Universal Thread(http:/ /www. universalthread . com/VisualFoxPro/) UT 是一个介绍 Visual FoxPro 及其他一些开发工具的综合性专业技术网站,在这个网站上 有许多高水平的技术文章和最新的资源下载,每期的电子杂志更是其中的精选。  West Wind(http:/ /www. west - wind . com/) West Wind 的口号是“用 Visual FoxPro 在 Web 上兴风作浪”,可见它主要关注的是 Visual FoxPro 在网络上的应用,这是当前最时髦的一个应用领域,对于正在从事这方面工作的朋友, West Wind 将是一个好去处。  FoxTools(http:/ /www. foxtools . com/) Foxtools 主要提供各种关于 FoxPro 的资源和第三方控件、工具,也收集了很多关于 COM 技 术在 Visual FoxPro 中应用的精彩文章。  《Special Edition Using Visual FoxPro 6》 (Menachem Bazian 等著,MacMillan Publishing Company 出版,ISBN: 0789718081) 这是一本介绍 Visual FoxPro 的经典好书,主要面向初、中级读者。  《Visual FoxPro 及其应用系统开发》 (史济民等著,清华大学出版社出版,ISBN: 7 - 30 - 203778 - 7) 这是作者在平时的 Visual FoxPro 教学过程中所使用的教材。  《Visual FoxPro 6 . x 中文版程序设计》 (章立民著,中国铁道出版社,ISBN: 7 - 113 - 03437) 这是台湾章立民先生写的关于 Visual FoxPro 程序设计的系列图书,包括教学指南篇、应用 务实篇、基础加强篇和问题篇,适用于从初级到中、高级的读者。  FoxPro 编程天堂(http:/ /www. myf1 . net /)和梅子 FoxPro 编程技术论坛(http:/ /meizibbs . 3322 . org/) 这是国内两个人气比较旺的 Visual FoxPro 论坛,在那里作者接触到很多 Visual FoxPro 的编 程爱好者,一方面让作者知道了初学者对什么问题最迷惘,另一方面也从众多高手的经验分享 中获得了知识和灵感。 以上所列举的不是全部,由于篇幅的限制,无法一一列举,在此一并致谢。


后记 从编写本书的第一天起,作者就体会到利用业余时间著书是一件痛并快乐着的事情:痛的是经 常性的彻夜不眠,干涩的双眼,快乐的是“孕育新生儿”的喜悦,与他人分享知识的幸福。 一个人的能力是有限的,能够作出的成就也是有限的,要更好的实现个人价值,必须“站在巨人 的肩膀上”,作者以及这本书也不例外,在本书的编写过程中参考了大量的文档和技术资料,因此有 必要在最后把一些主要参考的资料列举出来,一方面为他人作出的工作表示尊重和感谢,另一方面 给读者指明一条进一步学习有关内容的途径。  MSND(http:/ /msdn . microsoft . com) MSDN 包含了最权威的 Visual Foxpro 开发文档,也是本书主要的参考资料之一。 MSDN 中还 包含了一些示例程序,它们为书中的部分实例代码提供了借鉴。  Universal Thread(http:/ /www. universalthread . com /VisualFoxPro /) UT 是一个介绍 Visual Foxpro 及其它一些开发工具的综合性专业技术网站,在这个网站上有许 多高水平的技术文章和最新的资源下载,每期的电子杂志更是其中的精选。  West Wind(http:/ /www. west - wind . com /) West Wind 的口号是“用 Visual Foxpro 在 Web 上兴风作浪”,可见它主要关注的是 Visual Foxpro 在网络上的应用,这是当前最时髦的一个应用领域,对于正在从事这方面工作的朋友,West Wind 将是一个好去处。  FoxTools(http:/ /www. foxtools . com /) Foxtools 主要提供各种关于 Foxpro 的资源和第三方控件、工具,也收集了很多关于 COM 技术 在 Visual Foxpro 中应用的精彩文章。  《Special Edition Using Visual FoxPro 6》 (Menachem Bazian 等著,MacMillan Publishing Company 出版,ISBN:0789718081) 这是一本介绍 Visual Foxpro 的经典好书,主要面向初中级读者。  《Visual Foxpro 及其应用系统开发》 (史济民等著,清华大学出版社出版,ISBN:7 - 30 - 203778 - 7) 这是作者在平时的 Visual Foxpro 教学过程中所使用的教材。  《Visual Foxpro 6 . x 中文版程序设计》 (章立民著,中国铁道出版社,ISBN:7 - 113 - 03437) 这是台湾章立民先生写的关于 Visual Foxpro 程序设计的系列图书,包括教学指南篇、应用务实 篇、基础加强篇和问题篇,适用于从初级到中高级的读者。  Foxpro 编程天堂(http:/ /www. myf1 . net /)和梅子 FoxPro 编程技术论坛 (http:/ /meizibbs . 3322 . org /) 这是国内两个人气比较旺的 Visual Foxpro 论坛,在这里作者接触到很多 Visual Foxpro 的编程 爱好者,一方面让作者知道了初学者对什么问题最迷惘,另一方面也从众多高手的经验分享中获得 了知识和灵感。 以上所列举的不是全部,但受到篇幅的限制无法一一列举,在此一并致谢。本书的编写期间, 正是 SARS 病魔在我中华大地肆虐之际,奋战在抗炎第一线的白衣天使们为作者写作此书提供了 强大的精神动力,谨向他们致以最崇高的敬意。

Visual FoxPro 高級應用實例  

Visual FoxPro 高級應用實例

Visual FoxPro 高級應用實例  

Visual FoxPro 高級應用實例

Advertisement