零基础学编程

零基础学Visual Basic

李俊民　赵东　等著

ISBN：978-7-111-28577-9

本书纸版由机械工业出版社于2010年出版，电子版由华章分社（北京华章图文信息有限公司）全球范围内制作与发行。

版权所有，侵权必究

客服热线：+ 86-10-68995265

客服信箱：service@bbbvip.com

官方网址：www.hzmedia.com.cn

新浪微博 @研发书局

腾讯微博 @yanfabook

目　录

出版说明

第2版图书所做的改进

包括的书籍

光盘内容

技术支持

前言

本书特点

本书内容

本书适合的读者

本书作者

第1章　Visual Basic的集成开发环境

1.1　VB概述

1.2　安装和卸载VB

1.3　集成开发环境介绍

1.3.1　控件工具箱

1.3.2　属性窗口

1.3.3　菜单栏和工具栏

1.4　窗体与控件

1.4.1　第一个VB程序

1.4.2　控件的属性

1.4.3　控件的事件

1.4.4　控件的方法

1.5　发行应用程序

1.6　综合练习

1.7　两个VB操作技巧

1.7.1　动态设定TextBox控件的文本靠左、右、中

1.7.2　让文本框输入完后直接跳入下一行

1.8　小结

1.9　本章练习

第2章　对象和窗体

2.1　对象

2.1.1　什么是对象

2.1.2　对象属性设置

2.2　窗体概述

2.2.1　窗体的构成

2.2.2　添加和删除窗体

2.3　窗体的属性

2.3.1　设置窗体的背景颜色

2.3.2　改变窗体边框的样式

2.3.3　设置窗体的标题

2.3.4　设置窗体的高度和宽度

2.3.5　改变窗体的图标

2.3.6　改变窗体的名称

2.3.7　设置窗体的背景图片

2.3.8　窗体的显示位置

2.3.9　窗体的可见性

2.3.10　窗体的显示状态

2.4　窗体的方法

2.4.1　Print（输出）方法

2.4.2　Move（移动）方法

2.4.3　Hide（隐藏窗体）方法

2.4.4　Show（显示窗体）方法

2.4.5　Unload（卸载）语句

2.5　窗体的事件

2.5.1　Click（单击）事件

2.5.2　DblClick（双击）事件

2.5.3　Load（载入）事件

2.5.4　Unload（卸载）事件

2.5.5　Resize（改变大小）事件

2.5.6　Activate（活动）/Deactivate（非活动）事件

2.5.7　Paint（绘制）事件

2.6　多窗体应用

2.6.1　多窗体应用实例

2.6.2　指定启动窗体

2.6.3　多窗体程序的存取

2.6.4　标准模块和Sub Main过程

2.6.5　MDI窗体

2.7　小结

2.8　本章练习

第3章　Visual Basic语言基础

3.1　程序设计的基本概念

3.1.1　关键字

3.1.2　标识符

3.2　数据类型

3.2.1　基本数据类型

3.2.2　自定义数据类型

3.2.3　枚举数据类型

3.2.4　类型之间的转换

3.3　常量和变量

3.3.1　常量

3.3.2　变量

3.3.3　作用域

3.3.4　Option Explicit语句

3.3.5　注释语句

3.4　运算符与表达式

3.4.1　赋值运算符

3.4.2　算术运算符

3.4.3　关系运算符

3.4.4　逻辑运算符

3.4.5　字符串运算符

3.4.6　表达式

3.5　工程管理

3.5.1　建立、打开及保存工程

3.5.2　在工程中添加、删除及保存文件

3.5.3　多个工程协同工作

3.6　Visual Basic应用程序的结构与工作方式

3.6.1　窗体模块

3.6.2　标准模块

3.6.3　类模块

3.6.4　事件驱动

3.7　小结

3.8　本章练习

第4章　控件

4.1　控件概述

4.2　控件的相关操作

4.2.1　向窗体上添加控件

4.2.2　调整控件的大小

4.2.3　移动控件到合适的位置

4.2.4　复制与删除控件

4.2.5　选择控件

4.2.6　使用格式菜单调整控件布局

4.3　输入输出函数

4.3.1　InputBox函数

4.3.2　MsgBox函数

4.3.3　MsgBox语句

4.4　命令按钮

4.4.1　命令按钮的常用属性

4.4.2　命令按钮的事件

4.4.3　应用案例——计算圆的面积

4.5　标签

4.6　文本框

4.6.1　文本框的常用属性

4.6.2　文本框的事件和方法

4.6.3　应用案例——运算器

4.7　单选按钮与复选框

4.7.1　单选按钮

4.7.2　复选框

4.7.3　框架

4.7.4　应用案例——文本编辑器

4.8　列表框与组合框

4.8.1　列表框介绍

4.8.2　列表框的常用属性

4.8.3　列表框的方法和事件

4.8.4　组合框介绍

4.8.5　组合框的属性、方法和事件

4.8.6　应用案例——计算机订购程序

4.9　图像框

4.9.1　设置图像框中的图形

4.9.2　使用LoadPicture函数设置图形

4.9.3　相对路径——App.Path

4.9.4　设置鼠标指针形状

4.9.5　应用案例——看图学英语

4.10　图片框

4.10.1　图片框介绍

4.10.2　应用案例——移动小蜜蜂

4.11　计时器

4.11.1　Interval属性

4.11.2　Timer事件

4.11.3　应用案例——实现弹球动画

4.12　滚动条

4.12.1　滚动条的属性

4.12.2　滚动条的事件

4.12.3　应用案例——选择背景颜色

4.13　驱动器列表、目录列表和文件列表

4.14　焦点与Tab顺序

4.15　在工具箱中增加其他控件

4.16　使用RichTextBox编写超级文本编辑器

4.17　树型视图控件与图像列表控件

4.17.1　创建树型视图

4.17.2　设置树型视图的外观

4.17.3　图像列表控件

4.17.4　给树型视图添加内容

4.17.5　设置展开/折叠和单击结点时的图标

4.17.6　获取被选中的结点的信息

4.18　SSTab控件

4.19　OLE容器控件

4.20　小结

4.21　本章练习

第5章　程序的基本控制结构

5.1　顺序结构程序设计

5.1.1　赋值语句

5.1.2　输入输出数据

5.2　选择结构程序设计

5.2.1　行If语句

5.2.2　块If语句

5.2.3　If语句的嵌套

5.2.4　Select Case语句

5.2.5　应用案例1——密码登录

5.2.6　应用案例2——用户注册

5.3　循环结构程序设计

5.3.1　For循环语句

5.3.2　While循环语句

5.3.3　Do循环语句

5.3.4　多重循环

5.3.5　应用案例1——色彩变幻的背景

5.3.6　应用案例2——列表框购物程序

5.4　控制语句

5.4.1　GoTo语句

5.4.2　Exit Do语句

5.4.3　End语句

5.5　小结

5.6　本章练习

第6章　数组

6.1　数组的概念

6.1.1　数组定义

6.1.2　在同一个数组中存放不同类型的数据

6.2　静态数组与动态数组

6.2.1　动态数组的定义

6.2.2　数组的清除和重定义

6.3　数组的基本操作

6.3.1　数组的输入和输出

6.3.2　求数组和与平均值

6.3.3　数组排序

6.3.4　插入数据

6.3.5　删除数据

6.3.6　For Each...Next语句

6.4　数组的初始化

6.5　控件数组

6.5.1　基本概念

6.5.2　建立控件数组

6.6　小结

6.7　本章练习

第7章　过程

7.1　Sub过程

7.1.1　建立Sub过程

7.1.2　调用Sub过程

7.1.3　通用过程与事件过程

7.2　Function过程

7.2.1　建立Function过程

7.2.2　调用Function过程

7.3　参数传递

7.3.1　值传递

7.3.2　地址传递

7.4　可选参数与可变参数

7.4.1　可选参数

7.4.2　可变参数

7.5　对象参数

7.5.1　窗体参数

7.5.2　控件参数

7.6　小结

7.7　本章练习

第8章　键盘、鼠标事件和图形

8.1　键盘事件

8.1.1　ASCII码

8.1.2　处理“敲击键盘”

8.1.3　处理“按键被按下”和“按键被松开”

8.1.4　应用案例——使用方向键移动坦克

8.2　鼠标事件

8.2.1　处理“鼠标键被按下”

8.2.2　处理“鼠标键被松开”

8.2.3　处理“鼠标移动”

8.3　拖放

8.3.1　拖放操作实例——垃圾桶的实现

8.3.2　OLE拖放操作

8.4　图形

8.4.1　直线控件

8.4.2　形状控件

8.4.3　应用案例——绘制圆柱体

8.4.4　用PSet方法编写画刷程序

8.4.5　用Line方法绘制直线

8.4.6　用Line方法绘制矩形

8.4.7　用Circle方法画圆或椭圆

8.4.8　用Circle方法画圆弧及扇形

8.4.9　用PaintPicture方法旋转图形

8.4.10　重绘图形和画笔模式

8.5　小结

8.6　本章练习

第9章　创建与使用菜单、工具栏和状态栏

9.1　菜单

9.1.1　菜单编辑器

9.1.2　使用菜单编辑器创建菜单

9.1.3　设计菜单

9.1.4　弹出式菜单的设计

9.1.5　弹出式菜单的调用

9.2　工具栏

9.2.1　利用Toolbar控件创建工具栏

9.2.2　为工具栏按钮设置分组

9.2.3　给工具栏按钮增加事件处理代码

9.2.4　美化工具栏

9.2.5　Shell函数

9.2.6　CoolBar控件

9.3　状态栏

9.3.1　设计状态栏

9.3.2　在状态栏中显示当前日期时间

9.3.3　在状态栏中显示鼠标位置

9.4　应用案例——编写类似Windows画图板的简单画图程序

9.4.1　设计菜单栏

9.4.2　设计工具栏

9.4.3　处理菜单项的Click事件

9.4.4　处理工具栏的代码

9.4.5　编写绘制图形的代码

9.4.6　处理状态栏

9.5　小结

9.6　本章练习

第10章　ActiveX控件

10.1　ActiveX控件概述

10.2　创建自己的控件

10.2.1　手动创建ActiveX控件

10.2.2　使用自己创建的ActiveX控件

10.2.3　使用向导定制控件

10.3　日历控件

10.3.1　通过日历控件获得日期

10.3.2　日历控件独有的事件

10.4　通用对话框控件

10.4.1　使用“打开”对话框

10.4.2　使用“保存”对话框

10.4.3　使用“颜色”对话框

10.4.4　使用“字体”对话框

10.4.5　使用“打印”对话框

10.4.6　对单击“取消”按钮的处理

10.5　小结

10.6　本章练习

第11章　文件处理技术

11.1　文件的类型

11.1.1　顺序存取文件

11.1.2　随机存取文件

11.1.3　二进制存取文件

11.2　顺序存取文件

11.2.1　打开顺序文件

11.2.2　写入顺序文件

11.2.3　读取顺序文件

11.2.4　关闭顺序文件

11.2.5　文件操作函数

11.3　随机存取文件

11.3.1　随机文件的打开和关闭

11.3.2　写入随机文件

11.3.3　读取随机文件

11.4　二进制存取文件

11.4.1　二进制文件的打开和关闭

11.4.2　二进制文件的读写操作

11.5　常用文件操作语句

11.5.1　复制、删除及重命名文件

11.5.2　判断文件或文件夹是否存在

11.5.3　新建文件夹和删除文件夹

11.6　应用案例——txt文件编辑器

11.7　小结

11.8　本章练习

第12章　数据库编程基础

12.1　数据库结构和数据访问方式

12.1.1　基本概念

12.1.2　Visual Basic数据库应用组成层次

12.1.3　Visual Basic数据访问方式

12.2　Access数据库

12.2.1　启动Access

12.2.2　建立数据库

12.2.3　建立数据表

12.3　SQL语言基础知识

12.3.1　SQL语言简介

12.3.2　SELECT语句基础

12.3.3　在Access中运行SELECT语句

12.3.4　WHERE子句

12.3.5　ORDER BY子句

12.3.6　SQL用于计算的几个函数

12.3.7　添加数据记录

12.3.8　修改数据记录

12.3.9　删除数据记录

12.3.10　多表联合查询

12.4　小结

12.5　本章练习

第13章　数据控件编程

13.1　Data控件

13.1.1　Data控件简单应用

13.1.2　Data控件的主要属性

13.1.3　Data控件的主要方法

13.1.4　Data控件的常用事件

13.1.5　Recordset（记录集）对象

13.1.6　数据绑定控件

13.1.7　Data控件应用案例

13.2　ADO Data控件

13.2.1　ADO Data控件的属性

13.2.2　ADO Data控件的方法

13.2.3　ADO Data控件的事件

13.2.4　应用案例——ADO控件与select语句联合应用

13.3　两种表格控件的用法

13.3.1　DataGrid控件

13.3.2　MSHFlexGrid控件

13.4　小结

13.5　本章练习

第14章　ADO数据库编程

14.1　ADO介绍

14.1.1　认识ADO

14.1.2　ADO编程模型

14.1.3　在工程中引用（添加）ADO

14.2　访问数据库

14.2.1　使用Connection对象连接Access数据库

14.2.2　使用Connection对象连接其他数据库

14.2.3　使用Connection对象连接ODBC数据源

14.2.4　应用案例——使用Connection执行SQL

14.3　使用RecordSet对象操作记录

14.3.1　使用RecordSet对象执行Select查询

14.3.2　显示RecordSet对象的查询结果

14.3.3　应用案例——向数据表添加记录

14.3.4　应用案例——从数据表删除记录

14.3.5　应用案例——修改数据表记录

14.4　小结

14.5　本章练习

第15章　网络编程技术

15.1　网络基础

15.1.1　IP地址

15.1.2　端口

15.1.3　协议

15.2　Winsock控件

15.2.1　Winsock控件的常用属性

15.2.2　Winsock控件的常用方法

15.2.3　Winsock控件的常用事件

15.2.4　Winsock编程模型

15.2.5　应用案例——简易聊天程序

15.3　制作自己的浏览器

15.3.1　WebBrowser控件

15.3.2　设计浏览器界面

15.3.3　编写代码实现浏览器

15.4　下载网页

15.4.1　Internet Transfer控件

15.4.2　实现下载网页

15.5　小结

15.6　本章练习

第16章　多媒体编程

16.1　文字处理特效

16.1.1　缩放文字特效

16.1.2　滚动文字特效

16.2　音频、视频处理

16.2.1　多媒体控件

16.2.2　使用Windows Media Player控件播放影音文件

16.2.3　使用Real Audio控件播放RM文件

16.2.4　应用案例——制作My Player播放器

16.3　使用Shockwave Flash控件播放动画

16.4　小结

16.5　本章练习

第17章　程序调试与错误处理

17.1　程序的错误类型

17.1.1　编译错误

17.1.2　运行时错误

17.1.3　逻辑错误

17.2　错误排错方法

17.2.1　编译错误排错与实时错误排错

17.2.2　逻辑错误调试方法

17.3　错误处理程序

17.3.1　Err对象

17.3.2　捕获错误

17.3.3　编写错误处理程序

17.3.4　集中错误处理程序

17.4　小结

17.5　本章练习

第18章　建立并使用帮助文件

18.1　建立帮助文件

18.1.1　建立目录文件

18.1.2　编写主题文件

18.1.3　建立项目文件

18.2　连接应用程序和帮助文件

18.3　HTML Help Workshop工具的使用

18.3.1　HTML Help Workshop的安装与启动

18.3.2　将帮助文件转换为HTML格式

18.3.3　为帮助系统建立索引文件

18.3.4　解决中文乱码问题

18.4　小结

18.5　本章练习

第19章　Visual Basic与Excel联合编程

19.1　Excel基础

19.1.1　Excel简介

19.1.2　创建Excel文件

19.2　使用OLE控件调用Excel

19.2.1　“插入对象”对话框的使用

19.2.2　编写程序在OLE控件中显示文件

19.3　针对Excel的程序设计

19.3.1　在程序中引用Excel类型库

19.3.2　操作Excel对象

19.3.3　向工作表输出数据

19.3.4　打印工作表

19.3.5　获取工作表数据

19.4　小结

19.5　本章练习

第20章　使用动态链接库和API

20.1　动态链接库（DLL）简介

20.2　理解DLL调用规范

20.3　API查看器

20.4　在应用程序中使用API函数

20.4.1　判断窗体是否已经显示

20.4.2　获取鼠标在屏幕上的位置

20.4.3　最前窗体（顶层窗体）

20.5　小结

20.6　本章练习

第21章　编译与制作安装文件

21.1　编译项目

21.2　工程属性的设置

21.2.1　“生成”选项卡设置

21.2.2　“编译”选项卡设置

21.3　使用Package＆Deployment Wizard制作安装文件

21.3.1　启动Package＆Deployment Wizard

21.3.2　制作安装文件

21.4　使用Installer VISE制作安装文件

21.4.1　安装Installer VISE

21.4.2　启动Installer VISE

21.4.3　制作安装文件

21.4.4　发布安装文件

21.5　小结

21.6　本章练习

第22章　学生考试系统

22.1　程序功能简介

22.2　设计思路和结构

22.3　程序设计步骤

22.3.1　主界面程序

22.3.2　SQL查询模块

22.3.3　系统登录模块

22.3.4　答题模块

22.3.5　成绩查询模块

22.4　本章常见问题

22.5　小结

第23章　学生信息管理系统

23.1　程序功能简介

23.2　设计思路和结构

23.3　程序设计步骤

23.3.1　主界面程序

23.3.2　SQL查询模块

23.3.3　系统管理模块

23.3.4　班级管理模块

23.3.5　档案管理模块

23.3.6　课程管理模块

23.3.7　成绩管理模块

23.4　本章常见问题

23.5　小结

第24章　可视电话

24.1　程序功能简介

24.2　设计思路和结构

24.3　程序设计步骤

24.3.1　窗体加载与卸载模块

24.3.2　呼叫模块

24.3.3　挂机模块

24.3.4　录像模块

24.3.5　视频处理模块

24.3.6　音频处理模块

24.3.7　数据传输模块

24.3.8　超时等待模块

24.4　本章常见问题

24.5　小结

光盘内容

出版说明

“零基础学编程”系列图书自2008年上市以来就受到了广大读者的青睐。本系列中的很多图书一上市就登上了编程类图书销售排行榜的前列。目前本系列中的很多图书已经多次印刷。之所以有如此好的市场表现，是与本系列书的品质和在读者中的口碑是分不开的。这套书上市后受到了广大读者的好评，很多学校也作为教材使用。

为了使本系列书能紧跟技术趋势，更加适合读者学习和学校教学，我们结合最新技术和读者的建议，对本系列书中的一些图书进行了改版（即第2版）。另外，还增加了一些新的品种，也一并放入本系列，以使本系列书更加完善。

第2版图书所做的改进

第2版图书在第1版图书的基础上主要有以下改进：

❑增加了数小时的多媒体教学视频，使得学习更加直观和高效；

❑增加了课后习题，使得本系列书更加适合读者自我检测和学校教学使用；

❑专门制作了教学PPT，以方便相关专业老师教学使用；

❑增加了更多的项目实践内容，以增强实用性，提高读者的动手能力；

❑对图书的编排体例进行了梳理，以增强条理性和可读性；

❑对第1版图书的内容和结构有所调整，使得其更加合理和科学；

❑补充完善了一些新的内容，使其内容更加完善；

❑更正了第1版图书中出现的一些疏漏。

包括的书籍

本系列书本次推出11个品种。其中，标注了第2版的为第1版的改版，未标注的为本次新增加品种。具体如下：

《零基础学Java第2版》

《零基础学SQL》

《零基础学Java Web开发第2版》

《零基础学Linux C程序设计》

《零基础学Visual C++第2版》

《零基础学数据结构》

《零基础学Visual Basic第2版》

《零基础学算法》

《零基础学JavaScript第2版》

《零基础学计算机英语》

《零基础学SQL Server 2008》

光盘内容

本系列书有配套光盘。光盘的主要内容如下：

❑书中所涉及的程序源代码；

❑多媒体语音教学视频；

❑教学PPT；

❑免费赠送的大量编程电子图书和编程视频。

技术支持

本系列书有专门的技术论坛（http://www.rzchina.net）和QQ群（群号：21948169）。读者学习过程中若有疑问，可以在论坛上或者QQ群里提问交流。另外，论坛上还有一些技术教程、视频动画，读者可以免费下载使用。

前言

在当今的信息化时代，计算机已经深入到了人们的日常工作和生活的方方面面。而各种行业的需求正是让软件业蒸蒸日上的原因。虽然现在世界上已经充满了各种各样的软件产品，但是依然不能满足用户的各种特殊需要，人们不得不再开发适合自己特殊需求的软件产品。

Visual Basic是Microsoft公司研制的软件开发工具。它继承了Basic语言易学易用的特点，特别适合初学者学习Windows系统编程。Visual Basic 6.0可谓是系列版本中最为成熟、并经过多年实践证明了的最优秀的版本。用其开发的软件产品不仅功能强大、而且非常稳定。相对于其他软件工具，用Visual Basic开发软件还会大大缩短开发周期，给用户节省宝贵的时间。

为了帮助读者迅速掌握编程方法和技巧，编者精心编著了本书。本书依照读者的学习规律，整本书都是以由浅入深、循序渐进的原则进行编写的，并在多数章节中都配有应用案例。读者可以参照应用案例，通过亲自动手学会编写出一些很有意思的程序。因此，读者在阅读本书时，会感觉很轻松地学会了很多知识。

本书特点

1.知识覆盖面非常广

本书从Visual Basic基础语法开始讲解，扩展到数据库控件编程、ADO数据库编程、客户端/服务器编程、Internet网络编程、多媒体动画编程、高级办公自动化软件Excel编程等多个方面的编程技术。

2.涵盖了软件开发全过程

本书从设计界面、编写程序到制作帮助文件、编译、制作安装文件发布，涵盖了使用Visual Basic开发应用软件的全部过程。

3.介绍了多种工具的用法

本书不仅讲解了关于Visual Basic的知识，而且还详细讲解了很多相关软件与工具的用法。例如，使用Access建立数据库、执行SQL语句、使用WinHelp和HTML Help Workshop制作帮助文件、使用Package＆Deployment Wizard制作安装文件、使用Installer VISE制作特色安装文件、使用API浏览器查看API函数等。

4.案例丰富、讲解细致

本书融入编者自身多年的编程与教学经验，精心设计了丰富的应用案例，并对其进行了非常细致的讲解。我们相信，读者通过学习这些案例，会得到很好的启发。

5.配有3个综合实例

本书最后3章为“学生考试系统”、“学生信息管理系统”和“可视电话”三个综合实例。这些实例详细地介绍了开发软件的全过程，综合运用了以往所学的全部知识。避免初学者只会使用一两个控件，在单窗体上编写小程序；而不会结合多种控件，在多窗体环境下开发大型程序。

6.详尽代码分析

完整的小案例中，代码后面都附代码说明，让读者更透彻地了解程序的原理与过程，让读者更深入地掌握案例的应用。

本书内容

第1章：介绍了Visual Basic的集成开发环境。同时，介绍了使用各种帮助系统的方法。并创建了第一个Visual Basic应用程序。

第2章：首先介绍了对象和窗体的基本概念，之后详细讲述了窗体常用属性、方法和事件，并配有大量的实例，最后还讲解了MDI窗体。

第3章：讲解了Visual Basic的基础知识，包括数据类型、变量常量的概念和用法；以及Visual Basic的运算符和表达式；Visual Basic程序的保存、打开和运行的方法。

第4章：介绍了大量的标准控件和ActiveX控件的使用方法，并配有大量的实例。本章还包括了InputBox函数和MsgBox函数的用法。

第5章：介绍了程序设计的基本结构，主要讲述了分支结构和循环结构。对于初学程序设计的读者来说，这一章非常有用。

第6章：讲述了Visual Basic中一维、二维数组的用法，动态数组的用法，数组的求和、排序、插入、删除等内容。此外，还讲述了控件数组的用法。

第7章：讲述了通用过程的建立和调用的方法，并详细讲解了可选参数过程、可变参数过程、对象参数过程的用法。

第8章：介绍了键盘、鼠标事件、图形控件和Pset、Line、Circle等图形方法的用法，并配有大量的实例。

第9章：介绍了建立和使用菜单、工具栏和状态栏的方法，并在最后加入了一个画图软件的制作案例。

第10章：介绍了创建用户自制控件的方法和两个常用的ActiveX控件——日历控件和通用对话框控件的用法。

第11章：介绍了处理不同类型文件的知识和常用文件操作函数、语句。

第12章：介绍了数据库的基本概念、Access的应用和SQL语言的基础知识。

第13章：介绍了常用数据库控件的编程方法。有Data控件、ADO Data控件和两个表格控件——DataGrid、MSHFlexGrid的用法。

第14章：介绍了ADO数据库编程的知识，内容从引用ADO开始到使用ADO对象操作数据库的各种方法为止。

第15章：介绍了网络编程技术。主要内容有网络协议的介绍、Winsock控件编程、WebBrowser控件编程、Internet Transfer控件的使用等。

第16章：介绍了多媒体编程的方法和几个多媒体控件的用法，并制作了一个自己的播放器。

第17章：细致地描述了调试程序的方法和错误处理的方法。

第18章：介绍了两种建立帮助文档的工具——WinHelp和HTML Help Workshop。并介绍了帮助文件如何与程序链接起来的方法。

第19章：首先，介绍了使用OLE控件，将Excel文件显示到程序中的方法；其次，介绍了通过Visual Basic编程操作Excel文件的具体方法。

第20章：介绍了在Visual Basic中，如何使用Windows API函数的方法。

第21章：介绍了编译与制作安装文件的方法，包括使用Package＆Deployment Wizard和Installer VISE两种工具建立安装文件的方法。

第22章：介绍了学生考试系统的全部过程和源代码的分析。

第23章：介绍了学生信息管理系统的全部过程和源代码的分析。

第24章：介绍了可视电话功能实现的全部过程和源代码的分析。

本书适合的读者

本书具有知识全面、实例精彩、指导性强的特点，力求以全面的知识及丰富的实例来指导读者透彻学习Visual Basic各方面的知识。本书可作为：

❑初学编程者的入门教材。

❑中级读者提高技能的查询资料。

❑高级读者的参考资料。

❑学习快速编程的参考资料。

❑大中专院校或培训学校的教材。

本书作者

本书由李俊民、赵东主笔编写，同时参与编写和资料整理的有刘亮亮、丁士锋、何涛发、陈杰、黄曦、罗嘉、段春江、韩红宇、李嵩峰、莫光胜、王天国、李蓉、吴荣、宋祥亮、刘宇、吕晓鹏、王大伟、吴小平、张卫忠、施佳鹏、王嘉、吴雪、阳婷、张秀妍、王江、王志永、杨红、郑维龙、王松、张文。

编者

第1章　Visual Basic的集成开发环境

Visual Basic（以下简称VB）在IDE（集成开发环境）中创建、开发、运行和调试Windows程序。IDE是集程序代码编制、界面设计、程序调试和查错功能于一体的应用程序开发系统。本章主要介绍集成开发环境IDE和控件的基础知识。本章包括以下知识点：

❑VB的起源及其特点；

❑VB软件的安装和卸载；

❑使用集成开发环境IDE创建VB工程；

❑窗体和控件的3个基本要素是属性、事件和方法；

❑生成可执行文件和安装文件，发布VB程序。

控件的数量繁多、功能齐全，但是控件基本都具备属性、事件和方法要素。窗体和控件具有很多类似的特点。通过本章的学习，即使没有编程经验的读者，也可以通过鼠标单击或拖动VB控件，设计自己的程序运行界面，从而感受到VB程序设计的简单和方便，使读者能够基本掌握VB控件的使用方法。

1.1　VB概述

Basic语言起源于20世纪60年代，是美国Dartmouth学院的John G. Kemeny和Thomas Kuntz教授发明的。Basic是Beginner's All-purpose Symbolic Code的缩写，"Beginner"指出了Basic语言的对象。该语言简洁易学，但是运行效率不高。

Alan Cooper在Basic语言的基础上融合了Windows的编程思想和概念，将Basic语言简洁易学的特点与可视化的编程方法结合起来，使得VB在20世纪80年代末逐渐流行。虽然很多高级语言都可以编写Windows程序，但是，没有哪一门语言像VB那样可以快速被初学者掌握。

自1991年Microsoft公司推出可视化编程、事件驱动的VB 1.0以来，VB已经发布了6个历史版本。1998年，Microsoft公司发布了VB 6.0。

VB 6.0是面向图形用户界面的可视化程序设计工具，可以快速构建图形用户界面。事实上，VB 6.0是一种典型的面向对象的程序设计语言，将Windows程序的风格体现得淋漓尽致。VB的窗体和控件都是对象，其属性和方法都封装在了该对象的定义中，这是Windows程序设计的精髓。这些控件可以构成丰富的Windows图形界面，而程序员却不必关心这些对象的内部实现，可以通过简单的代码实现复杂的功能。

VB 6.0的另一个特征是事件驱动编程。程序员在需要的响应事件中编写响应的程序，就能实现用户需要的操作，而不必关心程序的内部执行顺序等复杂情况。

对于大型复杂的应用程序，VB还可以采用功能模块的形式轻松地实现功能分解，然后通过接口实现复杂的功能。这样，能够使得程序层次分明、结构合理、调试方便。

VB的突出特点就是比其他Windows编程语言更简洁，界面设计简单、代码编写量小、编程效率高。VB最大的缺点是运行速度慢，在程序功能等价的情况下，VB程序确实比C/C++程序慢很多。但是，绝大部分人都不会感觉到两者在运行速度上的差异。而且，目前的计算机配置越来越高，运行速度也越来越快，因此提高VB程序的运行速度已经不再是程序员首要考虑的问题了。程序员只需要考虑程序的开发周期和程序调试问题，这才是应用程序开发中最重要最昂贵的部分。这种情况下，VB成为广大程序员的首选。

目前，VB在数据库管理、Internet/Intranet分布式应用程序、客户端/服务器软件等应用中，都体现了高效编程的特点，大大缩短了开发周期。

1.2　安装和卸载VB

VB是Microsoft公司开发的Visual Studio开发工具集中的一个。Visual Studio包含Visual Basic 6.0、Visual C++6.0、Visual Foxpro 6.0和ActiveX等。VB的安装过程如下所示。

（1）将Visual Studio安装光盘放入计算机光驱。

（2）从光驱运行Visual Studio安装程序。

（3）根据安装向导，选择安装路径，默认的路径一般是C:\Program File\Microsoft Visual Studio\Common。

（4）选择安装类型。VB有两种安装类型，分别是典型安装和自定义安装。初学者可以选择“典型安装”的安装类型。

（5）Visual Studio是一个开发工具集，安装时，系统弹出“用户自定义安装”选项对话框，用户需要选择"Visual Basic 6.0"和"ActiveX"选项。

（6）系统提示的其余选项可以选择默认操作。

Visual Studio安装结束后，程序提示安装MSDN帮助软件，MSDN不是Visual Studio自带的软件，需要购买。

卸载VB有以下3种途径。

（1）找到安装目录，运行其中的"Uninstall.EXE"程序。

（2）从“开始”菜单进入“控制面板”，单击其中的“添加/删除程序”选项，从展开的列表中找到"Microsoft Visual Studio"选项，单击“删除”按钮即可。

（3）从光驱运行Visual Studio安装程序，选择“删除”方式卸载VB。

1.3　集成开发环境介绍

打开VB的集成开发环境，系统弹出“新建工程”对话框，如图1.1所示。VB提供了很多种类型的程序，默认的是标准EXE类型，该类型允许程序员创建标准的可执行文件，这是最常用的类型。另外，“新建工程”对话框还提供了创建一个已存在或最近装入的工程。

图　1.1　“新建工程”对话框

这里，选择创建默认的标准EXE类型的工程，如图1.2是标准EXE的IDE窗口。标准EXE的IDE窗口由标题栏、菜单栏、工具栏、控件工具箱、对象窗口、代码编写窗口、项目资源管理窗口、属性窗口、窗体布局窗口等组成。

图　1.2　标准EXE的IDE窗口

控件工具箱、属性窗口、立即窗口、菜单栏和工具栏在程序设计中非常重要，熟悉和掌握它们能够使程序设计工作事半功倍。

1.3.1　控件工具箱

控件工具箱上列出了20个常用的控件，如图1.3所示。用鼠标选中控件，放到窗体上就能设计界面。使用鼠标拖动或移动控件就能改变控件的大小和在窗体中的位置。这些控件可以重复使用，可以利用它们的属性、事件和方法方便地编写程序。这些控件的具体功能和使用方法将在后面介绍。

图　1.3　控件工具箱

1.3.2　属性窗口

属性窗口用于显示窗体或控件的属性，如控件的尺寸、颜色、位置和字体等。有些属性是公用的，有些属性是窗体或控件特有的。属性列表中显示的属性都是在程序设计时可以使用的。还有一些属性在设计的时候不可用，只能用代码设置，在程序运行时才能体现出来。对同一个窗体，属性窗口可以用两种方式显示其属性。一种是选择“按字母序”选项卡，按字母顺序排列各属性，如图1.4所示；另一种是选择“按分类序”选项卡，按类型顺序排列各属性，如图1.5所示。对于控件的属性列表也有同样的排列方式。

图　1.4　按字母顺序排列的属性窗口

图　1.5　按类型顺序排列的属性窗口

1.3.3　菜单栏和工具栏

菜单栏和工具栏包含了VB程序设计所需要的绝大部分功能。与很多Windows风格的应用软件一样，IDE的菜单栏的大多数功能和工具栏是对应的。通过菜单栏和工具栏选择对应的命令使编程显得简单和方便。VB的菜单栏如图1.6所示。

图　1.6　菜单栏

VB常用的工具栏有多种类型，包括标准工具栏、调试工具栏、编辑工具栏和窗体编辑器工具栏。另外，程序员还可以自定义一些工具放到工具栏上供编程时使用。VB的标准工具栏如图1.7所示。

图　1.7　标准工具栏

VB的调试工具栏如图1.8所示。调试工具栏提供了数据变量监控、立即输出、逐行运行和整段运行等功能。熟练使用调试工具栏，可以大大提高程序调试的效率。

图　1.8　调试工具栏

VB的编辑工具栏如图1.9所示。编辑工具栏主要在程序编辑和调试的时候提供多行程序缩进和突出、整段程序注释和取消注释等功能。

图　1.9　编辑工具栏

VB的窗体编辑器工具栏如图1.10所示。虽然使用VB控件构造应用软件界面时，只需要在窗体上拖动这些控件到合适的位置或调整合适的大小就能实现。但是，靠程序员的判断来保持这些控件大小相同、对齐一致和均匀分布等，还是存在一定的困难。窗体编辑器工具栏上的命令按钮正好可以解决这些问题，使得VB的界面设计更加美观，控件布局更加专业。

图　1.10　窗体编辑器工具栏

1.4　窗体与控件

大部分Windows应用软件由一个主窗体和若干个子窗体组成。使用VB创建一个标准EXE类型的工程，第一步就是要进行用户界面的设计，这也是VB可视化编程的特点。窗体和控件都是对象，是VB可视化界面设计中的基本构造元素。窗体是控件的容器，控件只有添加到窗体上才可以使用。

在VB开发的软件项目中，一个窗体通常包含很多不同的控件，有时将这些控件组合起来实现某个功能。控件有多种类型，每种类型的控件又包含多个不同的控件。但是，这些控件都具有一些共同的特点，就是包含了3个基本要素：属性、事件和方法。窗体的用法和控件有很多类似之处，其很多属性、事件和方法与控件是相同的。下面以显示"Hello,Visual Basic 6.0"的程序为例，介绍窗体和控件的使用。

1.4.1　第一个VB程序

图1.11是"Hello,Visual Basic 6.0"的最终运行效果。

图　1.11　"Hello,Visual Basic 6.0"的最终运行效果图

❑单击“显示”按钮，在界面上显示"Hello,Visual Basic 6.0"。

❑单击“隐藏”按钮，"Hello,Visual Basic 6.0"从界面上消失。

❑单击"Hello,Visual Basic 6.0"链接，"Hello,Visual Basic 6.0"移动到窗体左上角。

❑单击"Hello,Visual Basic 6.0"链接，"Hello,Visual Basic 6.0"移动到初始位置。

❑单击“退出”按钮，或者按下键盘Esc按键，程序结束运行。

首先，根据上一节的介绍，建立一个标准EXE类型的工程，工程名为prjHelloVB.vbp。然后，在窗体中添加一个Label控件和两个CommandBox控件。"Hello,Visual Basic 6.0"的具体设计将结合控件的属性、事件和方法展开。

1.4.2　控件的属性

控件的属性就是控件的性质，如控件的名称、外形、背景颜色、标题、尺寸、位置、风格等。在VB程序设计时，程序员可以通过属性窗口查看和设置控件的属性值。表1.1是控件公用属性的名称和说明。

"Hello,Visual Basic 6.0"的窗体和控件的部分属性设置见表1.2，其余属性为默认设置。

"Hello,Visual Basic 6.0"的窗体大小是固定的，可以通过其BoderStyle属性实现。StartUpPosition属性是程序运行时窗体的启动位置，可以通过属性窗口下面的窗体布局窗口浏览窗体的启动位置。本例将窗体的启动位置设置到显示器的中间。

Label控件的AutoSize属性使得该控件的大小随其Caption值的多少和字体的大小而变化。将AutoSize属性设置为True的好处是：只要不超过窗体大小，无论Label控件要显示的文本有多大都可以正常显示。否则，当文本较多或字体较大时，只能部分显示。在不知道文本大小的情况下，AutoSize属性非常有用。

CommandBox控件的Cancel属性值默认为False。如果将其设置为True，CommandBox控件会响应键盘Esc按键的命令，执行Click事件。本例“退出”按钮的Cancel属性设置为True。顺便指出，欲使CommandBox控件响应键盘Enter按键的命令，可以设置该控件的Default属性值为True。

说明　窗体和控件的Height和Width属性，控件的Height、Width、Left和Top属性一般都不需要设置，用户只需用鼠标拖动即可设置窗体和控件的大小和位置。本书随后所述的部分将省略大小和位置属性的设置。

1.4.3　控件的事件

控件的事件就是控件对外部操作的响应。产生事件的方式有很多，常用的有鼠标事件、键盘事件、焦点事件等。每种类型的事件又有很多不同的形式。如鼠标事件中，有MouseDown事件、MouseMove事件和MouseUp事件。在进行VB程序设计时，事件的响应操作是需要程序员自己开发代码来实现的。表1.3是控件公用事件的名称和说明。

【实例1.1】"Hello,Visual Basic 6.0"的窗体和控件的事件代码如下所示。

01 '"退出"按钮的单击事件

02 Private Sub cmdExit_Click()

03 Unload Me

04 End Sub

05

06 '"隐藏"按钮的单击事件

07 Private Sub cmdHide_Click()

08 lblHelloVB.Visible=False

09 End Sub

10

11 '"显示"按钮的单击事件

12 Private Sub cmdShow_Click()

13 lblHelloVB.Visible=True

14 End Sub

15

16 '窗体加载事件

17 Private Sub Form_Load()

18 lblHelloVB.Visible=True

19 End Sub

20

21 'Label控件单击事件

22 Private Sub lblHelloVB_Click()

23 lblHelloVB.Move 0,0

24 End Sub

25

26 'Label控件双击事件

27 Private Sub lblHelloVB_DblClick()

28 lblHelloVB.Move 1680,2280

29 End Sub

【代码说明】代码第17～19行的Load事件是加载时产生的，但不是窗体启动时的第一个事件，窗体启动时的第一个事件是Initialize事件。本例中，单击“退出”按钮，或者按下键盘Esc键，这两种方式都可以产生第02～04行的“退出”按钮的Click事件。

说明　在代码编写窗口上方，选择对应对象。编写Click和Load等事件代码的时候，可以直接双击对应控件或窗体，就能进入VB自动生成的代码段，然后在代码段中编写即可。

1.4.4　控件的方法

控件的方法其实就是控件自身的动作，如控件的显示或隐藏。"Hello,Visual Basic 6.0"示例中Label控件单击事件和双击事件中都用到了该控件的Move方法。表1.4列出了控件公用方法的名称和说明。

实例1.1中的Move方法有两个参数，分别是Label控件在窗体容器中所要移动到窗体上的指定位置。图1.12是单击"Hello,Visual Basic 6.0"后的运行效果图。

图　1.12　单击"Hello,Visual Basic 6.0"后的运行效果图

1.5　发行应用程序

上述设计的程序还只能在IDE环境中打开和运行。如果要发行VB设计的应用程序，需要使用IDE将其生成可执行文件，然后使用VB自带的打包工具生成安装文件。这样该程序就可以脱离IDE环境，安装到任何一台计算机上都可以运行使用。以第一个VB程序"Hello,Visual Basic 6.0"为例，创建可执行文件的步骤如下所示。

（1）选择菜单栏“文件”菜单项，弹出下拉菜单。

（2）从下拉菜单中选择“生成prjHelloVB.exe”子菜单项，弹出“生成工程”对话框。

（3）使用“生成工程”对话框选择将要生成的可执行文件的保存路径。

（4）单击“确定”按钮即可创建可执行文件。

可执行文件可以在没有安装VB软件的计算机上运行。但是，作为发布的程序，将来还会有更多的资源和文件要和可执行文件一起使用。因此，就有必要将这些文件制作成安装文件。制作安装文件的步骤如下所示。

（1）单击计算机左下角“开始”按钮，弹出活动菜单。

（2）在活动菜单中选择“程序”下级菜单项中的“Microsoft Visual Basic 6.0中文版”选项。

（3）从弹出的下级菜单项中选择“Microsoft Visual Basic 6.0中文版工具”选项。

（4）单击弹出的下级菜单项中的“Package＆Deloyment向导”链接即可打开“打开和展开向导”对话框，如图1.13所示。

图　1.13　“打开和展开向导”对话框

（5）单击“浏览”按钮，选择要打包的工程，如上述创建的prjHelloVB.vbp。

（6）单击“打包”按钮，系统提示已经创建了可执行文件，重新编译程序还是使用该可执行文件打包。如果没有生成可执行文件，系统不会有此提示。

（7）单击“否”按钮，弹出“包类型”选择对话框。

（8）选择“标准安装包”选项，单击“下一步”按钮，弹出“打包文件夹”对话框。

（9）选择打包文件的保存路径，单击“下一步”按钮。系统提示没有找到打包文件夹，是否需要创建。

（10）单击“是”按钮，弹出“打包和展开向导-包含文件”对话框，如图1.14所示。通过“添加”按钮添加需要包含到安装文件中的文件。

图　1.14　“包含文件”对话框

（11）单击“下一步”按钮，弹出“压缩文件选项”对话框。

（12）选择默认选项单击“下一步”按钮，弹出“安装程序标题”对话框。

（13）设置安装文件的标题，单击“下一步”按钮，弹出“启动菜单项”对话框。

（14）选择默认选项，单击“下一步”按钮，弹出“安装位置”对话框。

（15）选择默认选项，单击“下一步”按钮，弹出“共享文件”对话框。

（16）选择默认选项，单击“下一步”按钮，弹出“已完成”对话框。

（17）单击“完成”按钮即可生成打包文件。

该打包文件包含了VB程序中需要使用的所有资源，可以安装到任何一台计算机上使用。

1.6　综合练习

【实例1.2】做一个普通的计算器界面，能够实现数字和运算符的输入功能。使用户通过单击计算器键盘上面的数字或运算符按钮，就能够在计算器的显示屏上显示对应的数字或运算符；并能够清除输入的内容。

【运行效果】图1.15是计算器的运行界面。本练习是一个简单的界面输入示例，不具备计算功能。读者通过学习更多的知识以后可以完善本练习的功能。

图　1.15　计算器示例运行界面

【实例目的】通过本练习，读者可以进一步掌握VB界面的设计方法，熟悉控件的3个基本要素。本练习主要涉及CommandBox控件和TextBox控件，通过综合练习，读者可以巩固CommandBox控件的Click事件编程方法以及TextBox控件的Text属性用法。

【实现步骤】计算器界面的实现过程如下。

（1）向窗体中添加TextBox控件，作为计算器的显示屏，数据或运算符的显示通过其Text属性实现。

（2）将TextBox控件的Alignment属性设置为"1-Right Justify"，这样才能从右边开始显示数据或运算符。

（3）向窗体中添加CommandBox控件，作为计算器键盘的按钮。

（4）在对应按钮的Click事件中，编程实现数据或运算符的输入，如下所示。

01 '输入1

02 Private Sub cmd1_Click()

03 txtScreen=txtScreen+"1"

04 End Sub

05

06 '输入2

07 Private Sub cmd2_Click()

08 txtScreen=txtScreen+"2"

09 End Sub

10

11 '输入/

12 Private Sub cmdDivide_Click()

13 txtScreen=txtScreen+"/"

14 End Sub

15

16 '输入=

17 Private Sub cmdEqual_Click()

18 txtScreen=txtScreen+"="

19 End Sub

【代码说明】计算器中的每个按钮都需要针对一个事件。代码第2～4行是针对“1”按钮的事件；代码第7～9行是针对“2”按钮的事件；代码第12～14行是针对“/”按钮的事件；代码第17～19是针对“=”按钮的事件。读者可以参照上述代码完成其余部分。

说明　本练习使用TextBox控件的Text属性显示数据或运算符，但是上述给出的代码中没有txtScreen.Text出现。这是因为VB允许程序员省略.Text，这里的txtScreen与txtScreen.Text作用是等价的。

1.7　两个VB操作技巧

使用VB语言的目的是快速开发，本例为了让读者能在初期就掌握快速开发的特点，这里先介绍两个最常用的技巧：设置文本的位置和自动跳行。

1.7.1　动态设定TextBox控件的文本靠左、右、中

可以通过TextBox控件的Alignment属性进行设置。如本章综合练习中txtScreen.Alignment=1就是对应TextBox控件的文本靠右显示；Alignment属性值为0时靠左显示文本；Alignment属性值为2时居中显示文本。很多控件在显示文本的时候有同样的用法。

1.7.2　让文本框输入完后直接跳入下一行

在编写程序的时候，特别是编写数据库软件，经常会遇到这样一个问题。在文本框中输入数据以后，用户会习惯性地按下Enter键，表示输入结束，并希望进入下一行继续输入数据。

新建一个VB工程，在窗体中添加两个TextBox控件。在第一个文本框中输入结束后，按下Enter键光标进入第二个文本框，示例代码如下。

01 Sub Text1_keyPress(KeyAscii As Integer)

02 If KeyAscii=13 Then

03 SendKeys"{tab}"

04 End If

05 End Sub

06

07 Sub Text2_keyPress(KeyAscii As Integer)

08 If KeyAscii=13 Then

09 SendKeys"{tab}"

10 End If

11 End Sub

技巧　Enter键的ASCII码是13，KeyAscii=13表示从键盘输入回车键。SendKeys语句的作用是将按键消息发送到活动窗口，与从键盘输入的效果一样。

1.8　小结

本章主要介绍VB的集成开发环境和VB窗体和控件的使用。集成开发环境有很多功能，熟悉这些功能可以使读者在程序开发中达到事半功倍的效果。但是，熟悉集成开发环境的全部功能比较困难，初学者可以先掌握一些基本的功能。VB的窗体和控件是VB可视化编程的重点对象，读者只要理解其属性、事件和方法的内涵，那么VB程序设计就显得非常简单。所有的控件用法都类似，读者可以从Form窗体、CommandBox控件和Label控件着手学习。第2章将介绍VB语法方面的基本知识，如变量等。

1.9　本章练习

1.VB控件的三个基本要素是什么？

2.响应CommandBox控件的Click事件的常用方式是哪些？

3.给出下列程序的执行结果。

01 Private Sub Form_Load()

02 BackColor=vbYellow

03 End Sub

4.通过CommandBox控件的Enabled属性，对"Hello,Visual Basic 6.0"示例进行一些改进，使得“显示”按钮只在"Hello,Visual Basic 6.0"被隐藏时可用，“隐藏”按钮在"Hello,Visual Basic 6.0"显示时可用。

提示　在窗体Load事件中将“显示”按钮的Enabled属性设置为False，将“隐藏”按钮的Enabled属性设置为True；在“显示”按钮的Click事件中将该按钮自身的Enabled属性设置为False，将“隐藏”按钮的Enabled属性设置为True；在“隐藏”按钮的Click事件中将该按钮自身的Enabled属性设置为False，将“显示”按钮的Enabled属性设置为True。

第2章　对象和窗体

对象是面向对象语言里非常重要的概念，也是作为面向对象语言的Visual Basic的非常重要的概念。Visual Basic的对象主要分为窗体和控件两类。本章将介绍Visual Basic最基本的两种对象（窗体和控件）之一的窗体。本章包括以下知识点。

❑窗体的属性、方法和事件。

❑MDI窗体的应用。

❑了解对象及其属性。

通过本章的学习应该掌握窗体的三大特点：属性、方法和事件。

2.1　对象

在Visual Basic中，所有窗体和控件都是对象，每一个对象都拥有同类对象的各种特点和功能。在面向对象程序设计语言里将其特点称为属性，将其功能称为事件或者方法。

2.1.1　什么是对象

在面向对象的程序设计思想里，大千世界的万物都是对象。为了方便研究和管理，人们把拥有共同特征和行为的对象都放到了一起，并引入了类的概念。如黑猫属于猫类，白猫也属于猫类，因为它们都具有猫的共同特征和共同行为，例如，外表都像猫，都会抓老鼠。但也有各自独特的特征和各自独特的行为，例如，黑猫的颜色是黑色的，白猫的颜色是白色的。在Windows的对话框中，确定按钮属于按钮类，取消按钮也属于按钮类，它们都有按钮类的特征和行为，但是，上面显示的文字是不一样的，单击后的效果也不一样。这里的黑猫、白猫、确定按钮和取消按钮都是对象，都具有本类的共同特征和行为，也有自己独特的特征和行为。

Visual Basic中的对象与普通的面向对象语言的对象在概念上是一样的，但使用方法有很大的区别。在Visual Basic中经常用到的对象主要有窗体和控件两种。在Visual Basic中将对象的特征称为属性，将行为称为事件或方法。按钮上显示的文字、按钮的高度和宽度等都称为属性，单击按钮称为事件。

2.1.2　对象属性设置

对象大部分属性可以通过属性窗口设置，也可以在程序代码里设置。在属性窗口里设置对象属性的方法如下：

（1）通过用鼠标左键单击，选中对象。使其周围有8个小方块出现。

（2）在属性窗口里找到相应属性，改变该属性右侧栏里的值后，对象属性即可被改变。

改变属性栏里的值，根据属性的不同，方法也不同。通常有以下3种。

❑直接输入新属性值。例如，改变按钮上显示的文字时，Caption属性栏内就是直接输入的文字。

❑选择新属性值。有一些属性的值是系统提前设定好的，只能从列表中进行选择，而不能直接输入。例如，控制窗体边框样式的BorderStyle属性。

❑利用对话框设置新属性值。例如，控制字体、字号等的Font属性。

通过代码设置对象属性的方法是在具体的过程里写入改变对象属性的代码行，通用格式如下：

对象．属性=属性值

例如，想要通过程序将一个名叫Command1的按钮的Caption属性设置为"OK"。其代码应该写成：

Command1.Caption="OK"

又例如，想要设置Command1按钮的高度为1000（按钮的高度由其Height属性控制）。其代码应该是：

Command1.Height=1000

读者可能已经注意到了一个细节，在代码中为什么"OK"上有双引号，而1000上没有双引号。这就引出了一个概念——数据类型。因为在本书第3章中会详细讲解数据类型，所以在此将简单介绍。

Visual Basic的对象属性主要有数值型、字符型、逻辑型和枚举型等几种数据类型。如上述的Caption属性属于字符型属性，Height属性属于数值型属性等。设置字符型属性时要在属性值上加双引号，设置数值型属性时不用加双引号直接写数字。逻辑型属性只有两种值，分别为真和假。Visual Basic中用True代表真，用False代表假。如按钮对象的Visible（可见）属性就是逻辑型属性。枚举型属性是指其值只可以在固定的几个值中进行选择的属性。

2.2　窗体概述

在Windows编程中，窗体是界面设计的基础，没有窗体也就无从谈起用户界面。因此窗体在程序设计中的重要性是可想而知的。所以掌握窗体的相关知识是非常必要的一件事情。

2.2.1　窗体的构成

首先介绍窗体的构成，如图2.1所示。窗体最上边的蓝色横条是标题栏。标题栏内右侧三个按钮从左到右分别为“最小化”按钮、“最大化”按钮和“关闭按钮”。如果单击窗体的“最小化”按钮，窗体会从屏幕上消失，并缩小为Windows任务栏上的一个按钮，再次单击其就会恢复原状。单击“最大化”按钮可以将窗体扩充至整个屏幕。单击“关闭”按钮可以关闭窗体。标题栏内最左侧的图标为控制菜单图标，单击它就会出现一个下拉菜单。控制菜单图标右侧的文字"Form1"是该窗体的标题。

图　2.1　窗体构成

2.2.2　添加和删除窗体

在一个大型的应用程序中，一般会有多个窗体。而新建的Visual Basic工程中，只包含一个窗体。下面介绍在工程中添加新窗体的3种方法。

❑单击工具栏上的“添加窗体”
按钮。在出现的“添加窗体”对话框的“新建”选项卡中，双击“窗体”图标，如图2.2所示，这时会看到集成开发环境里多出了一个标题为Form2的新窗体，在工程资源管理器中也多了一个Form2，如图2.3所示。

技巧　使用“现存”选项卡可以把以前设计好的窗体添加到当前工程中。

图　2.2　添加窗体对话框（新建选项卡）

图　2.3　工程资源管理器

❑选择“工程”|“添加窗体”命令，其余操作同上。

❑在工程资源管理器中单击鼠标右键（后面称为右击），在弹出的菜单中选择“添加”|“添加窗体”菜单项，其余操作同上。

在程序设计过程中，有时也需要将没用的多余窗体从工程中删除。例如，想删除工程中的Form2窗体。具体删除方法为：在工程资源管理器中右击Form2，并在弹出菜单中选择“移除Form2”。

说明　移除窗体不能把保存了的窗体文件从计算机中删除，而只能从当前工程中移除。

2.3　窗体的属性

窗体的属性决定了窗体的外观以及其工作方式。Visual Basic中每个对象都至少有一个属性，大多数对象有多个属性，窗体也是如此。下面将窗体的常用属性介绍给读者。

2.3.1　设置窗体的背景颜色

设置窗体背景颜色的属性为BackColor属性。该属性为数值型属性，其值是一个十六进制数值。可以通过属性窗口和程序代码改变其值。

【实例2.1】假设，已经新建了一个Visual Basic工程，工程包含一个名为Form1的窗体。使用属性窗口将窗体Form1的背景颜色改为蓝色。操作步骤如下。

（1）单击Form1的空白处，选中Form1。如果操作正确，Form1的四周会有8个小方块出现。

（2）在属性窗口里找到BackColor属性并单击它。在右侧会出现一个向下的箭头。单击这个箭头会出现如图2.4所示的下拉框。该下拉框中有两个选项卡“调色板”和“系统”。

图　2.4　BackColor属性（系统选项卡）

（3）单击“调色板”选项卡，在调色板中用鼠标左键单击蓝色，如图2.5所示。这时窗体的背景已经变为蓝色了。

图　2.5　BackColor属性（调色板选项卡）

说明　Visual Basic用十六进制数代表颜色。所以，从调色板中选择颜色后，BackColor属性中显示的是一串十六进制数。

2.3.2　改变窗体边框的样式

设置窗体边框样式的属性为BorderStyle属性。该属性为枚举型属性，其值只能从6个固定值中选择，每个值的作用见表2.1。该属性只能通过属性窗口改变其值。

2.3.3　设置窗体的标题

设置窗体标题的属性为Caption属性。该属性为字符型属性。可以通过属性窗口和程序代码改变其值。

【实例2.2】将窗体Form1的标题改为"Hello"（默认情况下，窗体Form1的标题为"Form1"）。有两种方法可以完成本实例。第一种方法：在设计状态下通过属性窗口设置；第二种方法：通过编写程序代码，在程序运行状态下设置。第一种方法读者可以参考实例2.1来完成。在此用第二种方法完成本题的要求。

【实现步骤】方法如下。

（1）双击Form1窗体，打开代码编辑器。Visual Basic自动生成了Form_Load事件的过程头和过程尾两行代码。

（2）在两行代码的中间写入如下代码：

01 Form1.Caption="Hello"

（3）单击工具栏中的运行按钮，运行程序。这时会看到窗体的标题变为了"Hello"。

该程序的原理是当启动程序的一刹那，窗体就会被加载，从而自动触发了Form_Load事件，随之写在Form_Load事件中的程序代码会被执行。因此运行后Form1窗体的标题为"Hello"。

2.3.4　设置窗体的高度和宽度

设置窗体高度和宽度的属性分别为Height和Width属性。这两个属性为数值型属性，其计量单位为twip（1英寸=1440twip）。可以通过属性窗口和程序代码设置其值。将Form1的高度和宽度分别设置为5000和10000，如果通过代码应该写为：

01 Form1.Height=5000

02 Form1.Width=10000

在Visual Basic中，除窗体以外大多数控件也有Height和Width属性，其作用也是用来设置高度和宽度。

2.3.5　改变窗体的图标

设置窗体图标的属性为Icon属性。一般情况下此属性的设置，都会在设计阶段通过属性窗口设置。其值为.ICO或.CUR格式的图标文件。通过属性窗口的设置方法为：用鼠标单击Icon属性，其右端会出现一个带有三个小点的按钮
。单击该按钮后，会出现一个“加载图标”对话框，通过对话框选择合适的图标文件即可改变窗体图标。

如果通过程序来设置，则需使用LoadPicture函数。LoadPicture函数的具体用法在以后的章节里详细讲解。

2.3.6　改变窗体的名称

设置窗体名称的属性为Name属性。该属性只能在设计阶段通过属性窗口设置。上面实例中出现的Form1.Caption、Form1.Height的Form1就是窗体的名称。该属性的作用是在程序里用来唯一确定一个对象。

给窗体或者控件起名字要按照一定的规则，例如给窗体起名字时开头应该是frm，后面跟着的是窗体的具体名称。例如，一个登录窗体的名称为：frmLogin，这里frm说明了该对象是一个窗体，后面的Login说明了该窗体是一个登录窗体。当一个工程里有几十个窗体时，这样起名会给编程带来很多的好处。当然，也可以不遵循这样的规则。但是，迟早会出现很多的问题。例如，下面两种情况。

❑在程序里看到form23时不知道该窗体的内容是什么，从而必须打开form23窗体查看。

❑想改变某一个窗体的界面或者里面的代码时就会傻眼了，必须一个一个的打开窗体查看是不是要找的那个窗体。

因此，强烈建议读者从开始就养成一种按照规则给对象起名的好习惯。在此，还有一个提醒，在Visual Basic初学者中，经常会有一部分人将Caption属性和Name属性混淆。Caption设置窗体的标题，就像设置人的帽子一样，而Name设置窗体的名称，就像设置人的名字一样。在通常情况下，一个人的帽子可以经常换，而名字生来只能有一个。

2.3.7　设置窗体的背景图片

设置窗体背景图片的属性为Picture属性。可以通过属性窗口和程序代码改变其值。通过属性窗口设置的方法与设置窗体Icon属性的方法一样，在此不再重复。代码改变其值时也需要用到LoadPicture函数。通用格式如下：

窗体.Picture=LoadPicture("文件名")

其中，窗体为窗体文件的名称，即用Name属性设定的名称。文件名是包含路径和扩展名的完整图片文件名。例如，要给Name属性为frmTest的窗体设置背景图片，并且该背景图片带有路径和扩展名的名称为：c:\aa.jpg。代码应当写为：

frmTest. Picture=LoadPicture("c:\aa.jpg")

2.3.8　窗体的显示位置

设置窗体显示位置的属性有两个，分别为Top和Left。Top用来设置窗体的顶边位置，其值代表窗体顶边和屏幕顶边之间的距离。Left用来设置窗体左边的位置，其值代表窗体左边和屏幕左边之间的距离。这两个属性为数值型属性，计量单位为twip。

【实例2.3】如图2.6所示，窗体中两个白色框为文本框控件，其默认名称分别为Text1和Text2，下面有两个按钮控件，名称分别为Command1和Command2，上面分别显示“获得窗体的位置”和“设置窗体的位置”。

图　2.6　实例2.3界面（设计状态）

【运行效果】程序运行后的效果为：（1）单击“获得窗体的位置”按钮时在第一个文本框中显示窗体的Top属性的值，在第二个文本框中显示窗体的Left属性的值。（2）单击“设置窗体的位置”按钮时，窗体将以第一个文本框和第二个文本框内的值移动自己的位置。

【实现步骤】编程操作步骤如下。

（1）新建一个标准EXE工程，里面自动包含窗体Form1。

（2）在工具箱中找到TextBox（文本框）
控件并单击。

（3）将鼠标箭头移动到窗体中，参照图2.6的布局，按下鼠标左键，向右下角拖拽，感觉大小合适时，松开鼠标左键，画出窗体上侧的文本框。系统给该文本框的默认名称为Text1，并在文本框里自动显示"Text1"字样，如果想更改文本框的名称可以设置其Name属性，更改里面显示的文字使用Text属性。本例中为了简单起见，所有对象均使用默认名称。

（4）使用步骤3的方法继续在窗体上画第二个文本框。默认名称为Text2，文本框里自动显示"Text2"字样。

（5）在工具箱中找到CommandButton（命令按钮）控件并单击。

（6）将鼠标移动到窗体合适的位置，按下鼠标左键，向右下角拖拽，感觉大小合适时，松开鼠标左键。画出的按钮控件默认名称为Command1，上面显示的文字也为"Command1"。

（7）使用步骤（6）的方法继续在窗体上画第二个按钮控件。默认名称为Command2，其上会显示"Command2"字样。

（8）为了启动程序时文本框内不显示文字，单击Text1文本框，将其选中。在属性窗口内找到Text属性，将其值"Text1"删除。

（9）使用上一步的方法将Text2文本框内显示的文字"Text2"删除。

（10）用鼠标左键单击Command1按钮，将其选中。在属性窗口内找到Caption属性，将其值改为“获得窗体的位置”。

（11）使用上面的方法将Command2按钮的Caption属性改为“设置窗体的位置”。

（12）双击Command1按钮。在打开的代码编辑器中Visual Basic会自动生成Command1_Click事件的开头和结尾的两行代码，在其中间编写代码如下。

01 Text1.Text=Form1.Top

02 Text2.Text=Form1.Left

说明　第一条语句的作用是将Form1.Top的值，即窗体顶边到屏幕顶边的距离，显示到文本框Text1里面。第二条语句是将Form1.Left的值，即窗体左边到屏幕左边的距离，显示到文本框Text2里面。

（13）双击Command2按钮。在打开的代码编辑器中Visual Basic会自动生成Command2_Click()事件的开头和结尾的两行代码，在其中间写入代码如下。

03 Form1.Top=Text1.Text

04 Form1.Left=Text2.Text

说明　第一条语句的作用是用Text1.Text的值，即Text1文本框中的内容，设置Form1的Top属性。第二条语句是用Text2.Text的值，即Text2文本框中的内容，设置Form1的Left属性。

（14）单击工具栏上的启动按钮或者按键盘上的F5键启动程序。

（15）单击“获得窗体的位置”按钮，这时两个文本框内分别显示窗体的Top和Left属性当前值，如图2.7所示。通过标题栏移动窗体后再单击“获得窗体的位置”按钮，观察其变化。

图　2.7　程序运行效果

（16）在两个文本框内输入想要设置的Top和Left属性的值。例如，在Text1中输入0，在Text2中输入1000。单击“设置窗体的位置”按钮，观察窗体位置的变化。

【代码说明】通过上面的实例应该得知，在程序代码里，不仅可以用属性设置窗体的状态也可以用属性代表当前窗体的各种状态的值。例如，本例Command1_Click()事件中的Form1.Top代表了当前窗体顶边和屏幕顶边之间的距离。又例如，Form1.Caption可以代表窗体当前的标题等。同时，也应该知道不仅是窗体的属性如此，其他控件如文本框的属性也是如此。

2.3.9　窗体的可见性

设置窗体可见性的属性为Visible属性。可以通过属性窗口和程序代码改变其值。该属性为逻辑型属性，因此只有两种值分别为True和False。如果在设计阶段将窗体的Visible属性设置为False，运行后该窗体将不可见，其默认值为True。通过代码改变的格式为：

窗体.Visible=True（或false）

2.3.10　窗体的显示状态

设置窗体显示状态的属性为WindowState属性。窗口的显示状态主要有正常、最小化和最大化等三种状态。如果想启动窗体时就让窗体以最大化的状态显示，应将该属性设置为2；如果想要让窗体以设计时的大小显示，就将设置为0；如果以最小化的状态显示则将该属性设置为1即可。通过代码改变的格式为：

窗体.WindowState=0(或1，或2)

在Visual Basic中，调用属性时窗体名称可以省略，省略后默认窗体对象为属性调用所在模块相关联的窗体。

2.4　窗体的方法

窗体有一些特殊的命令，用来命令窗体应该做什么。在Visual Basic中将其称为方法（Method）。在本节讲述窗体的几个常用方法。

2.4.1　Print（输出）方法

该方法用来向窗口、图形对象或打印机输出数据。在DOS操作系统下运行的老Basic语言中Print是输出数据的主要手段。在正式的Visual Basic程序中已经很少见到用Print方法作为输出数据的手段了。只是在做小编程实验时，偶尔用其输出数据到窗口上。Print方法更多的用处是在立即窗口中输出数据，以便调试程序。下面用一个实例简单介绍该方法。

【实例2.4】（1）在窗口上输出"Hello World！"。（2）输出表达式23+7+8+9+128+99的结果到窗口上。（3）将窗口的高度和宽度输出到立即窗口中。

【实现步骤】首先，做个简单的界面。在窗体上画一个按钮。窗体和按钮的名称均取默认值Form1和Command1。双击按钮，并打开代码编辑器，在Command1_Click()事件中写入如下代码：

01 Form1.Print"Hello World!"

02 Form1.Print 23+7+8+9+128+99

03 Debug.Print Form1.Height,Form1.Width

【运行效果】按下F5键运行程序后，单击Command1按钮后，在窗体的第一行输出了"Hello World！"。第二行输出了加法表达式的结果。并在集成开发环境底部的立即窗口中输出了窗口的高度和宽度值，如图2.8和图2.9所示。

图　2.8　Print语句示例（Form1窗体）

图　2.9　Print语句示例（立即窗口）

【代码说明】代码第1～3行的Command1_Click()事件中出现的Debug是Visual Basic中的对象，主要用来在程序运行时向立即窗口输出数据。从这个例子，引出Print方法的通用格式为：

对象.Print [表达式]

其中，对象为对象的名称，表达式为各种类型的表达式，如果有多个表达式，中间可以用逗号“，”或者分号“；”分隔，是可选项。如果，省略表达式，该语句起到换行的作用。

窗体有两个属性CurrentX和CurrentY，这两个属性在属性窗口中是找不到的，只有在程序运行时才可用，它们的作用是获取或设置窗体当前的光标（看不见的）位置。使用这两个属性可以在窗体上定位要打印的数据。

在讲述这两个属性的用法之前，必须清楚Visual Basic的默认坐标系。在Visual Basic中，原点（0，0）的位置是窗体的左上角（不包括标题栏）。从原点向右横坐标x的值会不断增大，向下则纵坐标y的值不断增大。了解这一点后再看一个CurrentX和CurrentY两个属性的用法。例如，要在窗体的（1000，1000）这个位置上输出"Hello World！"的代码可以写为：

CurrentX=1000

CurrentY=1000

Print"Hello World！"

2.4.2　Move（移动）方法

该方法用于移动窗体或控件。其通用格式如下。

对象.Move left,top,width,height

其中，对象为对象名称，如果省略对象名称，将移动该方法所在模块相关联的窗体。left、top、width、height分别为窗体或对象的left、top、width、height属性。这里left是必须要的，其他3个是可选的。

举个简单的例子说明Move方法，让Form1窗体移动到left为0，top为0的地方，并将其宽度变为5000，高度变为2000。使用Move方法的代码如下。

Move 0,0,5000,2000

2.4.3　Hide（隐藏窗体）方法

该方法用于隐藏窗体对象，但不能使其卸载。通用格式如下：

窗体.Hide

其中，窗体为窗体名称，如果省略窗体名称，将隐藏该方法所在模块相关联的窗体。该方法等同于将窗体的Visible属性设置为False。

2.4.4　Show（显示窗体）方法

该方法用于显示窗体对象。通用格式如下：

窗体.Show style，ownerform

其中，窗体为窗体名称，如果省略窗体名称，将显示该方法所在模块相关联的窗体。style为可选项，其值为0或1。如果style为0，则窗体是无模式的；如果style为1，则窗体是模式的。

模式窗体指要想将焦点移到其他窗口时必须让用户对其采取行动的窗体，例如只有关闭模式窗体后才能选择其他窗口。ownerform也为可选项。其值一般情况下为Me。Me代表所在模块相关联的窗体名称。

2.4.5　Unload（卸载）语句

Unload不是窗体的方法，是Visual Basic用来卸载窗体的语句。其通用格式如下：

Unload窗体

其中，窗体为窗体名称，也可以写成Me。该语句是从内存里将窗体彻底删除。而Hide语句的作用只是将窗体隐藏起来，并不是卸载。

2.5　窗体的事件

事件是窗体或控件能识别的动作。Visual Basic的每一个窗体和控件都有一个预定义的事件集。如果其中有一个事件发生，而且，在关联的事件过程中存在代码，则Visual Basic将会执行这些代码。窗体有很多的事件，本节将介绍其中常用的几个窗体事件。

2.5.1　Click（单击）事件

该事件在窗体或控件被单击时发生。因为事件发生时如果在该事件中写有程序代码，这些程序代码将会被执行，所以Click事件也是如此。在此需要说明的是，必须在窗体的空白区域或无效控件上单击才可以触发此事件。

【实例2.5】用鼠标单击窗体时将窗体的标题改为“窗体Click事件试验”。

【实现步骤】因为只有单击窗体时，才将窗体的标题改为“窗体Click事件试验”。所以，代码应该写在Click事件中。编程步骤如下。

（1）新建一个标准EXE工程。

（2）在设计状态下，双击Form1窗体空白处，打开代码编辑器窗口。

（3）在代码编辑器的过程框中选择Click事件，如图2.10所示。

图　2.10　选择Click事件

（4）在自动产生的Form_Click()事件的开头和结尾之间写入代码如下。

01 Me.Caption="窗体Click事件试验"

说明　Me代表所在模块相关联的窗体的名称。在本例中代表Form1。

【运行效果】按下F5键运行程序，并单击窗体空白处测试运行结果，这里不再给出效果图。

2.5.2　DblClick（双击）事件

该事件在窗体或控件被双击时发生。实际上用鼠标双击窗体或控件时不仅会触发DblClick事件，同时也会触发Click事件。

2.5.3　Load（载入）事件

该事件在窗体被加载时自动发生，也可以说是窗体在屏幕上显示之前发生。Load事件是一个很重要的事件。对于各种初始化的操作通常情况下都要在该事件中进行。

说明　上面所说的加载是将一个文件从磁盘或磁盘缓冲区调入到内存的过程。

【实例2.6】启动程序后，让窗体在屏幕的正中央出现。

【实现步骤】编程步骤如下。

（1）新建一个标准EXE工程。

（2）在设计状态下，双击Form1窗体空白处，打开代码编辑器窗口。

（3）找到Form_Load事件。如果在代码编辑器中没有此事件的开头和结尾，从过程框中选择Load即可。

（4）在该事件开头和结尾之间写入代码后的代码编辑区中内容为：

01 Private Sub Form_Load()

02 Me.Move (Screen.Width-Me.Width)/2,(Screen.Height-Me.Height)/2

03 End Sub

【代码说明】程序中的Screen.Width为显示屏幕的宽度,Screen.Height为显示屏幕的高度。

【运行效果】按下F5键运行程序，将会看到窗体在屏幕的正中央出现。

技巧　使用窗体布局窗口也可以设置窗体在屏幕中出现的位置。不过，由于是手动设置，所以出现位置的精确度不如上面的程序。

2.5.4　Unload（卸载）事件

该事件在窗体被卸载时发生，也可以说是在单击窗口的关闭按钮
或使用卸载（Unload）语句时发生。Unload事件同样是一个很重要的事件。窗口关闭时所有的善后工作都要在此事件中进行。

Unload事件的一个典型应用是解决关闭窗体时跳出是否真的要关闭，或是否要保存等提示框。由于这个典型应用使用到的内容是本书后面章节的内容，因此下面实例只是叙述一下编程步骤，对于具体的代码内容不在此处讲解。

【实例2.7】单击窗口的关闭按钮时，弹出一个小对话框进行确认操作。

【运行效果】如图2.11所示。当用户单击“是”按钮时，关闭窗口；当单击“否”时，此窗口不被关闭继续运行。

图　2.11　实例2.7的运行效果

【实现步骤】编程步骤如下。

（1）新建一个标准EXE工程。

（2）在设计状态下，双击Form1窗体空白处，打开代码编辑器窗口。

（3）从代码编辑器的过程框中选择Unload。

（4）在Unload事件开头和结尾之间写入代码后的代码编辑区中内容为：

01 Private Sub Form_Unload(Cancel As Integer)

02 Dim x As Integer

03 x=MsgBox("您真的要关闭窗口么？",vbYesNo)

04 If x=vbNo Then

05 Cancel=-1

06 End If

07 End Sub

（5）按下F5键运行程序，试着关闭窗口。

【代码说明】在Visual Basic的语句中，所有的标点符号都必须是英文状态下的标点符号。因此上面实例中的，“您真的要关闭窗口么？”两边的双引号是英文状态下的双引号，输入时注意切换输入法状态。

技巧　频繁地切换输入法状态确实很让人无奈，有时更会因为一时的疏忽忘了切换输入法，因此输入了中文标点符号从而引起程序运行出错。有一个技巧是先将英文符号和表达式都写完。然后回过头来，再切换成中文输入法将中文文字添加到英文符号之间。

Unload事件在代码编辑器中自动生成的事件开头和上面介绍的其他事件开头有所不同。在Unload后面的括号里有串文字，其中将"Cancel"称为事件的参数。该参数被设置为任何一个非零值时会取消对窗体的卸载。

2.5.5　Resize（改变大小）事件

该事件在窗体第一次显示或者改变窗体大小时发生，主要作用是其中写入改变窗体大小时移动控件或调整控件大小的程序代码。

【实例2.8】窗体Form1上有一个文本框Text1占满整个窗体，如图2.12所示。要求：不管怎样改变窗体大小，Text1都会占满该窗体。

图　2.12　实例2.8（设计状态）

编写程序如下。

01 Private Sub Form_Resize()

02 Text1.Move 0,0,Width,Height

03 End Sub

【代码说明】Resize事件中语句的作用是将Text1文本框用其Move方法移动到紧挨着窗体的左边和顶边，并且将其宽度和高度设为窗体的宽度和高度。

【运行效果】通过下面两种方法观察本实例的运行结果

❑运行程序后，改变窗体的大小观察其变化

❑在设计状态，删除Resize事件中的语句，然后运行程序通过改变窗体大小观察其变化。

说明　控件的Move方法和前面讲过的窗体的Move方法的格式和用途是一样的。区别在于一个是移动控件，一个是移动窗体。Width和Height前没有加对象名代表本窗体的宽度和高度。

2.5.6　Activate（活动）/Deactivate（非活动）事件

当窗口变为活动窗口时触发Activate事件。当窗口变为非活动窗口，即其他窗口成为了活动窗口时触发Deactivate事件。

一个窗体可以通过被单击，或使用代码中的Show或SetFocus方法之类的用户操作而变成活动窗体。

2.5.7　Paint（绘制）事件

在一个窗体被移动或放大之后，或在一个覆盖该窗体的窗体被移开之后，该窗体部分或全部暴露时，触发此事件。Paint事件主要用于在窗体上绘图。

2.6　多窗体应用

在大型的应用程序中通常会有多个窗体存在。本书在前面的2.2节中讲述了怎样在一个工程中添加窗体的方法。本节将讲述通过程序代码怎样让窗体之间互相调用和一些与多窗体应用相关的知识。

2.6.1　多窗体应用实例

【实例2.9】设计一个包含两个窗体的程序，两个窗体分别如图2.13和图2.14所示。第一个窗体里有一按钮，上面写着“显示第二个窗体”。第二个窗体里也有一个按钮，上面写着“显示第一个窗体”。通过单击按钮，可以切换显示这两个窗体。

图　2.13　第一个窗体

图　2.14　第二个窗体

【实现步骤】具体编程步骤如下。

（1）新建一个标准EXE工程。里面自动包含一个名称为Form1的窗体，将其Name（名称）属性改为frmFirst（即将窗体名称从Form1改为了frmFirst），将其Caption属性改为“第一个窗体”，并用Picture属性为窗体增加一个背景图片。

（2）在窗体frmFirst上，使用CommandButton（按钮）控件画一个按钮，并将其Name属性改为cmdShowSecond，将其Caption属性改为“显示第二个窗体”。

（3）选择“工程”菜单，从里面选择“添加窗体”菜单项。

（4）在“添加窗体”对话框的“新建”选项卡内选择窗体图标，单击“打开”按钮。这时，工程资源管理器中会出现Form2窗体的名称，表明工程中已经添加了一个新窗体。

（5）将窗体Form2的Name属性改为frm，将其Caption属性改为“第二个窗体”，并用Picture属性也为其增加一个背景图片。

（6）在窗体frmSecond上，同样使用CommandButton控件画一个按钮，将其Name属性改为cmdShowFirst，将其Caption属性改为“显示第一个窗体”。本例工程文件中对象的属性设置见表2.2。

（7）在设计状态下，用鼠标单击窗体frmFirst（窗体1）的任一露出部位将其激活（或者在资源管理器中双击frmFirst将其激活）。

（8）双击该窗体上的“显示第二个窗体”按钮，打开与该窗体相关联的代码编辑器。并在cmdShowSecond_Click()事件中写入如下代码。

01 frmSecond.Show

02 frmFirst.Hide

其中，第一条语句的作用是将窗体frmSecond（窗体2）显示到屏幕上。第二条语句的作用是将窗体frmFirst从屏幕上清除，隐藏起来。在此语句frmFirst.Hide可以直接写为Hide，起到的效果是一样的，因为省略了窗体名的Hide会以代码所在的模块相关联的窗体名为默认名，即程序代码是通过哪个窗体打开的默认名就是哪个窗体的名称。所以Hide前可以不加窗体名。

（9）将窗体frmSecond激活，并双击其上“显示第一个窗体”按钮，打开代码编辑器。并在cmdShowFirst_Click()事件中写入如下代码。

03 frmFirst.Show

04 frmSecond.Hide

（10）按下F5键运行程序，查看结果。

【代码说明】给窗体或者控件起名字时，应当尽量考虑到见名知意。除此之外，虽然Visual Basic中不区分英文字母的大小写，但是建议最好采用驼峰效应，即从第二个单词开始以后的所有单词的头一个字母都要大写，这样会使自己或者其他人员更方便地读懂程序。

说明　在编写代码时不必注意区分大小写。只是在给控件起名时注意就可以了。例如，在代码编辑区内写frmfirst也是正确的，而且Visual Basic会自动将其变为frmFirst。

本实例虽然简单，但是可以将编写多窗体应用程序的步骤和方法较好的体现了出来。现在读者肯定有了一个问题，为什么总是第一个窗体先启动，如何才能让第二个窗体先启动。在下一小节将讲述解决此问题的方法。

2.6.2　指定启动窗体

在默认情况下，多窗体应用程序中的第一个窗体被指定为启动窗体。应用程序开始运行时，此窗体就会被显示出来。如果想在应用程序启动时显示别的窗体，那么就必须指定启动窗体。例如，想要先启动实例2.9的第二个窗体。操作步骤如下。

（1）选择“工程”菜单中最下面的“工程1属性”菜单项。

（2）在打开的“工程属性”对话框的“通用”选项卡内找到“启动对象”，并在下拉列表中选择需要最先启动的窗体名称frmSecond，然后单击“确定”按钮。

（3）按下F5键运行程序。这时会看到第二个窗体先启动了。

2.6.3　多窗体程序的存取

多窗体程序的存取和单窗体程序的存取基本上一样。只是在存储时要注意将多个窗体一个一个的保存好，最后还要记得保存工程。打开时要注意打开工程文件，Visual Basic会自动将属于这个工程文件的窗体全部打开，将其列在工程资源管理器中。下面以保存和打开实例2.9，说明多窗体程序的存取方法。

1.保存

（1）单击工具栏上的保存工程
按钮。

（2）在第一个跳出的“文件另存为”对话框中，选择好保存路径，并输入将要保存的文件名，这里取默认名。单击“保存”按钮。

（3）保存了第一个窗体文件后，还会跳出第二个“文件另存为”对话框，这是在保存另一个窗体。用步骤2的方法保存即可。

（4）保存完两个窗体之后，会跳出“工程另存为”对话框，其默认名称为“工程1”，将其改为“多窗体应用实例”后保存。

说明　工程文件中有多少个窗体，“文件另存为”对话框就会跳出多少个。

2.打开

（1）单击工具栏中的“打开工程”
按钮。

（2）在“打开工程”对话框中，选择需要打开的工程文件，如选择上面保存的“多窗体应用实例.vbp”，单击“打开”按钮。

2.6.4　标准模块和Sub Main过程

1.标准模块

在使用Visual Basic编程时，编程人员通常会把一些公共变量的声明、公共函数或者过程写在标准模块里。在工程中建立标准模块有如下几种方法。

❑选择“工程”菜单内的“添加模块”菜单项。在打开的“添加模块”窗口中用“新建”选项卡可以新建一个新的模块，用“现存”选项卡可以将一个现存的模块文件导入到本工程中。

❑在工程资源管理器内单击鼠标右键。从弹出的菜单中选择“添加”|“添加模块”菜单项。

❑单击工具栏中“添加窗体”快捷按钮右侧的小三角，从下拉列表中选择“添加模块”。

经过上述操作，可以在工程资源管理器中看到多出了一个Module1，这就是标准模块文件，其扩展名为.bas。双击即可打开Module1中的代码。

2.Sub Main过程

在指定启动窗体时，打开的“工程属性”对话框“启动对象”下拉列表里除了可以看到工程中的窗体名以外还有一个Sub Main，即Sub Main也可以成为启动对象。在多窗体应用程序中，有时需要在启动窗体之前执行一些代码。Sub Main就是为了解决这个问题而应运而生的。

在Visual Basic中称Sub Main为启动过程（注意：这里的过程是名词，并非动词），并且需要写在标准模块中。方法是打开标准模块的代码区后写入

Sub Main

然后回车，将显示该过程的开头和结尾语句，在这两条语句之间输入程序代码即可。通常Sub Main过程中的语句结构是：

Sub Main()

...

...

窗体.show

End Sub

其中，在省略号的位置输入在窗体启动前需要执行的代码。等这些代码一条条从上到下执行完毕后最后执行显示窗体的语句，这时窗体才会显示到屏幕上。

2.6.5　MDI窗体

MDI窗体是一种有父子关系的窗口。在绝大多数应用程序中，编程人员通常都要建立一个标准的“主”窗体，用户在其中完成几乎所有的工作。有时，还要添加一些特殊用途的窗体用来处理程序的输入和输出。其实，Visual Basic支持层次结构，这种结构是程序中的多个窗体一起工作时所表现的一种特殊关系。这种特殊的窗体称作MDI（多文档界面）窗体。由于作用不同，将MDI窗体分为了父窗体和子窗体。在“工程”菜单中选择“添加MDI窗体”菜单项，可以创建MDI父窗体。如果要创建子窗体，首先，在“工程”菜单中选择“添加窗体”菜单项，然后将添加进来的新窗体的MDIChild属性设置为True。除下列情况外，MDI窗体和普通窗体在运行时的行为相同。

❑所有的子窗体都在其父窗体的窗口中显示。

❑当将一个子窗体最小化时，它不是变成任务栏上的一个按钮，而是缩小为MDI窗体上的一个小标题条。

❑当将一个父窗体最小化时，该父窗体和其所有子窗体都变为任务栏上的一个按钮。

❑所有子窗体菜单都在其父窗体菜单上显示。当最大化子窗体时，其标题显示在其父窗体的标题栏中。

当将某个父窗体的AutoShowChildren属性设置为True，就可以显示其所有的子窗体。父窗体和子窗体在所谓的“以文档为中心的应用”中特别有用，在这种应用程序中，通常同时使用多个窗口来显示或编辑文档。其实，Visual Basic6.0本身就是很好的一个MDI窗体应用例子。本书第23章的综合实例也用到了MDI窗体。

2.7　小结

本章从窗体方面介绍了各种窗体的属性、事件等，包括窗体运行时需要加载的事件，还包括窗体的单击、双击、卸载、绘制等常见事件。通过本章的学习，读者应该熟悉我们可以在窗体中进行什么操作，可以为窗体实现哪些功能。

2.8　本章练习

1.MDI窗体和普通窗体有什么区别？如何创建MDI窗体？

2.Form_Load()事件中书写下面代码后的效果是什么样的？

Me.Move (Screen.Width-Me.Width)/2,(Screen.Height-Me.Height)/2

提示　以上代码经常用在登录窗体中。

3.有一个子窗体有自己的菜单，如果该子窗体获得了焦点，那么该子窗体的菜单会显示在什么位置？

说明　这个问题需要读者亲自动手来试验。

第3章　Visual Basic语言基础

人们都知道计算机能够完成各种复杂的任务。其实不管是简单的或者是复杂的、抽象的，甚至是不可思议的任务，计算机执行的每一个操作都是按照人们事先指定的内容和步骤进行。这些事先指定的内容和步骤就称为程序。本章包括以下知识点。

❑VB的基本数据类型。

❑VB变量的使用方法和命名原则。

❑运算和内部函数可以提高编程效率。

❑VB程序设计中推荐的编码规则。

Visual Basic是在BASIC、GW-BASIC、Quick BASIC等语言的基础上发展起来的，它保留了原来BASIC版本中的数据类型和语法，对其中的某些语句和函数的功能作了修改或扩展，并根据语言的可视性要求增加了一些新的功能。本章将简单介绍Visual Basic程序设计的基本语法，为今后程序设计过程提供语言基础。

3.1　程序设计的基本概念

Visual Basic语言编写的程序是由各种不同的词法记号构成的，所谓词法记号，是指程序中具有独立含义的不可进一步分割的单位。具体地说，Visual Basic语言的词法记号包括关键字、标识符、变量等元素。

Visual Basic中由各种词法记号组成一条条语句，执行具体操作指令，并且每个语句以回车键结束。如果设置了“自动语法检测”（用“工具”菜单中“选项”命令对话框中的“编辑器”选项卡），则在输入语句的过程中，Visual Basic将自动对输入的内容进行语法检查，如果发现了语法错误，则弹出一个信息框，提示出错的原因。

Visual Basic会按自己的约定对语句进行简单的格式化处理，例如，命令词的第一个字母大写，运算符前后加空格等。无论输入Print、print，还是输入PRINT，按回车键后都变成Print，为了提高程序的可读性，在代码中应加上适当的空格，同时按惯例处理字母的大小写。下面先介绍组成语句的词法记号：关键字和标识符的含义。

3.1.1　关键字

关键字是Visual Basic语言中预定义的符号，它们有固定的含义，用户定义的任何名字不得与它们冲突。

例如：If、Then、End、While、For等，表示命令语句，用于控制结构。

例如：Boolean、Byte、Integer、Long、Single、Private、Public等，用于数据类型。

例如：Print、InputBox( )、Sub、Function等，用于方法、函数和过程。

在Visual Basic中关键字以蓝色显示。

3.1.2　标识符

标识符是用户自行定义的符号，用来标示常量、变量、数据类型、过程、函数、类等。利用标识符可以完成对变量、常量、数据类型、过程、函数、类等的引用。Visual Basic 6.0中标识符的命名按如下原则。

❑第一个字必须是字母（没有大小写之分），后跟字母（A～Z，a～z）、数字（0～9）或下划线（_）。

❑可以长达255个字。

❑变量内可以包含字母、数字或下划线，但是不能包含小数点、%、＆、！、#、@、$等代表变量类型的结尾符号。

❑不能使用保留字作变量名。

3.2　数据类型

在Visual Basic语言中，为解决具体问题，要采用各种类型的数据，数据的类型不同，它所表达的数据范围、精度和所占据的存储空间均不相同。数据类型体现了数据结构的特点。Visual Basic提供了系统定义的数据类型，并允许用户根据需要定义自己的数据类型。

3.2.1　基本数据类型

Visual Basic 6.0提供的基本数据类型主要有字符串型数据和数值型数据，此外还提供了字节、货币、对象、日期、布尔和变体数据类型。

1.字符串（String）

字符串是一个字符序列，由ASCII字符组成，包括标准的ASCII字符和扩展ASCII字符。在内存中一个字符用一个字节来存放。在Visual Basic中，字符串是放在双引号内的若干个字符，其中长度为0（即不含任何字符）的字符串称为空字符串。

字符串通常放在引号中，例如：

"hello"

"请输入用户名："

"你的用户名或密码错误！请重新输入"

""

Visual Basic中的字符串分为两种，即变长字符串和定长字符串。其中变长字符串的长度是不确定的，可以有0～2^31
（2的31次幂，约21亿）个字符。而定长字符串含有确定个数的字符，最大长度不超过2^16
（2的16次幂，65535）个字符。

2.数值

Visual Basic的数值型数据分为整型数和浮点数两类。其中整型数又分为整数和长整数，浮点数分为单精度浮点数和双精度浮点数。

（1）整型数

整型数是不带小数点和指数符号的数，在机器内部以二进制补码表示。例如，整型数2、-2分别以下面的形式存放：

2 00000000 00000010

-2 11111111 11111110

❑整数（Integer）：整数以两个字节（16位）的二进制码表示和参加运算，其取值范围为-32 768～32 767。对于-32 768～32 767之间的一个数字，在尾部加一个“%”符号也表示一个整型数，如123%，-45%。

❑长整数（Long）：长整数以带符号的4个字节（32位）二进制数存储，其取值范围为-2 147 483 648～+2 147 483 647。在-2 147 483 648～+2 147 483 647之间的数尾数加一个“＆”符号也表示一个长整数，如123 456＆。

（2）浮点数

浮点数也称实型数或实数，是带有小数部分的数值。它由3部分组成：符号、指数及尾数。单精度浮点数和双精度浮点数的指数分别用"E"（或"e"）和"D"（或"d"）来表示。例如：

12.345E6或12.345e+6

单精度数，相当于12.345乘以10的6次幂。

12.34567D6或12.34567d+6

双精度数，相当于12.34567乘以10的6次幂。

在上面的例子中，12.345或1234567是尾数部分，e+6（也可以写作E6或e6）和d+3（也可以写作D3或d3）是指数部分。

❑单精度浮点数（Single）：以4个字节（32位）存储，其中符号占1位，指数占8位，其余23位表示尾数，此外还有一个附加的隐含位。通常以指数形式（科学记数法）来表示，以"E"或"e"表示指数部分。单精度浮点数可以精确到7位十进制数，其负数的取值范围为-3.402 823E+38～-1.401 298E-45，正数的取值范围为1.401 298E-45～3.402 823E+38。

❑双精度浮点数（Double）：用8个字节（64位）存储，其中符号占1位，指数占11位，其余52位用来表示尾数，此外还有一个附加的隐含位。通常以指数形式（科学技术法）来表示，以"D"或"d"表示指数部分。双精度浮点数可以精确到15或16位十进数，其负数的范围为-1.797 693 134 862 316D+308～-4.940 65D-324，正数的范围为4.940 65D-324～1.797 693 134 862 316D+308。

3.货币（Currency）

货币数据类型是为表示钱款而设置的。该类型数据以8个字节（64位）存储，精确到小数点后4位（小数点前有15位），在小数点后4位以后的数字将被舍去。一般数值型数据在计算机内是通过二进制方式进行运算的，因而有误差，而货币型数据是用十进制方式进行运算的，所以具有比较高的精确度，其取值范围为-922 337 203 685 477.580 8～922 337 203 685 477.580 7。对于该取值范围内的数，尾部带“@”符号也表示货币型数，如123@、456.5@。

浮点数中的小数点是“浮动”的，即小数点可以出现在数的任何位置，而货币数据类型的小数点是固定的，因此称为定点数据类型。

4.变体（Variant）

变体数据类型也称通用型，是一种通用的、可变的数据类型，可以表示任何值，包括数值、字符串、日期/时间等。假设定义x为通用型变量：

Dim x As Variant

在变量x中可以存放任何类型的数据，如：

x=3.5 '存放一个实数

x="BASIC" '存放一个字符串

x="08/31/2007" '存放一个日期型数据

根据赋给的值的类型不同，变量x的类型也在不断变化，这就是称之为变体型的由来。当一个变量为定义类型时，Visual Basic自动将该变量定义为Variant类型。不同类型的数据在Variant变量中是按其实际类型存放的（例如将一个整数赋值给x，在内存区中按整型方式存放），用户不必作任何转换，转换的工作由Visual Basic自动完成。

Visual Basic提供一种VarType函数，用来测定一个Variant变量的实际数据类型。VarType函数的值是一个数值，其含义见表3.1。

例如：

01 Dim Var1 As Variant

02 Int1=123

03 Long1=456＆

04 Single1=12.3！

05 Double1=45.6

06 Str1="abc"

07 Cur=1234@

08 Da=#07/12/2003#

09 Print VarType(Var1),VarType(Int1),VarType(Long1),VarType(Single1)

10 Print VarType(Str1),VarType(Cur),VarType(Double1),VarType(Da)

Var1被定义成Variant型变量，程序中未对它赋值，其他各变量均未声明何种类型，也一样按Variant型对待。分别对8个Variant型变量赋值。然后用VarType函数测试这8个变量的实际类型，可以从输出结果中看到它们的实际数据类型。

为运行以上程序段，可以在窗体上添加一个命令按钮，设名称为Command1，再将以上程序段写在Command1_Click事件过程中。运行程序，单击命令按钮，输出结果如下：

0（未赋值）　2（整型）　3（长整型）　4（单精度型）

8（字符串型）　6（货币型）　5（双精度型）　7（日期型）

5.其他数据类型

除上面介绍的数据类型外，在Visual Basic 6.0中还可以使用其他数据类型，如下所示。

（1）字节（Byte）

字节实际上是一种数据类型，以1个字节的无符号二进制数存储，其取值范围为0～255。

（2）布尔（Boolean）

布尔型数据是一个逻辑值，用两个字节存储，它只取两种值，即True（真）或False（假）。

（3）日期（Date）

日期存储为IEEE 64位（8个字节）浮点数值形式，可以表示的日期范围从公元100年1月1日～9999年12月31日，而时间可以从0:00:00～23:59:59。任何可辨认的文本日期都可以赋值给日期变量。日期文字须以符号“#”括起来，例如，#January 1 2006#。

日期型数据用来表示日期信息，其格式为mm/dd/yyyy或mm-dd-yyyy，取值范围为1/1/100到12/31/9999。

注意　在当前的Visual Basic版本中，输出年份时通常只输出后两位，例如“1997”输出时为“97”。对于2000年以后的年份，其输出为“00”、“01”等。因此，在输出2000以后的年份时，应作适当处理（例如在前面加上“20”）。

（4）对象（Object）

对象型数据用来表示图形、OLE对象或其他对象，用4个字节存储。

（5）Decimal数据类型

Decimal变量存储为96位（12个字节）无符号整型形式，并除以一个10的幂数，称为变比因子。这个变比因子决定了小数点右面的数字位数，其范围从0～28。当变比因子为0（没有小数位）时，最大的可能值为±79 228 162 514 264 337 593 543 950 335。而在有28个小数位的情况下，最大值为±7.922 816 251 426 433 759 354 395 033 5，而最小的非零值为±0.000 000 000 000 000 000 000 000 000 1。

注意　目前Decimal数据类型只能在变体类型（Variant）中使用，也就是说，不能把一个变量声明为Decimal类型。

以上介绍了Visual Basic中的基本数据类型。表3.2列出了这些数据类型的名称、取值范围和存储要求。

3.2.2　自定义数据类型

有时会感到仅有以上基本数据类型是不够的，还需要将不同类型的数据组合成一个有机的整体，以便于引用。这样一个整体是由若干不同类型的、互相有联系的数据项组成的。它类似与C语言中的“结构体”类型的数据。这种结构称为“纪录”。Visual Basic提供了Type语句让用户自己定义这种数据类型。它的格式如下。

Type 数据类型名

数据类型元素名 As 类型名

数据类型元素名 As 类型名

…

End Type

其中“数据类型名”是要定义的数据类型的名字，其命名规则与变量的命名规则相同（见下一节）；“数据类型元素名”也遵守同样的规则，但不能是数组名；“类型名”可以是任何基本数据类型，也可以是用户定义的类型。

用Type语句可以定义类似于Pascal、Ada语言中“记录类型”和C语言中“结构体”类型的数据，因而通常把用Type语句定义的类型称为记录类型。

例如，定义一个名为Student（学生）的类型，其中包括学号、姓名、年龄、电话和住址等信息，定义语句如下。

01 Type Student

02 StuNo As Integer

03 Name As String*10

04 Age As Integer

05 Tel As String*10

06 Address As String*20

07 End Type

在定义了Student类型之后，就可以定义Student类型的变量，例如，定义一个Stu的变量：

Dim Stu As Student

此语句定义了Student类型的变量Stu，它包括5个成员。在后面的程序中可以用“变量.元素”这样的形式来引用各个成员，如下所述。

❑Stu.Name表示Stu变量中的Name成员的值（某学生的名字）。

❑Stu.Address表示Stu变量中的Address成员的值（某学生的家庭住址）。

❑Stu.StuNo表示Stu变量中的StuNo成员的值（某学生的号码）。

在使用Type语句时，应注意以下几点。

❑记录类型中的元素可以是变长字符串，也可以是定长字符串。当在随机文件中使用时，必须使用定长字符串，其长度用类型名称加上一个星号和常数指明，一般格式为：String*常数。这里的“常数”是字符个数，它指定定长字符串的长度，例如：rMonth As String*3。

❑记录类型的定义必须放在模块（包括标准模块和窗体模块）的声明部分，在使用记录类型之前，必须用Type语句加以定义。在一般情况下，记录类型在标准模块中定义，其变量可以出现在工程的任何地方。当在标准模块中定义时，关键字Type前可以有Public（默认）或Private；而如果在窗体模块中定义，则必须在前面加上关键字Private。

❑在记录类型中不能使用动态数组。

❑在随机文件操作中，记录类型数据有着重要的作用。

3.2.3　枚举数据类型

前面介绍了Visual Basic的基本数据类型，包括整型、实型（浮点）、字符串型等。在程序设计中，有些数据无法直接用整型数或实型数来表示，其取值为部分可列举的数值或者字符串等。为此，Visual Basic提供了枚举数据类型。当一个变量只有几种可能的值时，可以定义为枚举类型。所谓“枚举”是指将变量的值一一列举出来，变量的值只限于列举出来的值的范围内。

枚举类型放在窗体模块、标准模块或公用类模块中的声明部分，通过Enum语句来定义，格式如下。

[Public|Private] Enum 类型名称

成员名 [=常数表达式]

成员名 [=常数表达式]

...

End Enum

说明：

（1）Enum语句以Enum开头，以End Enum结束，各参量含义如下。

❑Public：可选。表示所定义的Enum类型在整个工程中都是可见的，在默认情况下，Enum类型被定义为Public。

❑Private：可选。表示所定义的Enum类型只在所声明的模块中是可见的。

❑类型名称：必须有。表示所定义的Enum类型的名称。“类型名称”必须是一个合法的Visual Basic标识符，在定义Enum类型的变量或参数时用该名称来指定类型。

❑成员名：必须有。是一个合法的Visual Basic标识符，用来指定所定义的Enum类型组成元素的名称。

❑常数表达式：可选。元素的值是Long类型。它可以是其他Enum类型。

（2）在Enum语句的格式中，“常数表达式”可以省略。在默认情况下，枚举中的第一个常数被初始化为0，其后的常数则初始化为比前面的常数大1的数值。例如：

01 Public Enum Days

02 Sunday

03 Monday

04 Tuesday

05 Wednesday

06 Thursday

07 Friday

08 Saturday

09 End Enum

定义了一个枚举类型Days，它包括7个成员，都省略了“常数表达式”，因此常数Sunday的值为0，常数Monday的值为1，常数Tuesday的值为2等。

（3）可以用赋值语句显示地给枚举中的常数赋值，即不省略“常数表达式”，所赋的值可以是任何长整数，包括负数。如果希望用小于0的常数代表出错条件，则可以给枚举常数赋一个负值。例如：

01 Public Enum WorkDays

02 Monday

03 Tuesday=0

04 Wednesday

05 Thursday

06 Friday

07 Saturday

08 Sunday

09 Invalid=-1

10 End Enum

在上面的枚举中，常数Invalid被显式地赋值-1，而常数Tuesday被赋值为0。因为Monday是枚举中的第一个元素，所以也被赋值0。Wednesday的数值为1（比Tuesday的数值大1），Thursday的数值为2等。

（4）Visual Basic将枚举中的常数数值看作长整数。如果将一个浮点数值赋给一个枚举中的常数，Visual Basic将该数值取整数为最接近的长整数。

（5）当对一个枚举中的常数赋值时，可以使用另一个枚举中的常数的数值。例如：

01 Public Enum WorkDays

02 Sunday=0

03 Monday

04 Tuesday

05 Wednesday

06 Thursday

07 Friday

08 Saturday=Days.Saturday-6

09 Invalid=-1

10 End Enum

这里的WorkDays枚举的声明与前面的声明等同。

说明　声明枚举类型后，就可以声明该枚举类型的变量，然后使用该变量存储枚举常数的数值。

3.2.4　类型之间的转换

Visual Basic提供了几种数据类型的转换函数，可以实现数据类型之间的转换。表3.3是各种转换数据类型的函数。

除表3.3中的转换函数以外，经常会遇到的转换函数还有Val和Str两个函数。Val用于将数值字符串转换为数值型，例如，Val("123")的结果为数值123，又例如Val("999abc111")的结果为999。而Str用于将数字转换为字符串，其功能正好与Val函数相反，例如，Str(678)的结果为字符串“678”。

3.3　常量和变量

前一节介绍了Visual Basic中使用的数据类型。在程序中，要用到各种数据类型的数据，有些类型的数据在程序运行期间，其值是不发生改变的，即以常量形式出现；而有些数据在程序运行期间，其值是可变的，即以变量的形式出现，它代表的则是内存中指定的存储单元。

3.3.1　常量

常量在程序运行的过程中，其值不变。常量也有数据类型，Visual Basic中的常量分为两种，一种是文字常量，一种是符号常量。

1.文字常量

Visual Basic的文字常量分为两种，即字符串和数值。

（1）字符串常量

字符串常量由字符组成，可以是除双引号和回车符之外的任何ASCII字符，其长度不能超过65535个字符（定长字符串）或231（约21亿）个字符（变长字符串）。例如：

"￥123.50"

"Hello the Word!"

（2）数值常量

数值常量共有4种表示方式，即整数、长整型数、货币型数和浮点数。

❑整型数：有3种形式，即十进制、十六进制和八进制。

➣十进制整型数：由一个或几个十进制数字（0～9）组成，可以带有正号或负号，其取值范围为-32768～32767，例如：123、-4567、+89等。

➣十六进制整型数：由一个或几个十六进制数字（0～9及A～F或a～f）组成，前缀必须是＆H（或＆h），其取值范围为＆H0～＆HFFFF。例如，＆H21A、＆h191等。

➣八进制整型数：由一个或几个八进制数字（0～7）组成，前缀必须是＆（或＆O），其取值范围为＆O0～＆O177777。例如，＆127、＆O213等。

❑长整型数：也有3种形式。其组成与整型数组成相同，数的尾部加“＆”符号，取值范围见前一节数据类型的范围。例如，784＆、＆H123＆、＆O217＆，分别表示十进制长整型数、十六进制长整型数和八进制长整型数。

❑货币型数：也称为定点数，其取值范围见前一节。例如，3200.5@。

❑浮点数：也称为实数，分为单精度浮点数和双精度浮点数，指数符号为E（单精度）或D（双精度）。例如，123.45E-6!、2345D3#。

2.符号常量

在Visual Basic中，定义符号常量的格式如下。

[Public|Private] Const 常量名 [As 数据类型] =表达式[，常量名=表达式]……

❑Public是全局常量关键字，全局常量要在模块中声明，不能用于过程。

❑Private是局部变量关键字，局部常量要在模块和窗体中声明，同样不能在过程中使用。

❑常量名用来表示常量的实际名字。

❑数据类型是常量的类型，可以是Byte、Boolean、Integer、Long、Currency、Single、Double、Date、String及Variant。

3.3.2　变量

在计算机中，变量名用标识符表示，所以变量名遵循标识符的命名规则。变量随程序调入内存，并被分配一定的存储空间，通过名字引用变量。Visual Basic语言中，提供了两种声明变量方法。

1.用Dim和Static显式声明变量

❑使用Dim声明变量

格式：

Dim 变量名 As 数据类型

例如：

Dim Age As Integer

Dim Name As String

❑使用Static声明变量

格式：

Static 变量名 As 数据类型

使用Static声明的变量为静态变量。它与Dim声明的变量不同之处在于执行一个过程结束时，过程中所用到的Static变量的值会保留，下次再调用此过程时，变量的初值是上次调用结束时被保留的值，而Dim声明的变量在过程结束时不保留，每次调用时需要重新初始化。

2.隐式声明

使用变量时，Visual Basic不要求对所有变量事先声明，不加声明的变量系统会默认为变体类型。对应于强制显式声明，在程序中不经声明而使用变量称为变量的隐式声明。隐式声明使用起来比较随意，但也会带来一些麻烦。例如，误拼一个变量名就会被隐式声明为另一个新变量，由此会产生问题。因此，建议对所有变量作显式声明。

3.3.3　作用域

一个变量被定义后，并不是在任何地方都能使用，每一个变量都有它的作用范围。变量的有效范围就是变量的作用域。由于变量的有效范围不同，所以也就有了变量的不同级别。

1.局部变量

在一个过程内部（例如Command1_Click）定义的变量称为局部变量。这个变量只能在定义它的过程内使用。一个窗体可以包括很多过程，在不同过程中定义的变量可以同名，因为它们互相独立、互不干扰。例如：

01 Private Sub Form1_Click()

02 Dim Num1 As Integer

03 ...

04 End Sub

05 Private Sub Command1_Click()

06 Dim Num1 As Integer

07 Static Sum1 As Single

08 ...

09 End Sub

在Form1_Click过程中定义了局部变量Num1，在Command1_Click过程中定义了Sum1和Num1，两个同名变量Num1没有任何关系。

2.窗体和模块级变量

如果一个窗体中的不同过程要使用同一个变量，就需要在该窗体或模块内的过程外面定义一个变量，它在整个窗体或模块中有效，即其作用域为整个窗体或模块，本窗体或本模块内的所有过程都能访问它，这就是窗体或模块级的变量。

声明模块级变量要先进入程序代码窗口，单击左侧下拉框右端的向下箭头并选择“通用”，再单击右侧下拉框右端的向下箭头并选择“声明”，如图3.1所示（红色区域）。

图　3.1　模块级变量的声明

现在就可以声明变量了。在窗体声明中的所有变量都属于这类变量。这一级的变量只在本窗体或本模块中有效，在其他窗体或模块中不能以该变量名来访问它。如果在窗体中以Private或Dim声明变量，其他窗体或模块不能引用这个变量，如果以Public声明这个变量，则允许在其他窗体和模块中引用它，但必须指出其所在的对象。例如，在窗体Form1中有如下的变量声明。

Public a As Integer

在另一窗体和模块中可以引用此变量，但必须用Form1.a，而不能直接用a。请注意，不能把a误认为全局变量（一个全局变量名在程序中全程有效，如果a是一个全局变量，那么在其他窗体和模块中引用它时只要写变量名a，而无须写Form1.a）。

在模块中以Private或Dim声明的变量是模块级的变量，只能在本模块中被引用。

3.全局变量

全局变量可以被程序中任何一个模块和窗体访问。在窗体中不能定义全局变量，全局变量要在模块文件（.bas）中的声明部分用Global或Public关键字声明。它的一般格式如下。

Global 变量名 As 数据类型

或

Public 变量名 As 数据类型

例如：

Global Score As Integer

Public Sum1 As Long

三种变量的作用域总结见表3.4。

3.3.4　Option Explicit语句

使用Option Explicit语句对普通对象进行的变量声明，必须写在模块中所有过程之前。

如果模块中使用了Option Explicit，则必须使用Dim、Private、Public或Static来显式声明所有的变量。这时，如果使用了未声明的变量名，在编译时会出现错误。

如果所有模块都使用Option Explicit语句，除非使用Deftype语句指定了缺省类型，否则所有未声明的变量都是隐含为Variant类型的。

3.3.5　注释语句

在编写程序时，为了让其他人能够读懂自己的程序代码，在较难懂的地方应当加入注释语句。Visual Basic的注释语句以英文的单引号（'）开始，在代码窗口中以绿色显示，如图3.2所示。

图　3.2　带注释语句的代码段

注释语句不参加程序的执行，它们仅仅起到说明代码语句的作用。编程时，在代码中加入注释语句是一种非常好的习惯。不仅是要让别人看懂程序，对于写程序的人也会起到提示作用。

3.4　运算符与表达式

运算符是代表数据运算某种操作的符号，被运算的对象，即数据，称为操作数，由运算符和操作数组成的式子，称为表达式。操作数可以是常量、变量或函数。

Visual Basic提供了丰富的运算符，包括赋值运算符、算术运算符、关系运算符、逻辑运算符和字符串运算符等，由此可以构成多种表达式。

3.4.1　赋值运算符

赋值运算符用来给变量、变长数组或对象的属性赋值，即把运算符右边的内容赋给运算符左边的变量或属性。Visual Basic中的赋值运算符是“=”，其一般格式如下。

变量名=表达式

其中的“变量名”可以是变量名、数组的元素名或属性名。“表达式”可以是常量、变量、表达式或函数返回值。例如：

01 a=1

02 Form1.Caption="登录窗口"

03 str="请输入用户名"

04 Form1.Label1.Caption=str

05 sum=a+1

06 q=sin(2)

3.4.2　算术运算符

Visual Basic中提供了完备的算术运算符，可以进行复杂的数学运算。其中，“-”运算符在单目运算（单个操作数）中作取负号运算，在双目运算（两个操作数）中作算术减运算，其余都是双目运算符。运算符优先级表示当表达式中含有多个操作符时，先执行哪个操作数。表3.5按优先级从高到低的顺序列出了Visual Basic的算术运算符（设a变量为整数，值为2）。

注意　算术运算符两边的操作数应是数值型。若是数字字符或逻辑型，则自动转换成数值类型后再进行运算。

例如：

9-True '结果是10，逻辑值True转为数值-1，False转换为数值0

False+3+"4" '结果是7

3.4.3　关系运算符

关系运算符也称比较运算符，是双目运算符，用来确定两个操作数之间的关系。其优先级低于数学运算符，各个关系运算符的优先级是相同的，结合顺序从左到右。若关系成立，则返回True，否则返回False。在Visual Basic中，关系运算符常用于条件语句和循环语句的条件判断部分。表3.6列出了Visual Basic中的关系运算符。

在比较时应注意以下规则。

❑如果两个操作数是数值型，则按其大小比较。

❑如果两个操作数是字符型，则按字符的ASCII码值从左到右一一比较。

❑如果是字符串，默认大小写区分。

❑"Like"运算符以及“？”、“*”、“#”等通配符，常用于数据库中的SQL命令中的模糊查询。

3.4.4　逻辑运算符

逻辑运算符除Not是单目运算符外，其余都是双目运算符，作用是进行逻辑运算，其结果为True或False。表3.7列出了Visual Basic中的逻辑运算符、运算优先级。

3.4.5　字符串运算符

算术运算符“+”也可以用作字符串运算符，它可以把两个字符串连接在一起，生成一个较长的字符串。在Visual Basic中，还可以用“＆”作为字符串的连接符。其作用与“+”相同。“+”既可以用作加法运算符，也可用作字符串连接运算符，而“＆”专门用作字符串连接运算符。在有些情况下，用“＆”比用“+”更安全。

3.4.6　表达式

表达式由变量、常量、运算符、函数和圆括号按一定的规则组成。表达式通过运算后有一个结果，运算结果的类型由数据和运算符共同决定。

1.表达式的书写规则

❑乘号不能省略。例如：a乘以b应写成a*b。

❑括号必须成对出现，均使用圆括号；可以出现多个圆括号，但要配对。

❑表达式从左到右在同一个基准上书写，无高低、大小区分。例如：数学表达式（xy）4写成Visual Basic表达式为（x*y）^4。

2.表达式的执行顺序

一个表达式可能含有多种运算，计算机按一定的顺序对表达式求值。前面已在运算符中介绍过，算术运算符、逻辑运算符都有不同的优先级，关系运算符的优先级相同。当一个表达式中出现了多种不同类型的运算符时，不同类型的运算符优先级如下。

括号＞算术运算符＞字符运算符＞关系运算符＞逻辑运算符

3.5　工程管理

本节通过工程的各个操作，熟悉如何建立、打开、保存工程，并学习如何让多个工程协同工作，如何在一个工程中添加另一个工程。

3.5.1　建立、打开及保存工程

工程可以是单个的工程，也可以是工程组。这里介绍如何操作单个工程和工程组。

1.单个工程

在程序中只有单个工程存在的情况下，可以使用“文件”菜单中的几个命令来建立、打开及保存文件。

❑新建工程：选择此选项，可以建立一个新工程。若当前有其他工程存在，则系统会关闭当前工程，并提示用户保存所有修改过的文件。然后会出现一个关于新建工程类别的对话框，用户可以进行选择，然后系统会根据用户的选择建立一个带有单个文件的新工程。

❑打开工程：选择此选项，可以打开一个已经存在的工程。若当前有工程存在，会先关闭当前工程，提示用户保存修改过的文件，然后打开一个现有的工程，包括工程文件中所列的全部窗体、模块等。

❑保存工程：用于将当前工程中的工程文件和所有的窗体、模块、类模块等进行重新保存，更新原有的此工程的全部存储文件。

❑工程另存为：用于以一个新名字将当前工程文件加以保存，同时系统会提示用户保存此工程中修改过的窗体、模块等文件。

2.工程组

在程序中存在由多个工程组成的工程组时，“文件”菜单中的“保存工程”和“工程另存为”选项被自动修改为“保存工程组”和“工程组另存为”。所以在保存工程组文件时可以使用这两个选项，这两个选项的区别与单个工程文件中相应选项的区别相同。

在工程组中要建立一个新工程，应该是向原有的工程组中添加一个工程。添加方法如下。

❑在“文件”菜单中选择“添加工程”选项，会出现一个对话框，要求用户在“新建”卡片内选择合适的工程文件类型。

❑在工具栏中选择“添加工程”的快捷按钮，会出现同上的对话框，由用户选择。

3.5.2　在工程中添加、删除及保存文件

这里为读者介绍几个常见的操作：添加文件、删除文件及保存文件。

1.添加文件

【实例3.1】向一个工程中添加一个文件，具体步骤如下。

（1）选择“工程”菜单中的“添加”选项，根据要添加的文件类型的不同选择相应的选项。

（2）在出现的对话框中，根据要添加的是已经存在的文件还是新文件，来选择“现存”选项卡或“新建”选项卡。

（3）根据选定的选项卡，在其中选择新建文件的类型或现存文件的名字，并选择“打开”按钮即可。

有时需要在代码中引用一部分文本文件的内容，此时可以使用向代码中插入文件来实现。具体步骤如下。

（1）在“工程资源管理器”窗口内选定要插入文本文件的窗体或模块文件。

（2）选择“查看代码”按钮，将该窗体或模块文件的代码窗口调出，将光标移动到要插入文本文件的位置。

（3）在“编辑”菜单中选择“插入文件”，然后在出现的浏览对话框中查找文本文件的名字即可。

2.删除文件

【实例3.2】在工程中删除一个文件，可以按照如下步骤执行。

（1）在“工程资源管理器”窗口内选定要删除的窗体或模块文件。

（2）在“工程”菜单中选择“移除该文件名”选项。在“工程”菜单中对于工程中的每个文件都具有一项对应的“移除该文件名”的选项。

注意　按照上述方法删除的文件，只是不再属于工程，但仍在磁盘上存在，可以被其他工程使用。在保存当前删除过文件的工程时，系统会自动将被删除文件与工程的连接断开。如果使用其他方法将磁盘上的某个文件删除，则再打开包含该文件的工程时，就会出现错误信息，提示有一个文件丢失。

3.保存文件

【实例3.3】有些情况下需要只保存某个文件而不保存整个工程，可以按照如下步骤执行。

（1）在“工程资源管理器”中选定欲保存的文件。

（2）在“文件”菜单中选择“保存该文件名”。在“文件”菜单中对于工程中的每个文件都具有一项对应的“保存该文件名”的选项。

上面的添加、删除和保存的方法都是通过菜单实现的，实际操作中也可以在工程资源管理器中右击，通过弹出的快捷菜单进行操作。

3.5.3　多个工程协同工作

在Visual Basic中可以有多个工程协同工作，此时将其称为工程组。工程组中的工程可以是新建的工程，也可以将以前存在的工程添加进来。

1.添加或删除工程

在Visual Basic启动时或者通过在“文件”菜单中选择“新建工程”项创建一个新的工程时，Visual Basic自动创建一个工程组，然后可以将新的或现有的工程添加到该工程组中。

要向工程组中添加新的工程，可单击工具栏中的“添加工程”按钮或从“文件”菜单中选择“添加工程”项，出现“添加工程”对话框。其中有“新建”、“现存”和“最新”三个选项卡，用户可进行相应的操作。例如，先打开工程1，然后单击“添加工程”按钮，选择“新建”选项卡，建立工程2，此时工程资源管理器窗口如图3.3。

图　3.3　工程组

要从工程组中删除一个工程，先从“工程”窗口中选择要删除的工程的名称，然后从“文件”菜单中选择“删除工程”项即可。

2.如何指定一个启动工程

由于一个工程组中包含了多个工程，因此当从运行菜单中选择“启动”或者在工具栏中单击“启动”按钮时，Visual Basic需要知道运行哪个工程。在默认情况下，Visual Basic运行添加到工程组中的第一个可执行工程。但是，可以指定一个不同的启动工程。其操作过程是先在“工程”窗口中选择一个工程，然后右击并从弹出的快捷菜单中选择“设置启动为”菜单项即可。Visual Basic在工程窗口中以粗体显示启动工程的名称。

3.6　Visual Basic应用程序的结构与工作方式

应用程序是一个指令集，用来指挥计算机完成指定的操作。应用程序结构指的是组织指令的方法，即指令存放的位置和指令的执行顺序。对于只有一行代码的程序来说，程序的组织结构并不重要。应用程序越复杂，对组织或结构的要求也越高。除了控制应用程序的执行外，对于在应用程序中查找特定的指令，结构也有着重要的作用。Visual Basic应用程序通常由3类模块组成，即窗体模块、标准模块和类模块。

3.6.1　窗体模块

Visual Basic应用程序是基于对象的，应用程序的代码结构就是该程序在屏幕上物理表示的模型。根据定义，对象由数据和代码组成。在屏幕上看到的窗体是由其属性规定的，这些属性定义了窗体的外观和内在特性。在Visual Basic中，一个应用程序包含一个或多个窗体模块。每个窗体模块分为两部分，一部分是作为用户界面的窗体；另一部分是与该窗体相关联的代码。

每个窗体模块都包含事件过程，即代码部分，这些代码是为响应特定事件而执行的指令。在窗体上可以有各种控件，这些控件都有一个相对应的事件过程集。除事件过程外，窗体模块中还可以含有通用过程，它可以被窗体模块中的任何事件过程调用。

3.6.2　标准模块

标准模块完全由代码组成，不含有窗体，所以这些代码不与具体的窗体或控件相关联。通常写入标准模块中的代码如下。

❑声明全局变量。在标准模块中声明的全局变量可以被工程中的任何模块引用。

❑定义全局函数过程或者全局子程序过程。

❑编写Sub Main过程。

3.6.3　类模块

类是具有多态性的用户自定义类型。封装功能将代码和数据组织在同一个类模块中，使得对象能保护和验证其中的数据。借助创建对象的类，能将数据和过程组织成一个整体。在Visual Basic中创建自已的类是通过在工程中添加类模块，然后设计类模块的相关代码实现的。

前面所述的标准模块只包含代码，而类模块既包含代码又包含数据。每个类模块定义了一个类，可以在窗体模块中定义类的对象，调用类模块中的过程。建立类模块的方式是在工程资源管理器中右击，从弹出的快捷菜单内选择“添加”|“添加类模块”菜单项即可。

3.6.4　事件驱动

事件是窗体或控件识别的行为和动作。在响应事件时，事件驱动应用程序会执行Basic代码。Visual Basic的每一个窗体或控件都有一个预定义的事件集。如果其中有一个事件发生，而且在关联的事件过程中存在代码，则Visual Basic将调用该代码。

尽管Visual Basic中的对象会自动识别预定义的事件集，但要判定它们是否响应具体事件以及如何响应具体事件则需要通过编程来实现。代码部分（即事件过程）与每个事件对应。想让控件响应事件时，则可以把代码写入这个事件的事件过程中。

对象所识别的事件类型多种多样，但多数类型为大多数控件所共有。例如，大多数对象都能识别Click事件：如果单击窗体，则执行窗体的单击事件过程中的代码；如果单击命令按钮，则执行命令按钮的Click事件过程中的代码。但是，每种情况中的实际代码几乎完全不一样。以下是事件驱动应用程序中的典型事件序列。

（1）启动应用程序，加载和显示窗体。

（2）窗体（或窗体上的控件）接收事件。事件可由用户引发（例如，鼠标操作），也可以由系统引发（例如，定时器事件），还可由代码间接引发（例如，当代码加载窗体时的Load事件）。

（3）如果在相应的事件过程中存在代码，就执行代码。

（4）然后，应用程序等待下一次事件。

3.7　小结

本章主要介绍了VB的基本数据类型、变量的使用方法和规则、常用的内部函数和VB编码规则。变量类型声明和变量的作用范围等，都是读者不易掌握的知识。这些内容将在后面章节作进一步的讲解，通过练习也会很快掌握。对于初学者来说，VB的编码规则可能使用起来比较麻烦，但掌握编码规则是程序必备素养。第4章将介绍VB控件的应用。

3.8　本章练习

1.在VB中，默认的关键字用什么颜色显示？

A.红色

B.紫色

C.蓝色

D.黑色

2.Visual Basic中的常量分为哪两种？

3.连接"Welcome to"和"Beijing!"，并将其显示在TextBox文本框中。

第4章　控件

从前几章的学习中，对于控件应该有了基本的了解。控件其实就是构成应用程序的最基本的组成部分，学习Visual Basic语言，其实大部分都是在学习各类控件的用法。本章将系统深入地介绍部分常用控件的使用方法。本章包括以下知识点。

❑TextBox控件和Label控件的主要属性及其使用方法。

❑CheckBox控件和OptionButton控件的主要属性及其使用方法。

❑Button控件主要属性及其使用方法。

❑Image控件和PictureBox控件的主要属性及其使用方法。

❑HScrollBar控件和VScrollBar控件的主要属性及其使用方法。

❑Timer控件的主要属性和事件及其使用方法。

通过本章的学习，读者可以熟练掌握上述VB基本控件的重要属性、事件和方法。可以进行功能较为复杂的程序设计。

4.1　控件概述

控件是Visual Basic应用程序的基本组成部分，有了控件才可以方便地搭建程序界面及开发应用程序。通过改变控件的属性和处理控件的事件，在Visual Basic中很快就能够编写出功能强大的应用程序。

Visual Basic的控件分为两大类，分别是标准控件和ActiveX控件。在默认情况下，在Visual Basic工具箱内的控件都是标准控件，如图4.1所示。每类控件都有自己的作用。例如，文本框控件可以接收用户输入的数据，可以显示输出的内容。按钮控件可以接收用户的响应。图片框控件可以显示图片，也可以在其上绘制图形等。也有些控件虽然在运行状态下是不可见的，如计时器控件，但是，它的作用也是非常大的。

图　4.1　工具箱

图中每个控件的具体名称见表4.1。

除了标准控件以外，Visual Basic中还能使用外部ActiveX控件。这一功能拓展了Visual Basic的功能。也使得在Visual Basic中进行二次开发变得非常容易。

4.2　控件的相关操作

下面介绍有关控件的操作方法，例如，向窗体添加控件、调整画好的控件的尺寸、移动控件到合适的位置、复制与删除控件、用鼠标选择控件、使用工具调整多个控件的大小位置等。

4.2.1　向窗体上添加控件

向窗体上添加控件的方法与前面章节中讲到的将按钮或者将文本框添加到窗体上的方法一致。主要有如下两种。

❑在工具箱中点击控件，将鼠标放到窗体的适当位置后，按下鼠标左键不放，然后拖拽，直到控件大小合适的时候，再松开左键。

❑双击工具箱中的控件，也能将控件添加到窗体上。

如果想在窗体上画多个同一类型的控件，可以在按住Ctrl键的同时单击工具箱中的控件，然后使用上面介绍的第一种方法在窗体上绘制。

工具箱中的任何控件都可以采用这两种方法向窗体上添加。只是采用第二种方法添加控件后，需要在窗体上重新调整控件的大小和位置。

4.2.2　调整控件的大小

调整已经画到窗体上的控件大小的方法如下。

（1）用鼠标单击选中该控件。

（2）将鼠标放到该控件四周的8个小方块中的任一方块上。

（3）当鼠标变为双箭头时，按住鼠标左键不放，然后拖拽。

（4）感觉大小合适时松开鼠标左键。

通过上述方法，可以改变控件的大小。读者可以细心观察，拖拽控件四角上的方块与拖拽控件四边上的方块有什么区别。

4.2.3　移动控件到合适的位置

在设计界面时，经常遇到移动控件到新的位置上的情况。移动控件的方法如下。

（1）将鼠标箭头放到该控件内部。

（2）按住鼠标左键不放，将其拖拽到合适的位置。

（3）松开鼠标左键。

4.2.4　复制与删除控件

将窗体界面上的多余控件删除掉的方法如下。

（1）用鼠标左键单击，选中该控件。

（2）按键盘上的删除键，或者在控件上单击鼠标右键，在弹出的菜单中选择“删除”。

控件就像文字一样也可以被复制和剪切，操作方法如下。

（1）选中控件。

（2）用工具栏上的“复制”快捷按钮复制，或者在控件内部单击右键，在弹出的快捷菜单里选择“复制”。剪切的操作方法类似于复制，在此不再重复。

（3）在需要粘贴的窗体上，单击鼠标右键，在弹出的快捷菜单中选择“粘贴”。（使用工具栏上的粘贴快捷按钮也可以。）

4.2.5　选择控件

前面讲述的操作都基于单个控件，有时，可能需要对多个控件进行操作，例如移动多个控件、删除多个控件、对多个控件设置相同的属性等。为了对多个控件进行操作，必须先选择需要操作的控件，这通常有两种方法。

❑第一种方法：按住Shift键不放，然后单击每个要选择的控件。被选择的每个控件的周围都有8个方块。

❑第二种方法：把鼠标移到窗口中适当的位置（没有控件的地方），然后拖动鼠标画出一个虚线矩形，在该矩形内的控件（包括边线所经过的控件）即被选择。

注意　在被选择的多个控件中，有一个控件的周围是实心小方块（其他为空心小方块），这个控件称为“基准控件”。当对被选择的控件进行对齐、调整大小等操作时，将以“基准控件”为准。单击被选择的控件中的某个控件，即可把它变为“基准控件”。

选择了多个控件以后，在属性窗口中只显示它们共同的属性，如果修改其属性值，则被选择的所有控件的属性都将作相应的改变。

4.2.6　使用格式菜单调整控件布局

当一个窗体中有多个控件时，需要调整控件的布局，例如，对齐控件、统一控件的尺寸或者调整控件之间的距离等。用手动调整不仅速度慢而且达到的效果也不会很好。Visual Basic为此给用户提供了一些调整布局的功能。这些功能可以从“格式”菜单中找到，例如，“对齐”菜单可以使选中的控件用各种对齐方式对齐，“统一尺寸”菜单可以统一选中控件的大小等。具体操作步骤如下。

（1）设置好将要作为基准控件的那一个控件的大小和位置。

（2）选中要调整的所有控件，并设置好基准控件。

（3）通过选择“格式”菜单里的命令完成布局。

4.3　输入输出函数

输入输出对于每一个编程语言都是至关重要的。它们就像人的耳朵、眼睛和嘴巴，担任着与外界交流的重要任务。Visual Basic的输入输出有着十分丰富的内容和形式，它提供了多种手段，例如前面用过的Print方法，文本框输入输出等。本节为了方便在后面章节中使用，在此将讲述Visual Basic编程中经常用到的InputBox和MsgBox输入输出函数。

4.3.1　InputBox函数

InputBox函数用来弹出一个输入对话框，通过该对话框用户可以输入数据，并返回输入的数据。InputBox函数的通用格式为:

InputBox(prompt[,title][,default][,xpos][,ypos][,helpfile,context])

说明　InputBox函数的后面有对圆括号，圆括号内用逗号分隔的每一项都叫做参数。该函数一共有7个参数。方阔号括住的参数为可选参数，可以不写。

InputBox函数的每一个参数的含义如下所示。

（1）prompt：是一个字符串，其长度不得超过1024个字符，它是在对话框内显示的信息，用来提示用户输入。在对话框内显示prompt时，可以自动换行。如果想按自己的要求换行，则须插入回车换行操作，即：

Chr$(13)＆Chr$(10)

或vbCrLf

（2）title：字符串，它是对话框的标题，显现在对话框顶部的标题区。

（3）default：字符串，用来显示输入缓冲区的默认信息。也就是说，在执行InputBox函数后，如果用户没有输入任何信息，则可用此默认字符串作为输入值；如果用户不想用这个默认字符串作为输入值，则可在输入区直接键入数据，以取代默认值；如果省略该参数，则对话框的输入区为空白，等待用户键入信息。

（4）xpos，ypos：是两个整数值，分别用来确定对话框与屏幕左边的距离（xpos）和上边距离（ypos），它们的单位均为twip。这两个参数必须全部给出，或者全部省略。如果省略这一对位置参数，则对话框显示在屏幕中心线向下约三分之一处。

（5）helpfile，context：helpfile是一个字符串变量或字符串表达式，用来表示帮助文件的名字；context是一个数值变量或表达式，用来表示相关帮助主题的帮助目录号。这两个参数必须同时提供或同时省略。当带有这两个参数时，将在对话框中出现一个“帮助”按钮，单击该按钮或按F1键，可以得到有关的帮助信息。

【实例4.1】编写程序，试验InputBox函数的功能。

01 Private Sub Form_Click()

02 Dim str1 As String

03 Dim str2 As String

04 Dim str3 As String

05 Dim msg As String

06 Dim stuname As String

07 str1="输入学生姓名"

08 str2="输入后按回车键"

09 str3="或单击'确定'按钮"

10 msg=str1＆vbCrLf＆str2＆vbCrLf＆str3

11 stuname=InputBox(msg,"InputBox函数试验","张三")

12 Print stuname

13 End Sub

【运行程序】上述过程用来建立一个输入对话框，并把InputBox函数返回的字符串赋值给变量stuname$，然后在窗体上显示该字符串。程序运行后，单击窗体，所显示的对话框如图4.2所示。

图　4.2　实例4.1的输入对话框

【代码说明】在上面的过程中，代码第11行的InputBox函数使用了3个参数。第一个参数msg用来显示3行信息，通过vbCrLf常量换行。第二个参数“InputBox函数试验”用来显示对话框的标题。第三个参数“张三”是默认输入值，在输入区显示出来。在函数中省略了xpos、ypos、helpfile和context等4个参数。在使用InputBox函数时，应注意以下几点。

❑执行InputBox函数后，会产生一个对话框，提示用户输入数据，光标位于对话框底部的输入区中。如果第三个参数（default）不省略，则在输入区中显示该参数的值，此时如果按回车键或单击对话框中的“确定”按钮，则输入该默认值，并把它赋给一个变量；如果不想输入默认值，则可直接键入所需要的数据，然后按回车键或单击“确定”按钮输入。

❑在默认情况下，InputBox的返回值是一个字符串（不是变体类型）。也就是说，如果没有事先声明返回值变量的类型（或声明为变体类型），则当把该函数的返回值赋给这个变量时，Visual Basic总是把它作为字符串来处理。因此，当需要用InputBox函数输入数值，并且需要输入的数值参加运算时，必须在进行运算前用Val函数（或其他转换函数）把它转换为相应类型的数值，否则有可能会得到不正确的结果。如果正确地声明了返回值的变量类型（或者加了类型说明符），则可不必进行类型转换。

❑在执行InputBox函数所产生的对话框中，有两个按钮，一个是“确定”，另一个是“取消”。在输入区输入数据后，单击“确定”按钮（或按回车键）表示确认，并返回在输入区中输入的数据；而如果单击“取消”按钮（或按Esc键），则使当前的输入作废，在这种情况下，将返回一个空字符串。

❑每执行一次InputBox函数只能输入一个值，如果需要输入多个值，则必须多次调用InputBox函数。输入数据并按返回键或单击“确定”后，对话框消失，输入数据必须作为函数的返回值赋给一个变量，否则输入的数据不能保留。在实际应用中，函数InputBox通常与循环语句和数组结合使用，这样可以连续输入多个值，并把输入的数据赋给数组中各元素。

❑和其他返回字符串的函数一样，InputBox函数也可以写成InputBox$的形式，这两种形式完全等价。

4.3.2　MsgBox函数

在Windows操作中，经常会遇到一种小对话框，例如，关闭程序时会跳出一个类似于“是否真的退出程序”提示对话框，并根据用户的选择确定其后的操作。MsgBox函数就是实现这一功能的函数。通常称MsgBox函数调用的对话框为消息框。该函数的格式如下。

MsgBox(prompt[,buttons][,title][,helpfile,context])

1.MsgBox函数的参数

该函数有5个参数，除第一个以外，其他都是可选参数。各参数的含义如下。

（1）prompt：作为消息显示在对话框中的字符串表达式。prompt的最大长度大约是1024个字符。如果prompt中包含多个行，则可在各行之间用回车符(Chr(13))、换行符(Chr(10))或回车换行符的组合(Chr(13)＆Chr(10))分隔。

（2）buttons：是一个数值表达式或常量表达式，用来控制显示按钮的数目和类型以及使用的图标样式，该参数的值由4类数值相加产生，这4类数值或符号常量分别表示按钮的类型、显示图标的种类、默认按钮的位置及强制返回，有关数值，请参阅表4.2。如果省略，则buttons的默认值为0，即对话框内只显示一个“确定”命令按钮，并把该按钮设置为默认按钮，不显示任何图标。

上述表中的数值分为4类，其作用如下。

❑数值0～5：对话框内按钮的类型和数量。按钮共有7种，即确认、取消、终止、重试、忽略、是、否。每个数值表示一种组合方式。

❑数值16、32、48、64：指定对话框所显示的图标。共有4种，其中16指定暂停；32表示疑问；48表示警告；64用于忽略。

❑数值0、256、512、768：指定默认按钮。

❑数值0、4096：分别用于应用程序和系统强制返回。

buttons参数由上面4类数值组成，其组成原则是：从每一类中选择一个值，把这几个值加在一起就是buttons参数的值（通常只使用前3类数值）。不同的组合会得到不同的结果。例如：

49=1+48+0　显示“确定”、“取消”两个按钮，“警告”图标，默认按钮为第一个按钮，即“确定”按钮。

35=3+32+0　显示“是”、“否”、“取消”3个按钮，“疑问”图标，默认按钮为第一个按钮。

buttons参数也可以写成常量累加的样式，类似于下面的表达式：

vbQuestion+vbYesNo+vbDefaultButton1显示“疑问”图标，“是”、“否”两个按钮，并设置第一个按钮为默认按钮。

（3）title：是一个字符串，用来显示对话框的标题。如果省略该参数，则对话框的标题为当前工程的名称。

（4）helpfile、context两个参数的含义同Input函数一样。

2.MsgBox函数的返回值

MsgBox函数的返回值与所选择的按钮有关。具体选择哪个按钮返回什么值，见表4.3。

3.MsgBox函数应用

【实例4.2】下面的程序用来试验MsgBox函数的功能。

01 Private Sub Form_Click()

02 Dim x As Integer

03 x=MsgBox("您确认删除么？",vbQuestion+vbYesNo+vbDefaultButton1)

04 Print x

05 End Sub

【运行效果】程序运行后，单击窗体，运行结果如图4.3所示。

【代码说明】在上面的程序中，代码第03行的MsgBox函数有两个参数，第一个参数用来显示对话框内的信息，第二个参数是个常量累加表达式，具体意义为：第一个常量显示“疑问”图标，第二个常量显示“是”、“否”两个按钮，第三个常量的作用是让第一个按钮成为默认按钮。

说明　默认按钮是内部文字的周围有虚线框的按钮，如图4.3中的“是”按钮。这种按钮可以响应回车键，即回车就等于用鼠标单击它。

图　4.3　实例4.2的消息框

选择了MsgBox函数消息框内的按钮后，该函数会返回一个整数给变量x。如果选了“是”按钮，x就被赋值为6（见表4.3所示）；如果选了“否”按钮，x就被赋值为7。最后一条语句用于在窗体上打印变量x中的值。

在编写实用的应用程序时，通常使用MsgBox函数的返回值来决定程序往下该怎么执行。例如上面的例子，可以根据x的值来决定是执行删除操作的代码，还是执行取消删除的代码。格式类似于：

x=MsgBox("您确认删除么？",vbQuestion+vbYesNo+vbDefaultButton1)

If x=6 Then

'添加删除代码

Else

'添加取消删除的代码

End If

其中，If…Else…End if语句是程序设计语言里的一种语句结构，将其称为选择结构。If后面要写条件表达式。执行的过程是先判断If后面的条件是否为真，如果是真则执行If下面的语句块，如果为假则执行Else下面的语句块。在这段程序的执行过程中，这两块语句绝对不能同时执行，只能执行其中的一块，因为变量x只有两种值，要么是6，要么不是6，绝对不可能出现既是6又不是6的情况。具体选择结构的详细内容在本书第5章中讲述。

上面程序运行后先执行第一条语句右面的MsgBox函数，从而会出现一个消息框（参考图4.3），根据选择的按钮不同，赋予x的值也会不一样。如上所述，选择了“是”按钮x就会被赋值为6，条件表达式x=6的值就会为真，从而执行If语句下面的删除语句块；如果选择了“否”按钮x的值为7，条件表达式x=6的值就会为假，从而会执行Else语句下面的取消删除语句块。

4.3.3　MsgBox语句

MsgBox函数也可以写成语句的形式，即：

MsgBox prompt[,buttons][,title][,helpfile,context]

各参数的含义及作用与MsgBox函数相同，只是不返回值，所以不必放到赋值表达式中。因而常用于较简单的信息显示。例如：

MsgBox"Hello World!"

说明　MsgBox函数和语句的区别在形式上是函数后面带括号，语句后面不带括号。函数因为有返回值所以只能写在表达式内，而语句没有返回值所以可以独立作为一个表达式。

由MsgBox函数和MsgBox语句所显示的消息框有一个共同特点，就是当出现消息框后，必须对其作出选择操作（单击某个按钮等），否则不能执行其他任何操作。这样的窗口成为“模态窗口”（模式窗口）。

4.4　命令按钮

命令按钮是Visual Basic编程中使用方法最简单，但却是最常用的控件之一。它主要用来接受用户的单击响应。下面将详细讲述命令按钮最常用的属性、方法和事件。

4.4.1　命令按钮的常用属性

1.Name（名称）属性

Name属性用来在程序中识别此控件，与窗体的Name属性一样。该属性只能在设计阶段设置。给命令按钮起名字时按约定前缀应该是cmd，后面根据命令按钮的作用起名。如一个“确定”按钮的名字可以是cmdOk，又如“取消”按钮的名字可以是cmdCancel。当然，也可以使用按钮的默认名字Command1等，甚至也可以叫做aa，但是为了能让自己和其他人能够很容易地读懂程序，为了给以后的编程生涯一个好的开始，编者再次强烈建议养成按约定起名字的好习惯。表4.4中列出了一些常用控件名称前缀的约定，如果还想知道其他ActiveX控件的前缀命名约定可以查看联机帮助文档，在MSDN搜索栏内输入“命名约定”后搜索既可。

2.Caption属性

Caption属性用来设置按钮上显示的文字。如果想给按钮设置一个热键，也是通过该属性完成。例如，Visual Basic中“文件另存为”对话框的“保存（S）”按钮，在“保存”两个字后面括号里的S就是该按钮的热键，即按下键盘上的ALT键的同时再按S键的效果和用鼠标单击该按钮的作用一样。设置的方法为在按钮的Caption属性内填写“保存（＆S）”即可。

3.Enabled属性

Enabled属性用来设置按钮的可用与否。在应用程序的界面中，有时并不希望所有的按钮都是可用的。例如通过一个窗体浏览学生信息时，界面中显示的如果是第一个学生的信息，那么“上一条”按钮就应该是不可用的，而“下一条”按钮是可用的。当单击“下一条”按钮后，界面中的信息成为第二个学生的信息时，“上一条”就会变成可用。改变按钮可用与否的代码格式为：

按钮.Enabled=True（或False）

其中，按钮是按钮的名称。如果给Enabled属性设置为True，该按钮为可用；如果设置为False，该按钮将不可用。前面所说的不可用的意思是按钮不能被单击，颜色为灰色。

4.Font属性

Font属性用来设置按钮上显示文字的字体、字号、粗体斜体等字体样式。

5.Visible属性

Visible属性用来设置按钮的可见与否。将其设置为True时，按钮是可见的；将其设置为False时按钮是不可见的。可见与否在设计状态下没有任何区别，只有在运行阶段才有效果。

6.Default属性和Cancel属性

如果将一个按钮的Default属性设置为True，那就意味着默认情况下，在窗体上回车就等于用鼠标单击了该按钮。而如果将一个按钮的Cancel属性设置为True，当按下键盘上的ESC键时就等同于单击了此按钮。通常情况下，应当将窗体中的“确定”按钮的Default属性设置为True，将“取消”按钮的Cancel属性设置为True。这会使Windows的老用户感觉到非常顺手。

按钮的属性除了上面提到的以外还有很多。如果读者想使用一个本书中没有提到的属性，请不要忘记通过F1键调用联机帮助查看详细信息。

4.4.2　命令按钮的事件

命令按钮有很多事件，但是最重要、最常用的就是以前所涉及的Click（单击）事件。该事件在用户使用鼠标单击按钮时发生，当然设置了Default和Cancel属性的按钮也可以使用回车和ESC键触发此事件。

【实例4.3】设计一个程序，界面如图4.4所示。当按下“确定”按钮时，会跳出个消息对话框，里面显示的内容是“您单击了确定按钮。”；而按下“取消”按钮时，将卸载窗体。该程序同时还响应回车和ESC键，即回车等同于单击“确定”，按ESC键等同于单击“取消”。

图　4.4　实例4.3界面

【实现步骤】首先，按照界面设计画好控件后，在属性窗口中设置各对象的属性。具体属性设置见表4.5。实际上，在开发应用程序时为了使界面的设计清晰而有条理，通常在设计前将界面中所需要的对象及其属性画成一个表，然后按照这个表来设计界面。

两个按钮单击事件中的代码为：

01 Private Sub cmdCancel_Click()

02 Unload Me

03 End Sub

04

05 Private Sub cmdOk_Click()

06 MsgBox"您单击了确定按钮！"

07 End Sub

【代码说明】这段代码的运行原理是，当单击“确定”按钮时，触发了该按钮，即代码第05～07行的cmdOk按钮的Click事件，并随之执行了其内的程序代码，因而会弹出消息框；当单击“取消”按钮时，会触发第01～03行“取消”按钮的Click事件，从而执行了其内的卸载窗体的语句，并结束程序。

4.4.3　应用案例——计算圆的面积

【实例4.4】用以前的知识，编写一个可以计算圆的面积的程序。

界面设计如图4.5所示，单击“计算面积”按钮时让输入框跳出来，在里面输入圆的半径并确定后，会弹出一个消息框，里面显示计算出来的圆的面积。

图　4.5　实例4.4界面

程序中对象的属性设置见表4.6。

编写程序如下。

01 Private Sub cmdArea_Click()

02 Dim r As Single

03 Dim s As Single

04 r=Val(InputBox("请输入圆的半径","输入框"))

05 s=3.14*r*r

06 MsgBox"圆的面积为："＆s

07 End Sub

【代码说明】代码第04行按要求首先出现一个输入框，让用户输入一个圆的半径，代码第05行是数学运算中计算圆的面积的公式。代码第06行输出圆的面积。

【运行效果】单击“计算面积”按钮会弹出输入框，在文本框中输入半径，然后单击“确定”按钮。如图4.6所示。

图　4.6　实例4.4运行效果

4.5　标签

标签主要用来显示文本信息，通常的作用就是在窗口中显示各种操作提示，以免用户操作错误。标签也有属性、事件和方法，但是其事件和方法一般很少用到。因此下面只介绍标签的常用属性。

1.Alignment属性

该属性用来决定标签内容的对齐方式，可以设置为0、1、2三个数字。其作用如下。

0——左对齐

1——右对齐

2——居中

2.AutoSize属性

该属性决定标签是否自动改变大小以显示其全部内容。如果设置该属性为True，则标签会根据标签内容自动调整大小；如果设置为False，标签将保持设计时定义的大小。

3.Caption属性

该属性是标签最重要的属性，用来在标签中显示文本。该属性与其他控件的Caption属性用法一样。

4.BackStyle属性

该属性决定标签的背景是否透明。如果没有这一功能，当在有背景图片的窗体上放置标签时就会发现界面真是出奇的别扭。BackStyle属性有0和1两个值，如果为0，标签背景就是透明的，如果为1标签背景为不透明。

5.Font属性

用其设置显示文本的字体、字号、粗体斜体等。

4.6　文本框

文本框控件既可以用来输入数据，也可以用来输出数据。其作用在Visual Basic程序设计中是不可估量的。本节将详细讲述文本框控件最重要的属性、方法和事件。

4.6.1　文本框的常用属性

1.Text属性

Text属性是文本框最重要的属性，其作用为设置或返回文本框中显示的内容。例如：

Text1.text="Hello!"

该语句的作用是将字符串"Hello"显示到文本框Text1中。又例如语句：

x=Text1.text

的作用是将文本框Text1中现有的内容提取出来，赋值给变量x。这里需要注意Text属性返回值的类型是字符类型与InputBox函数的返回值一样。假如，在文本框中用户输入了15，那么执行刚才的语句之后x的值就会变为字符串“15”。如果想将变量x赋值为数值型的数据，应该使用Val函数转换，比如上面的代码应当写为：

x=Val(Text1.text)

2.Font属性

文本框的Font属性和其他控件Font属性一样。可以用其设置显示文本的字体、字号、粗体斜体等。用属性窗口设置Font属性前面已经讲述过，下面带领读者学习如何用代码设置该属性。

使用代码设置时，Font被分割成FontName、FontSize、FontBold、FontItalic、FontUnderline等属性。分别控制字体名称、字体大小、粗体、斜体和下划线。例如下面的代码分别实现了将Text1的字体设置为宋体、将字号设置为20、并加上了粗体、斜体、下划线的效果。

01 Text1.FontName="宋体"

02 Text1.FontSize=20

03 Text1.FontBold=True

04 Text1.FontItalic=True

05 Text1.FontUnderline=True

3.MaxLength属性

MaxLength属性用来设置允许在文本框中输入的最大字符个数。通常会取默认值0，即在文本框中输入的字符数不能超过32K。登录窗体的文本框通常会用到此属性。

4.MultiLine属性

MultiLine属性用来控制文本框内是否能够换行，即是否能显示多行文本。如果将其设置为True，文本框内可以写多行，否则只能显示单行。

5.PasswordChar

PasswordChar用来设置密码文本框内显示的内容。例如，将文本框的该属性设置为星号（*）后在运行状态下，用户输入任何字符都会显示为星号。

6.SelLength、SelStart和SelText属性

❑SelLength：返回或设置所选择的字符数。

❑SelStart：返回或设置所选择的文本的起始点；如果没有文本被选中，则指出插入点的位置。

❑SelText：返回或设置含有当前所选择的文本字符串；如果没有字符被选中，则含有一个空字符串。

这3个属性只能在运行状态下才能被设置。例如，

01 Text1.Text="Studing VB!"

02 Text1.SelStart=8

03 Text1.SelLength=2

04 MsgBox Text1.SelText

这段代码第一条语句的作用是将一个字符串"Studing VB!"显示到Text1中。第二条和第三条语句为从Text1中字符串的第8个字符位开始向后选择两个字符长度的文本，即选中了"VB"两个字符。第四条语句的意思是将Text1文本框中被选中的文本（VB）显示到消息框中。

4.6.2　文本框的事件和方法

文本框支持Click、DblClick等鼠标事件，同时支持Change、GotFocus、LostFocus等事件。

1.Change事件

当用户通过键盘改变文本框中的内容，或通过程序代码改变Text属性时将触发Change事件。程序运行后，在文本框中每键入一个字符或者删除一个字符都会触发一次Change事件。例如，在运行状态下，当用户使用键盘向一个空文本框中输入"abc"时，会触发三次Change事件，如果该事件过程中用户写入了代码，该代码将会被执行三次。

2.GotFocus事件

当文本框获得焦点（光标）时触发GotFocus事件。获得焦点可以通过TAB键切换，或单击对象之类的用户动作，或在代码中用SetFocus方法改变焦点来实现。要注意的是一个控件仅当其Enabled和Visible属性都设置为True时才能接收焦点。Visual Basic中的可见对象都拥有该事件。

3.LostFocus事件

当文本框失去焦点（光标）时触发LostFocus事件。该事件主要是用来对文本框中的数据进行检查和确认的。

4.SetFocus方法

SetFocus方法是文本框应用中经常遇到的一种方法，其作用主要是让某个文本框获得焦点。例如，假设当前Text1文本框拥有焦点，而想让Text2文本框获得焦点的代码可以写为：

Text2.SetFocus

4.6.3　应用案例——运算器

【实例4.5】实现一个简单的运算器。运行界面如图4.7所示。

图　4.7　实例4.5界面

程序中各对象的属性设置如表4.7所示。

当运行程序后，在第一个文本框和第二个文本框中分别录入两个操作数，然后单击任意运算按钮之后，在第三个文本框中显示运算结果。编写程序如下。

01 Private Sub cmdAdd_Click()

02 Dim x As Single

03 Dim y As Single

04 Dim z As Single

05 x=Val(txtFirst.Text)

06 y=Val(txtSecond.Text)

07 z=x+y

08 txtResult.Text=Str(z)

09 End Sub

10

11 Private Sub cmdSub_Click()

12 Dim x As Single

13 Dim y As Single

14 Dim z As Single

15 x=Val(txtFirst.Text)

16 y=Val(txtSecond.Text)

17 z=x-y

18 txtResult.Text=Str(z)

19 End Sub

20

21 Private Sub cmdMul_Click()

22 Dim x As Single

23 Dim y As Single

24 Dim z As Single

25 x=Val(txtFirst.Text)

26 y=Val(txtSecond.Text)

27 z=x*y

28 txtResult.Text=Str(z)

29 End Sub

30

31 Private Sub cmdDiv_Click()

32 Dim x As Single

33 Dim y As Single

34 Dim z As Single

35 x=Val(txtFirst.Text)

36 y=Val(txtSecond.Text)

37 z=x/y

38 txtResult.Text=Str(z)

39 End Sub

40

41 Private Sub cmdClear_Click()

42 txtFirst.Text=""

43 txtSecond.Text=""

44 txtResult.Text=""

45 txtFirst.SetFocus

46 End Sub

【代码说明】代码第02～04行声明了x、y、z三个单精度实数型变量（因为用户有可能会输入带有小数点的数值）；第05～06行的作用是将两个文本框里的内容转换成数值型的数据后赋给了变量x、y；第07行将x、y两个变量之和赋值给了变量sum；第08行用Str函数将sum中的值转换成字符串后将其显示到第三个文本框内。其他运算按钮单击事件中的语句可以参考“加”按钮。“清除”按钮Click事件里第42～44行的作用是将三个文本框中的内容清空，第45行的作用是让第一个文本框获得焦点，即让光标跳到第一个文本框。其目的在于方便用户直接输入，省去拿起鼠标点击该文本框的动作。

Visual Basic中虽然不要求变量声明，但是笔者强烈建议在用到变量时尽量声明，这样可以省去以后编程中的很多麻烦，为了强迫自己进行变量声明，可以选择“工具”|“选项”菜单项，在打开的“选项”对话框的“编辑器”选项卡内“要求变量声明”前打上钩。该对话框其他内容请参考本书第1章1.3.6小节。Visual Basic中除了不要求变量声明以外还会为用户自动转换类型，如上面的语句txtResult.Text=Str（z）可以写为txtResult.Text=z。但是，笔者建议最好使用显式转换方法。

注意　本实例代码中存在一个严重的缺点，当第二个文本框内输入的是0时，点击“除”按钮就会出现异常。原因是除数成为了零，因此应该在执行除操作之前进行判断，具体方法可以参考第5章选择结构的内容。

4.7　单选按钮与复选框

如图4.8所示，喜欢在网上冲浪的读者应该很熟悉类似的界面。其中，用来选择性别的控件叫做单选按钮，选择爱好的控件叫做复选框。单选按钮与复选框的主要区别是：单选按钮只能在一组选项里选择一个，如性别；复选框可以在一组选项里选择多个，也可以一个都不选，如图中的爱好。

图　4.8　单选按钮与复选框应用举例

4.7.1　单选按钮

单选按钮的常用属性有Caption、Value、Alignment和Style，常用事件有Click事件。

1.Caption属性

Caption属性用于设置单选按钮的标题。如图4.7中的“男”、“女”等就是改变了单选按钮控件的Caption属性。

2.Value属性

该属性用来表示单选按钮的状态。对于单选按钮有两种操作，即选择和取消选择。选择的方法是用鼠标左键单击该单选按钮；取消选择的方法是单击其他单选按钮。因此，单选按钮也有两种状态，即被选择状态（内部有黑色圆点）和不被选择状态（内部没有黑色圆点）。当单选按钮在选择状态时其Value属性为True，而在不被选择状态时其Value属性为False。例如，图4.7中的第一个单选按钮（标题为“男”）的Value属性为True，第二个单选按钮的Value为False。该属性也可以通过程序设置。例如：

Option2.Value=True

其含义为将单选按钮Option2的状态改为被选择状态，即Option2内会出现黑色圆点。

3.Alignment属性

该属性用来设置单选按钮标题的对齐方式，其取值范围为0和1，具体值的含义如表4.8所示。

4.Style属性

该属性用来设置单选按钮的显示方式，以改善视觉效果。其取值范围也是0和1。具体值的含义如表4.9所示。

5.Click事件

单击单选按钮时会触发该事件或者将单选按钮的Value属性设置为True时该事件同样会被触发。

【实例4.6】如图4.9所示，在文本框中输入文字后，可以通过单击下面的单选按钮改变文本框中文字的对齐方式。

图　4.9　实例4.6界面

程序中各对象的属性设置如表4.10所示。

因为单击单选按钮时才会发生文本对齐的动作，所以应当在单选按钮的Click事件内写入代码。具体代码如下。

01 Private Sub optCenter_Click()

02 txtShowWord.Alignment=2

03 End Sub

04

05 Private Sub optLeft_Click()

06 txtShowWord.Alignment=0

07 End Sub

08

09 Private Sub optRight_Click()

10 txtShowWord.Alignment=1

11 End Sub

【代码分析】文本框的对齐方式由Alignment属性控制。通过代码改变对齐方式的语句应该写成：

txtShowWord.Alignment=0或1或2

让文本左对齐时赋值0给Alignment属性，居中时赋值2，右对齐时赋值1。如代码第06、02和10行所示。

4.7.2　复选框

复选框的常用属性和事件与单选按钮的基本上一样。只是其Value属性与单选按钮的Value属性有较大的区别。复选框的Value属性的取值范围为0、1或2。其中：

❑0——表示没有选择该控件。

❑1——表示选中了该控件。

❑2——表示该控件被禁止使用（以灰色显示）。

【实例4.7】分析下面的程序。界面如图4.10所示。

图　4.10　实例4.7运行界面

【运行效果】

❑假设选择了足球和乒乓球并单击“确定”按钮后会跳出一个消息框如图4.11所示。

图　4.11　消息框

❑假设没有选择任何爱好直接单击“确定”按钮，在跳出的消息框内显示“您没有选择爱好！”。

各对象的属性设置如表4.11所示。

本实例代码如下。

01 Private Sub cmdOk_Click()

02 Dim msg As String '声明了一个字符串变量

03 msg="您选择的体育爱好是：" '给变量赋值

04 If chk1.Value=1 Then msg=msg＆"足球" '如果选择了chk1，则在原来的字符串上连接"足球"

05 If chk2.Value=1 Then msg=msg＆"羽毛球" '如果选择了chk2，则在原来的字符串上连接"羽毛球"

06 If chk3.Value=1 Then msg=msg＆"篮球" '如果选择了chk3，则在原来的字符串上连接"篮球"

07 If chk4.Value=1 Then msg=msg＆"乒乓球" '如果选择了chk3，则在原来的字符串上连接"乒乓球"

08 '如果没有选择任何复选框，则给msg变量赋"您没有选择爱好！"

09 If chk1.Value=0 And chk2.Value=0 And chk3.Value=0 And chk4.Value=0 Then msg="您没有选择

10 爱好！"

11 MsgBox msg

12 End Sub

【代码说明】代码第04～07行通过chk.Value属性来判断是否选择了这个复选框。代码第09～10行看起来有些复杂，其实是判断这4个复选框是否都没有被选中。

4.7.3　框架

单独使用框架控件没有什么意义。其主要用途是为了将窗体上的控件分组，而且通常是为了把单选按钮分成若干组。因为不管窗体上有多少个单选按钮，用户却只能选择其中的一个。但是在实际应用中有时有多组选项，希望在每组选项中各选一项，这时用框架将这些单选按钮分组就很有意义了。

注意　要将控件放入框架中时，必须将控件画到框架内部或者粘贴到框架内部，这时移动框架时控件也会随着被移动。否则如果先画到窗体上然后移动到框架内的控件是不属于该框架的，既不会起到分组的效果，移动框架时控件也不会被移动。

4.7.4　应用案例——文本编辑器

【实例4.8】编写一个简单的文本编辑器，如图4.12所示。

图　4.12　实例4.8运行界面

【运行效果】当选择好了字体、字号和字形之后单击“窗口”下面的“应用”按钮时，文本框中的文本会随着设置而改变其字体、字号和字形。

各对象的属性设置如表4.12所示。

编写程序如下。

01 Private Sub cmdApply_Click()

02 Dim strfont As String

03 Dim numsize As Integer

04

05 If optFont1.Value=True Then strfont="宋体"

06 If optFont2.Value=True Then strfont="黑体"

07 If optFont3.Value=True Then strfont="楷体_GB2312"

08

09 If optSize1.Value=True Then numsize=10

10 If optSize2.Value=True Then numsize=20

11 If optSize3.Value=True Then numsize=30

12

13 If chk1.Value=1 Then

14 txtShowWord.FontBold=True

15 Else

16 txtShowWord.FontBold=False

17 End If

18 If chk2.Value=1 Then

19 txtShowWord.FontItalic=True

20 Else

21 txtShowWord.FontItalic=False

22 End If

23 If chk3.Value=1 Then

24 txtShowWord.FontUnderline=True

25 Else

26 txtShowWord.FontUnderline=False

27 End If

28

29 txtShowWord.FontName=strfont

30 txtShowWord.FontSize=numsize

31 End Sub

【代码说明】代码第05～11行用来判断窗体中所有的单选按钮是否被选择；代码第13～27行用来判断窗体中的复选框是否被选择；代码第29～30行是根据单选按钮的选择结果来设置字体和字号。

【常见错误分析】运行程序后，大部分读者不会遇到麻烦。但是，肯定会有一部分人的程序会出现错误提示，如图4.13所示。

图　4.13　实例4.8错误提示

此时，可以单击错误提示窗口中的“结束”按钮，结束程序的运行，然后回到代码窗口中一条条语句的找错误。当然，这种做法是不被提倡的。通常的做法是遇到错误提示窗口时单击“调试”按钮，这时Visual Basic集成开发环境里会出现代码编辑器窗口，其中一条语句以黄色背景显示，如图4.14所示。这就是Visual Basic给使用者提供的自动找错的功能，那条有黄色背景的语句就是当前错误的根源。

图　4.14　调试状态下的代码窗口

说明　要从调试状态回到设计状态，可以单击工具栏上的“结束”快捷按钮。

知道了错误的根源后，一起来分析一下错误的原因吧。错误提示窗口中显示的内容是“无效属性值”，因此带有黄色背景的语句错误的原因初步判定为给txtShowWord. FontName属性赋了无效的值，即变量strfont中的值有问题。分析了给strfont赋值的3条语句后会发现：如果3个If语句都不执行，则strfont就不会被赋值，仍旧保持默认值，即空字符串。从而赋给txtShowWord.FontName属性的值为空字符串。而那3个If语句没有执行的原因是其后条件表达式都为假，即optFont1、optFont2和optFont3三个单选按钮都没有被选择。

接下来，仔细看图4.13错误提示窗口后面的程序界面。不难发现，出错的原因就是因为没有在字体框架里选择任何单选按钮，而直接单击“应用”按钮造成的。其实，字号也会出现这样的错误，只是Visual Basic还没有检测到而已。上面的错误是一种较典型的错误，它并非是语法错误，而是因为思维不严谨所致，在编程时应当注意。并且应当学会给程序设置不同的输入数据和状态来测试程序的健壮性。解决本例错误的方法有如下两种。

❑方法1：在紧挨着变量声明的下方，给变量赋值，在程序设计中将这种赋值操作叫做初始化，如图4.15所示。

图　4.15　错误解决方法1

❑方法2：当窗体加载时在每一组里都指定一个默认单选按钮。为了达到此目的，应当在窗体的Form_Load()事件中加入如下语句。

optFont1.Value=True

optSize1.Value=True

如果采用了方法2，其执行过程是：当运行程序后，窗体被加载的一刹那，就会触发Form_Load()事件，随即执行了该事件过程内的代码，因此窗体出现在屏幕上的时候，其内的optFont1（宋体）和optSize1（10）两个单选按钮的状态为被选中状态。在编程时，经常会用方法2来设置控件的初始状态和初始值。修改完程序后，读者可以重新运行程序，并设计不同的单选按钮的选择状态测试本实例。

4.8　列表框与组合框

利用列表框可以直观地选择所需要的项目，而组合框可以把一个文本框和列表框组合为一个整体控件。在编程时如果要给用户一种直观的选择，可以使用列表框；当窗体空间很紧张的时候，因为列表框会占据很大的空间，所以这时采用组合框较好。

4.8.1　列表框介绍

列表框用来让用户从中选择所需要的项目。如果有多种选项让用户选择，使用列表框是个不错的方案。比如，Word2003的“主题”对话框内，列出所有可选主题的区域就是一个列表框控件，如图4.16所示。又例如聊天室软件的界面内列出所有聊天室成员的控件也是列表框。Visual Basic给列表框起的默认名字是List1、List2、List3…等。

图　4.16　列表框

4.8.2　列表框的常用属性

列表框除了拥有大部分控件的通用属性以外还有如下几个常用属性。

1.Columns属性

该属性用来确定列表框的列数。其取值含义如下。

❑设置为0：（默认值）以单列显示。如果项目的条数较多，在列表框中显示不下时，自动出现垂直滚动条。

❑设置为1到n：多列显示。先填第一列，再填第二列，以此类推。ListBox水平滚动并显示指定数目的列。

2.List属性

该属性在设计时，可以通过属性窗口为列表框添加内容。同时也可以在程序中提取列表项的内容和设置列表项的内容。通过程序语句引用列表项内容的通用格式为：

列表框. List（索引号）

其中，索引号为列表项的序号。列表项的序号从0开始。因此，列表框第一条列表项的序号（索引号）为0，依次类推。

例如：假设列表框List1中的内容如图4.17所示。

图　4.17　列表框

s=List1.List（3）

上面表达式的含义为：将列表框List1的第4项的内容“桔子”赋值给变量s。又如，

List1.List（0）="葡萄"

含义为：将列表框List1的第1项的内容“苹果”改为“葡萄”。

技巧　当在属性窗口中使用List属性为列表框添加项目时，在List的下拉编辑器里如果要换行可以使用Ctrl+回车来避免每次回车都会关闭编辑器的问题。

List属性其实是数组。数组可以被简单理解成拥有同一个名字的变量的组合，每一个变量称为数组中的元素。其后跟随的圆括号内的数值称为下标，在程序中通过下标来区分数组中具体的元素。关于数组详细的知识在本书的第6章讲解。

3.ListCount属性

该属性中存放列表框中列表项的项数（条数）。列表框中各列表项的排列从0开始，最后一项的序号为ListCount-1。例如语句：

MsgBox List1.ListCount

的作用是将列表框List1的总项数显示到了一个消息框中。

4.ListIndex属性

该属性的设置值是已选中的表项的位置。表项位置由索引值指定，第1项的索引值为0，第2项为1，依次类推。如果没有选中任何项，ListIndex的值将设置为-1。在程序中设置ListIndex后，被选中的条目反相显示。例如执行：

List1.ListIndex=1

后可以让List1中的第2项被选中。又例如在图4.17中当选中第4项“桔子”时List1.ListIndex的值会被设置为3。

5.MultiSelect属性

该属性决定了是否在列表框框内可以选择多项。其值可以被设置为0、1或2。具体意义如下。

❑设置为0：表示一次只能选择一项，不能在列表框中选择多项。

❑设置为1：表示允许选择列表框中多个项，每用鼠标单击一个项，则该项被选中。

❑设置为2：表示可以选择列表框中某个范围内的项，即可以用Shift+单击鼠标，选择一组连续排列的项；或用Ctrl+单击鼠标，选择一组不连续排列的项。

6.Selected属性

该属性也是一个数组。使用该属性可以判断列表框中的列表项是否被选中，也可以通过该属性设置列表项的选择状态，其值可以是True或False，当为True时表示此项被选中，当为False时表示此项没有被选中。例如用下面的语句可以取消List1中第4项被选择的状态。

List1.Selected(3)=False

又例如可以用下面的程序判断列表框中的第1项是否被选中。

If List1.Selected(0)=True Then

MsgBox"第1项被选中了！"

Else

MsgBox"第1项没有被选中！"

End If

7.Sorted属性

该属性用来确定列表框中的列表项是否排序。如果将其设置为True则列表项按字母、数字升序排序，如果设置为False则不排序，即列表项按照加入列表框的先后次序排列。

8.Style属性

该属性用来确定列表框的外观，其值可以设置为0和1。具体外观如图4.18所示。

图　4.18　Style属性设置

9.Text属性

在只能选择一项（MultiSelect=0）的列表框里Text属性的值是被选中的列表项的内容，而在能够多选（MultiSelect=1或2）的列表框里Text属性的值为最后一次选中的列表项的内容。在此说明一点，列表框的Text属性不能被赋值，只能提取内容。

4.8.3　列表框的方法和事件

1.AddItem（插入列表项）方法

AddItem方法用来在列表框中插入列表项。虽然在设计阶段可以通过属性窗口里的List属性向列表框内添加列表项，但是考虑到实际应用通常会用程序向列表框内添加列表项。AddItem方法的通用格式为：

列表框.AddItem 项目字符串[,索引号]

AddItem方法把“项目字符串”的文本内容放入到列表框中。“索引号”可以指定插入项在列表框中的位置。如果省略“索引号”，该项就会被添加到列表框的末尾。

【实例4.9】编写程序，试验AddItem方法。

新建一个标准EXE工程后，在Form1窗体上画一个列表框，并使用其默认名称List1。双击Form1窗体后写入如下代码：

01 Private Sub Form_Load()

02 List1.AddItem"Visual Basic教程"

03 List1.AddItem"Visual C++教程"

04 List1.AddItem"Visual FoxPro数据库应用"

05 List1.AddItem"java语言程序设计"

06 End Sub

【代码说明】代码第02～05行非常简单，只是演示了AddItem方法，其中的参数值就是显示在列表框中的内容。

【运行效果】运行该程序后，窗体的Form_Load事件自动被触发，随之执行其内的语句，向列表框List1添加了4个列表项。运行结果如图4.19所示。

图　4.19　实例4.9运行结果

2.Clear（清除列表框）方法

该方法用来清除列表框中的所有内容。通用格式为：

列表框.Clear

3.RemoveItem（删除列表项）方法

该方法用来删除列表框中指定的项目。通用格式为：

列表框. RemoveItem索引号

例如，要删除列表框List1中第3项时可以用如下语句来完成。

List1. RemoveItem 2

又例如要删除当前被选择的列表项时可以使用语句：

List1.RemoveItem List1.ListIndex

因为，List1.ListIndex代表的是当前被选择的列表项的索引号。

4.DblClick（双击）事件

列表框可以响应Click和DblClick事件。其Click事件和其他控件的Click事件没有什么区别，在此不再重复。DblClick事件在双击列表框时被触发。

【实例4.10】如图4.20所示，编写一个选购物品的程序。

图　4.20　实例4.10运行效果

【运行效果】

❑在左侧的列表框内列出所有可以购买的物品。

❑当要购买某物品时用鼠标双击该物品，此时该物品就会被添加到右侧的列表框内。

❑如果想取消购买的某物品，可以在右侧的已经购买的列表框中双击该物品名称，即可从列表框中删除。程序中各对象属性设置如表4.13所示。

【代码说明】

（1）要实现第一种效果，应当在窗体的加载事件中写入给列表框List1添加项目的代码，代码清单如下。

01 Private Sub Form_Load()

02 List1.AddItem"牛奶"

03 List1.AddItem"奶茶粉"

04 List1.AddItem"啤酒"

05 List1.AddItem"羊绒衫"

06 List1.AddItem"西装"

07 List1.AddItem"领带"

08 End Sub

（2）要实现双击List1中的列表项时将其添加到List2中，代码应该写在List1的双击事件里，并应当使用List2列表框的AddItem方法将List1列表框当前被选择的列表项的内容添加到List2中。代码清单如下。

09 Private Sub List1_DblClick()

10 List2.AddItem List1.Text

11 End Sub

（3）实现在List2中双击删除列表项时，代码应该写在List2的双击事件里，并使用List2的RemoveItem方法删除当前所选择的项目。代码清单如下。

12 Private Sub List2_DblClick()

13 List2.RemoveItem List2.ListIndex

14 End Sub

4.8.4　组合框介绍

组合框可以说是列表框和一个文本框的组合。在Visual Basic的属性窗口里用到Font属性时，其弹出的“字体”对话框内选择“字体”、“字形”、“大小”和“字符集”的控件都是组合框控件，如图4.21所示。组合框解决了列表框内只能选择给定的选项而不能输入用户选项的缺点。同时组合框可以结合下拉部分，这就意味着组合框可以比普通的列表框占用更小的窗体空间。

图　4.21　字体对话框

4.8.5　组合框的属性、方法和事件

列表框的属性、方法和事件基本上都可用于组合框，此外它还有一些自己的属性和事件。

1.组合框的属性

（1）Style属性

该属性不同的取值决定了组合框3种不同的类型，如图4.22所示。其取值范围为0、1或2。具体取值含义如下。

图　4.22　组合框的3种类型

❑当Style的属性值为0时，组合框称为“下拉式组合框”。它由可输入的编辑区和一个下拉列表框组成，用户可以用键盘直接向文本编辑区输入内容，也可以单击右端的箭头，从下拉的列表框中选择一项，单击该项使之被选中，则上面文本区就出现该项的内容。

❑当Style的属性值为1时，称为“简单组合框”，它也是由一个文本区和一个列表框组成，但该列表框不是下拉式的，而是始终显示在屏幕上的。在画组合框时可按自己的意愿选择组合框的大小。如果所选择的组合框的大小不能将全部项目在列表框中显示出来，在列表框的右侧就会出现一垂直滚动条，用户可以通过滚动条浏览无法直接显示在列表框中的项目。如果所选择的组合框的大小能将全部项目在列表框中显示出来，在列表框的右侧不会出现垂直滚动条。用户可以从列表框中选择所需要的项目，也可以直接向文本区输入信息。这种组合框可以识别DblClick事件（其他两种不能识别DblClick事件）。

❑当Style的属性值为2时，称为“下拉式列表框”，它的形状与“下拉式组合框”相似，右端也有一个箭头能弹出一个下拉式列表框，但用户只能从列表框中选择，而不能直接向文本区输入。Visual Basic代码编辑器窗口的对象框和过程框都是这种类型的组合框。在实际编程中，可以根据需要选择使用以上3种组合框。

（2）Text属性

Text属性的值是在其文本区中显示出来的内容。例如，如图4.21的最左侧的组合框的名称为Combo1，则Combo1.Text的值为“桔子”。利用Text属性也可以设置组合框的文本区中显示的内容，例如，语句

Combo1.Text="请在此选择水果种类"

的作用为在文本区中显示字符串“请在此选择水果种类”。通常用此方法在窗口显示的最初时，提示用户对组合框进行操作。

注意　不能用Text属性向组合框添加列表项，只能用其控制文本区中显示的内容。

2.组合框的方法

前面介绍的AddItem、Clear和RemoveItem方法也同样适用于组合框，其用法和列表框的用法也相同。

3.组合框的事件

组合框响应的事件依赖于其Style属性。例如，只有简单组合框（Style=1）才能接收DblClick事件，其他两种组合框可以接收Click事件和DropDown事件。对于下拉式组合框（Style=0）和简单组合框，可以在编辑区输入文本，当输入文本时可以接收Change事件。当用户单击组合框中向下的箭头时，将触发DropDown事件。

4.8.6　应用案例——计算机订购程序

【实例4.11】如图4.23a所示，编写一个计算机订购程序。

【运行效果】

❑用两个单选按钮“台式机”（optPC）和“笔记本电脑”（optNoteBook）选择机器的类型，当选择“台式机”时右侧的列表框（List1）中会显示字符串“类型：台式机”，如果选择“笔记本电脑”列表框中会显示“类型：笔记本电脑”。其中，“台式机”单选按钮为默认选项。

❑用3个复选框cboCPU、cboMemory、cboHardDisk来分别选择“CPU型号”、“内存容量”和“硬盘容量”，在选择的同时，右侧的列表框中将出现选择的结果，如图4.23b所示。

❑单击“全部重选”（cmdClearList）按钮，会将界面中的各选项还原到图4.23a所示的初始状态；单击“确定订购”（cmdOrder）按钮会弹出一个消息框；单击“取消订购”（cmdCancel）按钮会卸载窗体。

图　4.23　实例4.11效果

程序中各对象需要更改的属性如表4.14所示，其他属性均取默认值。

编写事件过程如下。

01 Private Sub Form_Load()

02 List1.AddItem"类型：台式机" '给List1添加初始列表项，以配合单选按钮“台式机”的被选择状态

03 optPC.Value=True '将单选按钮“台式机”的状态设置为被选择状态

04

05 cboCPU.Text="——请选择CPU型号——" '设置组合框文本区中的提示文字

06 cboMemory.Text="——请选择内存容量——"

07 cboHardDisk.Text="——请选择硬盘容量——"

08

09 cboCPU.AddItem"CPU:英特尔奔腾2.0G处理器" '给组合框添加列表项

10 cboCPU.AddItem"CPU:英特尔酷睿2双核处理器"

11 cboCPU.AddItem"CPU:英特尔奔腾D双核处理器"

12 cboCPU.AddItem"CPU:AMD速龙64 3000+处理器"

13 cboCPU.AddItem"CPU:AMD速龙64X2处理器"

14

15 cboMemory.AddItem"内存：256M"

16 cboMemory.AddItem"内存：512M"

17 cboMemory.AddItem"内存：1G"

18 cboMemory.AddItem"内存：2G"

19

20 cboHardDisk.AddItem"硬盘：80G"

21 cboHardDisk.AddItem"硬盘：120G"

22 cboHardDisk.AddItem"硬盘：160G"

23 cboHardDisk.AddItem"硬盘：320G"

24 End Sub

25

26 Private Sub optPC_Click()

27 List1.RemoveItem 0 '将List1中的第一项删除

28 List1.AddItem"类型：台式机",0 '将列表项添加到List1的第一个位置上

29 End Sub

30

31 Private Sub optNoteBook_Click()

32 List1.RemoveItem 0

33 List1.AddItem"类型：笔记本电脑",0

34 End Sub

35

36 Private Sub cboCPU_Click()

37 List1.AddItem cboCPU.Text '将列表项添加到List1中

38 End Sub

39

40 Private Sub cboMemory_click()

41 List1.AddItem cboMemory.Text

42 End Sub

43

44 Private Sub cboHardDisk_Click()

45 List1.AddItem cboHardDisk.Text

46 End Sub

47

48 Private Sub cmdOrder_Click()

49 MsgBox"恭喜您订购成功！您的订单编号是0001",vbInformation+vbOKOnly,"提示框"

50 End Sub

51

52 Private Sub cmdCancel_Click()

53 Unload Me

54 End Sub

55 Private Sub cmdClearList_Click()

56 List1.Clear

57

58 List1.AddItem"类型：台式机"

59 optPC.Value=True

60

61 cboCPU.Text="——请选择CPU型号——"

62 cboMemory.Text="——请选择内存容量——"

63 cboHardDisk.Text="——请选择硬盘容量——"

64 cboCPU.SetFocus

65 End Sub

【代码说明】其中，代码第01～24行的Form_Load事件和代码第55～65行的cmdClearList_Click事件中，以下两条语句：

List1.AddItem"类型：台式机"

optPC.Value=True

的位置不能互换，原因是语句optPC.Value=True会触发optPC_Click事件，随即会执行其内的语句

List1.RemoveItem 0

用来删除List1中的第1项。但此时，列表框List1中没有列表项，因此会产生“无效的过程调用或参数”的错误。

本程序只是用于演示组合框、列表框、单选按钮、命令按钮、框架和标签的协同工作时的一些编程方法，目的在于对上面讲述的知识进行融会贯通，因此程序的功能并不完善。

4.9　图像框

在程序界面中经常会放置一些图形图像来美化自己的界面。因此Visual Basic为用户提供了两个处理图形图像的控件，分别是图片框（PictureBox）和图像框（Image）。在工具箱中，图片框的图标为
，图像框的图标为
。

图像框控件用来显示图形，它可以说是图片框控件的一个缩写版，如果仅仅要显示一个图形可以选择此控件，因为该控件占用的内存很少。图像框控件最重要的属性有Picture和Stretch两个属性。

4.9.1　设置图像框中的图形

在图像框里显示图形文件，通常会用到Picture和Stretch两个属性。Picture属性用来给窗体、图片框、图像框添加图形，这些图形文件的类型可以是：位图文件（.BMP或.DIB）、图标文件（.ICO或.CUR）、图元文件（.WMF或.EMF）、JPEG文件(.JPG)和GIF文件（.GIF）。Picture属性可以在设计阶段通过属性窗口设置，也可以在运行阶段通过LoadPicture（）函数设置。通过属性窗口设置的操作步骤如下。

（1）在窗体上画出一个图像框，并将其选中。

（2）在属性窗口中找到Picture属性并单击该属性条，最右侧会出现一个带有3个点的按钮
。

（3）单击
按钮，在打开的“加载图片”对话框中找到需要的图形文件即可。

Stretch属性是一个逻辑值属性，只能取True和False两种值。当Stretch设置为True时图形的大小会按照图像框的大小自动调整，而如果设置为False（默认值），图像框会根据图形的大小而变化。如果希望加载进来的图形完整地显示在已经规定好尺寸的图像框中，就应该设置Stretch属性为True。

4.9.2　使用LoadPicture函数设置图形

要在运行阶段给图像框设置图形，必须使用LoadPicture函数，LoadPicture函数的格式为：

图像框.Picture=LoadPicture("文件名")

其中，“文件名”是带有完整路径和扩展名的文件名。例如：

Imag1.Picture=LoadPicture("c:\vbtest\pic\111.jpg")

使用LoadPicture函数不仅可以在运行阶段给图像框、图片框、窗体添加或改变图形文件，也可以删除其中的图形文件，例如下面的语句删除了Image1中的图形：

Imag1.Picture=LoadPicture()

在LoadPicture函数后面的括号里什么也不写就等于删除了对象中的图形文件。该函数除了给Picture属性设置值以外，也可以给其他图形属性设置值。例如，可以给窗体设置图标，语句可以写为：

Form1.Icon=LoadPicture("c:\vbtest\icon\main.ico")

4.9.3　相对路径——App.Path

上一小节讲述的LoadPicture函数中带有盘符的路径在程序设计语言里被称为绝对路径，在编程时写绝对路径是一个很不好的习惯，因为开发出来的应用程序会被安装到千万台不同的计算机上，这时谁也保证不了这些计算机的C盘下会有什么程序使用到的那个图形文件。因为上述原因，通常在程序里都要使用相对路径。用法如下：

Imag1.Picture=LoadPicture(App.Path＆"\111.jpg")

其中，App.Path会自动获得工程文件所在的路径，程序中其他资源的位置可以通过参照这个路径获得。当然，前提是所有资源应该有条理地和工程文件放到一起。例如，假设工程文件“工程1”被保存到了“D:\vb试验”路径下，那么程序运行后App.Path的值就是字符串“D:\vb试验”，而字符串连接表达式App.Path＆"\111.jpg"的值就会是“D:\vb试验\111.jpg”。

4.9.4　设置鼠标指针形状

在使用Windows应用程序时，经常会看到不同的鼠标指针形状，例如，有箭头、十字、小工字、双向箭头等等。这些指针形状提示了用户下一步应该做什么样的操作。试想一下如果用户想要通过拖拽窗口的边界改变窗口的大小时，鼠标指针的形状怎么也不变成双箭头时的情景，用户肯定会认为不能改变这个窗口的尺寸，从而放弃这个操作。因此，在应用程序中设置好指针形状是一件很必要的事情。下面讲述设置指针形状时用到的两个属性MousePointer和MouseIcon。

MousePointer属性用来设置鼠标移动到某对象的一个特定部分时鼠标指针的形状。例如，假设将一个图像框控件的MousePointer属性设置为2-Cross后，当将鼠标移动到该图像框上时鼠标指针形状就会变为十字形。其具体值的含义可通过在属性窗口中单击MousePointer属性，然后按键盘上的F1键查看帮助文档。

MouseIcon属性主要用来设置自定义的鼠标指针的图标。如果希望鼠标移动到某对象上的时候其指针形状变为自己设定的图形，就可以设置该属性，但是前提是必须将MousePointer属性设置为99才可以。

4.9.5　应用案例——看图学英语

图像框与其他大部分控件一样也有单击、双击等事件。下面的实例使用了其单击事件。

【实例4.12】编写一个看图学英语的程序。实现目标为：

❑当鼠标移动到图片上的时候，鼠标形状就会从箭头变成自定义的手形，离开图像时又恢复成箭头。

❑当用鼠标单击图片时，相应的英语单词就会显示到窗体相应位置上。

界面设计如图4.24所示。

图　4.24　实例4.12界面设计

表4.15是本例中各对象的属性设置，在此要注意6个图像框控件的MousePointer和MouseIcon属性的设置。

编写事件过程如下。

01 Private Sub Form_Load()

02 imgClock.Picture=LoadPicture(App.Path＆"\pic\clock.jpg")

03 imgApple.Picture=LoadPicture(App.Path＆"\pic\apple.jpg")

04 imgHat.Picture=LoadPicture(App.Path＆"\pic\hat.jpg")

05 imgBasketball.Picture=LoadPicture(App.Path＆"\pic\basketball.jpg")

06 imgPen.Picture=LoadPicture(App.Path＆"\pic\pen.jpg")

07 imgDog.Picture=LoadPicture(App.Path＆"\pic\dog.jpg")

08 End Sub

09

10 Private Sub imgApple_Click()

11 lblWord.Caption="apple"

12 End Sub

13

14 Private Sub imgBasketball_Click()

15 lblWord.Caption="basketball"

16 End Sub

17

18 Private Sub imgClock_Click()

19 lblWord.Caption="clock"

20 End Sub

21

22 Private Sub imgDog_Click()

23 lblWord.Caption="dog"

24 End Sub

25

26 Private Sub imgHat_Click()

27 lblWord.Caption="hat"

28 End Sub

29

30 Private Sub imgPen_Click()

31 lblWord.Caption="pen"

32 End Sub

【代码说明】代码第02～07行使用LoadPicture方法来加载指定地址的图片。其中的App.Path自动获取工程所在的路径。代码第10～32行不过是显示每个图片单击后的效果。

注意　在运行程序之前应当先保存工程文件，否则App.Path的路径是错误的。而且，还需要注意的是一定要在设计程序阶段将图形文件找好，并放到正确的路径下。本书将所有图形文件都放到了和工程文件在同一个路径下的"pic"文件夹中。

【运行效果】最后运行结果如图4.25所示。

图　4.25　实例4.12的运行结果

4.10　图片框

图片框控件相对于图像框控件来说可以说是重量级控件，因为其占用的内存比较大。如果只是显示图片，可以采用前面介绍的图像框，而如果要在其中使用画图语句绘制图形，或者要在其中放置控件，则应该使用图片框。

4.10.1　图片框介绍

图片框不仅可以用来显示图形，也可以作为其他控件的容器，而且还能用Print在其上输出文字，用Line、Circle等方法在其上绘制直线、圆等几何形状。例如语句：

Picture1.Print"Free！"

可以在图片框Picture1中输出字符串"Free!"，又例如：

Picture3.Line (0,0)-(1000,5000)

可以在图片框Picture3中画出一条起始点坐标为（0，0），终止点坐标为（1000，5000）的直线。具体详细内容见本书第8章8.4节。

在图片框中加载图形文件的方法和图像框一样，也是使用Picture属性来完成。图片框没有Stretch属性，因此图片框里的图形文件大小永远不会变化，最多只能按照图形文件的尺寸改变图片框的大小。AutoSize属性就是为了这个目的而存在的，该属性为True时图片框的大小会随着图形文件的尺寸而变化，而如果为False时图片框大小不会被改变，因此图形文件只能露出一部分。图片框有一个很有意思的属性是Image属性，该属性从属性窗口中是找不到的，只能在运行阶段使用，用来拷贝图片框中露出的那部分图形而并非是整个图形。

【实例4.13】做一个简单的关于Image属性的试验。下面一切对象的名称都用默认名称。按照图4.26的布局，在窗体上画3个图片框和两个按钮，图片框所有属性均取默认值，按钮只改变其Caption属性。

图　4.26　实例4.13运行结果

编写程序如下。

01 Private Sub Command1_Click()

02 Picture2.Picture=Picture1.Image '将Picture1.Image赋值给了Picture2.Picture

03 End Sub

04

05 Private Sub Command2_Click()

06 Picture3.Picture=Picture1.Picture '将Picture1.Picture赋值给了Picture3.Picture

07 End Sub

08

09 Private Sub Form_Load()

10 Picture1.Picture=LoadPicture(App.Path＆"\pic\hat.jpg")

11 End Sub

【代码说明】当单击Command1按钮时，代码第02行将Picture1.Image赋值给了Picture2.Picture，所以虽然Picture2的尺寸够大，但是在Picture2中却只能看到在的Picture1中露出来的部分。而单击Command2按钮时，代码第0 6行将Picture1.Picture赋值给了Picture3.Picture所以在Picture3中可以看到Picture1中载入的全图。

【运行效果】程序运行后首先自动触发Form_Load事件，将图形文件载入到图片框Picture1内，因为该图片框比图形文件的尺寸小，所以只露出了图形的一部分。运行结果如图4.27所示。

图　4.27　实例4.13运行结果

4.10.2　应用案例——移动小蜜蜂

【实例4.14】如图4.28所示，编写一个使用按钮移动小蜜蜂的程序。实现的效果是：单击窗口上的方向按钮时小蜜蜂就会做相应的移动。其中，显示蜜蜂的图像框在图片框内部。

图　4.28　实例4.14运行结果

各对象的属性设置如表4.16所示。

编写事件过程如下。

01 Private Sub cmdDown_Click()

02 imgBee.Top=imgBee.Top+100

03 End Sub

04

05 Private Sub cmdLeft_Click()

06 imgBee.Left=imgBee.Left-100

07 End Sub

08

09 Private Sub cmdRight_Click()

10 imgBee.Left=imgBee.Left+100

11 End Sub

12

13 Private Sub cmdUp_Click()

14 imgBee.Top=imgBee.Top-100

15 End Sub

【代码说明】本例中，图片框充当了容器的作用，而图像框imgBee是这个容器中的对象。所以对于imgBee来说，其Top属性是图片框的顶端到imgBee上边界之间的距离，其Left属性是图片框左边到imgBee左边界之间的距离。代码第02、06、10行分别设置其Left属性，而代码第14行设置其Top属性。

4.11　计时器

Visual Basic为用户提供了一个名叫计时器（Timer）的控件，该控件在运行阶段是不可见的，因此可以将其放到窗体的任何地方。计时器的属性和事件很少，最重要的属性有Interval和Enabled，最重要的事件也是唯一的事件有Timer。计时器控件在Enabled属性为True的前提下，每隔Interval/1000秒就会自动触发Timer事件。

4.11.1　Interval属性

Interval（时间间隔）属性以千分之一秒为单位，该属性用来控制计时器事件触发的时间间隔。例如，如果将该属性设置为1000，Visual Basic就会每隔1秒钟触发一次Timer事件，如果想让Timer事件每隔3秒钟就触发一次，那应该设置Interval为3000。当然前面已经提到过是在Enabled属性为True的前提下上面的描述才有效。

4.11.2　Timer事件

Timer事件是计时器控件的唯一事件。当Enabled属性为True的前提下，它在每隔Inerval/1000秒就会自动发生，根据此特性编程人员经常使用计时器控件计时或实现动画效果。

【实例4.15】编程实现时间走动的电子表的效果，窗体中有一个文本框和一个计时器，如图4.29所示。

图　4.29　实例4.15设计界面

各控件的属性设置如表4.17所示，其中将计时器的Interval属性设置为1000的目的是让Timer事件每隔一秒钟自动发生一次。

编写事件过程如下。

01 Private Sub tmrTime_Timer()

02 txtShowTime.Text=Time()

03 End Sub

说明　程序中的Time()是一个系统函数，用来提取系统当前时间。需要注意的是Time()函数提取的当前时间是一个点，而不是连续的时间。

【代码说明】程序运行后等待Interval属性设置的时间间隔为1秒钟，当过了1秒钟之后自动触发计时器tmrTime的Timer事件，代码第02行在此事件中，将当前系统时间显示到了文本框中，接下来再次等待1秒钟后再次触发Timer事件并执行其内的表达式提取当前系统时间（注意这时系统时间已经向后走了一秒）然后显示到文本框里（替换了以前的内容），就这样一直反复，直到结束程序为止。

【运行效果】运行结果如图4.30所示。

图　4.30　实例4.15运行结果

说明　计算机动画和普通动画其实是一个原理，都是将一个一个场景快速地播放来引起人们视觉上的动感错觉。

4.11.3　应用案例——实现弹球动画

上一节的应用案例“移动小蜜蜂”实现了通过单击按钮来移动图形的效果，这一节将带领读者使用图片框、图像框和计时器完成图形自动移动的功能。

【实例4.16】编程实现弹球动画，最终效果为图中的圆球以直线自动移动，当遇到边界时向相反的方向移动，类似于打砖块游戏中的弹球，界面设计如图4.31所示，图中白色区域为图片框，虚框是一个图像框。

图　4.31　实例4.16的程序界面

窗体中，各控件的属性设置如表4.18所示。

编写事件过程如下。

01 Option Explicit

02 Dim x As Integer

03 Dim y As Integer

04

05 Private Sub Form_Load()

06 imgBall.Picture=LoadPicture(App.Path＆"\pic\ball.gif")

07 x=100

08 y=100

09 End Sub

10

11 Private Sub tmrMoveBall_Timer()

12 If imgBall.Top＞=pic1.Height-imgBall.Height Then '判断圆球的下边界是否移到了图片框下边界处

13 y=-100

14 End If

15

16 If imgBall.Left＞=pic1.Width-imgBall.Width Then '判断圆球的右边界是否移到了图片框右边界处

17 x=-100

18 End If

19

20 If imgBall.Top＜=0 Then '判断圆球的上边界是否移到了图片框上边界处

21 y=100

22 End If

23

24 If imgBall.Left＜=0 Then '判断圆球的左边界是否移到了图片框左边界处

25 x=100

26 End If

27

28 imgBall.Left=imgBall.Left+x

29 imgBall.Top=imgBall.Top+y

30 End Sub

程序中的x，y两个变量是窗体级变量，其作用范围是整个窗体代码模块。关于程序中用到的判断边界的方法可以参考图4.32。

图　4.32　判断边界的参考图

【代码说明】首先自动触发第05～09行的窗体加载事件，图像框中载入了图形文件"ball.gif"，并初始化x，y两个变量为100。等待0.1秒钟（因为Interval为100）后，计时器中第11～30行的Timer事件被自动触发，随之执行其内的语句。首先执行第一个If语句，因为这时图像框在初始位置（参考图4.30），其属性imgBall.Top的值不大于pic1.Height-imgBall.Height的值，所以该If语句因为条件为False而不执行。后面的3个If语句因为类似的原因也不被执行直接跳过，然后执行了最后两条语句，将图像框imgBall向右、下方向分别移动了100，改变了图像框的位置。

直到圆球碰到下边界以前，程序都按以上分析的情况执行，一旦碰到下边界图像框的属性imgBall.Top的值就会不小于pic1.Height-imgBall.Height的值，从而执行第一个If语句，将变量y的值改变为-100，其他3个If语句此时因为条件还是为False所以还是不执行，执行到最后两条语句时图像框imgBall因为y的值变为了-100所以会向上移动，而x的值因为还是100所以继续向右移动，因此得到了向右上移动的效果。读者可以用此思路继续分析程序的运行效果。

【运行效果】程序运行结果请参考图4.33。

图　4.33　实例4.16程序运行结果

说明　图中黑线为圆球前5步的大致移动路径

4.12　滚动条

在Windows中，会经常遇到滚动条，有垂直、水平两种，都附在窗口、组合框、列表框的上面，用来拖动观察窗口或框中未能显示出来的部分，这种滚动条是系统自动添加上的，不需要用户自己设计。而下面要介绍的不是这种滚动条。

Visual Basic提供了滚动条控件，编程人员可以利用滚动条输入数据或确定位置。在工具箱中，有水平滚动条
和垂直滚动条
两个控件，除方向不同外，这两个滚动条的结构和操作是一样的。滚动条的两端各有一个滚动箭头，在中间有一个滑块，如图4.34所示。

图　4.34　滚动条

4.12.1　滚动条的属性

通常，垂直滚动条的值由上往下递增，最上端代表最小值（Min），最下端代表最大值（Max）。水平滚动条的值从左向右递增，最左端代表最小值，最右端代表最大值。滚动条的值均以整数表示，其取值范围在-32768～32767。

滚动条的坐标系与它当前的尺寸大小无关。可以把每个滚动条当作有数字刻度的直线，从一个整数到另一个整数。这条直线的最小值和最大值分别在该直线的左、右端或上、下端，其值分别用属性Min和Max代表。滚动条的长度与坐标系无关。

1.Max属性

滚动条所能表示的最大值，取值范围为-32768～32767。当滑块位于最右端或最下端时，滚动条的值（Value）为该值。

2.Min属性

滚动条所能表示的最小值，取值范围也为-32768～32767。当滑块位于最左端或最上端时，滚动条的值（Value）为该值。设置好Max、Min属性之后，滚动条的值就会在这两个数的范围内变化。

3.LargeChange属性

单击滚动条时，Value增加或减小的增量值。

4.SmallChange属性

单击滚动条两端的箭头时，Value增加或减小的增量值。

5.Value属性

该属性值表示滑块在滚动条上的当前位置。在运行阶段用该属性也可以设置滑块在滚动条中的位置。

4.12.2　滚动条的事件

滚动条最有用的事件只有两个，分别是Scroll和Change事件。当在滚动条内拖动滑块时触发Scroll事件，而改变滑块的位置后触发Change事件。这样听起来这两个事件好像没什么区别，实际上Scroll事件在拖动滑块的过程中不断地被触发，而Change事件只是在最后停止拖拽而且滑块位置被改变后才会被触发。例如，如果将滑块从最左侧拖拽到最右侧，并且在不松开鼠标左键的状态下再拖回到最左侧，整个过程中Scroll事件会不断地被触发，而Change事件因为滑块的位置最终没有被改变，所以一次都没有被触发。

4.12.3　应用案例——选择背景颜色

【实例4.17】如图4.35所示，窗体中有3个水平滚动条，分别用来控制红色、绿色和蓝色值，通过拖动滑块来控制窗体背景颜色。

图　4.35　实例4.17的运行结果

窗体中，各控件的属性设置如表4.19所示。

编写事件过程如下。

01 Private Sub hsbBlue_Scroll()

02 Form1.BackColor=RGB(hsbRed.Value,hsbGreen.Value,hsbBlue.Value)

03 End Sub

04

05 Private Sub hsbGreen_Scroll()

06 Form1.BackColor=RGB(hsbRed.Value,hsbGreen.Value,hsbBlue.Value)

07 End Sub

08

09 Private Sub hsbRed_Scroll()

10 Form1.BackColor=RGB(hsbRed.Value,hsbGreen.Value,hsbBlue.Value)

11 End Sub

【代码说明】上面代码中第02、06、10行出现的RGB是一个可以代表颜色值的函数，其格式为：

RGB(red,green,blue)

RGB函数有3个参数，从左至右分别代表颜色的红色成分、绿色成分和蓝色成分，其每一个参数的取值范围是0～255之间的整数。例如，RGB（255，0，0）代表红色，RGB（0，255，0）代表绿色而RGB（0，0，255）代表蓝色。众所周知，通过红绿蓝三色可以调制出几乎所有的可见色，因此，RGB函数可以通过控制其3个参数的值来代表所有可见色，上面的程序就是用了这种原理。

4.13　驱动器列表、目录列表和文件列表

驱动器列表
控件（DriveListBox）用来显示计算机中的驱动器盘符，目录列表
控件（DirListBox）用来显示某驱动器的目录树，而文件列表
控件（FileListBox）用来列出某目录下所有文件的清单。在设计应用程序时，通常是一起联合使用这三个控件。下面通过一个例子讲解这三个控件的用法。

【实例4.18】如图4.36所示，当用户在驱动器列表控件Drive1中改变盘符时，下面的目录列表中的内容也会随之改变成Drive1中新的驱动器下的目录，当目录列表控件Dir1中的当前目录（文件夹）被改变时，右侧的文件列表控件File1中就会显示新的当前目录下的所有文件清单（简言之就是这三个控件达到同步的效果），并且在File1中双击某文件名时能够显示该文件的完整路径和名称。为了方便调试，该程序所有控件均取默认值。

图　4.36　实例4.18界面

编写代码如下。

01 Private Sub Drive1_Change()

02 Dir1.Path=Drive1.Drive '设置目录控件的当前驱动器路径

03 End Sub

04

05 Private Sub Dir1_Change()

06 File1.Path=Dir1.Path '设置文件列表控件的当前目录路径

07 End Sub

08

09 Private Sub File1_DblClick()

10 MsgBox File1.Path＆"\"＆File1.FileName '显示所选文件的完整路径和文件名

11 End Sub

【代码说明】熟悉代码必须先了解以下知识点。

❑驱动器列表控件最常用的事件是Change事件，该事件在用户改变驱动器列表控件中的驱动器时被触发。例如，当前驱动器列表控件中的驱动器为C盘，接下来如果从控件中选择了驱动器D盘就会触发此事件。驱动器列表控件最常用的属性为Drive属性，该属性在程序运行阶段可用，其值为用户在驱动器列表控件中选中的驱动器。

❑目录列表控件最常用的事件也是Change事件，该事件在改变所选的目录时被触发。有两种方法可以改变所选的目录：一是双击其他目录；二是通过代码改变目录列表控件的Path属性。Path属性用来设置或返回目录控件中的当前目录，该属性只能在程序运行阶段可用，通常为了达到驱动器控件和目录控件同步，所以将对Path属性的设置语句写在驱动器控件的Change事件内。

❑文件列表控件常用的事件是双击，和其他控件一样，当用鼠标双击时该事件被触发。文件列表控件常用的属性有FileName、Path、Pattern等。其中，FileName属性用来获取文件列表控件中所选文件的文件名（不包括路径）；Path属性用来获取或设置当前路径，Path和FileName结合起来就可以得到所选文件的带有路径的文件名，这两个属性都是在运行阶段才可用；Pattern属性通常用来设置控件中显示的文件的扩展名。该属性在设计阶段和运行阶段均可用，其默认值为*.*，就是说显示所有类型的文件。

4.14　焦点与Tab顺序

在本书前面的内容中曾经提到过焦点。所有可见控件都可以获得焦点。例如文本框获得焦点的方法一是用鼠标单击文本框，二是在程序中使用文本框的SetFocus方法。除此之外，实际上窗体中所有可见对象还有一种获得焦点的方法，那就是使用键盘上的Tab键切换焦点。

大多数Windows的老用户对Tab键不陌生，用其可以切换选择窗体中的对象，这一功能对没有鼠标的Windows用户简直就是救命稻草。其实，在用Tab键时不难发现，焦点在对象上的移动是有顺序的。在Visual Basic中，使用对象的TabIndex属性设置对象获得焦点的顺序。下面举例说明设置对象TabIndex属性的意义。

例如，在窗体上画了3个控件，一个文本框和两个按钮，如图4.37所示。画控件的顺序是先画文本框，再画Command1按钮，然后画Command2按钮。运行程序后，首先文本框Text1会得到焦点，敲击一下Tab键后，焦点会落到Command1按钮上，第二次敲击Tab键时，焦点落到Command2按钮上，再次敲击Tab键焦点就会重新落到Text1上，如果一直敲击Tab键，焦点会按照上面的顺序不断地循环落到每一个对象上。

图　4.37　Tab顺序实例

这时就会有一个问题：程序运行时能不能先让Command1按钮获得焦点，然后是Command2按钮，最后是Text1文本框。解决的方法就是设置控件的TabIndex属性。Visual Basic让对象获得焦点的标准如下。的TabIndex值小，谁就先获得焦点。TabIndex的最小值为0。所以解决问题的操作步骤如下。

（1）选中Command1按钮，在属性窗口里找到其TabIndex属性并设置为0。

（2）用同样的方法将Command2按钮和Text1文本框的TabIndex属性分别设置为1和2。

运行程序后就可以发现首先获得焦点的是Command1按钮，使用Tab键后第二个获得焦点的是Command2，最后才是Text1文本框。在Visual Basic中也可以屏蔽某对象获得焦点的能力，方法是将某对象的TabStop属性设置为False。

4.15　在工具箱中增加其他控件

Visual Basic自带了很多控件，但并未在工具箱中全部显示出来。下面举例说明如何将一些没有显示出来的控件添加到工具箱中。

例如，把下一节将要讲到的RichTextBox控件添加到工具箱中，操作步骤如下。

（1）在工具箱上单击右键，并从弹出的快捷菜单中选择“部件”菜单项。

（2）在“部件”对话框的“控件”选项卡内找到如图4.38所示的"Microsoft Rich Textbox Control 6.0"项，在其前面的复选框内打钩。

图　4.38　部件窗口（控件选项卡）

（3）单击“应用”按钮后即可将此控件的图标
添加到工具箱中。其他控件的添加方法也是如此。

4.16　使用RichTextBox编写超级文本编辑器

通过上一节讲述的方法已经将RichTextBox控件添加到了工具箱中。该控件可用于输入和编辑文本，它同时提供了比以前所学的TextBox控件更高级的格式特性。比如，TextBox控件里当改变字体、字号时，所有文字的字体、字号都会被改变，而RichTextBox可以只改变所选择的文字的字体、字号，而其他未被选择的文字的字体、字号不会被改变，RichTextBox甚至可以显示图形。下面用一个实例说明RichTextBox控件在文字编辑方面的强大功能。RichTextBox控件的名称前缀是rtf。

【实例4.19】使用RichTextBox控件编写一个超级文本编辑器，在文本编辑区可以自由地设置所选文字的字体、字号和字体颜色等，如图4.39所示。

图　4.39　实例4.19运行效果

程序中各对象的属性设置见表4.20。

编写事件过程如下。

01 Private Sub Form_Load()

02 cboFontName.AddItem"宋体"

03 cboFontName.AddItem"黑体"

04 cboFontName.AddItem"隶书"

05 cboFontName.AddItem"幼圆"

06 cboFontName.ListIndex=0 '将组合框中的第一项“宋体”显示到文本区中

07

08 cboFontSize.AddItem"10"

09 cboFontSize.AddItem"20"

10 cboFontSize.AddItem"30"

11 cboFontSize.AddItem"40"

12 cboFontSize.ListIndex=0 '将组合框中的第一项“10”显示到文本区中

13

14 rtfDOC.Font.Name="宋体" '设置RichTextBox的字体为“宋体”，与组合框中的字体内容相符

15 rtfDOC.Font.Size=10 '设置RichTextBox控件的字号为10，与组合框中的字号内容相符

16 End Sub

17

18 Private Sub cboFontName_Click()

19 rtfDOC.SelFontName=cboFontName.Text '设置RichTextBox中所选文字的字体

20 End Sub

21

22 Private Sub cboFontSize_Click()

23 rtfDOC.SelFontSize=Val(cboFontSize.Text) '设置RichTextBox中所选文字的字号

24 End Sub

25

26 Private Sub optRed_Click()

27 rtfDOC.SelColor=RGB(255,0,0) '设置RichTextBox中所选文字的颜色为红色

28 End Sub

29

30 Private Sub optGreen_Click()

31 rtfDOC.SelColor=RGB(0,255,0) '设置RichTextBox中所选文字的颜色为绿色

32 End Sub

33

34 Private Sub optBlue_Click()

35 rtfDOC.SelColor=RGB(0,0,255) '设置RichTextBox中所选文字的颜色为蓝色

36 End Sub

【代码说明】代码第02～06行是将字体添加到组合框1中，其中第06行显示默认的字体；代码第08～12行将字号添加到组合框2中，其中第12行显示默认的字号；代码第18～36行分别根据组合框的选择来设置RichTextBox控件中的文字。

说明　读者可以结合普通TextBox中讲述的应用案例继续完善本实例。关于RichTextBox更详细的内容可以使用F1键调用联机帮助查看。

4.17　树型视图控件与图像列表控件

树型视图控件是Visual Basic中的高级控件之一，通常用其显示有目录结构的内容。其使用方法相对其他普通控件较复杂。图像列表控件是Visual Basic中较常用的控件之一，主要用其存放图形文件。本节将介绍树型视图控件的一部份内容和图像列表控件的使用方法。

4.17.1　创建树型视图

Windows资源管理器左边的目录树就是一个树型视图，如图4.40所示。这种视图形式主要是用来清晰地表达层次结构。在一些复杂的资料管理系统中为了方便地组织和管理资料文档会经常使用这种视图形式。

创建树型视图要用到树型视图（TreeView）控件。该控件与RichTextBox控件一样也需要手动添加到工具箱中。方法如前所述，在工具箱上单击右键，从弹出式快捷菜单中选择“部件”菜单项，打开“部件”对话框的“控件”选项卡，从列表中找到"Microsoft Windows Common Control 6.0"项，在其前面的复选框内打勾，并单击“应用”按钮后，工具箱内会多出TreeView、ListView、ImageList、Toolbar、StatusBar、ProgressBar等9个控件。TreeView控件在工具箱中的图标是
，单击此图标然后在窗体上画出一个树型视图，其默认名称应该为TreeView1。

图　4.40　资源管理器界面

4.17.2　设置树型视图的外观

设置树型视图的外观，主要用到Style和LineStyle两个属性。Style属性有7种值可以选择，其意义如表4.21所示。

LineStyle属性设置在结点之间显示的线的样式，其值可以设置0和1。如果设置为0则根结点之间没有线连接，而如果设置为1根结点之间就会有连线出现，如图4.41所示。

图　4.41　LineStyle属性效果

说明　只有将控件的Style属性设置为有连接线时，LineStyle属性才会起效果。

只设置上述两个属性并不能真正地达到设置外观的目的，因为树型视图结点前的图标仍旧没有出现。要设置图标必须将树型视图控件和图像列表控件链接起来才可以。

4.17.3　图像列表控件

图像列表（ImageList）控件在程序运行时是不可见的。该控件是加入树型视图控件时在工具箱中多出来的9个控件之一，它也位于Microsoft Windows Common Control 6.0控件中。其在工具箱中的图标为
。ImageList控件的用法很简单，作为图片的容器主要用来给其他控件提供图标，比如树型视图控件。下面讲述向ImageList中添加图片的方法。

1.向ImageList中添加图片

向ImageList中添加图片的具体操作步骤如下。

（1）在窗体中画一个ImageList控件，其默认名应该是ImageList1。并在其上单击鼠标右键，在弹出的快捷菜单中选择“属性”菜单项。

（2）在打开的“属性页”窗口中的“通用”选项卡内选择16×16单选按钮（当然也可以选择其他选项，该选项用来设置图标的大小）。

（3）打开“图像”选项卡，使用“插入图片”按钮添加图片，添加进来的图片会以缩略图的形式显示在图像栏中，如图4.42所示。也可以使用“删除图片”按钮将多余的图删掉。

图　4.42　ImageList属性页窗口（图像选项卡）

注意　每一个图标都对应一个索引号，例如上图中第一个图的索引号为1，第二个图的索引号为2，依次类推。在程序中或者在设计中通过该索引号来区分列表中的图标。

（4）单击“确定”按钮退出窗体。

2.链接TreeView控件和ImageList控件

通过上述步骤设置好图像列表控件后，接下来就应该把树型视图控件和图像列表控件链接起来，具体操作步骤如下。

（1）在TreeView1上单击鼠标右键，在弹出的快捷菜单中选择“属性”菜单项。

（2）在打开的“属性页”窗口中的“通用”选项卡内找到“图像列表”栏，从中选择上面设置好的图像列表控件——ImageList1，如图4.43所示。

图　4.43　TreeView控件的属性页窗口（通用选项卡）

（3）单击“确定”按钮退出“属性页”窗口后就可以看到TreeView1中的结点前出现了“绿色书”的图标，如图4.44所示。这个图标其实就是ImageList1控件中的第一幅图。

图　4.44　TreeView1

4.17.4　给树型视图添加内容

要给树型视图添加内容只能通过程序实现，而且要用到Node对象，因为视图中的每一个结点都是一个Node对象。下面的代码可以给TreeView1添加内容。

01 Private Sub Form_Load()

02 Dim nt As Node

03 Set nt=TreeView1.Nodes.Add(,,"root","大陆",1)

04 Set nt=TreeView1.Nodes.Add("root",tvwChild,,"孙楠",1)

05 Set nt=TreeView1.Nodes.Add("root",tvwChild,,"那英",1)

06 Set nt=TreeView1.Nodes.Add("root",tvwChild,,"零点",1)

07 Set nt=TreeView1.Nodes.Add("root",tvwChild,,"韩红",1)

08

09 Set nt=TreeView1.Nodes.Add(,,"root1","港台",1)

10 Set nt=TreeView1.Nodes.Add("root1",tvwChild,,"刘德华",1)

11 Set nt=TreeView1.Nodes.Add("root1",tvwChild,,"张学友",1)

12 Set nt=TreeView1.Nodes.Add("root1",tvwChild,,"刘若英",1)

13 Set nt=TreeView1.Nodes.Add("root1",tvwChild,,"王力宏",1)

14

15 End Sub

【代码说明】第02行声明了一个结点（Node）型的变量。第03行是给树TreeView1增加了一个根结点，其关键字为"root"、其标题文本为“大陆”。第04、05、06、07行是在刚才的“大陆”根结点下建立的4个子结点。其他语句的作用同上。运行结果如图4.45所示。

图　4.45　TreeView1中的内容

4.17.5　设置展开/折叠和单击结点时的图标

1.设置展开/折叠结点时的图标

运行程序后，内容是添加到TreeView1上了，可是根（父）结点展开和折叠时的图标是相同的，这样不利于观察树的层次结构。为了解决这一问题，首先了解一下树型视图的两个事件——Expand和Collapse。其中，Expand事件是在展开结点时被触发，Collapse事件是在折叠结点时被触发。所以，解决展开/折叠时更换图标的代码如下。

01 Private Sub TreeView1_Expand(ByVal Node As MSComctlLib.Node)

02 Node.Image=2

03 End Sub

04

05 Private Sub TreeView1_Collapse(ByVal Node As MSComctlLib.Node)

06 Node.Image=1

07 End Sub

代码中Expand事件的括号内有个参数Node，这个Node代表当前被展开的那个结点，例如单击“大陆”结点前的加号（+），展开结点时，就会触发Expand事件，此时参数Node代表“大陆”结点。事件中的语句：

Node.Image=2

的作用是将当前Node结点的图标变更为图像列表控件（ImageList1）中的第二幅图。Collapse事件Node参数的意义和Expand事件的相同，事件过程中语句的作用是将当前Node结点的图标变更为图像列表控件（ImageList1）中的第一幅图。加完代码后运行效果如图4.46所示。

图　4.46　展开/折叠结点图标

2.设置单击结点时的图标

树型视图最重要的一个事件是NodeClick事件，该事件在单击树中某个结点时会被触发。因此该事件内可以写入单击结点后要执行的程序。例如，下面的程序可以在程序运行时单击某个结点就会改变那个结点的图标。

01 Private Sub TreeView1_NodeClick(ByVal Node As MSComctlLib.Node)

02 Node.SelectedImage=3

03 End Sub

NodeClick事件的Node参数的作用与Expand、Collapse两个事件的Node参数的作用相同，代表当前被单击的那个结点。事件中唯一的一条语句的作用是把单击选中的结点的图标改变为图像列表控件中的第三幅图。读者可以试着将上面的语句改为

Node. Image=3

运行后多选择几个结点，这样就会发现所有单击过的结点图标都会变成第三幅图。若只想让被选中的结点图标变化，必须使用Node对象的SelectedImage属性，而并非Image属性。

4.17.6　获取被选中的结点的信息

反映结点信息的常用属性有FullPath、Text、Key等属性。

1.FullPath属性

该属性获取结点的完整路径，例如下面的程序：

01 Private Sub TreeView1_NodeClick(ByVal Node As MSComctlLib.Node)

02 MsgBox Node.FullPath

03 End Sub

运行程序，并单击“零点”结点时，弹出的消息框如图4.47所示，消息框中的内容为被单击结点的完整路径。路径中间的“\”可以被设置成其他的分隔符，设置的方法是通过树型视图控件的PathSeparator属性。

图　4.47　FullPath属性结果

2.Text属性

可以用该属性获取结点的标题文本，例如将上面NodeClick事件中的语句换为：

MsgBox Node.Text

后，消息框中显示的内容为“零点”两个字。编程时可以用此属性值判断用户单击了哪个结点，根据单击不同的结点执行不同的语句。

3.Key属性

该属性用来获取结点的关键字，关键字就跟身份证号码一样，用于识别树中唯一的结点，一棵树中任意两个结点的关键字都不能相同。例如，上面的TreeView1中两个根结点的关键字分别为"root"和"root1"，而其他的子结点的关键字都被省略掉了。如果想在某个结点下面添加子结点时必须要设置该结点的关键字。例如，在TreeView1中，如果要想在“那英”结点下添加一个“雾里看花”子结点，那么Form_Load()事件中的程序应该改写为：

01 Private Sub Form_Load()

02 ......

03 Set nt=TreeView1.Nodes.Add("root",tvwChild,,"孙楠",1)

04 Set nt=TreeView1.Nodes.Add("root",tvwChild,"naying","那英",1)

05 Set nt=TreeView1.Nodes.Add("naying",tvwChild,,"雾里看花",1)

06 Set nt=TreeView1.Nodes.Add("root",tvwChild,,"零点",1)

07 ......

08 End Sub

该程序中，给“那英”结点设置关键字为"naying"，为了让“雾里看花”结点变为其子结点，语句

Set nt=TreeView1.Nodes.Add("naying",tvwChild,,"雾里看花",1)

中Add方法的第一个参数写为了"naying"。Key属性比起Text属性更能准确无误地判断用户单击了哪个结点，因为在复杂的系统里有时树中会有不同根结点下的两个或多个结点的名称相同。

4.18　SSTab控件

在程序设计中，经常会需要具有多个选项卡的对话框，SSTab控件就是Visual Basic为用户制作这种对话框而提供的控件，它位于Microsoft Tabbed Dialog Control 6.0部件中。将该控件添加到工具箱中后其图标为
。在SSTab控件中有一组选项卡，它们都能够作为其他控件的容器。在SSTab控件中一次只能有一个选项卡被激活。当某个选项卡被激活后，该选项卡中的控件被显示出来，而其他选项卡和其中的控件都会被隐藏起来，如图4.48a)所示为第一个选项卡的内容，图4.48b)为第二个选项卡的内容。

图　4.48　SSTab控件选项卡

SSTab控件默认选项卡的个数为3，其选项卡的标题依次是Tab0、Tab1、Tab2。要更改这些属性，可以在该控件上单击鼠标右键，从弹出的快捷菜单内选择“属性”菜单项，打开其“属性页”窗口，如图4.49所示。在矩形选择区域内可以设置选项卡的个数，而在椭圆形区域内可以设置当前选项卡的标题。

图　4.49　SSTab控件“属性页”窗口

4.19　OLE容器控件

OLE的全称是Object Linking and Embedding（对象链接与嵌入）。在Visual Basic中，用户可以将外部数据或软件载入到自己的程序中，其使用工具便是OLE容器控件。OLE控件在工具箱中的图标是
。下面用一个简单的例子说明该控件的基本用法。

【实例4.20】将Word文档嵌入到Visual Basic程序里。

【实现步骤】

（1）在窗体Form1上画一个OLE控件。

（2）会弹出一个“插入对象”对话框，如图4.50所示。

（3）在“对象类型”列表中选择“Microsoft Word文档”（参考图4.50），单击“确定”按钮。

图　4.50　“插入对象”对话框（新建）

（4）单击“确定”按钮后，在设计状态会出现如图4.51所示的界面，在窗体中出现了一个Word文档编辑区域，并且可以进行文档编辑，除此之外Visual Basic集成开发环境的菜单栏有了一些微妙的变化，里面出现了一些Word软件的菜单，而Visual Basic中的工具栏干脆就消失了。

图　4.51　实例4.20程序界面（设计状态）

（5）如果不想在Word文档编辑区编辑文档，想恢复到Visual Basic的环境，单击编辑区以外的任何部位都可以返回，比如窗体露出来的空白部分。

（6）运行程序后，如果想在编辑区编辑Word文档，可以双击编辑区激活它，这时窗体顶端会出现Word软件的一些菜单，通过这些菜单项，用户可以像在Word里一样工作了，如图4.52所示。

图　4.52　实例4.20程序界面（运行状态）

上面实例使用了“插入对象”对话框的“新建”功能，在实际应用中可能还会遇到将已经存在的文档显示到OLE控件里。方法是单击“插入对象”对话框中的“由文件创建”单选按钮，如图4.53所示。

图　4.53　“插入对象”对话框“由文件创建”

用户可以通过对话框中的“浏览”按钮选择现存的文档，如果选中了“浏览”按钮后面的复选框“链接”，当用户使用其他软件改变文档时，OLE控件中的文档内容也会同步更新。

4.20　小结

VB语言主要用来开发窗体程序，在窗体中，我们经常碰到一些操作，如单击按钮、选择列表框、选择复选框等，这些都是在窗体中的控件。本章主要讲解的就是一些常见的控件，并针对这些控件的应用做了一些案例，涉及到控件的属性、事件等。通过本章的学习，读者应该能开发简单的窗体应用程序。

4.21　本章练习

1.使用计时器控件让文本框的内容显示当前的时间，并且是在走动的时间。

2.创建一个登录窗口，要求验证用户名"admin"和密码"admin2009"。用户名显示在输入框中，密码要以符号“*”代替。另外，登录框上有两个按钮，分别为“登录”和“取消”。单击“登录”按钮时，检查用户名和密码是否正确。如果正确，提示“您已成功登录”；如果不正确，提示“用户名或密码错误，请重新输入”。单击“取消”按钮时，退出程序。

3.OLE的全称是什么？

第5章　程序的基本控制结构

从前面几章的学习中，知道了Visual Basic是一种面向对象的程序设计语言。而对于具体的过程本身，仍然要用到结构化程序设计的方法与结构化程序设计的三种结构，即顺序结构、选择结构和循环结构。这些结构是程序设计的基础，由这三种基本结构可以派生出多样、复杂的“多分支结构”，用其解决各种问题。本章包括以下知识点。

❑顺序结构，赋值语句的使用。

❑选择结构有If和Select Case两种语句格式。

❑循环结构有For-Next和Do-Loop等语句。

❑选择嵌套、循环嵌套和强制退出循环技术，可以实现更多的功能，增加了程序设计的灵活性。

本章将逐一介绍顺序结构的基本语句、选择结构、多分支结构及循环结构语句。熟练掌握这些结构语句，就可以编写较为复杂的程序了。

5.1　顺序结构程序设计

所谓顺序结构，就是按照语句的书写顺序依次执行。一般的程序设计语言中，顺序结构的语句主要是赋值语句、输入/输出语句等。在Visual Basic中也有赋值语句，而输入/输出可以通过文本框控件、标签控件、InputBox函数、MsgBox函数等来实现。下面介绍几种基本顺序结构的语句和方法。

5.1.1　赋值语句

赋值语句是任何程序设计语言中的最基本的语句。形式如下：

变量名=表达式

或

对象名.属性=表达式

赋值语句的作用是计算右边表达式的值，然后把值赋给左边的变量或对象属性。给变量、属性赋值是Visual Basic编程中常见的两个任务。

例如：

01 Text1.Text=""

02 temp=1

03 r=5

04 Area=3.1415926*r*r

需要注意的如下。

❑赋值号左边必须是变量或对象的属性，不能是数值、常量或表达式。

❑赋值时，两边的数据类型应该匹配，不匹配时应该用转换函数进行转换。

5.1.2　输入输出数据

除界面外，一个计算机程序通常可分为3部分，即输入、处理和输出。本书第4章中介绍了Visual Basic输入和输出丰富的内容和形式，提供了多种手段，并可通过各种控件实现输入输出操作，使输入输出灵活多样、方便直观。

【实例5.1】编写一个求圆面积的程序。

【实现步骤】具体操作步骤如下。

（1）新建一个标准EXE工程。

（2）双击默认窗体Form1，打开其代码编辑器窗口。

（3）在代码编辑器窗口的过程框中选择Click事件。并编写如下事件过程：

01 Private Sub Form_Click()

02 Const pi=3.1415926 '定义常量：圆周率PI，其值为3.1415926

03 Dim r As Single '定义变量r，表示圆半径，变量类型为单精度实数

04 Dim Area As Single '定义变量Area，表示圆的面积，变量类型为单精度实数

05 Print"欢迎学习Visual Basic！" '输出语句，在Form1窗口输出字符串常量“欢迎学习Visual Basic”

06 r=Val(InputBox("输入圆半径：","求圆面积")) '利用输入函数，输入的字符串转换成数值型赋给半径r

07 Area=pi*r*r '赋值语句求圆面积公式

08 Print"半径为"＆r;"的圆面积为："＆Area '输出圆面积

09 End Sub

程序流程图如图5.1所示。

图　5.1　实例5.1程序流程图

【代码说明】代码第05行使用Print方法输出字符串，然后代码第06行使用InputBox输入函数，允许用户输入数据，并使用Val将其转换为数值型数据。

【运行效果】运行程序，在窗体任意位置单击鼠标左键，会触发窗体的Click事件，随之其内的程序会被依次执行。运行结果如图5.2和图5.3所示。

图　5.2　在输入框中键入10

图　5.3　实例5.1最后运行结果

5.2　选择结构程序设计

顺序结构是结构化程序设计中最简单也是最常用的流程控制语句，只能解决流水作业问题。在日常生活中，常常需要对给定的条件进行分析、比较和判断，并根据结果采取不同的操作。在Visual Basic中，这样的问题通过选择结构来解决。选择结构的语句包括If语句和Select Case语句。

5.2.1　行If语句

单行条件语句比较简单，其格式如下。

If 条件 Then语句1 [Else 语句2]

该语句的功能是如果“条件”为真（True），则执行Then后面的“语句1”，如果“条件”为假（False），则不执行Then后面的“语句1”，而去执行Else后的“语句2”。

行If语句说明：

（1）在上面的格式中，“条件”是一个逻辑表达式。程序根据这个表达式的值（True或False）执行相应的操作。

（2）If语句中的“Else语句2”是可选的，该项被省略后，If语句简化如下。

If 条件 Then 语句1

该语句的功能是如果“条件”为True，则执行“语句1”，否则跳过这条语句，执行下一行程序。例如：

If x＞y Then Print"aaaa"

Print"bbbb"

如果x＞y为True，则执行Print"aaaa"；而如果x＞y为False，则跳过该语句，执行其下面的语句Print"bbbb"。

【实例5.2】两数比较求最大值。

使用"If...Then"语句，编写程序如下。

01 Private Sub Form_Click()

02 Dim x As Single

03 Dim y As Single

04 x=2

05 y=5

06 If x＜=y Then x=y

07 Print"大值为："＆Str(x)

08 End Sub

【代码说明】程序开始时按照顺序从上到下执行，直到遇见第06行的If语句，先判断其后的条件表达式x＜=y的值，因为2＜=5为True，所以执行Then后的语句，将y的值5赋值给了变量x，然后执行第07行的语句Print将x的值输出到窗体上，所以窗体上显示的结果为5。

【运行效果】程序流程如图5.4所示。

图　5.4　实例5.2流程图

如果本例将第03、04行x，y的赋值语句改为：

x=10

y=8

则遇见If语句后，因为10＜=8为False，所以不会执行Then后的语句，因此x的值不会变，最后输出到窗体中的结果过为10。本例也可以使用"If…Then…Else"语句完成如下。

01 Private Sub Form_Click()

02 Dim x As Single

03 Dim y As Single

04 x=2

05 y=3

06 If x＜=y Then Print"大值为："＆Str(y) Else Print"大值为："＆Str(x)

07 End Sub

5.2.2　块If语句

因为单行If语句后不能写多条语句（实际上，用“:”号分隔，也可以写多条语句，但通常不这么做），所以有时满足不了程序的需求。下面介绍可以将多条语句写入其内的块IF语句。块If语句的格式如下。

If 条件1 Then

语句块1

[ElseIf 条件2 Then

语句块2]

[ElseIf 条件3 Then

语句块3]

…

[Else 条件n Then

语句块n]

[Else

语句块n]

End If

块If结构的工作原理是先检查“条件1”，如果“条件1”成立（True），则执行其下的“语句块1”，执行完“语句块1”后，直接跳到End If后执行下面的语句；如果“条件1”不成立，继续检查“条件2”，如果“条件2”成立（True），则执行其后的“语句块2”……如果所有条件都不成立，则执行最后一个Else后面的“语句块n”。

块If结构的说明：

（1）格式中的“条件1”、“条件2”等都是逻辑表达式，通常数值表达式和关系表达式看作是逻辑表达式的特例。“条件”表达式非0值表示True，0值表示False。

（2）“语句块1”、“语句块2”等是一条或多条Visual Basic语句。

（3）“语句块”中的语句不能与其前面的Then在同一行上，否则Visual Basic认为是一个单行结构的条件语句。也就是说，块结构与单行结构条件语句的主要区别，就是看Then后面的语句（注释语句除外）是否和Then在同一行上。如果在同一行上，则为单行结构，否则为块结构。对于块结构，必须以End If结束，单行结构没有End If。

（4）块结构的条件语句中，ElseIf子句的数量没有限制，可以根据需要加入任意多个ElseIf子句。

（5）块结构条件语句中的ElseIf子句和Else子句都是可选的。如果省略这些子句，则块结构的条件语句简化如下。

If 条件 Then

语句块

End If

例如：求任意值的绝对值程序代码段。

If x＜0 Then

x=-x

End If

这种形式的条件语句也可以写成单行形式，即：

If x＜0 Then x=-x

【实例5.3】编写程序，求分段方程的值：

编写程序如下。

01 Private Sub Form_Click()

02 Dim x As Single

03 Dim y As Single

04 Print"y=x+1 (x＜0)"

05 Print"y=x (x=0)"

06 Print"y=x-1 (x＞0)"

07 x=Val(InputBox("输入分段方程x的值","求分段方程y的值"))

08 If x＜0 Then '如果x的值小于0则执行y=x+1

09 y=x+1

10 ElseIf x=0 Then '如果x的值等于0则执行y=x

11 y=x

12 Else '如果x的值不满足上面的条件则执行y=x-1

13 y=x-1

14 End If

15 Print y

16 End Sub

程序流程如图5.5所示。

图　5.5　实例5.3流程图

【代码说明】代码第08～14行是一个最完整的IF语句块，首先用代码第08行的If语句进行判断，如果不满足条件，则执行第10行的ElseIf判断语句，如果还不满足，则执行第12行的Else语句块。

【运行效果】运行本例，在窗体任意位置单击后，激活窗体Form1的Click事件，执行该事件过程，程序按照代码书写顺序自上向下依次执行：定义变量x、y为单精度实数类型（第1条代码），在窗体左上依次输出信息"y=x+1(x＜0)"、"y=x(x=0)"、"y=x-1(x＞0)"，程序第05条代码遇到输入函数InputBox()，会弹出输入框，如图5.6所示。

图　5.6　实例5.3程序运行过程

在输入框内键入5，并确定后，程序将输入函数InputBox（）接收的字符串“5”转换成数值型赋给变量x后，继续向下执行"If...Then...Else"语句，因为x=5，If条件“5＜0”和“5=0”的值都为假（False），则执行Else后面的语句"y=5-1"即y=4后，执行End if语句结束分支语句，紧接着执行输出语句"Print y"，在窗体Form1输出y的值4。程序运行结果如图5.7所示。

图　5.7　实例5.3程序运行结果

5.2.3　If语句的嵌套

条件语句中的“Then语句块”和“Else语句块”都可以是条件语句，即条件语句可以嵌套，其深度（嵌套层数）没有具体规定，但受到每行字数（1024）的限制。当嵌套层数较多时，应注意嵌套的正确性，一般原则是每一个“Else语句块”部分与它前面的，且未曾配对的"If...Then"配对。

【实例5.4】大学里对不同性质的学生听课收费不同。某校是这样规定的：本校全日制学生不收费。本校自费大学生选课12学分及以下付300元，在此基础上，每增加一学分付50元。对外校学生选课12学分以下付600元，在此基础上，每增加一个学分付80元。输入某个学生的编号、选课学分以及学生类型，编程计算该学生应付的学费。

【实例分析】学分为credit，收费为fee，编号为number，学生的类别为class。根据题设：

本校全日制大学生：fee=0

本校自费生：credit＜=12,fee=300

credit＞12,fee=300+(credit-12)*50

外校学生：credit＜=12,fee=600

Credit＞12,fee=600+(credit-12)*80

编写程序如下。

01 Private Sub Form_Click()

02 Dim credit As Integer

03 Dim fee As Integer

04 Dim number As String

05 Dim class As String

06 fee=0

07 Print"学生类别："

08 Print

09 Print"1、在校全日制大学生。"

10 Print

11 Print"2、自费生。"

12 Print

13 Print"3、外校学生。"

14 number=InputBox("请输入学生编号:","编号")

15 class=InputBox("请输入学生类别：1、在校全日制大学生。2、自费生。3、外校学生","学生类别")

16 credit=Val(InputBox("请输入学生所修学分数：","学分数"))

17 If class=1 Then

18 fee=0

19 ElseIf class=2 Then

20 If credit＜=12 Then

21 fee=300

22 Else

23 fee=300+(credit-12)*50

24 End If

25 Else

26 If credit＜=12 Then

27 fee=600

28 Else

29 fee=600+(credit-12)*80

30 End If

31 End If

32 Print"学生编号："＆number＆"；类别："＆class＆"；学分："＆Str$(credit)＆"，应收费"＆Str$(fee)

33＆"元"

34 End Sub

【代码说明】代码第07～13行都是输出语句，代码第14～16行接收3个输入值。代码第17～31行判断这些输入值，然后第32行输出不同的判断结果。

【运行效果】运行程序，单击窗体后，便执行上面的事件过程。首先，弹出如图5.8所示的“编号”输入框，按提示输入值20070101。单击"OK"按钮后，弹出“学生类别”输入框，直接输入2，表示类别为自费生，如图5.9所示。单击"OK"按钮关闭对话框，然后弹出“学分数”输入框，输入值30，如图5.10所示。单击"OK"按钮关闭该对话框，最后结果如图5.11所示。

图　5.8　实例5.4“编号”输入框

图　5.9　实例5.4“学生类别”输入框

图　5.10　实例5.4“学分数”输入框

图　5.11　实例5.4程序结果

5.2.4　Select Case语句

在有些情况下，对某个条件判断后可能会出现多种取值的情况，此时不适合再使用If语句结构，例如实例5.4。在Visual Basic中，专门为此种情况设计了一个Select Case语句结构。在这种结构中，只有一个用于判断的表达式，根据此表达式的不同计算结果，执行不同的语句体部分。这种结构本质上是If嵌套结构的一种变形，主要差别在于If嵌套结构可以对多个表达式的结果进行判断，从而执行不同的操作；而Select Case结构则只能对一个表达式的结果进行判断，然后再执行不同的操作。

Select Case语句的一般格式如下。

Select Case 表达式

Case 表达式值1

语句块1

[Case 表达式值2

语句块2]

......

[Case Else

语句块n]

End Select

Select Case语句用来实现多分支选择，Select Case中的“Case表达式值”可以使用任意多个。“表达式”可以是数值表达式或字符串表达式。根据“表达式”的取值来与下列的各个“表达式值”去进行比较。若与其中某个值相同，则执行该Case子句中的语句，然后跳到End Select，从End Select出口。如果表达式的值与任何一个Case子句都不匹配，看Select Case结构中是否有Case Else语句，如果有此子句，则执行Case Select子句后面的“语句块n”，然后从Select Case出口，执行其后的语句，否则不执行任何结构内的语句，整个Select Case结构结束，再执行其后的语句。

Select Case语句一般格式中的表达式值可以有如下4种格式，即允许出现4种Case形式。

❑表达式值：“表达式值”中只有一个数值或字符串，供用户与表达式的值进行比较。例如：Case 1或者Case"char"等。

❑表达式值1[，表达式值2]……[，表达式值n]：在某一个“表达式值”中有多个数值或字符串，供用户与表达式的值进行比较，多个取值之间用逗号间隔。如果“表达式”的值与这些数值或字符串中的一个相等，即可执行此“Case表达式值”相应的子句；否则，若表达式的值与这些取值均不相等，可以再与其他“Case表达式值”进行比较。例如：Case 1,3,5,7或者Case"a","b","c","d"等。

❑表达式值1 To表达式值2：在“表达式值”中提供了一个数值或字符串的取值范围，可以将此范围内的所有取值与表达式的值进行比较。要求“表达式值1”的值必须小于“表达式值2”的值，这样才能提供一个范围。如果表达式的值与此范围内的某个值相等，则可执行此“Case表达式值”的相应子句；否则，若表达式的值与这个取值范围内的值均不相等，则继续向下比较。例如：Case 1 To 4或者Case"a"To"z"等。

❑Is关系运算符、数值或字符串：使用了关键字"Is"，其后只能使用各种关系运算符：“=”、“＜”、“＞”、“＜=”、“＞=”和“＜＞”等。可以将“表达式”的值与关系运算符后的数值或字符串进行关系比较，检验是否满足该关系运算符。若满足，则执行此“Case表达式值”后的相应子句；否则，与其后的“表达式值”进行比较。例如：Case Is＜3或者Case Is＞"Apple"等。

在实际使用时，以上这几种格式允许混合使用。例如：

Case Is＜5，7，8，9，Is＞12

或

Case Is＜"z","A"To"Z"

注意

在使用Select Case结构时，需要注意以下两点。

（1）若在多个Case子句中有同一种取值重复出现，则只执行第一个出现此取值的Case语句后的相应语句体。

（2）Select Case结构中的Case Else子句部分必须放在其他Case子句后面，用于表达式的值与前面所有Case子句均不匹配时，执行其后的语句块。这个子句可以省略，此时若出现与所有Case子句均不匹配的情况，则不执行任何语句块，直接退出Select Case结构，执行其后的语句。

【实例5.5】使用Select Case语句改写实例5.4程序。

编写程序如下。

01 Private Sub Form_Click()

02 Dim credit As Integer

03 Dim fee As Integer

04 Dim number As String

05 Dim class As String

06 fee=0

07 Print"学生类别："

08 Print

09 Print"1、在校全日制大学生。"

10 Print

11 Print"2、自费生。"

12 Print

13 Print"3、外校学生。"

14 number=InputBox("请输入学生编号:","编号")

15 class=InputBox("请输入学生类别：1、在校全日制大学生。2、自费生。3、外校学生","学生类别")

16 credit=Val(InputBox("请输入学生所修学分数：","学分数"))

17 Select Case class

18 Case"1"

19 fee=0

20 Case"2"

21 If credit＜=12 Then

22 fee=300

23 Else

24 fee=300+(credit-12)*50

25 End If

26 Case"3"

27 If credit＜=12 Then

28 fee=600

29 Else

30 fee=600+(credit-12)*80

31 End If

32 End Select

33 Print"学生编号："＆number＆"；类别："＆class＆"；学分:"＆Str$(credit)＆"，应收费"＆Str$(fee)

34＆"元"

35 End Sub

【代码说明】代码第07～16行是输出和输入语句，代码第17～32行将上一个案例的If...Else语句替换成了Select Case语句，然后第33行输出条件判断后的结果。

【运行效果】程序运行过程参见图5.8～图5.11。

5.2.5　应用案例1——密码登录

【实例5.6】编程实现如图5.12所示的密码登录程序。

图　5.12　密码登录窗口

该界面功能：当用户名和密码都正确时，弹出显示信息为“欢迎登录”的对话框；当用户名正确而密码错误时，弹出显示信息为“密码错误”的对话框；当用户名错误时，弹出显示信息为“此用户不存在”的对话框。

首先，按照界面设计画好控件后，在属性窗口中设置各对象的属性。具体属性设置见表5.1。

技巧　本例中文本框2中的PasswordChar属性值为“*”，表示作为密码内容输入的显示字符，对密码内容进行保护。

编写事件过程如下。

01 Private Sub cmdCancel_Click()

02 Unload Me

03 End Sub

04 Private Sub cmdOk_Click()

05 If txtUserName.Text="sqbt"Then

06 If txtPassword.Text="123"Then

07 MsgBox"欢迎登录！"

08 Else

09 MsgBox"密码错误！"

10 txtPassword.Text=""

11 txtPassword.SetFocus

12 End If

13 Else

14 MsgBox"此用户名不存在！"

15 txtUserName.Text=""

16 txtUserName.SetFocus

17 End If

18 End Sub

【代码说明】代码第01～03行是取消按钮的事件，表示关闭窗体，代码第04～19行执行确定按钮的判断事件，其中包含两个嵌套的If语句块。第一个嵌套判断用户名是否正确，第二个嵌套判断用户密码是否正确。

5.2.6　应用案例2——用户注册

【实例5.7】如图5.13所示，编程实现会员注册界面。

图　5.13　实例5.7用户注册界面

在图5.13窗口界面中输入用户名、密码及重新输入密码，如果密码和重新输入密码的内容不同，弹出对话框，显示信息：“两次输入密码不一致，请重新输入！”；选择性别，选择一或多个爱好选项。当单击“用户注册”窗口中的“注册”按钮后，在列表框中显示所填入和选择的注册信息；当单击“用户注册”窗口中的“取消”按钮后，弹出标题为“显示”的对话框，该对话框显示信息为“您真的要关闭窗口吗？”，单击“显示”对话框“确定”按钮，则关闭“用户注册”窗口，单击“显示”对话框中的“取消”按钮，返回到“用户注册”窗口继续操作。

程序中各对象需要更改的属性见表5.2，其他属性均取默认值。

编写事件过程如下。

01 Private Sub cmdCancel_Click()

02 Unload Me

03 End Sub

04

05 Private Sub cmdOk_Click()

06 Dim msg As String

07

08 lstShowInfo.Clear '将列表框清空

09 '判断用户名文本框是否为空

10 If txtUserName.Text=""Then

11 MsgBox"用户名不能为空！"

12 txtUserName.SetFocus

13 Exit Sub '结束事件过程

14 End If

15 '判断两次密码输入是否一致

16 If Trim(txtPass1.Text)=Trim(txtPass2.Text) Then

17 lstShowInfo.AddItem"您的用户名是："＆txtUserName.Text

18 lstShowInfo.AddItem"您输入的密码是："＆txtPass1.Text

19 '判断选择了哪个单选按钮

20 If optMan.Value=True Then

21 lstShowInfo.AddItem"您的性别是："＆optMan.Caption

22 ElseIf optWomen.Value=True Then

23 lstShowInfo.AddItem"您的性别是："＆optWomen.Caption

24 End If

25 '判断选择了哪些复选框

26 If chk1.Value=1 Then msg=msg＆"看书"

27 If chk2.Value=1 Then msg=msg＆"听音乐"

28 If chk3.Value=1 Then msg=msg＆"爬山"

29 If chk4.Value=1 Then msg=msg＆"散步"

30 If chk5.Value=1 Then msg=msg＆"看电影"

31 If chk6.Value=1 Then msg=msg＆"球类"

32 lstShowInfo.AddItem"您选择的爱好是："＆msg

33 Else

34 MsgBox"两次密码输入不一致，请重新输入！"

35 txtPass1.Text=""

36 txtPass2.Text=""

37 txtPass1.SetFocus

38 End If

39 End Sub

40

41 Private Sub Form_Load()

42 optMan.Value=True

43 End Sub

44

45 Private Sub Form_Unload(Cancel As Integer)

46 Dim x As Integer

47

48 x=MsgBox("您真的要关闭窗口么？",vbYesNo+vbQuestion,"提示")

49 If x=7 Then

50 Cancel=1

51 End If

52 End Sub

【代码说明】上述代码不仅使用了选择结构的语句，还演示了窗体控件的一些应用。代码第12行表示txtUserName控件获取焦点，即光标停留在此文本框内。代码第10～14行是判断用户名不能为空的选择结构。代码第16～38行是多个嵌套选择。

【运行效果】程序运行，填写信息，单击“注册”按钮后结果如图5.14所示。

图　5.14　“会员注册”单击注册后的结果

5.3　循环结构程序设计

循环结构也称重复结构。在程序处理中，经常需要重复执行某一条语句或某一组程序代码，以最终完成某项任务，使用循环可以简化程序，循环处理需要考虑循环对象和循环控制条件。Visual Basic循环语句有For、While和Do三种基本循环结构。

5.3.1　For循环语句

For循环属于计数型循环，程序按照此种结构中指明的循环次数来执行循环体部分。For循环格式如下：

For 循环变量=初始值 To 终值[Step 步长]

[循环体]

[Exit For]

[循环体]

Next 循环变量

For循环语句说明：

在For循环结构中，使用了如下几个参数。

❑循环变量：用于统计循环次数的变量，“循环变量”可以从某个值变到另一个值，该变化的两个相邻数值之间的差值由“步长”决定。由此可以决定该循环执行了几次，即该循环的循环次数可以确定。“循环变量”为数值型变量。

❑初始值：用于设置“循环变量”的初始取值，为数值型变量。

❑终值：用于设置“循环变量”的最后取值，为数值型变量。

❑步长：用于决定“循环变量”每次增加的数值，即变量在变化时的增值，为数值型变量。一般默认值为1，此时可以省略“Step步长”部分。“步长”的取值可以根据“初始值”和“终值”的关系分为正值或负值两种，若“初始值”大于“终值”，则必须将“步长”设为负值才有可能执行内部循环体；若“初始值”小于“终值”，则必须将“步长”设为正值才有可能执行内部循环体。

❑循环体：需要重复执行的部分。

❑Exit For：在某些情况下，需要中途退出For循环时使用。

❑Next循环变量：用于结束一次For循环，根据“终值”和现在“循环变量”的值的大小关系决定是否执行下一次循环。其中的“循环变量”名字必须与For循环开始时的“循环变量”名字相同。

上述For循环不存在中间的"Exit For"语句时的执行过程是先令“循环变量”取为“初始值”，然后检验“循环变量”的取值是否超出“终值”：若“步长”为正值，则检验“循环变量”是否大于“终值”，大于为超出状态；若“步长”为负值，则检验“循环变量”的取值是否小于“终值”，小于为超出状态。若“循环变量”没有超出“终值”，则执行一次内部的循环体，然后将“循环变量”加上“步长”赋值给“循环变量”本身，再与“终值”进行比较，判断是否可以执行下一次循环，接着重复上述过程；否则不再执行循环体，直接退出循环，执行循环后的语句。

若存在中间的"Exit For"语句，则在执行循环体的过程中，满足一特定的条件即可中间跳出For循环，执行其后的语句。一般循环体内不会单独存在此语句，总是用一个条件进行控制，满足时跳出；不满足继续执行循环体。使用"Exit For"语句只能跳出一层循环，若存在两层For循环嵌套，则只能跳出内层，继续执行外层循环。

若For循环的循环体内没有具体语句，可以用来作为延时使用。“循环变量”循环了一定次数，虽不执行任何操作，但却花费了一定时间，故可作为延时工具使用。

【实例5.8】编写一个函数，求1～n的所有正整数之和。

新建标准EXE工程，双击Form1窗体在打开的代码窗口中，选择其Click事件，在Click事件过程中键入如下代码。

01 Private Sub Form_Click()

02 Dim s As Integer

03 Dim n As Integer

04 Dim i As Integer

05 s=0

06 n=Val(InputBox("求1至n的和，请输入n的值：","n值"))

07 For i=1 To n

08 s=s+i

09 Next i

10 Print"1至"＆Str$(n)＆"的和为："＆Str(s)

11 End Sub

程序的设计流程图如图5.15所示。

图　5.15　实例5.8流程图

【代码说明】代码第07～09行是一个For循环，参数n去决定于第06行的输入。代码第10行输出循环结果s变量。

【运行效果】执行本窗体，在其中任意位置处单击鼠标，执行上面的事件过程打印出如图5.16所示运行结果，在“n值”输入对话框中输入100，单击"OK"确定按钮，给n赋值100，Form1窗口显示1～n即1～100的和：5050。如图5.17所示。

图　5.16　实例5.8输入框

图　5.17　实例5.8运行结果

同样要求n的阶乘（即n！=1×2×3…×n），方法与实例5.8类似。代码参考如下。

01 Private Sub Form_Click()

02 Dim s,i As Integer

03 s=1

04 n=Val(InputBox("求1至n的和，请输入n的值：","n值"))

05 For i=1 To n

06 s=s*i

07 Next i

08 Print"1至"＆Str$(n)＆"的和为："＆Str(s)

09 End Sub

【实例5.9】某公司2007年的销售总收入为200亿元，如果年增长率为30%，按这样的速度，到2010年，该公司的销售总收入预计为多少？

新建标准EXE工程，设置默认窗体Form1的AutoRedraw属性为True，在窗体Click事件和Load事件中键入如下代码。

01 Private Sub Form_Click()

02 Dim year As Integer

03 Dim income As Single

04 income=200

05 For year=2008 To 2010

06 income=income*(1+0.3)

07 Next year

08 Print"到2010年销售收入达到"＆Str$(income);"亿元。"

09 End Sub

10

11 Private Sub Form_Load()

12 Print"测算某公司2010年销售收入"

13 Print"该公司2007年销售收入为200亿元，年增长率为30%"

14 End Sub

【代码说明】窗体From1的Click事件过程中第02行的变量year表示年份，income表示销售收入，year=2007，年增长率为30%，则income*(1+0.3)表示2008年的销售收入，所以第05行的循环从year=2008开始，结束为year=2010。窗体Form1的Load事件中第12～13行代码显示程序的题设。

【运行效果】运行程序后，窗体加载时如图5.18所示，单击窗体，程序运行如图5.19所示。

图　5.18　实例5.9程序运行结果1

图　5.19　实例5.9程序运行结果2

除了上述For循环外，还有一个集合For循环语句，其使用语法如下。

For Each 集合中元素 in 集合

[循环体]

Next [元素]

该语句对集合中的每个元素进行一次循环，直到集合中无更多的元素时，执行Next后面的语句。

【实例5.10】列出窗体上的所有控件。

新建窗体Form1，在窗体Form1上添加一个按钮（CommandButton）对象Command1，修改Command的Caption属性为“显示窗体中控件”，双击按钮Command1对象，在其Click事件过程中键入代码如下。

01 Private Sub Command1_Click

02 For Each Control in Me.Controls

03 Print Control.Name

04 Next Control

05 End Sub

【代码说明】一般我们把“For Each集合中元素in集合”这种形式称为遍历，就是搜索集合中的每个元素。不用于数值的运算，一般针对控件集合。

【运行效果】运行程序，单击Form1窗口中的“显示窗体中控件”按钮后，显示结果如图5.20所示。

图　5.20　实例5.10单击按钮后结果

5.3.2　While循环语句

While循环用于对条件进行判断，如果条件为真，可以循环执行循环体，直到条件为假，循环结束为止。与For循环最大的差别在于For循环用于循环次数已知的情况，执行一定次数后即可结束循环；而While循环用于不知道循环次数，但可以用一个条件来进行判断是否结束。所以对于循环次数有限，可以用一个条件限制的情况，使用While循环较好，比较灵活。

While循环的格式如下。

While 条件

[循环体]

Wend

该循环格式中的条件是数值或字符表达式，结果为True或False。

此种循环的执行过程为首先判断“条件”是否成立，若条件成立，则执行循环体内的语句，执行完后再继续判断“条件”，重复上述过程；否则结束循环，执行循环后的语句。所以给定的“条件”必须在循环体内有所变动才可。否则，若初始“条件”成立，则每次执行完循环体后再检验“条件”，“条件”仍然成立，此循环可以无限执行下去，变成所谓的“死循环”；若初始“条件”不成立，则“循环体”一次都不能执行。

前面介绍的实例5.8和5.9均可以使用While循环实现。

【实例5.11】使用While循环改写实例5.8。

编写程序如下。

01 Private Sub Form_Click()

02 Dim s,i As Integer

03 i=0

04 s=0

05 n=Val(InputBox("求1至n的和，请输入n的值：","n值"))

06 While i＜n '如果条件i＜n成立则继续循环，否则结束循环

07 i=i+1

08 s=s+i

09 Wend

10 Print"1至"＆Str$(n)＆"的和为："＆Str(s)

11 End Sub

【代码分析】代码第06～09行是一个While循环，循环的条件是i＜n，n取自第05行的用户输入值。代码第09行是循环的结束标识。代码第10行输出循环的计算总和。

【运行效果】参数实例5.8的运行效果。

【实例5.12】使用While循环改写实例5.9。

编写程序如下。

01 Private Sub Form_Click()

02 Dim year As Integer

03 Dim income As Single

04 income=200

05 year=2007

06 While year＜2010

07 year=year+1

08 income=income*(1+0.3)

09 Wend

10 Print"到2010年销售收入达到"＆Str$(income);"亿元。"

11 End Sub

12

13 Private Sub Form_Load()

14 Print"测算某公司2010年销售收入"

15 Print"该公司2007年销售收入为200亿元，年增长率为30%"

16 End Sub

【代码说明】代码第05行首先设置一个year变量的初始值，代码第06行判断其是否满足条件，如果满足则执行第07～08行。不满足条件则结束循环输出第10行代码。代码第13～16行输出题设。

【运行效果】参数实例5.9的运行效果。

5.3.3　Do循环语句

Do循环也是根据某个条件是否成立来决定能否执行相应的循环体部分，与While循环不同的是While循环只能在初始位置检查条件是否成立，若成立，进入循环体；不成立，不进入循环体，执行循环体后的语句。Do循环可以有两种格式，既可以在初始位置检验条件是否成立，也可以在执行一遍循环体后的结束位置判断条件是否成立，能否进入下一次循环。Do循环的两种格式如下。

❑格式一：

Do

[语句体]

['满足某条件时

Exit Do]

[语句体]

Loop [While或Until 条件]

❑格式二：

Do [While或Until 条件]

[语句体]

['满足某条件时

Exit Do]

[语句体]

Loop

上述两种格式构成了Do循环的两种使用方法。这两种格式既有共同点又有明显的差异。其共同点如下。

❑都可以有一定的循环条件，但循环体部分为空：此种情况下，可以用于延时，对于某些程序需要一个短时间的暂停时可以使用这样一个空循环。此方法对于上述For循环和While循环也同样适用。

❑都可以从中间用"Exit Do"语句退出循环：在Do循环中可以使用一个或多个Exit Do语句，用于在某些需要中途跳出的情况时使用。可以从所在的Do循环中跳出，执行循环后的语句。

❑都可以没有循环条件：此种情况下，只有Do…Loop结构的循环，整个程序没有结束条件。若循环体为空，则成为一个死循环；否则，若在非空循环体中有Exit Do语句存在，也可以在某些条件满足时退出，否则不能退出，也成为死循环。

❑都有两种判断条件的格式：一种为“While条件”，判断时只要条件成立，则继续执行循环体，然后重复上述判断过程；而如果条件不成立，则退出循环，执行其后的语句。一种为“Until条件”，判断时直到条件成立才退出循环。也即，条件不成立时，继续执行循环体，然后重复判断过程；当条件成立时，就要退出循环体，执行其后的语句。

上述两种格式的不同点如下。

❑第一种格式中的判断条件“While或Until条件”的位置在整个循环体的最后；而第二种格式中的判断条件“While或Until条件”的位置在整个循环体的起始位置。

❑第一种格式的执行过程为：先执行一遍循环体，在碰到Loop后的条件时再进行判断，根据不同的判断条件格式执行不同的判断过程，决定是继续执行循环体，还是退出循环。而第二种格式的执行过程为：先对条件进行判断，仍然要根据不同的判断条件格式执行不同的判断过程，判断为能够执行循环体时，才能进入循环体内执行相应语句；否则只能退出循环体。所以，第一种格式至少要执行一遍循环体，而第二种格式则可能循环体一遍也不执行。

例如，对于前面的求1～n的所有正整数之和的函数可以使用Do循环，使用两种格式编写程序如下。

01 Private Sub Form_Click()

02 Dim s,i As Integer

03 i=0

04 s=0

05 n=Val(InputBox("求1至n的和，请输入n的值：","n值"))

06 Do

07 i=i+1

08 s=s+i

09 If i＞=n Then

10 Exit Do

11 End If

12 Loop

13 Print"1至"＆Str$(n)＆"的和为："＆Str(s)

14 End Sub

15

16 Private Sub Form_Click()

17 Dim s,i As Integer

18 i=0

19 s=0

20 n=Val(InputBox("求1至n的和，请输入n的值：","n值"))

21 Do While (i＜n)

22 i=i+1

23 s=s+i

24 Loop

25 Print"1至"＆Str$(n)＆"的和为："＆Str(s)

26 End Sub

【实例5.13】输入一个正整数，判断其是否为素数。

【实例分析】所谓“素数”是指除了1和该数本身，不能被任何一个整数所整除的自然数。判断一个自然数n（n≥3）是否为素数，只要依次用
作除数去除n，若n不能被其中任何一个数整除，则n即为素数，程序运行界面如图5.21所示。

图　5.21　实例5.13程序

程序中各对象需要更改的属性见表5.3，其他属性均取默认值。

编写事件过程如下。

01 Private Sub cmdOK_Click()

02 Dim n As Integer

03 Dim i As Integer

04 Dim flag As Boolean

05 n=Val(Me.txtNum.Text)

06 i=2

07 flag=False '素数标记变量

08 Do While (i＜=Sqr(n))

09 If n Mod i=0 Then

10 flag=True

11 Exit Do

12 Else

13 i=i+1

14 End If

15 Loop

16 If flag=False Then

17 MsgBox Str$(n)＆"是素数！"

18 Else

19 MsgBox Str$(n)＆"不是素数。"

20 End If

21 End Sub

【代码说明】代码第07行表示设置一个flag标记默认值为false。代码第08～15行通过Do循环判断变量n是否为素数，如果是则更改flag标记为true。代码第16～20行的判断语句根据flag标记值输入不同的内容。

【运行效果】运行程序，任意输入一个自然数，单击“判断素数”按钮，弹出判断结果的提示信息。如图5.22所示。

图　5.22　实例5.13程序运行结果

5.3.4　多重循环

For循环、While循环和Do循环都允许嵌套，可以嵌套多层，遵循就近配对原则。

【实例5.14】设计一个窗体，打印一个九九乘法表。

新建标准EXE工程，在其默认窗体Form1中编写如下事件过程。

01 Private Sub Form_Click()

02 FontSize=12

03 Height=4000

04 Width=10000

05 Print Tab(35);"九九乘法表"

06 Print Tab(33);"-----------------------"

07 For i=1 To 9

08 For j=1 To i

09 Print Tab((j-1)*9+1);i＆"*"＆j＆"="＆i*j;

10 Next j

11 Print

12 Next i

13 End Sub

【代码说明】代码第07～12行演示了For循环的嵌套，其中执行完第07行的判断后，会一直执行第08～10行的循环，直到第08行的判断不再满足条件，才通过第12行再重复执行第07～12行的循环。

【运行效果】执行本窗体，在其中任意位置处单击鼠标，执行上面的事件过程打印出如图5.23所示的乘法表。

图　5.23　实例5.14九九乘法表

5.3.5　应用案例1——色彩变幻的背景

【实例5.15】编程实现自动变换背景颜色的窗体。代码如下。

01 Private Sub Form_Click()

02 Dim i As Integer

03 Dim j As Integer

04 Dim k As Integer

05 For i=0 To 255

06 For j=0 To 255

07 For k=0 To 255

08 Me.BackColor=RGB(i,j,k)

09 Next k

10 Next j

11 Next i

12 End Sub

【代码说明】代码第08行的RGB函数有3个参数，分别代表红色、绿色和蓝色，通过对3种颜色的混合可以得到很多的颜色，每个参数的变化范围为0～255，程序中的变量i、j、k控制RGB函数中的三原色参数变换。

【运行效果】因为本例就是一个窗体的颜色变化，这里不再给出运行窗体。

技巧　当运行该程序后，就进入了一段时间的循环，如果要提前结束程序的执行，可以按下键盘上的Ctrl+PauseBreak组合键。

5.3.6　应用案例2——列表框购物程序

【实例5.16】如图5.24所示，编程实现列表框购物程序界面，左侧为待选物品列表框，右侧为选中物品列表框。

图　5.24　实例5.16列表框购物程序界面

列表框购物程序的实现功能如下。

❑从左侧列表框中选中任一或者多个物品后，单击“＞”按钮，则所有选中物品移到右侧列表框中作为选中物品显示。

❑从右侧列表框中选中任一或者多个物品后，单击“＜”按钮，则该物品移到左侧列表框中。

❑单击“＞＞”按钮，左侧列表框中的所有物品移到右侧列表框中，表示选中所有物品。

❑单击“＜＜”按钮，则右侧列表框中的所有物品都移到左侧列表框中，表示撤销所有选中物品。程序中各对象需要更改的属性见表5.4，其他属性均取默认值。

编写事件过程如下。

01 Private Sub Form_Load()

02 '下面的程序段向列表框添加列表项

03 lst1.AddItem"牙膏"

04 lst1.AddItem"毛巾"

05 lst1.AddItem"香皂"

06 lst1.AddItem"洗发露"

07 lst1.AddItem"沐浴乳"

08 lst1.AddItem"花露水"

09 lst1.AddItem"牙刷"

10 lst1.AddItem"刮胡刀"

11 End Sub

12

13 Private Sub cmdRight_Click()

14 '下面的程序用循环来一个一个地判断列表框1中的列表项，是否被选中

15 '如果选中，则将列表框1中的该列表项内容添加到列表框2中，并从列表框1内删除

16 For i=lst1.ListCount-1 To 0 Step-1

17 If lst1.Selected(i)=True Then '判断如果索引为i的列表项被选中

18 lst2.AddItem lst1.List(i) '将lst1中的索引为i的列表项内容，添加到lst2中

19 lst1.RemoveItem i '从lst1中删除索引为i的列表项

20 End If

21 Next i

22 End Sub

23

24 Private Sub cmdLeft_Click()

25 '下面的程序用循环来一个一个地判断列表框2中的列表项，是否被选中

26 '如果选中，则将列表框2中的列表项内容添加到列表框1中，并从列表框2内删除

27 For i=lst2.ListCount-1 To 0 Step-1

28 If lst2.Selected(i)=True Then '判断如果索引为i的列表项被选中

29 lst1.AddItem lst2.List(i) '将lst2中的索引为i的，列表项内容，添加到lst1中

30 lst2.RemoveItem i '从lst2中删除索引为i的列表项

31 End If

32 Next i

33 End Sub

34

35 Private Sub cmdRightAll_Click()

36 '下面的程序用循环将列表框1中的列表项内容，全部添加到列表框2中，

37 '同时删除了其内部所有的列表项

38 For i=lst1.ListCount-1 To 0 Step-1

39 lst2.AddItem lst1.List(i)

40 lst1.RemoveItem i

41 Next i

42 End Sub

43

44 Private Sub cmdLeftAll_Click()

45 '下面的程序用循环将列表框2中的列表项内容，全部添加到列表框1中，

46 '同时删除了其内部所有的列表项

47 For i=lst2.ListCount-1 To 0 Step-1

48 lst1.AddItem lst2.List(i)

49 lst2.RemoveItem i

50 Next i

51 End Sub

【代码说明】代码第03～10行是动态地往列表框中添加选择项。代码第13～22行是“＞”按钮的事件，主要是通过第16～21行的循环，将左侧列表框选择的项添加到右侧列表框，并从左侧列表框中删除选择的项。代码第24～33行是“＜”按钮的事件，和“＜”按钮操作的方向相反，是从右侧列表框选择的项移动到左侧列表框。代码第35～42行是“＞＞”按钮的事件，将左侧列表框所有选项移动到右侧列表框。代码第44～51行是“＜＜”按钮的事件，操作与“＞＞”按钮正好相反。

【运行效果】因为本例运行效果与图5.24类似，这里不再给出运行窗体。

5.4　控制语句

Visual Basic中除了上面介绍的语句以外，还有一些控制语句，例如惹人争议的GoTo语句、退出循环的Exit Do语句、结束程序运行的End语句等。

5.4.1　GoTo语句

GoTo语句可以改变程序执行的顺序，跳过程序的某一部分去执行另一部分，或者返回已执行过的某语句使之重复执行。在BASIC发展史上，该语句的诞生曾经引起很多人的争议，因为它破坏了程序良好的结构。但实际上其存在也有自己的理由，在编程时有时使用该语句，可以简化程序。由于GoTo可以让程序流程转移，所以用GoTo语句也可以完成循环操作。

GoTo语句的一般格式如下。

GoTo 标号|行号

说明：

（1）“标号”是一个以冒号结尾的标识符；“行号”是一个整型数。例如：

Start:

是一个标号，而

100

是一个行号。

（2）“标号”遵循标识符命名规则，以英文字母开头，以冒号结束，而“行号”由数字组成。GoTo语句中的行号或标号在程序中必须存在，并且是唯一的，否则产生错误。

（3）GoTo语句改变程序执行顺序，无条件地把控制转移到“标号”或“行号”所在的程序行，并从该行开始向下执行。GoTo语句常常与条件语句结合使用。

（4）标号或行号可以在GoTo语句之前，也可以在GoTo语句之后。当在GoTo语句之前时，是实现循环的一种途径。

（5）Visual Basic对GoTo语句的使用有一定限制，它只能在一个过程中使用。

例如：求1～n的所有正整数之和的程序可以使用GoTo语句改写如下。

Private Sub Form_Click()

Dim n As Integer

Dim s As Double

s=0

n=1

add:

s=s+n

n=n+1

If n＜=100 Then GoTo add

Print s

End Sub

5.4.2　Exit Do语句

Exit Do用于中途跳出Do循环，既可以直接使用，也可以用条件判断语句限制使用。在满足某个条件时才能执行此语句，跳出Do循环。例如：在Do循环内部添加语句“If条件Then Exit Do”。

例如，对于前面的求1～n的所有正整数之和的程序可以使用Exit Do语句改写如下。

01 Private Sub Form_Click()

02 Dim s As Integer

03 Dim i As Integer

04 i=1

05 Do

06 If (i＞n) Then Exit Do

07 s=s+i:i=i+1

08 Loop

09 Print s

10 End Sub

5.4.3　End语句

End语句终止一个过程或程序。End配合过程或程序使用，其使用语法如下。

End

End停止执行过程或块。可以在过程中的任何位置关闭代码执行。在执行时，End语句会重置所有模块级别变量和所有模块的静态局部变量。

End语句提供了一种强迫中止程序的方法。Visual Basic程序正常结束应该卸载所有的窗体。只要没有其他程序引用该程序公共类模块创建的对象并无代码执行，程序将立即关闭。

以上语句有：

End

End If

End Sub

End Select

End Function

5.5　小结

本章讲述了VB程序中的一些基本控件语句，我们在日常编程中必然会碰到这些语句，不管是条件选择还是循环判断，都是组成一个程序的基本要素。本章第5.4节是一些特殊的控制语句，它允许中断和跳转语句，虽然很多编程高手并不建议用这些跳转语句，但使用它们也会带来一些程序上的便利。希望读者通过本章的学习，能掌握一些程序的开发流程和算法上的简单循环。

5.6　本章练习

1.简述For循环、While循环和Do循环的区别，并分别用这3个循环输出数字1到10的和。

2.给出下列程序代码的执行结果。

Dim i As Integer

Dim intCount As Integer

i=1

intCount=0

Do While (i＜50)

intCount=intCount+i

i=i+1

Loop

第6章　数组

前面使用的都是属于基本数据类型（字符串、整型、实型等）的数据，通过简单变量名来访问它们的内容。除基本数据类型外，Visual Basic还提供了数组类型。利用数组，可以方便灵活地组织和使用数据。对于数组来说，不能用一个简单变量名来访问它的某个元素。本章包括以下知识点。

❑数组的声明和应用。

❑静态数组的使用方法。

❑动态数组的使用方法以及动态数组的定义、重定义的要求。

❑控件数组的创建和使用。

通过本章的学习，读者可以快速掌握各种数组的定义和使用方法，以及数组和循环程序控制结构的结合使用，从而提高编程效率，优化程序结构。

6.1　数组的概念

在实际应用中，常常需要处理同一类型的成批数据。在Visual Basic中，使用一组具有同一名字、不同下标的下标变量存放成批数据，这些变量被称为数组，其一般形式如下：

X(n)

其中X称为数组名，n是下标，也称为下标变量，一个数组可以含有若干个下标变量（或称数组元素），下标用来指出某个数组元素在数组中的位置，X（9）代表X数组中的第9个元素。

数组可以被分为一维数组和多维数组。只有一个下标的数组被称为一维数组，例如M（10）；而将有多个下标的数组称为多维数组，例如Y（3,4）、Z（5,8,10）等。写多维数组时，下标变量之间用逗号分开。

6.1.1　数组定义

数组应当先定义后使用。在Visual Basic中，可以用4个关键字来定义数组，这4个关键字使用格式相同，但适用范围不一样。

❑Dim：用在窗体模块或标准模块中，定义窗体或标准块数组，也可用于过程中。

❑ReDim：用在过程中。

❑Static：用在过程中。

❑Public：用在标准模块中，定义全局数组。

以上4个关键字都可以用来定义数组，下面以Dim语句为例来介绍数组定义的格式，当用其他语句定义数组时，其格式是一样的。在定义数组时，Visual Basic提供了两种格式。

1.第一种格式

第一种格式与传统的数组定义格式相同，对于数组的每一维，只给出下标的上界，即可以使用的下标的最大值。对于一维数组，格式如下。

Dim 数组名 (下标上界) As 类型名称

例如：

Dim Score(9) As Integer

定义了一个一维数组，该数组的名字为Score，类型为Integer（整型），占据10个（0～9）整型变量的空间。

对于二维数组，格式如下。

Dim 数组名 (第一维下标上界，第二维下标上界) As 类型名称

例如：

Dim Test(2，3)As Integer

定义了一个二维数组，名字为Test，类型为Integer，该数组有3行(0～2)、4列(0～3)，占据12个整型变量的空间，如图6.1所示。

图　6.1　二维数组

说明：

（1）格式中的“数组名”与简单变量相同，可以是任何合法的Visual Basic变量名。“As类型名称”用来说明“数组”的类型，可以是Integer、Long、Single、Double、Currency、String等基本类型或用户定义的类型，也可以是Variant类型。如果省略“As类型名称”，则定义的数组为Variant类型。

（2）数组必须先定义，后使用。

（3）当用Dim语句定义数组时，该语句把数值数组中的全部元素都初始化为0，而把字符串数组中的全部元素都初始化为空字符串。

（4）在一般情况下，下标的下界默认为0。如果希望下标从1开始，可以通过Option Base语句来设置，其格式为：

Option Base n

格式中的n为数组下标的下界，只能是0或1，如果不使用该语句，则默认值为0。Option Base语句只能出现在窗体或模块的通用声明区，不能出现在过程中，并且必须放在数组定义之前。此外，如果定义的是多维数组，则下标的默认下界对每一维都有效。

（5）要注意区分“可以使用的最大下标值”和“元素个数”。“可以使用的最大下标值，指的是下标值的上界，而“元素个数”则是指数组中成员的个数。例如：

Dim Name(5)

中，数组可以使用的最大下标值是5，如果下标值从0开始，则数组中的元素为Name(0)、Name(l)、…、Name(5)，共有6个元素。如果下标从1开始，则元素的个数与最大下标值相同，为5。

2.第二种格式

用第一种格式定义的数组，其下标的下界只能是0或1，而如果使用第二种格式，则可根据需要指定数组下标的下界。格式如下。

Di m数组名 ([下界To]上界[，[下界To]上界]……) As 类型名称

例如：

Dim A (-2 To 3)As Integer

定义了一个一维数组A，其下标的下界为-2，上界为3，该数组可以使用的下标值在-2～3之间，数组元素为A(-2)、A(-1)、A(0)、A(1)、A(2)、A(3)，共有6个元素。

可以看出，第二种格式实际上已包含了第一种格式，只要省略格式中的“下界To”，即变为第一种格式。当下标为0或1时，可以省略“下界To”。因此，如果不使用Option Base语句，则下述数组说明语句是等效的。

Dim A(8,3)

Dim A(0 To 8,0 To 3)

Dim A(8,0To 3)

在某些情况下，使用To能更好地反映对象的特性，例如：

Dim Population(1949 To 2000)

Dim Age(10 To 100)

分别定义了一个人口(Population)数组和一个年龄(Age)数组，可以分别用Population(l978)和Age(35)表示1978年和年龄为35岁的人口数。

上面介绍了定义数组的两种格式。在定义数组时，要注意以下几点。

（1）数组名的命名规则与变量名相同，在命名时应尽可能做到“见名知义”。

（2）在同一个过程中，数组名不能与变量名同名，否则会出错。例如：

Private Sub Form_Click()

Dim x(5) As Integer

Dim x As Integer

x=8

x(2)=10

Print x,x(2)

End Sub

程序运行后，单击窗体，将显示一个消息框，如图6.2所示。

图　6.2　错误1

（3）在定义数组时，每一维的元素个数必须是常数，不能是变量或表达式。例如：

Dim A2(n)

Dim A3(n+5)

都是不合法的。即使在执行数组定义语句之前给出变量的值，也是错误的。例如：

n=InputBox("输入n的值”)

Dim A2(n)

执行上面的操作后，将产生出错信息，如图6.3所示。

图　6.3　错误2

如果需要在运行时定义数组的大小，可以通过以下两种方法来解决。

❑用ReDim语句定义数组。上例改为：

n=InputBox("输入n的值”)

ReDim A2(n)

❑使用动态数组(具体内容见下一节)。

（4）数组的类型通常在As子句中给出，如果省略As子句，则定义的是默认数组。

（5）数组可以通过前面介绍的两种格式定义，无论用哪一种格式定义数组，下界都必须小于上界。有时候，可能需要知道数组的上界值和下界值，这时可以通过LBound和UBound函数来测试，其格式为：

LBound(数组[，维])

UBound(数组[，维])

这两个函数分别返回一个数组中指定维的下界和上界。其中“数组”是一个数组名，“维”是要测试的维。

LBound函数返回“数组”某一“维”的下界值，而UBound函数返回“数组”某一“维”的上界值，两个函数一起使用即可确定一个数组的大小。

对于一维数组来说，参数“维”可以省略。如果要测试多维数组，则“维”不能省略。

例如：

Dim A(1 To 10，40 To 50，-4 To 4)

定义了一个三维数组，则用下面的语句可以得到该数组各维的上下界。

Print LBound(A，1)，UBound(A，1) '打印第一维的下界值和上界值

Print LBound(A，2)，UBound(A，2) '打印第二维的下界值和上界值

Print LBound(A，3)，UBound(A，3) '打印第三维的下界值和上界值

输出结果为：

1 10

40 50

-4 4

6.1.2　在同一个数组中存放不同类型的数据

在Visual Basic中，有一种数组可以存放不同类型的数据，将这类数组称为默认数组。所谓默认数组，就是数据类型为Variant（默认）的数组。在一般情况下，定义数组应指明其类型，例如：

Static A(1 To 100) As Integer

定义了一个数组A。该数组的类型为整型，它有100个元素，每个元素都是一个整数。如果把上面的定义改为：

Static A (1 To 100)

则定义的数组是默认数组，其类型默认为Variant，因此，该定义等价于：

Static A (1 To 100) As Variant

对于默认数组来说，同一个数组中可以存放各种不同的数据。因此，默认数组可以说是一种“混合数组”。例如：

01 Sub Form_Click()

02 Static Defau(4)

03 Dim i As Integer

04 Defau(1)=100

05 Defau(2)=2.5

06 Defau(3)="abcd"

07 Defau(4)=Date

08 For i=1 To 4

09 Print"Defau(";i;")=";Defau(i)

10 Next i

11 End Sub

该事件过程定义了一个静态数组Defau（默认数组一般应定义为静态的），然后对各元素赋予不同类型的数据，包括整型、实型、字符串型、日期和时间类型。执行该程序，然后单击窗体，输出结果如图6.4所示。

图　6.4　默认数组输出

6.2　静态数组与动态数组

定义数组后，为了使用数组，必须为数组开辟所需要的内存区。根据内存区开辟时机的不同，可以把数组分为静态(Static)数组和动态(Dynamic)数组。通常把需要在编译时开辟内存区的数组叫做静态数组，而把需要在运行时开辟内存区的数组叫做动态数组。

在前面的例子中，使用的都是静态数组，其最明显的特点为声明时指定了数组大小（数组元素个数）。

6.2.1　动态数组的定义

有时，编程人员在设计程序时，无法知道将要使用的数组的大小。例如，要用数组存放各班学生的VB成绩。此时，无法预料不同班级的人数，所以无法定义数组的大小。

注意　数组不能定义的过于庞大，否则会占满计算机内存，使机器不能正常运行，或者计算机直接给出不能分配内存的错误。

对于上面的问题，用动态数组解决是最完美不过的了。在Visual Basic中，可以先声明一个没有大小的数组，然后在使用时再定义其大小。这类数组就是动态数组。例如：

Dim Test() As Integer '在窗体层或标准模块中声明

Dim Size As Integer

Sub Form_Click()

...

Size=Val(InputBox("请输入一个数值："))

ReDim Test(Size)

...

End Sub

该例先在窗体层或标准模块中用Dim语句声明了一个空数组Test和一个变量Size，然后在过程中用ReDim语句重定义了该数组，下标Size在运行时输入。

其中，ReDim语句的格式为：

ReDim 变量（下标） As 类型

该语句用来重新定义动态数组，按定义的上下界重新分配存储单元，并可为定义的变量改变存储类型。ReDim只能出现在事件过程或通用过程中，用它定义的数组是一个“临时”数组，即在执行数组所在的过程时为数组开辟一定的内存空间，当过程结束时，这部分内存即被释放。

说明：

（1）在窗体层或模块层定义的动态数组只有类型，没有指定维数，其维数在ReDim语句中给出，最多不能超过8维。

（2）可以用ReDim语句直接定义数组。如果在标准模块层或窗体层没有用Public或Dim声明过同名的数组，则用ReDim定义的数组最多可达60维。

（3）在一个程序中，可以多次用ReDim语句定义同一个数组，随时修改数组中元素的个数。

6.2.2　数组的清除和重定义

有时候，可能需要清除数组的内容或释放数组所占的内存空间，这可以用Erase语句来实现，其格式为：

Erase数组名[，数组名]……

Erase语句用来重新初始化静态数组的元素，或者释放动态数组的存储空间。注意，在Erase语句中，只给出要刷新的数组名，不带括号和下标。例如：

Erase Test

说明：

（1）当把Erase语句用于静态数组时，如果这个数组是数值数组，则把数组中的所有元素置为0；如果是字符串数组，则把所有元素置为空字符串；如果是记录数组，则根据每个元素（包括定长字符串）的类型重新进行设置，见表6.1。

（2）当把Erase语句用于动态数组时，将删除整个数组结构并释放该数组所占用的内存。也就是说，动态数组经Erase后即不复存在；而静态数组经Erase后仍然存在，只是其内容被清空。

（3）当把Erase语句用于变体数组时，每个元素将被重置为“空”（Empty）。

（4）Erase释放动态数组所使用的内存。在下次引用该动态数组之前，必须用ReDim语句重新定义该数组变量的维数。

【实例6.1】编写程序，试验Erase语句的功能。

01 Static Sub Form_Click()

02 Dim Test(1 To 10) As Integer

03 Dim i As Integer

04 For i=1 To 10

05 Test(i)=i

06 Print Test(i);

07 Next i

08 Erase Test

09 Print

10 Print"执行Erase Test()语句后，数组Test的内容如下："

11 For i=1 To 10

12 Print Test(i);

13 Next i

14 End Sub

【代码说明】代码第01行的事件过程使用了关键字Static，因而在该过程中定义的变量为静态变量(包括数组)。过程中第02行定义了一个静态数组Test，用第04～07行的For循环语句为每个元素赋值，并输出每个元素的值，然后执行第08行的Erase语句，将各元素的值清除，使每个元素的值都为0。

【运行效果】程序的运行结果如图6.5所示。

图　6.5　实例6.1运行结果

6.3　数组的基本操作

建立一个数组之后，可以对数组或数组元素进行操作。数组的基本操作包括输入、输出及复制，这些操作都是对数组元素进行的。此外，在Visual Basic中还提供了For Each…Next语句，可用于对数组的操作。

6.3.1　数组的输入和输出

1.数组的引用

数组的引用通常是指对数组元素的引用，其方法是，在数组后面的括号中指定下标，例如

x(8)，y(2,3)，z(3)

要注意区分数组定义和数组元素，在下面的程序片断中：

Dim x(8)

......

Print x(8)

......

有两个x(8)，其中Dim语句中的x(8)不是数组元素，而是“数组说明符”，由它说明所建立的数组x的最大可用下标值为8；而赋值语句"Print x(8)"中的x(8)是一个数组元素，它代表数组x中序号为8的元素。

一般来说，在程序中，凡是简单变量出现的地方，都可以用数组元素代替。数组元素可以参加表达式的运算，也可以被赋值。例如：

x(5)=x(2)+x(4)

在引用数组时，应注意以下几点。

（1）在引用数组元素时，数组名、类型和维数必须与定义数组时一致。

（2）如果建立的是二维或多维数组，则在引用时必须给出两个或多个下标。

（3）引用数组元素时，其下标值应在建立数组时所指定的范围内。例如：

Dim A (5)

......

Print A (6)

运行时将出现“下标越界”错误。

2.数组元素的输入

【实例6.2】数组元素一般通过For循环语句输入。例如：

01 Sub Form_Click()

02 Dim x(4) As String

03 Dim i As Integer

04 For i=1 To 4

05 x(i)=InputBox("请输入姓名:")

06 Next i

07 End Sub

【代码说明】上述程序运行后，在第05行对话框中输入具体的姓名，它们被存入字符串数组x中。有时也可以用赋值语句来实现数组元素的输入。如上例可以用下面的语句为数组各元素赋值：

x(1)=“张三”

x(2)=“李四”

x(3)=“王五”

x(4)=“马六”

3.数组元素的输出

【实例6.3】数组元素的输出，通常也是使用For循环语句。例如，要更改上面的程序，使其能够将数组元素的内容输出到窗体上，编写程序如下。

01 Sub Form_Click()

02 Dim x(4) As String

03 Dim i As Integer

04 For i=1 To 4

05 x(i)=InputBox("请输入姓名:")

06 Next I

07

08 For i=1 To 4

09 Print x(i)

10 Next I

11 End Sub

【代码说明】第08～10行的循环，使用Print方法输出数组元素。第05行是设置数组元素。

4.多维数组元素的输入和输出

【实例6.4】多维数组元素的输入和输出，通过多重循环来实现。由于Visual Basic中的数组是按行存储的，因此把控制数组第一维的循环变量放在最外层循环中。例如：

01 Private Sub Form_Click()

02 Dim a(3,5)

03 Dim i As Integer

04 Dim j As Integer

05 For i=1 To 3

06 For j=1 To 5

07 a(i,j)=i*j

08 Print"a(";i;"，";j;")=";a(i,j)

09 Next j

10 Next i

11 End Sub

【代码说明】第06～09行是内循环，然后第05～10行是内循环。外循环判断的是数组的第一维，而内循环判断的是数组的第二维。

【运行效果】程序运行后，单击窗体，结果如图6.6所示。

图　6.6　多维数组输入输出试验

6.3.2　求数组和与平均值

【实例6.5】求一维数组中各元素之和与其平均值。

编写程序如下。

01 Option Explicit

02 Option Base 1

03

04 Private Sub Form_Click()

05 Dim s(5) As Single

06 Dim i As Integer

07 Dim sum As Single

08 Dim avr As Single

09

10 sum=0

11 '该循环用于给数组元素赋值

12 For i=1 To 5

13 s(i)=Val(InputBox("请输入第"＆i＆"个数据！"))

14 Next i

15 '该循环用于求各元素之和

16 For i=1 To 5

17 sum=sum+s(i)

18 Next i

19

20 avr=sum/5 '求平均值

21

22 Print"数组元素的总和为"＆sum

23 Print"数组元素的平均值为"＆avr

24 End Sub

上面的程序通用性较差，因为只能计算有5个元素的一维数组之和与数组的平均值。下面更改一下程序，加强本例的通用性。

25 Private Sub Form_Click()

26 Dim s() As Single

27 Dim i As Integer

28 Dim sum As Single

29 Dim avr As Single

30 Dim size As Integer

31

32 size=Val(InputBox("请输入数组元素个数!"))

33 ReDim s(size) '重定义数组大小

34

35 sum=0

36 '该循环用于给数组元素赋值

37 For i=LBound(s) To UBound(s)

38 s(i)=Val(InputBox("请输入第"＆i＆"个数据！"))

39 Next i

40 '该循环用于求各元素之和

41 For i=LBound(s) To UBound(s)

42 sum=sum+s(i)

43 Next i

44

45 avr=sum/(UBound(s)-LBound(s)+1) '求平均值

46

47 Print"数组元素的总和为："＆sum

48 Print"数组元素的平均值为："＆avr

49 End Sub

【代码说明】代码第04～24行与代码第25～48行输出的效果是相同的，代码第12～18行的循环都是用的固定值5，这就有一个数量的限制，而代码第36～43行则没有使用数字限制，而是使用了LBound和UBound函数。

【运行效果】本例就是一个计算和与平均值的案例，结果很简单，这里不再重复。

6.3.3　数组排序

【实例6.6】从键盘上输入10个整数，用冒泡排序法对这10个数从小到大排序。

【实例分析】排序是把一组数据按一定顺序排列的操作，它有很多种算法，其效率也有很大差别。冒泡排序是常用的一种排序方法。该排序法的思路是从数组的第一项开始，每一项（i）都与下一项（i+1）进行比较，如果下一项的值较小，就将这两项的位置交换，从而使值较小的数据项移动到上面。这种操作反复进行，直到数组结束，然后再回到开头进行重复处理。当整个数组自始至终再也不出现项目交换时，全部数据项的排序结束。

例如，为了把一组数10、7、3按上升顺序排序，其排序过程如下。

（1）10 7 3比较10和7，因为10＞7，所以交换位置。

（2）7 10 3比较10和3，因为10＞3，所以交换位置；由于所有数据还没有完全排好序，因此需重复处理。

（3）7 3 10比较7和3，因为7＞3，交换位置。

（4）3 7 10比较7和10，因为7＜10，不交换。至此，可以认为数据已排序完毕，但还要再重复一次比较，以进一步确认。

根据上面的分析，可以编写对数值数据进行升序排序的程序。首先在窗体上画一个命令按钮Command1，并将其Caption属性设置为“开始排序”。然后编写如下事件过程。

01 Private Sub Command1_Click()

02 Static a(1 To 10) As Integer

03 Dim temp As Integer

04 Dim i As Integer

05 Dim j As Integer

06 '该循环用于给数组元素赋值

07 For i=1 To 10

08 a(i)=Val(InputBox("请输入需要排序的第"＆i＆"个数值："))

09 Next i

10 '该循环用于排序数组元素

11 For i=1 To 9

12 For j=1 To 10-i

13 If a(j)＞a(j+1) Then

14 '下面3条语句用于交换数组元素的值

15 temp=a(j+1)

16 a(j+1)=a(j)

17 a(j)=temp

18 End If

19 Next j

20 Next i

21 '该循环用于输出数组元素

22 For i=1 To 10

23 Print a(i)

24 Next i

25 End Sub

【代码说明】在代码第11～20行的排序中，程序判断前一个数是否大于后一个数，如果大于，则通过第15～17行互相交换元素的数值。交换通过一个临时变量temp完成。

【运行效果】本例是输出一个排序结果，这里不再给出窗体效果。

6.3.4　插入数据

在典型的数组应用中还有一种插入数据的应用。其内容是：在一组有序的数据中，插入一个数，使这组数据仍旧有序。假设有数组A(n)，已按从小到大的顺序排列。那么插入的基本思路如下。

（1）首先，要查找待插入数据在数组中的位置k。

（2）然后从最后一个元素开始直到下标为k为止的元素依次往后移动一个位置，将第k个元素的位置腾出来。

（3）将数据插入到第k个元素的位置。

【实例6.7】在有序数组A中，插入具体数值10，插入过程如图6.7所示。

图　6.7　插入元素示意图

程序代码如下。

01 Private Sub Form_Click()

02 Dim A()

03 '给数组各元素赋初值，其中最后一个数为预留插入数位置

04 A=Array(2,3,6,8,9,12,14,19,0)

05 n=UBound(A)

06 x=10

07 Print"原始排列为："

08 '该循环用于输出数组A的元素

09 For i=0 To n-1

10 Print A(i);

11 Next i

12 Print

13 Print"插入数据以后的排列为："

14 '该循环用于查找欲插入数x在数组中的位置

15 For k=0 To n-1

16 If x＜A(k) Then Exit For

17 Next k

18 '从最后元素开始后移，腾出位置

19 For i=n-1 To k Step-1

20 A(i+1)=A(i)

21 Next i

22 '数插入在对应的位置

23 A(k)=x

24 '显示插入后的各数组元素

25 For i=0 To n

26 Print A(i);

27 Next i

28 End Sub

【代码说明】代码第04行首先定义数组，代码第05行的变量n取数组的个数。代码第09～11行通过一个循环输出原始数组的值。代码第15～17行的循环查找欲插入数x在数组中的位置。代码第19～21行实现元素后移操作。代码第24～27行输出调整后的数组。

【运行效果】程序运行后，单击窗体，结果如图6.8所示。

图　6.8　实例6.7插入数据10结果

说明　本程序中使用了Array函数初始化了数组。该函数的详细内容请参阅本章6.4节的内容。

6.3.5　删除数据

删除操作首先也要找到欲删除元素的位置k；然后从k+1到n个位置开始向前移动；最后将数组元素个数减一。

【实例6.8】如将值为12的元素删除，其删除过程如图6.9所示。

图　6.9　删除操作示意图

编写程序：

01 Private Sub Form_Click()

02 Dim A()

03 '给数组各元素赋初值

04 A=Array(2,3,6,8,9,12,14,19)

05 n=UBound(A)

06 x=12

07 Print"原始排列为："

08 '该循环用于输出数组A的元素

09 For i=0 To n

10 Print A(i);

11 Next i

12 Print

13 Print"删除数据以后的排列为："

14 '该循环用于查找欲删除数x在数组中的位置

15 For k=0 To n

16 If x=A(k) Then Exit For

17 Next k

18 If k＞n Then MsgBox ("找不到此数据"):Exit Sub

19 '将x后的元素左移

20 For i=k+1 To n

21 A(i-1)=A(i)

22 Next i

23 n=n-1

24 '数组元素减少一个

25 ReDim Preserve A(n)

26 '显示删除后的各数组元素

27 For i=0 To n

28 Print A(i);

29 Next i

30 End Sub

【代码说明】代码第04行首先定义数组，代码第05行的变量n取数组的个数。代码第09～11行通过一个循环输出原始数组的值。代码第15～17行的循环查找欲插入数x在数组中的位置。代码第20～25行数组元素左移并去掉一个元素，代码第27～29行输出调整后的数组。

【运行效果】程序运行后，单击窗体，结果如图6.10所示。

图　6.10　实例6.8删除元素值12结果

6.3.6　For Each...Next语句

For Each...Next语句类似于For...Next语句，两者都用来执行指定重复次数的一组操作，但For Each...Next语句专门用于数组或对象“集合”，其一般格式为：

For Each成员In数组

循环体

[Exit For]

......

Next [成员]

其中，“成员”是一个变体变量，它是为循环提供的，并在For Each...Next结构中重复使用，它实际上代表的是数组中的每个元素。“数组”是一个数组名，没有括号和上下界。

用For Each...Next语句可以对数组元素进行处理，它所重复执行的次数由数组中元素的个数决定，即数组中有多少个元素，就自动重复执行多少次。例如：

Dim Array (1 to 5)

For Each x in Arrray

Print x;

Next x

将重复执行5次，每次输出数组的一个元素的值。这里的x类似于循环体中的循环控制变量，但不需要为其提供初值和终值，而是根据数组元素的个数决定执行循环的次数，x是一个变体变量，它可以代表任何类型的数组元素。

可以看出，在数组操作中，For Each...Next语句比For...Next语句更方便，因为它不需要指明结束循环的条件。

6.4　数组的初始化

所谓数组初始化，就是给数组的各元素赋初值。前一节已介绍过如何用赋值语句或lnputBox函数为数组元素赋值，这两种方法都需要占用运行时间，影响效率。为此Visual Basic提供了Array函数。利用该函数，可以使数组在程序运行之前初始化，得到初值。

Array函数用来为数组元素赋值，即把一个数据集读入某个数组。其格式为：

数组变量名＝Array（数组元素值）

这里的“数组变量名”是预先定义的数组名，在“数组变量名”之后没有括号。之所以称为“数组变量”，是因为它作为数组使用，但作为变量定义，它既没有维数，也没上下界。“数组元素值”是需要赋给数组各元素的值。例如：

Static x As Variant

x=Array(1,2,3,4,5)

将把1、2、3、4、5这5个数值赋给数组x的各个元素，即x(0)=1，x(1)=2，x(2)=3，x(3)=4，x(4)=5。

注意　在默认情况下，数组的下标从0开始，数组x有5个元素。如果想使下标从1开始，则应当加上Option Base 1。

对于字符串数组，其初始化操作相同。例如：

Static Test

Test=Array("One","Two","Three","Four")

Print Test(4)

经过上面的定义和初始化后，Test (0)="One"，Test (1)＝"Two"，Test (2)＝"Three"，Test (3)="Four"。上面的语句在窗体上输出"Four"。需要注意，数组变量不能是具体的数据类型，只能是变体(Variant)类型。在上面的例子中，如果用下面的语句定义变量x和Test：

Static x As Integer

Static Test As String

则是错误的。

【实例6.9】编写程序，试验Array函数。

01 Option Explicit

02 Option Base 1

03 Private Sub Form_Click()

04 Dim MyWeek As Variant

05 Dim i As Integer

06 MyWeek=Array("星期一","星期二","星期三","星期四","星期五","星期六","星期日")

07 For i=1 To 6

08 Print MyWeek(i)

09 Next i

10 End Sub

【代码说明】代码第06行初始化一个数组，其中包含所有的星期，代码第07～09行使用For循环输出数组的每个元素。

【运行效果】程序的执行结果如图6.11所示。

图　6.11　实例6.9运行结果

说明　在一般情况下，数组元素的值通过赋值语句或InputBox函数读入数组，如果使用Array函数，则可使程序大为简化。

6.5　控件数组

前面介绍了数值数组和字符串数组。在Visual Basic中，还可以使用控件数组，它为处理一组功能相近的控件提供了方便的途径。

6.5.1　基本概念

控件数组由一组相同类型的控件组成，这些控件共用一个相同的控件名字，具有同样的属性设置。数组中的每个控件都有唯一的索引号，即下标，其所有元素的Name属性必须相同。

当有若干个控件执行大致相同的操作时，控件数组是很有用的，控件数组共享同样的事件过程。例如，假定一个控件数组含有3个命令按钮，则不管单击哪一个按钮，都会调用同一个Click过程。

控件数组的每个元素都有一个与之关联的下标，或称索引(Index)，下标值由Index属性指定。由于一个控件数组中的各个元素的Name属性均相同，所以只能用Index属性的值区分数组中的各个元素。与普通数组一样，控件数组的下标也放在圆括号中，例如Optionl(0)。

为了区分控件数组中的各个元素，Visual Basic把下标值传送给一个过程。例如，假定在窗体上建立了两个命令按钮，将它们的Name属性都设置为cmdTest。设置完第一个按钮的Name属性后，如果再设置第二个按钮的属性，则Visual Basic会弹出一个对话框，询问是否要建立控件数组。此时单击对话框中的“是”按钮，然后双击窗体上的第一个命令按钮，打开代码编辑器窗口，可以看到在事件过程中加入了一个下标(Index)参数，即

Sub cmdTest_Click(Index As Integer)

End Sub

此时，不论单击哪一个命令按钮，都会调用这个事件过程，按钮的Index属性将传给过程，由它指明单击了哪一个按钮。

在建立控件数组时，Visual Basic给每个元素赋一个下标值，通过属性窗口中的Index属性可以知道这个下标值是多少。可以看到，第一个命令按钮的下标值为0，第二个命令按钮的下标值为1，依次类推。在设计阶段，可以改变控件数组元素的Index属性，但不能在运行时改变。

6.5.2　建立控件数组

可以使用以下两种方法来建立控件数组。

❑第一种方法如下。

（1）在窗体上画出作为数组元素的各个控件。

（2）单击要包含到数组中的某个控件，将其激活。

（3）在属性窗口中选择“(名称)”属性，并键入控件的名称。

（4）对每个要加到数组中的控件重复（2）、（3）步，键入与第（3）步中相同的名称。当对第二个控件键入与第一个控件相同的名称后，Visual Basic将显示一个对话框，如图6.12所示，询问是否要建立控件数组。单击“是”将建立控件数组，单击“否”则放弃建立操作。

图　6.12　创建控件数组

❑第二种方法：

（1）在窗体上画出一个控件，将其激活。

（2）执行“编辑”菜单中的“复制”命令，将该控件放入剪贴板。

（3）执行“编辑”菜单中的“粘贴”命令，将会显示上图6.8的对话框。

（4）单击对话框中的“是”按钮，窗体的左上角将出现一个控件，它就是控件数组的第二个元素。

（5）不断执行“粘贴”命令，建立控件数组的其他元素。

控件数组建立后，只要改变一个控件的"Name"属性值，并把Index属性置为空，就能把该控件从控件数组中删除。控件数组中的控件执行相同的事件过程，通过Index属性可以决定控件数组中的相应控件所执行的操作。

【实例6.10】建立含有3个命令按钮的控件数组，当单击某个命令按钮时，分别执行不同的操作。

【实现步骤】操作步骤如下。

（1）在窗体上建立一个命令按钮，并将其Name属性设置为"cmd1"。使用上面介绍的复制粘贴的方法建立控件数组。

（2）将第1、第2、第3个命令按钮的Caption属性分别设置为“按钮1”、“按钮2”、“按钮3”。

（3）双击任意一个命令按钮，打开代码窗口，键入如下事件过程：

01 Private Sub cmd1_Click(Index As Integer)

02 Select Case Index

03 Case 0

04 MsgBox"您按了按钮1！"

05 Case 1

06 MsgBox"您按了按钮2！"

07 Case 2

08 MsgBox"您按了按钮3！"

09 End Select

10 End Sub

【代码说明】上述代码根据Index的属性值决定，单击某个命令按钮时所执行的操作，这里通过代码第02～09行的Select Case语句来进行判断。第一个命令按钮的Index为0，因此，当单击第一个命令按钮时，系统将其Index的值0，传递给cmd1_Click(Index As Integer)中的变量Index，然后在程序中根据变量Index的值执行不同的语句。单击其他两个按钮时，程序执行的情况与其相同。

【运行效果】运行结果如图6.13所示（单击了按钮2）。

图　6.13　实例6.10运行结果

控件数组可以在设计阶段通过设置相同的Name属性建立，可以通过改变数组中某个控件的Name和Index属性将其删除。此外，也可以在程序代码中通过Load方法建立控件数组，通过Unload方法删除数组中的某个控件。

【实例6.11】在窗体上建立两个命令按钮、一个文本框。将两个命令按钮的名称分别改为cmdAdd和cmdDele；将文本框的名称改为txt1，并将其Index设置为0。界面设计如图6.14所示。

图　6.14　实例6.11界面设计

编写如下事件过程。

01 Option Explicit

02 Dim x As Integer

03

04 Private Sub Form_Load()

05 x=0

06 End Sub

07

08 Private Sub cmdAdd_Click()

09 x=x+1

10 Load txt1(x) '给控件数组加入一个元素

11 txt1(x).Top=txt1(x-1).Top+txt1(x-1).Height+10 '设置新加入的数组元素的位置

12 txt1(x).Visible=True '设置新加入的数组元素为可见

13 End Sub

14

15 Private Sub cmdDele_Click()

16 Dim y As Integer

17 y=txt1.Count-1

18 If y=0 Then

19 MsgBox"不能删除设计时绘制的控件！"

20 Else

21 Unload txt1(y) '删除数组元素

22 x=x-1

23 End If

24 End Sub

【代码说明】在代码第15～24行的cmdDele_Click中出现的txt1.Count为数组元素的总个数，因为数组元素的下标是从0开始的，因此数组的上界值为txt1.Count-1。代码第08～13行的cmdAdd_Click事件给控件数组加入元素。

【运行效果】单击“添加”按钮可以在窗体上添加文本框；单击“删除”按钮将删除窗体中最下面的文本框。运行结果如图6.15所示。

图　6.15　实例6.11运行结果

6.6　小结

本章介绍的是数组，数组是一些同类型数据的集合。本章开始介绍的数组都很简单，就是一些普通的数字，然后介绍了一些固定字符串，本章最后的6.5节稍显复杂，介绍了控件数组，其实我们在访问窗体中的控件时，都是以数组的方式进行访问，因为这是一些有序的数据集合。希望读者通过本章的学习，了解数据集合的创建和读取方式。

6.7　本章练习

1.如何定义动态数组？

2.是否允许在同一个数组中存放不同类型的数据？

A.能

B.不能

3.判断下列数组声明或重定义是否正确。

Dim theArr1(-1 to 10) As Integer

Dim theArr2(10 to 10) As Integer

Dim theArr3(10)

ReDim theArr3(20 ,5)

Dim theArr4()

ReDim theArr4() As Integer

ReDim theArr5(2 ,5) As Integer

第7章　过程

为了简化程序设计的复杂程度，往往将一个大的程序分成较小的功能部件，人们称这些部件为过程。过程的作用除了可以简化大程序以外，还可以提高重复利用率并且使得修改程序也方便了很多。因为，有时多个不同的程序段中都会使用同样的代码实现同样的功能，甚至一个程序段中的不同位置也会需要编写大量的重复代码。这时复制和粘贴可能成为了一部分编程人员的法宝，不断地将复制的代码粘贴到不同的位置。但是，这样做一是会让程序变得冗长，不宜被阅读；二是如果突然发现被复制的代码段里有错误，修改程序就会成为很复杂的一件事情。本章包括以下知识点。

❑Sub过程和Function过程。

❑参数的传递方式。

❑参数的可选与可变。

❑对象参数的应用。

Visual Basic中过程分为两种，一种是事件过程，例如前面涉及的按钮的Click事件过程、窗体的Load事件过程等。另一种是通用过程，它主要用来让事件过程或其他通用过程调用。本章主要介绍的就是这种过程。

7.1　Sub过程

通用过程分为两种，一种是Sub过程（子程序过程），另一种为Function过程（函数过程）。Sub过程不返回值，而Function过程要向调用它的语句返回值。每个通用过程都应该实现一个或多个具体功能。

7.1.1　建立Sub过程

通用Sub过程可以在窗体模块中建立，也可以在标准模块中建立。如果要建立一个工程中所有位置的程序都能调用的Sub过程，最好是在标准模块中建立，其优点是后期维护方便。如果要建立只是在本窗体模块中调用的过程，则应当写在窗体模块中。具体在哪里建立Sub过程，以上只是一些建议，并非是Visual Basic严格的规则。建立Sub过程，也称为定义Sub过程。下面介绍建立Sub过程的具体方法，首先介绍其格式：

[ Static][Public|Private ] Sub 过程名（[形参表]）

[局部变量声明]

[语句块]

[Exit Sub]

[语句块]

End Sub

Sub和End Sub是一个Sub过程的开始和结束标志。Static表示该过程中的局部变量是静态变量，在过程调用结束后，其值仍然保留。Public或Private表示Sub过程是“公有”的还是“私有”的。Private过程能在本窗体模块中被调用。Public过程可在整个程序范围内被调用。如果省略[Private|Public]时，系统默认为Public。一般格式中的“（[形参表]）”的具体形式是（[变量名[As类型][,变量名[As类型]]……]。

建立一个Sub过程有两种方法，一是进入代码编辑器窗口，在左侧显示对象名的下拉框中选择“通用”，在右侧显示过程的下拉框中选择“声明”，然后键入Sub及过程名。按下Enter键后，Visual Basic系统会自动加上End Sub与过程名后的一对括号，此时便可在开始和结束之间输入语句；二是进入代码窗口后，单击“工具”菜单，选择“添加过程”菜单项后，系统会打开一个对话框，如图7.1所示。

图　7.1　“添加过程”对话框

按照对话框中的提示输入相应的内容。例如，要建立一个名为Area的Sub过程，在“名称”框中输入过程名Area，在“类型”中选择“子程序”，在“范围”中选择“公有的”，单击“确定”按钮退出对话框，系统自动给出Sub过程的头和尾，如图7.2所示，此时便可输入过程的具体内容。

图　7.2　自动生成的过程头尾

【实例7.1】在标准模块中建立一个能够计算矩形面积的Sub过程，使其能够在整个程序范围内被调用。

【实现步骤】操作步骤如下。

（1）启动Visual Basic。

（2）选择“工程”菜单下的“添加模块”菜单项，会出现如图7.3所示的“添加模块”窗口，直接单击“打开”按钮便可以创建一个标准模块。

图　7.3　“添加模块”窗口（新建选项卡）

技巧　通过“添加模块”窗口的“现存”选项卡，可以将已经存在的模块导入到当前工程，以提高开发效率。

（3）在工程资源管理器中双击Module1，便可打开标准模块的代码窗口，如图7.4所示。

图　7.4　Module1代码窗口

（4）在光标闪烁处直接输入Public Sub area(x As Single,y As Single)并按下Enter键，系统会自动加上End Sub结束标记。

其中，x、y是过程area的两个形式参数，它们用来接收外界传输过来的数据。本例中用形参x接收矩形的长度；用y接收矩形的宽度。

（5）在过程头尾之间，键入语句后为：

01 Public Sub area(x As Single,y As Single)

02 Dim s As Single

03 s=x*y

04 MsgBox"矩形面积为："＆s

05 End Sub

通过上述步骤，已经建好了一个能够计算矩形面积的Sub过程。这种过程建好后，必须让外界调用才能执行。

（6）编写窗体Form1的Click事件，调用area过程。

06 Private Sub Form_Click()

07 Call area(5,8)

08 End Sub

【代码说明】Click事件内唯一的语句用于调用area过程，过程名area后括号内的5是矩形的长度，8是矩形的宽度。在调用语句内的参数被称为实际参数，简称实参，本例中5和8就是实参。

【运行效果】当运行程序后，单击窗体时会触发Form_Click并执行其内的语句Call area(5,8)，这时程序流程会跳转到标准模块内的area过程那里，同时系统自动将5赋值给x，将8赋值给y，然后逐条执行area过程内的语句，先声明一个变量s，其次计算面积并赋值给s，而后以消息框的形式显示计算结果并结束area过程的调用。area过程结束后程序流程便会返回到Form_Click事件中，并执行语句Call area(5,8)的下一条语句，因为其下没有其他语句，所以直接执行End Sub，结束Form_Click事件，程序调用流程请参考图7.5。

图　7.5　调用流程

其实，Call area(5,8)的作用，类似于把area过程内的语句，写在了Form_Click事件的该语句位置上。例如下面的程序：

Private Sub Form_Click()

Dim x As Single

Dim y As Single

Dim s As Single

x=5

y=8

s=x*y

MsgBox"矩形面积为："＆s

End Sub

将计算面积的功能独立出来，专门写为一个area过程后，其他程序调用起来比较方便，程序结构会很清晰，需要修改时，只修改area过程内的语句即可，而不用涉及其他模块或程序。

7.1.2　调用Sub过程

要执行一个过程，则必须调用该过程。调用Sub过程有两种方式，一种是使用Call语句，另一种是把过程名作为一个语句来使用。

1.用Call语句调用Sub过程

格式：Call过程名[(实参)]

Call语句会使程序流程跳转到Sub过程。用Call语句调用一个过程时，如果过程本身没有参数，则“实参”和括号可以省略；否则应给出相应的实参，并把参数放在括号中。“实参”是传送给Sub过程的变量或者常量。上一节的实例7.1中，实参便是常量。

2.把过程名作为一个语句来使用

在调用Sub过程时，如果省略关键字Call，就成为调用Sub过程的第二种方式。与第一中相比，它有两点不同：

❑去掉了关键字Call。

❑去掉了“实参”的括号。

例如：area 10，20。

【实例7.2】编写一个用于打印分隔线的过程。程序如下：

01 Public Static Sub setLine()

02 Dim i As Integer

03 i=i+1

04 Print"-------------第"＆i＆"行---------------"

05 End Sub

06 Private Sub cmdOK_Click()

07 setLine

08 Print"AAAAAAAAAAAAAAAAAAAAAAAAAAA"

09 setLine

10 Print"BBBBBBBBBBBBBBBBBBBBBBBBBB"

11 setLine

12 Print"CCCCCCCCCCCCCCCCCCCCCCCCCC"

13 setLine

14 Print"DDDDDDDDDDDDDDDDDDDDDDDDDD"

15 setLine

16 Print"EEEEEEEEEEEEEEEEEEEEEEEEEE"

17 End Sub

【代码说明】代码第01～05行的setLine是一个Static类型的公用过程。因为第01行的Static标识，该过程内的变量都成了静态变量，例如i就是一个静态变量，这种变量在声明的同时就被赋值为0，而且在过程调用结束后其内的值不会被丢弃，有记忆功能。例如，第一次调用该过程结束时i=1；第二次调用时因为有记忆功能，所以执行语句i=i+1之后i的值就成了2，依此类推。代码第06～17行是一个调用setLine过程的程序。

【运行效果】程序中直接使用了过程名作为一个语句调用了setLine过程。其运行结果如图7.6所示。

图　7.6　实例7.2运行结果

7.1.3　通用过程与事件过程

事件过程也是Sub过程，但它是一种特殊的Sub过程，它附加在窗体和控件上。一个控件的事件过程由实际名称、下画线和事件名组成；而窗体的事件过程由"Form"、下画线和事件名组成。控件事件过程的一般格式为：

[Public|Private ] Sub 控件名 事件名（参数表）

语句块

End Sub

窗体的事件过程的一般格式为：

[Public|Private ] Sub Form 事件名（参数表）

语句块

End Sub

可以看出，除了名字外，控件事件过程和窗体事件过程的格式基本上是一样的。在通常的情况下，都是在事件过程中调用通用过程。但由于事件过程也是Sub过程，因此也可以被其他过程调用。

【实例7.3】编程试验调用事件过程。界面设计如图7.7所示。

图　7.7　实例7.3界面

编写事件过程如下。

01 Private Sub cmdCal_Click()

02 Dim r As Single

03 Dim s As Single

04

05 r=Val(Text1.Text)

06 s=3.14*r*r

07 MsgBox"圆的面积为："＆s

08 End Sub

09

10 Private Sub txtRadius_KeyPress(KeyAscii As Integer)

11 If KeyAscii=13 Then

12 Call cmdCal_Click

13 End If

14 End Sub

【代码说明】代码第10～14行的txtRadius_KeyPress事件当向文本框txtRadius中键入字符时被触发，而且该事件在文本框每输入一个字符就被触发一次。该事件过程第11行有一个参数为KeyAscii，当在文本框内按Enter键时KeyAscii的值为13。所以，在文本框内按下Enter时就会执行If语句内的调用cmdCal_Click事件过程的语句。其效果与单击cmdCal按钮的效果相同。

通用过程可以放在标准模块中，也可以放在窗体模块中。但事件过程只能放在窗体模块中。不同模块中的过程可以互相调用。当标准模块中的过程名在整个程序中唯一时，可以直接通过过程名调用。而如果两个或多个标准模块或窗体模块中都有相同名称的过程，则在互相调用时必须在过程名前加上模块名，其一般格式为：

模块名.过程名（参数表）

当在窗体模块中建立一个公用Sub过程后，其他窗体模块需要调用该过程时必须加窗体模块名。例如，在窗体Form1的代码模块中建立了一个公用Sub过程setLine，而窗体Form2要调用该过程时应当用语句：

Call Form1. setline()

又例如，窗体Form1中建立了一个公用Sub过程area(x,y)，用来计算矩形的面积；而窗体Form2中也建立了一个公用area(x,y)过程，用来计算三角的面积。此时，如果在Form1窗体模块中，需要调用Form2的area计算三角的面积，则应当在Form1的模块中使用类似于下面的语句：

Call Form2.area(5，6)

如果，直接使用语句

Call area(5，6)

则调用的是Form1中的计算矩形的area过程。

注意　如果模块中，某个过程用Private修饰，则其他模块中的程序无法调用该过程。因此，如果要使过程可以被其他模块的程序调用，则应当用Public修饰或省略修饰。

7.2　Function过程

前面介绍了Sub过程，它不直接返回值，可以作为独立的基本语句调用。而Function过程要返回一个值，通常出现在表达式中。本节将介绍Function过程的定义和调用。

7.2.1　建立Function过程

Function过程也被称为函数，其格式如下：

[Static][Public|Private]Function过程名（[形参表]）[As类型]

[局部变量声明]

[语句块]

[过程名=表达式]

[Exit Function]

[语句块]

End Sub

Function和End Function是一个Function过程的开始和结束标志。Static、Public、Private、形参表的含义与Sub过程相同。[As类型]是Function过程返回的值的数据类型，如果省略则为Variant类型。Function过程和系统内部函数一样也要返回值，所以[过程名=表达式]在大多数情况下是不能默认的，它的作用是设置函数的返回值为表达式的值。

建立Function过程的方法和建立Sub过程的方法稍有区别。一种方式是在进入代码编辑器窗口后，在左侧显示对象名的下拉框中选择“通用”，在右侧显示过程的下拉框中选择“声明”，然后输入Function及过程名。按下Enter键后，Visual Basic系统会自动加上End Function，此时便可在开始和结束之间输入语句。Function过程也可以在窗体模块和标准模块两类模块中建立。

【实例7.4】编写一个求1～n的阶乘（n!=1×2×3…×n）的Function过程。

01 Public Function fact(x As Long) As Long

02 Dim i As Long

03 Dim t As Long

04

05 t=1

06 For i=1 To x

07 t=t*i

08 Next

09 fact=t

10 End Function

【代码说明】从第01行可以看出，该过程有一个参数x，用来接收调用语句传过来的数据，程序根据传过来的x的值计算完阶乘赋值给了变量t，最后将t赋值给了Function过程名。所以过程返回的值就是t的值。

【运行效果】这里只是一个过程的编写，没有窗体界面，读者可自行编写调用程序。也可以通过后面的实例7.5继续学习如何调用函数。

7.2.2　调用Function过程

调用Function过程的方法和调用系统内部函数(如Val、Sqr、InputBox)没什么区别，只是内部函数由系统提供，而Function过程由用户自己编写而已。

【实例7.5】编程求：1！+2！+3！+…+10！的结果。要求使用实例7-4编写的fact函数。程序如下。

01 Private Sub Form_Click()

02 Dim i As Long

03 Dim s As Long

04

05 s=0

06 For i=1 To 10

07 s=s+fact(i)

08 Next

09 Print s

10 End Sub

【代码说明】本事件过程中第07行就是调用实例7.4的fact函数的语句。在此一共调用了10次fact函数。

【实例7.6】编写一个求最大公约数的函数，并编写相应程序调用该函数。

【实现步骤】操作步骤如下。

（1）首先，新建一个工程，双击默认窗体Form1，打开其代码编辑器窗口。

（2）在代码窗口的通用声明区，输入如下代码，定义一个求最大公约数的函数maxGYS：

01 Function maxGYS(ByVal x As Integer,ByVal y As Integer) As Integer

02 Dim z As Integer

03 Do While y＜＞0

04 z=x Mod y

05 x=y

06 y=z

07 Loop

08 maxGYS=x

09 End Function

（3）编写调用函数的程序如下：

10 Private Sub Form_Click()

11 Dim a As Integer

12 Dim b As Integer

13 Dim c As Integer

14 a=27

15 b=18

16 c=maxGYS(a,b)

17 Print"最大公约数为："＆c

18 End Sub

【代码说明】代码第01～09行的maxGYS函数通过辗转相除法求最大公约数，它的两个参数x,y前面的ByVal关键字用来说明这两个参数是传值参数。传值参数的具体内容在下一节介绍。该函数Function关键字前省略了Public。代码第16行是调用maxGYS函数的语句。

【运行效果】这里只是展示一个函数创建和被调用的过程，读者可自行测试。

7.3　参数传递

参数可以说是过程和调用程序之间的桥梁，通过参数可以将调用程序的数据传递给过程，过程得到数据后，才能进行相应的处理。在Visual Basic中，参数的传递分为两种，分别为值传递和地址传递。所谓值传递是将实参的内容传递给形参；而地址传递是将实参的内存地址传递给形参。

7.3.1　值传递

值传递方式就是将实参的内容传递给形参。如果在Sub过程执行中，形参的值被改变了，此值不会影响主调程序中的实参的值。如图7.8所示，如果实参a的值为5，则将5传给形参b，b的值也为5。如果Sub过程中将b的值改变为10，a的值不会发生变化。对于这种值传递方式，a和b两个变量在内存中分别占两个存储单元，它们之间互不干涉。在Visual Basic中，传值的形参前加ByVal关键字。

图　7.8　传值

【实例7.7】编程试验值传递的效果。编写如下程序。

01 Sub aa(ByVal x As Integer)

02 x=20

03 End Sub

04

05 Private Sub Form_Click()

06 Dim x As Integer

07 x=10

08 Call aa(x)

09 Print"x=";x

10 End Sub

【运行效果】运行程序，并在窗体上单击的结果如图7.9所示。

图　7.9　实例7.7运行结果

【代码说明】从运行效果可以看出，代码第01～02行的Sub过程aa中，参数x和第05～10行Form_Click事件过程中的x没有任何关系。它们只是同名的不同变量而已，好比同名的两个人，它们之间存在的唯一关系就是其中一个人将一件东西递给了另一人，在程序中体现为实参x将其内的值10传递给了aa的形参x。

注意　过程aa的参数x只在本过程范围内有效，Form_Click事件过程中的x也只在Form_Click中有效，它们互不干涉。

7.3.2　地址传递

在调用Sub过程时用地址传递方式，则不是将实参的值传给形参，而是将实参的内存地址传给形参。因此形参也具有和实参相同的内存地址。这意味着实参与形参共用同一个内存单元，在调用Sub过程时，如果形参的值改变了，则调用程序里的实参的值也会被改变，可以参考图7.10。在Visual Basic中用ByRef表示地址传递方式。如果形参前既没写ByVal也没写ByRef，则代表省略了ByRef，即该参数使用地址传递。

图　7.10　地址传递

【实例7.8】编程试验地址传递的效果。编写如下程序。

01 Sub aa(x As Integer)

02 x=20

03 End Sub

04

05 Private Sub Form_Click()

06 Dim x As Integer

07 x=10

08 Call aa(x)

09 Print"x=";x

10 End Sub

【代码说明】本例与实例7.7对比，可以发现仅仅是改变了过程aa形参x的传递方式，将传递方式设置为了地址传递。从下面结果中可以看出，Sub过程aa的参数x和Form_Click事件过程中的x可以说是一个变量。因此，改变其中的任何一个变量的值都会影响另一个变量。

【运行效果】最后运行结果如图7.11所示。

图　7.11　实例7.8运行结果

7.4　可选参数与可变参数

Visual Basic 6.0提供了十分灵活和安全的参数传递方式，允许使用可选参数和可变参数。在调用一个过程时，可以向过程传送可选的参数或者任意数量的参数。

7.4.1　可选参数

在前面的例子中，过程的形参个数都是固定的，在调用时提供的实参个数必须和形参个数相同才可以。但是，使用Visual Basic的内部函数时会发现，在调用某函数时，可以省略其后的一些参数，例如InputBox、MsgBox等函数的参数都可以省略。这种可以省略的参数被称为可选参数。下面介绍编写带有可选参数过程的方法。

为了定义可选参数的过程，必须在形参数表中使用Optional关键字，并在过程体中通过IsMissing函数测试调用时是否传送可选参数。例如：

01 Function Add(a As Integer,b As Integer,Optional c As Integer)

02 If Not IsMissing(c) Then

03 Add=a+b+c

04 Else

05 Add=a+b

06 End If

07 End Function

上述函数有3个参数，最后一个参数前面加上了"Optional"，表明该参数是一个可选参数。在执行过程时，首先判断第3个参数是否存在。如果存在，则将3个参数的值相加起来，否则只相加前两个参数的值。在调用该函数时，可以提供两个参数，也可以提供3个参数，都能得到正确的结果。例如，如果用下面的事件过程调用：

01 Private Sub Form_Click()

02 Print Add(1,2)

03 End Sub

则结果为3。而如果用下面的过程调用：

04 Private Sub Form_Click()

05 Print Add(1,2，3)

06 End Sub

则结果为6。

在过程定义时也可以直接给可选参数设置默认值。例如，以下函数的参数c是可选参数，并设置其默认值为10。

01 Function Add2(a As Integer,b As Integer,Optional c As Integer=10)

02 Add2=a+b+c

03 End Function

下面的两条语句调用该函数都是正确的：

Print Add2(1,2,5)

结果为8。

Print Add2(1,2)

结果为13。

7.4.2　可变参数

一般来说，调用过程中的实参个数应等于过程定义时的形参个数。若用ParamArray关键字指明，过程将接受任意个数的参数，其通用格式为：

Sub|Function 过程名（ParamArray 数组名）

其中，数组名只有名字和括号，不能写上下界。于是可以用下面的方法编写求总和的Add3函数。

01 Function Add3(ParamArray intNums())

02 Dim x As Variant

03 Dim s As Integer

04 For Each x In intNums

05 s=s+x

06 Next x

07 Add3=s

08 End Function

注意　ParamArray关键字后面的intNums()是一个数组。

下面的语句调用该函数都是正确的：

Print Add3(1,2,5,2,1,7)

结果为18。

Print Add3(1,5,5)

结果为11。

Print Add3

结果为0。

7.5　对象参数

Visual Basic中不仅可以向过程传递普通数据类型的数据，还可以向过程传递对象，在形参表中，把形参变量的类型声明为"Control"，则可以向过程传递控件；若声明为"Form"，则可向过程传递窗体。对象的传递只能按地址传递。

7.5.1　窗体参数

有时需要将工程内的多个窗体设置为统一的属性，这时用形参作为窗体参数的Sub过程非常合适。下面用一个实例介绍窗体参数的用法。

【实例7.9】编写一个能够设置窗体属性的Sub过程。

【实现步骤】操作步骤如下。

（1）首先，在工程中添加5个窗体，加上默认窗体共有Form1、Form2、…、Form6六个窗体。

（2）再添加一个标准模块Module1。

（3）打开Module1的代码窗口，在其内编写如下两个Sub过程：

01 Sub setForm(x As Form)

02 x.Left=1000

03 x.Top=2000

04 x.Width=6000

05 x.Height=4000

06 x.BackColor=＆H80000010

07 End Sub

08 Sub main()

09 '设置窗体的统一属性

10 setForm Form1

11 setForm Form2

12 setForm Form3

13 setForm Form4

14 setForm Form5

15 setForm Form6

16 '显示3个窗体

17 Form1.Show

18 Form2.Show

19 Form3.Show

20 Form4.Show

21 Form5.Show

22 Form6.Show

23 End Sub

【代码说明】代码第01～07行的setForm过程的形参类型是窗体，该过程将设置传递过来的窗体的出现位置（Left、Top）、窗体的大小（Width、Height）和背景颜色。

说明　常量＆H80000010是代表颜色的十六进制长整型数。

代码第08～23行的Sub main是Visual Basic中特殊的过程，可以将其设置为启动对象。该过程中的前6条语句用来调用上面的setForm过程，分别设置Form1、Form2、…、Form6的属性。后6条用来显示6个窗体。

试想一下，如果不用setForm过程设置6个窗体的属性，则Sub main中的代码就不可能这么短了，例如代码会是：

01 Sub main()

02 Form1.Left=1000

03 Form1.Top=2000

04 Form1.Width=6000

05 Form1.Height=4000

06 Form1.BackColor=＆H80000010

07 …

08 …

09 …

10 Form6.Left=1000

11 Form6.Top=2000

12 Form6.Width=6000

13 Form6.Height=4000

14 Form6.BackColor=＆H80000010

15 …

16 End Sub

这对阅读程序和修改程序都会是一件很麻烦的事情。例如，如果要将所有窗体的Width属性改变为8000，使用这段代码，就需修改6条语句。而如果使用setForm过程则只修改1条语句即可。

（4）将Sub main设置为启动对象。方法为：选择“工程”|“工程1属性”菜单项，在“工程属性”对话框内设置“启动对象”为Sub main。

【运行效果】运行程序，会出现位置、大小、背景颜色一样的6个窗体，这里不再给出。

7.5.2　控件参数

像窗体一样在编程时，经常会将很多控件的属性也设置为相同值。所以，控件作为形参类型的过程也会被经常用到。向过程传递控件时不论是什么控件，形参变量的类型都用关键字"Control"声明。

【实例7.10】编写一个能够设置文本框属性的Sub过程。

【实现步骤】操作步骤如下。

（1）在窗体Form1上绘制6个文本框，用其默认名称Text1、Text2…Text6。

（2）在Form1的代码窗口中编写如下程序：

01 Sub setTextBox(x As Control)

02 x.Text=""

03 x.FontSize=16

04 x.FontName="隶书"

05 x.FontBold=True

06 x.FontItalic=True

07 x.Alignment=1

08 x.BackColor=＆HC0FFC0

09 End Sub

10

11 Private Sub Form_Load()

12 setTextBox Text1

13 setTextBox Text2

14 setTextBox Text3

15 setTextBox Text4

16 setTextBox Text5

17 setTextBox Text6

18 End Sub

【代码说明】代码第01～09行的setTextBox过程用来设置文本框控件的属性，其形参类型为Control。第11～18行的Form_Load事件过程中的6条语句在调用setTextBox过程。

【运行效果】运行结果如图7.12所示。

图　7.12　实例7.10运行结果

控件参数的使用比窗体参数要复杂一些，因为不同的控件所具有的属性也不一样。在用指定的控件调用通用过程时，如果通用过程中的属性不属于这类控件，则会发生错误。例如语句：

setTextBox Command1

就会发生错误，因为Command1按钮没有Text、Alignment等属性。所以，在用控件作为参数时，必须考虑到作为实参的控件是否具有通用过程中所列的控件的属性。为此，Visual Basic提供了TypeOf语句，其用法为：

TypeOf 控件名 IS 控件类型

其中，“控件类型”是代表不同控件的关键字，如果不清楚某类控件的关键字是什么，则可以在属性窗口中查到，例如想知道按钮控件的关键字，可以在窗体上绘制一个按钮，并选中它，然后查看属性对话框的对象列表，在按钮名称后面的字符串就是控件类型关键字，如图7.13所示。

图　7.13　属性对话框

TypeOf表达式有True和False两种结果。例如表达式：

TypeOf Command1 IS CommandButton

的结果为True。而表达式：

TypeOf Command1 IS TextBox

的结果为False。通常将TypeOf表达式放在If语句后充当条件表达式，通过这种方法可以判断传递过来的具体是什么控件，然后根据具体的控件执行不同的属性设置语句。

7.6　小结

本章首先介绍了Sub过程和Function过程，其中为了区别，我们又把Function过程所编写的内容称为函数，可以用来完成一个运算。两个过程都是用来被调用的，可以在任何窗体事件或控件事件中调用这些过程，实现一次编写，多次调用的程序设计思路。为了方便数据的传输，过程中会用到不同类型的参数，如可选参数、对象参数等。希望读者通过本章的学习能掌握VB语句最基本的构成形式——过程。

7.7　本章练习

1.参数的传递分为哪两种？

2.如果要在VB中定义可选参数，必须使用什么关键字？

A.Enum

B.Optional

C.Ref

D.Param

3.说明下列代码的功能，并给出执行结果。

Private Function Sum(n As Integer,m As Integer) As Integer

If m=1 Then

Exit Function

Else

Sum=n+Sum(n,m-1)

End If

End Function

Private Sub Form_Load()

Debug.Print Sum(3,5)

End Sub

第8章　键盘、鼠标事件和图形

Visual Basic为用户提供了键盘、鼠标事件用于响应键盘操作和鼠标操作。在Visual Basic中，大多数控件都有键盘事件和鼠标事件。这一章将讲述与键盘和鼠标相关的内容，除此之外，还有通常在鼠标事件中出现的关于绘制图形方面的内容。本章包括以下知识点。

❑键盘事件以及键盘中的按键对应的ASCII码。

❑鼠标事件和对应鼠标各个按键的处理事件。

❑VB的绘图方法有PSet、Line和Circle等，可以绘制各种图形。

❑VB中的基本图形和图像控件。

通过本章的学习，了解鼠标和键盘在VB开发中的各种应用技巧。

8.1　键盘事件

键盘事件共有3种事件，分别为KeyPress（敲击键盘）事件、KeyDown（按键被按下）事件和KeyUp（按键被松开）事件。KeyPress事件在敲击键盘时触发；KeyDown事件在键盘上的按键被按下时触发；KeyUp事件则是当键盘上按下的键被松开时触发。

8.1.1　ASCII码

ASCII是美国标准信息交换码（American Standard Code for Information Interchange）的缩写。码值从0到255，代表了字母、数字、标点符号和其他字符。ASCII码是一种标准化的编码，用在计算机之间或计算机与外围设备之间交换信息。

每一个ANSI键都有ASCII码相对应，而且大写字母和小写字母分别对应不同的ASCII码。例如，小写字母"a"的ASCII码是97，而大写字母"A"的是65。具体对应为小写字母"a"～"z"的ASCII码是97～122，大写字母"A"～"Z"是65～90，数字字符“0”～“9”是48～57，Enter键是13，空格键是32，ESC键是27等。

键盘上有一部分键是没有相对应的ASCII码。例如，常见的107键键盘的F1～F12功能键、上下左右方向键等都没有ASCII相对应。为了区分这些没有ASCII对应的键，人们创造了键码。例如，上下左右方向键的码值分别为38、40、37和39。

8.1.2　处理“敲击键盘”

当用户敲击有ASCII相对应的键时，KeyPress事件就会被触发。Visual Basic中能够得到焦点的控件都能够响应该事件。例如，窗体、图片框、文本框、按钮等，下面通过文本框的KeyPress事件来说明该事件。首先，在窗体上画出一个文本框Text1，然后双击打开其代码编辑器窗口。在过程框中选择"KeyPress"后，代码编辑区内就会出现该事件的过程头和过程尾，例如：

Private Sub Text1_KeyPress(KeyAscii As Integer)

End Sub

其中，该事件的参数KeyAscii用来接收被敲击键的Ascii码。在编程时可以通过该参数判断用户敲击的是哪个键。例如，在上面的事件过程中写入如下语句就可以查看具体某个按键的Ascii码。

Print KeyAscii

KeyPress事件较典型的应用是控制文本框中的字符输入。编程人员有时希望界面中的文本框只让用户输入数字字符，例如，需要输入金额的文本框。因此为了避免用户输入数字以外的其他字符通常会对KeyPress事件编写代码。

【实例8.1】编写一个控制文本框中只能输入数字字符的程序。

界面非常简单，在一个窗体上画一个文本框。窗体的名称和文本框的名称均取默认值Form1和Text1。在属性窗口中将Text1文本框的Text属性栏的内容设置为空。然后编写事件过程如下。

01 Private Sub Text1_KeyPress(KeyAscii As Integer)

02 If KeyAscii＜48 Or KeyAscii＞57 Then

03 MsgBox"您输入了数字以外的字符！"

04 KeyAscii=0

05 End If

06 End Sub

【代码说明】代码第02行通过ASCII码判断是不是敲击了数字键，判断条件是根据数字键（0～9）的ASCII码是48～57这个原理来写的，如果被敲击键的ASCII码小于48或者大于57，那肯定不是数字键。因此，执行其内部语句前先弹出一个消息框提示“您输入了数字以外的字符”，然后消除该字符。语句

KeyAscii=0

的作用是避免输入的字符在文本框中回显。第01～06行的KeyPress事件的另外一个典型的应用是实现在文本框中回车来替代按键盘上的Tab键，这样在一个文本框较多的输入界面中是很有效的。

【运行效果】这里只是一个事件，下面的实例演示具体的效果。

【实例8.2】编写一个程序，用在文本框中回车来模仿按键盘上的Tab键。设计界面如图8.1所示。窗体中有8个文本框，这些文本框是文本框控件数组的8个元素，它们的名称为txtInfo。

图　8.1　实例8.2界面

首先，要设置好8个文本框的TabIndex值，将左上角文本框的TabIndex设定为0，然后按照从左到右，从上到下的顺序依次设定TabIndex值为1～7。其次，打开代码编辑器编写程序如下。

01 Private Sub txtInfo_KeyPress(Index As Integer,KeyAscii As Integer)

02 If KeyAscii=13 Then

03 SendKeys"{Tab}"

04 End If

05 End Sub

【代码说明】代码第03行的SendKeys是Visual Basic中的一个语句，用来模仿敲击键盘的动作。例如，如果将上面的SendKeys语句写为：

SendKeys"abc"

那么当用户敲击回车键时，文本框内就会出现字符串"abc"。如果一个窗体内有控件，控件会先于窗体接收到键盘事件，要想让窗体先得到键盘事件可以设置窗体的KeyPreview属性为True。

【运行效果】运行程序后，每当在文本框中敲击回车键时光标都会移动到下一个文本框中。

8.1.3　处理“按键被按下”和“按键被松开”

KeyDown事件在键盘上的某个键被按下的一刹那触发，而KeyUp事件是在松开（抬起）按键时被触发。其实，这两个事件可以说是KeyPress（敲击）动作的分解，因为KeyPress包括按下键和松开键两个动作。像KeyPress事件一样，Visual Basic中的大部分控件都能接收这两个事件。例如下面的程序：

01 Private Sub Form_KeyDown(KeyCode As Integer,Shift As Integer)

02 Form1.BackColor=RGB(255,0,0)

03 End Sub

04

05 Private Sub Form_KeyUp(KeyCode As Integer,Shift As Integer)

06 Form1.BackColor=RGB(0,0,255)

07 End Sub

运行程序后，当按下某个键时会触发KeyDown事件，随之执行Form_KeyDown事件过程中的代码将窗体的背景颜色设置为红色，当松开键时会触发KeyUp事件将窗体的背景颜色改变为蓝色。

KeyDown和KeyUp两个事件都有KeyCode、Shift两个参数。KeyCode参数和KeyPress事件的KeyAscii参数有所不同。KeyAscii参数用来获取按键的Ascii码，小写a和大写A的ASCII码因为不同，所以在键盘上敲击小写a和大写A时返回的KeyAscii参数值也不同。而KeyCode参数用来获取按键的码值，键盘上所有的键都有一个且仅有一个不同的码值，就是说小写字母和大写字母因为都是键盘上的同一个键所以它们的码值是一样的，都是大写字母的ASCII码。例如，小写a和大写A的码值都是65。

记忆每一个按键的KeyCode，对于编程人员是不现实的，因此Visual Basic给每一个按键都提供了一个便于记忆的常量KeyCode，例如A键的常量KeyCode为vbKeyA、向上方向键的常量KeyCode为vbKeyUp、F1键的是vbKeyF1等。

第二个参数Shift的作用是获取键盘上的Shift、Ctrl和Alt 3个键的状态。Shift参数共有8种可能的取值，分别对应按下这3个键中的某一个键、同时按下某两个键和同时按下某三个键的状态。Shift参数的取值和意义见表8.1。

从表8.1中可以看出一定的规律，比如代表同时按下Shift、Ctrl键的十进制数3是从代表Shift键的1加上代表Ctrl键的2得来的，其他组合键的数值也是通过上述方法相加而得到，所以读者只要记住单键值就可以了。

【实例8.3】编写一个试验Shift参数的程序。程序只有一个对象Form1窗体，编写事件代码如下：

01 Private Sub Form_KeyDown(KeyCode As Integer,Shift As Integer)

02 Select Case Shift

03 Case 1

04 Print"按下了Shift键"

05 Case 2

06 Print"你按下了Ctrl键"

07 Case 3

08 Print"按下了Alt键"

09 Case 4

10 Print"按下了Shift和Ctrl键"

11 Case 5

12 Print"按下了Shift和Alt键"

13 Case 6

14 Print"按下了Ctrl和Alt键"

15 Case 7

16 Print"按下了Shift、Ctrl和Alt键"

17 End Select

18 End Sub

【代码说明】当在键盘上按下Shift键时，就会触发窗体的KeyDown事件，同时该事件的Shift参数的值会为1，根据Shift参数的值，第02～17行的Select Case选择语句将执行Case 1下的Print语句在窗体上打印字符串“按下了Shift键”。而如果同时按下了Shift和Ctrl键，则KeyDown事件的Shift参数值会为3，因此会执行Case 3下的语句，其他情况可以用上面的方法分析。

【运行效果】运行该程序后可以在键盘上按Shift、Ctrl、Alt 3个键做试验，运行结果如图8.2所示。

图　8.2　实例8.3运行结果

8.1.4　应用案例——使用方向键移动坦克

【实例8.4】如图8.3所示，在窗体中画一个图片框将其名称设置为picGrand，在图片框内部再画一个图像框并起名叫imgTank。

图　8.3　实例8.4界面

【运行效果】

❑用上、下、左、右4个方向键控制坦克向4个方向移动。

❑按下方向键时移动，而松开键时坦克会停止。

❑坦克的炮筒方向要保持和坦克移动的方向一致。

【实现步骤】

（1）准备好坦克4个方向（炮筒方向）的图片，并分别起名为tankup、tankdown、tankleft和tankright。

（2）新建工程并保存工程，在工程文件所在的文件夹里新建一个文件夹pic。例如，工程文件保存在c:\vbtest\下，那么文件夹pic也在c:\vbtest\下建立。

（3）将刚才准备的4个图片文件放到文件夹pic中。

（4）设置图像框imgTank的Picture属性，将坦克图片添加到图像框中。

（5）为了掩饰坦克图片的白色背景，将图片框picGrand的BackColor属性也设置为白色。

（6）编写如下事件过程：

01 Private Sub picGrand_KeyDown(KeyCode As Integer,Shift As Integer)

02 Select Case KeyCode

03 Case vbKeyLeft

04 imgTank.Picture=LoadPicture(App.Path＆"\pic\tankleft.jpg")

05 imgTank.Left=imgTank.Left-20

06

07 Case vbKeyRight

08 imgTank.Picture=LoadPicture(App.Path＆"\pic\tankright.jpg")

09 imgTank.Left=imgTank.Left+20

10

11 Case vbKeyUp

12 imgTank.Picture=LoadPicture(App.Path＆"\pic\tankup.jpg")

13 imgTank.Top=imgTank.Top-20

14

15 Case vbKeyDown

16 imgTank.Picture=LoadPicture(App.Path＆"\pic\tankdown.jpg")

17 imgTank.Top=imgTank.Top+20

18 End Select

19 End Sub

【代码说明】运行程序后，假如按下向右键时，图片框picGrand的KeyDown事件（代码第01～19行）会被触发，从而运行其内的代码，这时KeyCode为向右键的码值，所以执行第07～09行的语句，首先将imgTank图像框中的图片改为炮筒向右的坦克图片，然后通过增大图像框的Left属性值起到向右移动坦克的效果。如果一直按着键不松开，KeyDown事件就会不断被触发，坦克也会一直移动，然而如果松开按键KeyDown事件就不会被触发了，其内的代码也不会被执行，坦克也就不再移动了。按下其他方向键时的情况可以参考上面的方法分析。

8.2　鼠标事件

鼠标事件一共有5个（不包括拖放事件），分别是Click、DblClick、MouseDown、MouseUp和MouseMove。前面的章节中已经讲述过关于Click、DblClick事件的内容。本节主要讲述后面3个事件。

Click、DblClick事件只能识别鼠标单击或双击动作，而通过MouseDown、MouseUp、MouseMove事件可以使应用程序对鼠标位置及状态的变化作出响应。Viusal Basic中窗体和大多数控件都能够识别这些事件。

8.2.1　处理“鼠标键被按下”

按下鼠标键的一刹那会触发MouseDown事件，其中鼠标键包括鼠标左键、中键（滑轮）和右键。下面通过一个例子说明该事件。

【实例8.5】编写一个MouseDown事件的实验程序，当鼠标键被按下的一刹那，将鼠标箭头的当前坐标位置打印到窗体上。本实验只有一个窗体对象Form1，所有属性均取默认值。

【实现步骤】操作步骤如下。

（1）双击窗体Form1，打开代码编辑器窗口。

（2）从过程框内选择MouseDown，编辑区中会出现Form_MouseDown事件过程的头和尾。在其内加入代码后为：

01 Private Sub Form_MouseDown(Button As Integer,Shift As Integer,X As Single,Y As Single)

02 Print"X坐标：";X,"Y坐标：";Y

03 End Sub

（3）运行该程序，当在窗体上按下鼠标任意键时都会在其上显示鼠标当前位置，如图8.4所示。

图　8.4　实例8.5运行结果

【代码说明】从代码第01行可以注意到MouseDown事件有Button、Shift、X、Y四个参数。各参数的解释如下。

❑Button参数：当按下鼠标左键时该参数值为1；当按下右键时其值为2；按下中键时其值为4。编程时，通常使用该参数判断用户按下的是哪个鼠标键。

❑Shift参数：该参数的意义和前面讲过的键盘KeyDown、KeyUp事件的Shift参数一样。

❑X、Y参数：这两个参数里放置的是鼠标当前的x、y坐标。

8.2.2　处理“鼠标键被松开”

MouseUp事件在松开鼠标键的一刹那被触发。MouseUp事件也有Button、Shift、X、Y四个参数，其解释意义和MouseDown事件的相同。通常，编程人员都将菜单弹出语句写在该事件里，详细内容在本书第9章讲述。在此可以做个小试验，在Windows的桌面上按下鼠标右键不放，这时不会有弹出式菜单出现，而当松开鼠标右键时弹出式菜单就出现了，这就说明程序写在了MouseUp事件里，而并没有写在MouseDown事件内。其原因是如果菜单弹出程序写在了MouseDown事件内，有时会屏蔽掉正常的鼠标选择功能。

【实例8.6】编写一个MouseUp事件的实验程序，当松开鼠标键时能够判断按下松开的是哪个鼠标键。本例只有一个对象窗体Form1，其属性均取默认值。编写事件过程如下。

01 Private Sub Form_MouseUp(Button As Integer,Shift As Integer,X As Single,Y As Single)

02 If Button=1 Then

03 Print"您松开了鼠标左键！"

04 ElseIf Button=2 Then

05 Print"您松开了鼠标右键！"

06 ElseIf Button=4 Then

07 Print"您松开了鼠标中键！"

08 End If

09

10 End Sub

【代码说明】代码第01行的参数在上一个案例已经介绍过，这些数值针对的依然是左键、右键和中键，不同的是这是鼠标松开的事件，所以输出语句都是“您松开了......”。

【运行效果】运行程序后，可以在窗体上使用鼠标左键、右键和中键进行测试。运行结果可以参考图8.5。

图　8.5　实例8.6运行结果参考

8.2.3　处理“鼠标移动”

MouseMove事件在鼠标移动时被触发。其格式为：

Form_MouseMove(Button As Integer,Shift As Integer,X As Single,Y As Single)

MouseMove事件的4个参数的意义与MouseDown、MouseUp两个事件的参数相同，在此不在重复。下面通过一个例子说明该事件。

【实例8.7】编写一个MouseMove事件的实验程序。如图8.6所示，在窗体Form1上放置一个标签Label1。运行效果为标签Label1跟随着鼠标箭头移动，并且标签内显示鼠标箭头的当前坐标。

图　8.6　实例8.7界面设计

编写程序如下：

01 Private Sub Form_MouseMove(Button As Integer,Shift As Integer,X As Single,Y As Single)

02 Label1.Caption="X:"＆Str(X)＆"Y:"＆Str(Y)

03 Label1.Move X,Y

04 End Sub

【代码说明】本实例中第03行使用了标签的Move方法来移动标签，该方法功能和语法与前面讲过的其他控件的Move方法相同。

【运行效果】运行程序后效果如图8.7所示，标签会跟随着鼠标在窗体上移动。

图　8.7　实例8.7运行结果

8.3　拖放

Visual Basic中除了如前所述的鼠标事件可以完成很多功能以外，实际上还有一种操作方法可以用来完成某些功能，即拖放操作——使用鼠标器将对象从一个地方拖放到另一个位置再放下。在整个“拖放”操作过程中，用户首先在源对象上按下鼠标左键不放，然后把源对象拖放到目标对象上释放（松开）鼠标键。为了有助于理解这种拖放，可以把整个拖放过程分解成两个操作：一个是发生在源对象的“拖”操作；另一个是发生在目标对象上的“放”操作，即把源对象“放”在目标对象上。在字处理程序中，拖放操作的用途之一就是将文本从一个地方移到另一个地方。另一个用途就是将不需要的东西拖到“回收站”里，从而在屏幕上将其清除。

8.3.1　拖放操作实例——垃圾桶的实现

通过两个事件过程和少量属性设置就可以控制拖放操作。将某对象的DragMode属性设置为1，用户就可以拖动该对象了。使用DragIcon属性可以指定对象拖动过程中鼠标指针的形状。当一个源对象被放在目标对象上时，就会触发目标对象的DragDrop事件。当某对象被拖动并经过窗体中的其他对象时，就会触发被经过对象的DragOver事件。下面通过一个实例说明在程序中使用拖放操作的用法。

【实例8.8】如图8.8所示，按照界面，设计一个实现垃圾桶的程序。实现的效果如下。

图　8.8　实例8.8设计界面

❑可以将窗体中的图像框img1、img2、img3用鼠标拖曳到垃圾桶内删除。

❑双击垃圾桶可以还原所有删除的图标。

程序中各对象的属性设置见表8.2。

上面对前三个图像框的属性设置时一定要将其DragMode属性设置为1，这样才能拖曳这三个图像框。而最后一个图像框（垃圾桶），因为用不着拖拽所以取其DragMode属性的默认值0即可。下面编写实现第一个效果的程序如下。

01 Private Sub imgUn_DragDrop(Source As Control,X As Single,Y As Single)

02 If Source=img1 Then

03 img1.Visible=False

04 ElseIf Source=img2 Then

05 img2.Visible=False

06 ElseIf Source=img3 Then

07 img3.Visible=False

08 End If

09 imgUn.Picture=LoadPicture(App.Path＆"\pic\Recycle2.ico")

10 End Sub

【代码说明】程序被写在了垃圾桶（图像框imgUn）的DragDrop事件内。这样以来，其他被拖曳的源控件放到垃圾箱上的时候就会触发垃圾桶的DragDrop事件，随即执行DragDrop事件内的程序，本例使用了将图像框的Visible属性设置为False的方法模拟了删除功能。程序中第09行的作用是将空垃圾桶的图片改变为里面有垃圾的垃圾桶的图片。

DragDrop事件有3个参数，其解释借用本例的内容说明如下。

Source参数：这是一个控件类型的变量，它可以存储各种类型的控件。在垃圾箱上放置图像框img1时，其值为该图像框img1；如果放置img2，其值便是img2。即Source参数的值为被拖动的控件。了解了这点以后，上面的程序可以改写如下。

11 Private Sub imgUn_DragDrop(Source As Control,X As Single,Y As Single)

12 Source.Visible=False

13 imgUn.Picture=LoadPicture(App.Path＆"\pic\Recycle2.ico")

14 End Sub

【代码说明】X、Y参数：是一个指定当前鼠标指针在目标窗体或控件中水平和垂直位置的坐标。其（0，0）点为目标窗体或控件的左上角，在本例中是垃圾箱图像框的左上角。

接下来，编写实现第二个效果的程序如下：

15 Private Sub imgUn_DblClick()

16 img1.Visible=True

17 img2.Visible=True

18 img3.Visible=True

19 imgUn.Picture=LoadPicture(App.Path＆"\pic\Recycle1.ico")

20 End Sub

【代码说明】代码第19行用来加载图片，代码第16～18行是显示图片控件，上述代码主要是实现双击垃圾桶后的还原。

编写了上面两个事件过程后，程序基本上满足了题目的要求，能够拖曳图标并且将其删除，双击垃圾桶也能还原了。可还是感觉有些不对劲，比如在拖曳第一个图标时，它会在原来的位置不变，只是鼠标图标（DragIcon设置的图标）在移动，如图8.9所示。而只有当将其放入垃圾桶时才会从屏幕上消失。

图　8.9　实例8.8运行效果1

为了实现Windows中拖曳图标时，图标能够从原位置移动的效果，编写事件过程如下。

21 Private Sub Form_DragOver(Source As Control,X As Single,Y As Single,State As Integer)

22 Source.Visible=False

23 End Sub

【代码说明】Form_DragOver事件在图标被拖曳经过窗体上的时候被触发，因为图标本身就在窗体中所以拖曳的一刹那就会触发窗体的DragOver事件从而使图标不可见，而只能见到鼠标箭头图标在移动。

虽然这么做实现了移动得效果，但是又出现了一个问题——结束拖放操作后，即使不放到垃圾桶内图标照样会消失。下面的程序弥补了这一缺点。

24 Private Sub Form_DragDrop(Source As Control,X As Single,Y As Single)

25 Source.Visible=True

26 End Sub

【代码说明】上述代码原理是在窗体上结束拖放操作时触发窗体的DragDrop事件，随之执行其内的语句，将被拖曳对象的Visible属性设置为True，使其可见。

【运行效果】程序最终运行效果如图8.10（图为删除了第一个图标，正准备删除第二个图标的运行界面）所示。

图　8.10　实例8.8运行效果2

8.3.2　OLE拖放操作

OLE拖放是Visual Basic应用程序中添加的最强大、最有用的功能之一，利用它可以在控件和控件之间、在控件和其他Windows应用程序之间拖动文本和图形。Visual Basic中的大部分控件都支持OLE拖放，但默认状态下控件的OLEDropMode属性被设置为0，也就是不允许拖放。如果要使用OLE拖放功能，必须先把OLEDropMode属性设置为1，这样控件才成为接受文件拖放的一个OLE容器。OLEDragMode属性决定了控件是自动还是需要手工实现“拖”操作。为了方便操作可以将其设置为1，让其自动实现“拖”操作。

【实例8.9】如图8.11所示，编写程序实现如下功能：从文本框Text1中可以直接将选中的文本拖放到列表框List1中。

图　8.11　实例8.9运行结果

【实现步骤】操作步骤如下。

（1）选中文本框，将其OLEDragMode属性设置为1，这将允许用户可以拖拽文本框里面的文本。

（2）选中列表框将其OLEDropMode属性设置为1，这会允许用户可以将拖拽过来的数据放到列表框中。

（3）当将文本框的数据放到列表框中的时候会触发列表框的OLEDragDrop事件。因此，编写事件过程如下：

01 Private Sub List1_OLEDragDrop(Data As DataObject,Effect As Long,Button As Integer,Shift As Integer,X

02 As Single,Y As Single)

03 List1.AddItem Data.GetData(vbCFText)

04 End Sub

（4）实际上用上面的3步就可以看到本例的运行结果了，但是，上面使用了Visual Basic中的一些默认设置，这样在编写大型应用程序时可能会埋下隐患，因此，标准的方法应当为在文本框Text1的OLEStartDrag事件中写入一条语句。

05 Private Sub Text1_OLEStartDrag(Data As DataObject,AllowedEffects As Long)

06 Data.SetData Text1.SelText,vbCFText

07 End Sub

【代码说明】代码第05～07行的OLEStartDrag事件在拖曳文本框中文本的一刹那会被触发。从上面的程序中看到不管是OLEDragDrop事件还是OLEStartDrag事件，都有一个Data参数，这个参数是传送数据和接收数据的关键，该参数用SetData方法设置其内的值，用GetData方法获取其内的值。第03行和第06行中的vbCFText是一个常量，它代表设置/获取的是文本数据。使用Data参数不仅可以传送文本数据还可以传送带格式的文本数据和图形数据。OLEDragDrop事件中的其他4个参数与前面讲过的鼠标事件中的同类参数的用法和意义相同，可以通过Shift参数决定所作的操作是移动（剪切）还是复制。

【运行效果】运行效果与图8.11一致。

8.4　图形

Visual Basic给用户提供了一些图形控件，用户可以利用这些图形控件让自己的窗体界面更加出彩。同时，Visual Basic也给用户提供了一些绘制图形的语句，例如Pset、Line、Circle等，利用这些语句用户甚至可以编写与Windows自带的与画图软件相仿的画图程序。

8.4.1　直线控件

直线（Line）用来在窗体、框架或图片框中创建简单的线段。通过属性设置可控制直线的位置、长度、颜色和样式来定义应用程序的外观。直线的常用属性如下。

（1）BorderColor属性：设置直线的颜色。

（2）BorderStyle属性：返回/设置对象的边框样式。取值如下：

❑0(Transparent)：透明，即看不见。

❑1(Solid)：实线，默认设置。

❑2(Dash)：虚线。

❑3(Dot)：点线。

❑4(Dash-Dot)：点画线。

❑5(Dash-Dot-Dot)：双点画线。

❑6(Inside Solid)：内实线。

（3）BorderWidth属性：返回/设置控件的边框宽度。

（4）X1，Y1属性：返回/设置直线控件的起始点。

（5）X2，Y2属性：返回/设置直线控件的终止点。

直线控件没有事件，只有两个方法但是很少使用。

8.4.2　形状控件

形状（Shape）控件可以在窗体、框架或图片框中创建矩形、正方形、椭圆形、圆形、圆角矩形或圆角正方形。可以设置形状的样式、颜色、填充样式、边框颜色和边框样式等。

除了BorderColor、BorderStyle和BorderWidth属性与直线对应的属性相同外，形状还有如下的一些常用属性。

（1）FillColor属性：返回/设置填充形状、圆环和方框所使用的颜色。

（2）FillStyle属性：返回/设置一个形状控件的填充样式。其取值及填充样式如图8.12所示。

图　8.12　FillStyle属性取值及填充样式

（3）Shape属性：用来设置形状控件的形状。其取值及边框形状如下。

❑0：矩形，默认设置。

❑1：正方形。

❑2：椭圆。

❑3：圆。

❑4：圆角矩形。

❑5：圆角正方形。

如图8.13所示。

图　8.13　Shape属性取值及边框形状

形状没有事件，只有3个很少使用的方法。

8.4.3　应用案例——绘制圆柱体

【实例8.10】使用形状控件、直线控件和标签绘制如图8.14所示的图案。

图　8.14　实例8.10运行结果

【实现步骤】

（1）添加一个窗体，在其中先放置一个形状控件，将其Shape属性设置为2-Oval，此时该控件会变成椭圆的形状，调整大小将其变为圆柱体的底面，选中该形状并复制，将粘贴后得到的第二个椭圆移动到适当的位置，将其变为圆柱体的顶面。

（2）在窗体中添加一个直线控件，将其大小、位置调整好，使其成为圆柱体的垂直边（左边），选中复制并粘贴得到第二条直线和第三条直线将其位置调整，使其成为另外一边和中间位置的高。再加入一条直线将其调整为圆的半径。

（3）选中底面的形状，将其BorderStyle属性设置为6；选中中间的直线将其BorderStyle属性设置为3。最后加入3个标签按图中的样式设置即可，为了不让标签的背景挡住直线或形状，可以将标签的BackStyle属性设置为1。

8.4.4　用PSet方法编写画刷程序

PSet方法可以在窗体或图片框指定的位置，用给定的色彩画一个“点”。点的大小由对象的DrawWidth属性指定。PSet方法的使用格式如下：

[窗体]或图片框.PSet[Step](x,y)[,color]

其中，(x，y)是画点的坐标。color用来指定绘制点的颜色，数据类型为Long。默认时，系统用画布对象的ForeColor属性值作为绘制点的颜色。color参数可以用RGB()函数或者颜色常量指定。Step关键字是下一个画点位置相对于当前坐标位置的偏移量的标记，即步长(水平、垂直两个方向，可正可负)。(x,y)坐标值是相对于当前位置的偏移量。

说明　对象的当前位置用CurrentX和CurrentY两个属性获得。

【实例8.11】编写程序，可以使用鼠标在窗体上随意绘制图形，类似于画图软件中的刷子。

本实例只有一个对象为窗体Form1。编写事件过程如下。

01 Dim flag As Boolean '用于标识是否按下鼠标键，窗体级变量

02 Private Sub Form_Load()

03 flag=False

04 Me.DrawWidth=2 '设置绘制点的大小(宽度)

05 End Sub

06 Private Sub Form_MouseDown(Button As Integer,Shift As Integer,X As Single,Y As Single)

07 flag=True

08 Me.PSet (X,Y),vbRed

09 End Sub

10 Private Sub Form_MouseMove(Button As Integer,Shift As Integer,X As Single,Y As Single)

11 If flag Then Me.PSet (X,Y),vbRed

12 End Sub

13 Private Sub Form_MouseUp(Button As Integer,Shift As Integer,X As Single,Y As Single)

14 flag=False

15 End Sub

【代码说明】第08行和第11行的PSet用来在窗体上画线，因为是用鼠标来画，所以将这个方法添加到MouseDown和MouseMove事件中，而MouseUp也就是鼠标抬起时，则停止画线。

【运行效果】运行程序后，在Form1窗体上按住鼠标左键拖动时，出现连续拖过的轨迹线，当释放鼠标时，轨迹线终止。图8.15就是用鼠标绘制的图案。

图　8.15　实例8.11运行结果

本例中使用了Visual Basic的颜色常量vbRed，关于颜色常量见表8.3。

8.4.5　用Line方法绘制直线

Line方法用于在窗体或图片框对象上画直线和矩形。绘制直线时，应给出起点和终点坐标。

用Line方法绘制直线的格式如下。

[窗体]或图片框.Line [[Step](x1,y1)]-[Step](x2,y2)[,color]

❑Step：表示其后的坐标值使用的是相对偏移（不经常使用）。

❑(x1,y1)：是直线的起点坐标，若前面没有Step关键字，(x1,y1)是相对于原点(0,0)的偏移量；否则，表示(x1,y1)是相对于当前位置的偏移量。若省略(x1,y1)，则起点为当前坐标位置(CurrentX,CurrentY)。

❑(x2,y2)：是直线的终点坐标，若前面有Step，则表示(x2,y2)是相对于(x1,y1)的偏移量，否则(x2,y2)是相对于原点的绝对坐标值。

❑color：指定要画直线的颜色（画笔颜色），可以使用颜色常量或颜色函数。省略时用对象的ForeColor属性指定的颜色绘制直线。

画直线时，使用DrawWidth属性指定直线的宽度，使用DrawStyle属性指定边线的样式，其取值见表8.4。

【实例8.12】编写程序，用Line方法绘制"Z"图形。

本实例中有3个对象分别为窗体Form1、图片框pic1和按钮cmdPaint。将窗体Form1的Caption属性设置为“用Line画直线”；将按钮cmdPaint的Caption属性设置为“绘制”，其他属性均取默认值。要求在图片框pic1中绘制图形。编写事件过程如下。

01 Private Sub cmdPaint_Click()

02 pic1.DrawStyle=1 '设置边线的样式为虚线

03 pic1.Line (1000,200)-(5000,200)

04

05 pic1.DrawStyle=2 '设置边线的样式为点线

06 pic1.Line (5000,200)-(1000,2000)

07

08 pic1.DrawStyle=3 '设置边线的样式为点画线

09 pic1.Line (1000,2000)-(5000,2000)

10 End Sub

11

12 Private Sub Form_Load()

13 pic1.BackColor=vbWhite '设置图片框背景颜色为白色

14 pic1.ForeColor=vbBlue '设置画笔的颜色为蓝色

15 End Sub

【代码说明】为了让画出的线更明显，代码第13～14行设置图片框的背景色和画笔颜色。代码第02～09行通过（x,y）坐标来实现位置不同的画线效果。

【运行效果】运行程序后，单击“绘制”按钮会看到如图8.16所示的运行结果。

图　8.16　实例8.12运行结果

8.4.6　用Line方法绘制矩形

用Line方法绘制矩形时与使用Line方法绘制直线时相比，多了两个参数B和F。Line方法绘制矩形的格式如下。

对象.Line[[Step](x1,y1)]-[Step](x2，y2)[,color],[B[F]]

❑Step关键字：其含义与画直线时完全相同。

❑(x1,y1)：为矩形左上角坐标，前面无Step关键字时，是相对于原点的坐标值；有Step时，是相对于当前位置（CurrentX,CurrentY）的坐标值，即偏移量。

❑(x2,y2)：为矩形右下角坐标，前面无Step关键字时，是相对于原点的坐标值：有Step时，是相对于左上角(x1,y1)的偏移量。

❑省略(x1,y1)：这时矩形的左上角在当前位置（CurrentX，CurrentY）处。

❑color参数：用来指定要绘制矩形边线的颜色（画笔颜色），与画直线时相同。省略时(逗号保留)，用ForeColor属性设置的颜色画边线。

❑B参数：用于说明使用Line方法绘制矩形（如果省略，则画直线）。

❑F参数：用于说明使用画笔的颜色来填充矩形。省略时绘制空心矩形。该参数必须与B参数一同使用。

【实例8.13】编写程序，用鼠标和Line方法在窗体中绘制矩形。运行效果如下。

❑以按下鼠标键时的坐标作为矩形左上角的坐标；以释放鼠标键时的坐标作为矩形的右下角坐标（拖曳操作）。

❑按鼠标左键拖曳时，绘制绿色空心矩形；按鼠标右键拖拽时，绘制黄色填充矩形。

编写事件过程如下。

01 Dim oldx As Long '用于记录按下鼠标键时的X坐标

02 Dim oldy As Long '用于记录按下鼠标键时的Y坐标

03

04 Private Sub Form_Load()

05 Me.ForeColor=vbGreen

06 Me.DrawWidth=5

07 End Sub

08

09 Private Sub Form_MouseDown(Button As Integer,Shift As Integer,X As Single,Y As Single)

10 oldx=X

11 oldy=Y

12 End Sub

13

14 Private Sub Form_MouseUp(Button As Integer,Shift As Integer,X As Single,Y As Single)

15 If Button=1 Then '按下鼠标左键

16 Line (oldx,oldy)-(X,Y),,B

17

18 ElseIf Button=2 Then '按下鼠标右键

19 Line (oldx,oldy)-(X,Y),vbYellow,BF

20 End If

21 End Sub

【代码说明】代码第09～12行的MouseDown事件，实现一个画线的结束操作，其实就是取结束时鼠标所在的位置。代码第14～20行的MouseUp是一个开始操作，代码第16～19行是两个不同颜色的画笔。

【运行效果】运行结果如图8.17所示。

图　8.17　实例8.13运行结果

8.4.7　用Circle方法画圆或椭圆

Circle方法可用于在窗体、图片框或打印机上绘制圆、椭圆、圆弧及扇形等图形。前面介绍的属性DrawWidth、DrawStyle等在Circle方法中也同样适用。

1.画圆

使用Circle方法绘制圆的格式如下：

对象.Circle [Step](x,y),radius[,color]

❑(x,y)参数：指定圆心的位置。Step关键字指定它后面圆心的坐标值(x,y)是相对于当前位置(CurrentX,CurrentY)。省略Step关键字，(x,y)为相对于坐标原点的绝对坐标值。

❑radius参数：用于设置圆的半径。

❑color参数：用于设置绘制圆的颜色，省略时，用对象的ForeColor属性设置的颜色画圆。

绘制的圆是实心圆、空心圆或者用指定的图案填充，与对象的FillColor属性、FillStyle属性设置有关。

【实例8.14】编写程序，用鼠标单击窗体时，在窗体中绘制多个同心圆，如图8.18所示。

图　8.18　实例8.14运行结果

编写事件过程如下。

01 Private Sub Form_Click()

02 Dim x As Long

03 Dim y As Long

04 Dim i As Integer

05

06 '下面两条语句用于获得窗体的中心点坐标

07 x=Me.ScaleWidth/2

08 y=Me.ScaleHeight/2

09

10 '绘制同心圆

11 For i=100 To 1500 Step 50

12 Circle (x,y),i

13 Next

14 End Sub

15

16 Private Sub Form_Load()

17 Me.ForeColor=vbBlue

18 End Sub

【代码说明】本例中第07～08行使用了窗体的ScaleWidth和ScaleHeight两个属性。这两个属性设置或返回窗体、图片框内部宽度和高度，这里的宽度和高度是指除去边界或标题栏后的净宽度和净高度。读者可以将本例中这两个属性换为Width和Height进行试验并观察它们之间的区别。

【运行效果】如图8.18所示。

2.画椭圆

画椭圆与画圆相比多一个纵横比参数，当纵横比为1:1时，即是圆。格式如下。

对象.Circle[Step](x,y),radius[,color],,,aspect

其中，aspect参数决定所画椭圆纵轴与横轴的比值。比值大于1时，绘制纵向椭圆(垂直方向大于水平方向)；小于1时绘制横向椭圆：等于1时绘制圆。

【实例8.15】编写程序，在窗体中绘制一个纵向椭圆和横向椭圆，要求横向椭圆内部用横道填充，如图8.19所示。

图　8.19　实例8.15运行结果

编写事件过程如下。

01 Private Sub Form_Click()

02 Circle (2000,1500),1000,,,,2.5 '绘制纵向的椭圆

03 Me.FillStyle=2

04 Circle (2000,1500),1000,RGB(255,0,255),,,0.4 '绘制横向填充的椭圆

05 End Sub

【代码说明】代码第02和第04行中，在最后一个参数前有3个逗号“ ， ， ，”不能省略，因为这两个位置是画圆弧时要用到的参数位置，下一节将对这两个参数给予介绍。其他几个参数与画圆时一样。

【运行效果】与图8.19效果一致。

8.4.8　用Circle方法画圆弧及扇形

绘制弧和扇形时仍使用Circle方法。弧与扇形既有相同点，也有不同点。弧可以视为由圆或椭圆的边线中截取的一部分，而扇形还要在弧的基础上，从弧的两端再分别画一条到圆心的直线，它是封闭的图形。绘制弧和扇形时使用的格式如下。

对象.Circle [Step](x,y),radius[,color][start,end][,aspect]

其中，start指定弧的起始角，end指定弧终止角。它们的单位均是弧度，范围从0～2π。画弧时，start、end都用正值。从start开始，逆时针画到end处结束。

【实例8.16】编写试验程序，在窗体中绘制一个圆弧和一个扇形，如图8.20所示。

图　8.20　实例8.16运行结果

编写事件过程如下。

01 Private Sub Form_Click()

02 Me.Circle (4000,1500),1000,vbBlue,1,3 '画圆弧

03

04 Me.FillStyle=7

05 Me.FillColor=vbRed

06 Me.Circle (2000,1500),1000,,-1,-3 '画扇形

07 End Sub

【代码说明】代码第06行是画扇形，则start、end都取负值，也是从start开始，逆时针绘制，到end结束。取负值是为了与画弧区分，并不代表角度的负值。

【运行效果】与图8.20效果一致。

8.4.9　用PaintPicture方法旋转图形

使用PaintPicture方法，可以在窗体、图片框和Printer对象上的任何地方绘制图形。也可以通过使用负的目标高度值(height1)和/或目标宽度值(width1)水平或垂直翻转位图。

该方法只能对用Picture属性、LoadPicture函数设置的图形进行操作，用绘图方法绘制的图形在没有存储成图形文件前不能用其操作。其使用格式如下。

object.PaintPicture picture,x1,y1,width1,height1,x2,y2,width2,height2,opcode

其中各参数的说明如下。

❑object：可选的。可以是一个窗体、一个图片框或打印机对象。如果省略object，默认对象为带有焦点的窗体。

❑picture：必需的。是要绘制到object上的图形源。它是窗体或图片框的Picture属性。

❑x1,y1：必需的。指定在object上绘制picture的目标坐标。也可以说是虚拟坐标系的原点。下面的width1,height1就是这个虚拟坐标系中的宽度和高度。

❑width1,height1：可选的。是目标矩形区域的宽和高。

❑x2,y2：可选的。是要传送图形矩形区域左上角坐标。

❑width2,height2：可选的。是要传送图形区域的大小。

❑opcode：可选的。指定传送的像素与目标中现有的像素组合模式。

应用时可以省略变换格式中任何可选的尾部参数。如果省略了一个或多个可选的尾部参数，则不能在指定的最后一个参数后面使用逗号。如果想指定某个可选参数，则必须先指定语法中出现在该参数前面的全部参数。

PaintPicture方法中的参数除Opcode外，度量单位受到ScaleMode属性的影响，结果受到AutoRedraw属性的影响，在使用该方法前，最好将ScaleMode属性设置为像素，AutoRedraw属性设置为True。

【实例8.17】使用PaintPicture方法将图片框中的图形垂直翻转后显示到窗体上面。

界面设计如图8.21所示，窗体中有一个图片框和一个命令按钮，图片框名称为pic1，按钮的名称为cmd1。将窗体的AutoRedraw属性设置为True，并在图片框中导入一副图。接下来编写按钮的单击事件如下。

图　8.21　实例8.17设计界面

01 Private Sub cmd1_Click()

02 Me.PaintPicture Picture1.Picture,1000,2000,1000,-1500

03 End Sub

【代码说明】代码第02行的作用是在窗体上绘制一副图片框pic1中的图形，将要绘制的图形的坐标系统以点（1000，2000）为虚拟原点，其宽度1000和高度-1500都是相对于这个虚拟坐标系来说的。

【运行效果】其运行结果和坐标系描述如图8.22所示。

图　8.22　实例8.17运行结果与虚拟坐标系描述

在使用PaintPicture方法翻转图形时只要改变坐标系即可。如果设置图形宽为负数，则水平翻转图形；如果设置图形高度为负数，则上下翻转图形；如果宽度和高度都为负数，则两个方向都翻转图形。例如，目标宽度为负数，PaintPicture方法将像素复制到原点的左边，如果控件的坐标原点在左上角，目标图形就在控件以外，为使目标图形复制到控件中，必须将原点设置到另一角，实现时可以任意选定源或目标的坐标系。

8.4.10　重绘图形和画笔模式

1.重绘图形

在窗体和图片框中使用Pset、Line等图形方法输出图形或者用Print方法打印文字后，应该有一个问题会让很多人恼火，那就是当将窗体最小化或者该窗体被其他窗体覆盖后，再显示的时候会发现所有输出的图形和文字全部消失了。下面解决这一问题。

解决上面的问题可以用两种方法。第一种是使用窗体或图片框的AutoRedraw属性，将其设置为True就可以解决上面的问题。第二种方法是使用Paint事件，该事件是在窗体或图片框上的覆盖窗口移开后被触发或者是在窗体加载、最小化、还原、最大化时被触发的事件。因此，该事件可用于重绘消失的图形或者文字，使用时只需要将这些绘图方法写在该事件过程中就可以了。

2.画笔模式

窗体和图片框有一个很有意思的属性——DrawMode（画笔模式），该属性有16种取值，具体描述见表8.5。

在16种取值中，经常用到的只有两个值，分别为7和13（默认）。如果将DrawMode属性设置为7（异或模式），在同样的位置画两次相同的图形后就会还原以前这个位置上的图形。这一特性在制作动画时非常有用，比如场景中让一个动画人物从画布（图片框或窗体）的左侧走到右侧，此时遇到的问题是在这一过程中怎样保持人物后面的背景不被擦出，解决这个问题只要将DrawMode属性设为7就可以了。

上面所说的异或操作的运算规则如下。

True xor True=False

True xor False=True

False xor True=True

False xor False=False

其实就是相同的值经过异或运算后为假，即代表消除，不相同的值异或运算后为真，即代表留下痕迹。

【实例8.18】编写一个程序，试验DrawMode属性为7时的应用。

设计如图8.23所示的程序界面。将图片框pic1的BackColor属性设置为白色，将其AutoRedraw属性设置为True。

图　8.23　实例8.18设计界面

编写事件过程如下。

01 Private Sub Form_Load()

02 Dim colornum As Double

03 colornum=RGB(255,0,0)

04

05 pic1.DrawMode=7 '设置为"异或"模式

06 pic1.ForeColor=pic1.BackColor Xor colornum '设置异或模式前景色

07 End Sub

08

09 Private Sub cmdLine_Click()

10 pic1.Line (0,0)-(2000,2000)

11 End Sub

12

13 Sub cmdCircle_Click()

14 Dim i As Integer

15 For i=1 To 500

16 Me.pic1.Circle (1000,1000),i

17 Next i

18 End Sub

【代码说明】代码第05行设置为“异或”模式，代码第10行是画线，代码第16行是画圆。具体效果看下面的实验。

【运行效果】运行本例后，操作方法和能够得到的实验结论如下。

当第一次单击“画直线”按钮，图片框中会出现一条红色的直线。而再次单击“画直线”按钮时，图片框中的直线会消失。

【实验结论】当DrawMode为7时，在同一个位置画第二个同样的图形，等同于擦除第一次的图形。

先单击“画直线”按钮，在图片框中绘制一条直线。然后，单击“画圆”按钮绘制500个同心圆，如图8.24所示。接下来，再次单击“画圆”按钮时的运行结果如图8.25所示。

图　8.24　实例8.18运行结果1

图　8.25　实例8.18运行结果2

当DrawMode为7时，在同一个位置第二次绘制图形，不仅可以擦除前一次绘制的图形，而且还会完好无损地恢复原来这一位置上的背景图形。

8.5　小结

本章虽然讲解了很多画图方法，让读者有些迷惑，但最主要的不是要读者去画图，学会用电脑制作图画，而是熟悉鼠标和键盘的操作，而且键盘上的操作都对应一个ASCII码，熟悉键盘操作，必须熟悉常见的回车、Shift、Alt等键对应的ASCII码。鼠标操作要求熟悉左键、右键、中键对应的值。希望读者通过本章的学习了解键盘和鼠标在窗体操作中如何被捕获。

8.6　本章练习

1.键盘中Enter键的ASCII码是？

A.13

B.7

C.9

D.15

2.用数字来代表鼠标按钮时，2代表哪个键？

A.左键

B.右键

C.中键

3.假设picPicture为PictureBox控件。则下列程序代码的功能是什么？

picPicture.ScaleLeft=0

picPicture.ScaleTop=1

picPicture.ScaleWidth=3.14159

picPicture.ScaleHeight=-2

第9章　创建与使用菜单、工具栏和状态栏

大多数基于Windows的应用程序均使用菜单、工具栏和状态栏。其中，用户通过菜单及工具栏实现各种期望的操作；使用状态栏获得系统以及软件的状态信息。本章将带领读者学习如何创建与使用这些控件。本章包括以下知识点。

❑使用菜单编辑器对话框设计菜单栏和弹出式菜单。

❑使用Toolbar控件的属性页对话框设计工具栏。

❑使用StatusBar控件的属性页对话框设计状态栏。

通过本章的学习，读者可以掌握部分常用的VB高级控件的使用方法。能够设计出具有典型Windows程序风格、人机界面友好和功能强大的应用软件。

9.1　菜单

在基于Windows的应用程序中，菜单可谓是最重要的工具之一。一个高质量的菜单程序，不仅能使软件系统美观大方，而且能够让用户感觉操作方便。在实际应用中，菜单可以分为两种基本类型：下拉菜单和弹出式菜单。

9.1.1　菜单编辑器

在Visual Basic中，通过菜单编辑器建立和管理菜单。可以通过以下两种方式打开菜单编辑器。

❑执行“工具”|“菜单编辑器”命令。

❑单击工具栏中的“菜单编辑器”
按钮。

打开后的菜单编辑器窗口如图9.1所示。

从图9.1可以看到，菜单编辑器窗口分为数据区、编辑区和菜单项显示区三部分。菜单要在数据区输入，而在菜单显示区显示。

图　9.1　菜单编辑器窗口

1.数据区

用来输入或修改菜单项、设置属性。下面简要说明经常用到的几个栏的作用。

❑标题：用来键入显示在菜单上的文字。相当于普通控件的Caption属性。如果在该栏中键入一个“-”（减）号，则可以在菜单中加入一条分隔线。

❑名称：用来输入各级菜单的名称。相当于普通控件的Name属性。在此说明一点，每一个菜单项其实都是控件，所以必须为其取一个名字，用来在程序中调用它们。

❑索引：用来为用户建立的控件数组设立下标。

❑快捷键：用来设置菜单项的快捷键（热键）。单击右端的箭头，将下拉显示可供使用的热键。

❑复选：当选择该项时，可以在相应的菜单项旁边加上指定的记号（例如“√”）。

❑有效、可用等选项用于控制菜单项是否有效、是否可用等。

2.编辑区

编辑区共有7个按钮，用来对输入的菜单项进行编辑。

❑左、右箭头：用来产生或取消内缩符号。单击一次右箭头可以产生4个小点，单击一次左箭头则可以删除4个点。这4个点，被称为内缩符号，用来设定菜单的层次级别。

❑上、下箭头：用来在菜单项显示区中移动菜单项的位置。

❑下一个：用来创建一个新的菜单项。（与Enter键的作用相同）。

❑插入：用来在当前菜单项（条形光标所在的菜单项）之前插入一个新的菜单项。

❑删除：删除当前菜单项。

3.菜单项显示区

输入的菜单项全部在这里显示出来，并通过内缩符号表明菜单项的层次。

9.1.2　使用菜单编辑器创建菜单

使用上面的方法打开菜单编辑器，然后完成本书第一个带有菜单的实例。

【实例9.1】设计一个窗体，如图9.2所示。画图菜单中包括“圆”和“矩形”两个菜单项。其中，背景颜色菜单实现改变窗体的背景颜色，画图菜单实现在窗体上画图。

图　9.2　实例9.1界面

下面列出完成实例的步骤，分为两大部分，界面设计和程序设计。

【界面设计】

（1）单击“文件”|“新建工程”菜单项，创建一个新的标准EXE工程。

（2）单击Form1激活此窗体。

（3）单击工具栏中的“菜单编辑器”
按钮，打开菜单编辑器。

（4）在“标题”栏中键入“背景颜色（＆B）”，在“名称”栏中键入"mnuBackcolor"，创建背景颜色菜单。

（5）在编辑区中，单击“下一个”按钮，创建新的菜单项。

（6）在编辑区中，单击“向右”
按钮，产生一个内缩符号，使此菜单项成为“背景颜色”菜单的子菜单。

（7）设置此菜单项的“标题”为“红色”，在“名称”栏中键入"mnuRed"，从“快捷键”栏内选择"Ctrl+R"，为其设置快捷键。

（8）在编辑区中，单击“下一个”按钮，这时会发现被创建的新菜单项之前有4个点，自动产生了内缩符号。如果，不想让其内缩，单击“向左”按钮可以删除内缩符号。

（9）通过上述操作步骤，读者可以按照表9.1的内容设置各个菜单项的属性，并完成所有菜单。最后，菜单编辑器的内容如图9.3所示。

图　9.3　实例9.1的菜单编辑器

【程序设计】

（1）在窗体编辑状态下，单击“背景颜色”|“红色”菜单项，打开代码编辑器窗口后，在mnuRed_Click()事件中写入如下代码。

Me.BackColor=RGB(255,0,0) '给窗体的背景颜色赋值为红色

（2）按照图9.3所示的方法，分别给“绿色”、“蓝色”和“退出”菜单项的单击事件中添加代码后，代码编辑器窗口内的内容如下。

01 Private Sub mnuBlue_Click()

02 Me.BackColor=RGB(0,0,255)

03 End Sub

04

05 Private Sub mnuExit_Click()

06 Unload Me

07 End Sub

08 Private Sub mnuGreen_Click()

09 Me.BackColor=RGB(0,255,0)

10 End Sub

11

12 Private Sub mnuRed_Click()

13 Me.BackColor=RGB(255,0,0)

14 End Sub

（3）在窗体编辑状态下，单击“画图”|“圆”菜单项，打开代码编辑器窗口后，在mnuCircle_Click()事件中写入如下代码。

15 Dim x As Integer '变量声明部分

16 Dim y As Integer

17 Dim r As Integer

18 x=InputBox("请输入圆心X坐标","输入框",1000) '给变量赋值部分

19 y=InputBox("请输入圆心Y坐标","输入框",1000)

20 r=InputBox("请输入圆的半径","输入框",800)

21 Circle (x,y),r '画圆

（4）请读者用上面的方法和思路结合8.4节的内容自行完成“画图”|“矩形”菜单项的功能。

说明　菜单项仅有Click一种事件。当用户单击菜单项是触发此事件。

【代码说明】代码第01～14行多次使用了RGB函数，这是一个颜色值，前面已经多次介绍过。代码第21行使用了画圆的方法Circle，其中的3个参数通过第18～19行输入。

【运行效果】本例不算是一个完整的案例，需要结合前面几章介绍的画图知识来完成，这里不再给出运行效果。

9.1.3　设计菜单

在学习工具栏之前，先来看看在Windows应用程序设计中常常被忽视的一个问题：标准。在设计菜单时，应尽可能地使菜单项标准化。有关图形用户界面标准的详细内容不在本书讨论范围之中，但是一开始就注意标准的问题是很必要的。在设计菜单时，尽可能地使它们与标准Windows应用程序中的菜单相一致。

读者可能已经注意到，在许多基于Windows的程序中，“文件”菜单总是在最左边，而“帮助”菜单总是在最“右边”。这已经成为了软件界不成文的规定。因为所有Windows用户已经习惯了这种布局。如果，某个程序员随便改动菜单的位置，用户会感觉很不习惯，从而会否定这些不守规矩的应用软件。

在基于Windows的应用程序中，不仅仅菜单要遵守规矩，快捷键也应该遵守标准。比如，“文件”菜单的快捷键为字母F、“退出”菜单项的快捷键为字母"X"等。对于怎样设置快捷键，读者可以参考基于Windows的其他应用程序。由于以上原因，在开发基于Windows的应用软件时，必须要注意这些细节问题。

9.1.4　弹出式菜单的设计

在大多数基于Windows的程序里，读者除下拉式菜单以外还会经常遇到另外一种菜单——弹出式菜单。在Windows系统中，几乎在所有的对象上单击鼠标右键都可以显示一个弹出式菜单。

建立弹出式菜单通常分两步进行：首先用菜单编辑器建立菜单，其次用PopupMenu方法弹出显示。第一步的操作与前面介绍的基本相同，唯一的区别是，必须把一级菜单（主菜单项）的“可见”属性设置为False。在此强调，千万不要把子菜单项也设置为False。

PopupMenu方法用来显示弹出式菜单，其格式如下。

[对象.]PopupMenu菜单名[，Flags[，X[，Y[，BoldCommand]]]]

其中“对象”是窗体名；“菜单名”是在菜单编辑器中定义的一级菜单（主菜单项）名；Flags参数是一个数值或符号常量，用来指定弹出式菜单的位置及行为，其取值分为两组，一组用于指定菜单位置，另一组用于定义特殊的菜单行为，见表9.2和表9.3。X、Y是弹出式菜单在窗体上的显示位置，当然，要和Flags参数配合使用。BoldCommand用来在弹出式菜单中用粗体显示某个子菜单项的标题。

说明　如果不设置X、Y坐标值和Flags参数，则弹出式菜单的位置取决于单击鼠标右键时鼠标在窗口中的位置。其实，大多数情况下，编程人员都会省略这几个参数。

9.1.5　弹出式菜单的调用

接下来通过改变实例9.1，学习制作弹出式菜单和调用弹出式菜单的详细方法。将“画图”菜单变为弹出式菜单。具体表现为在表单的任何一个位置上单击鼠标右键，都会弹出“画图”菜单的内容，如图9.4所示。修改步骤如下。

图　9.4　弹出式菜单

（1）激活窗体，打开菜单编辑器。

（2）在菜单项显示区中，单击“画图”菜单项。

（3）从数据区内找到“可见”属性，并将其选项去掉。

（4）关闭菜单编辑器，这时会发现“画图”主菜单项从菜单栏消失了。

（5）双击表单，打开代码编辑器窗口，找到窗体的MouseDown事件，输入如下代码。

If Button=2 Then '判断是否是右击

PopupMenu mnuPain '弹出“画图”菜单

End If

（6）运行程序，在表单上右击，这时，看到了渴望已久的弹出式菜单。单击“圆”菜单项，读者可以看到和操作下拉菜单时一样的情景。

说明　调用语句PopupMenu正常情况下都应该写在MouseDown事件中。

9.2　工具栏

在基于Windows的应用程序窗口中，除了菜单以外，工具栏是用户经常使用的另外一种工具。工具栏解决了用户访问菜单时相对烦琐的步骤。例如，在经常用到的Word软件中，需要保存文件时，必须经过两步操作，“文件”|“保存”。然而，如果使用工具栏，用户可以直接单击“保存”
快捷按钮一步操作就能完成保存任务。所以，一个好的工具栏带给用户的就是两个字“方便”。

9.2.1　利用Toolbar控件创建工具栏

Toolbar控件是Visual Basic系统提供给用户的常用ActiveX控件之一。因此，用户使用到该控件时必须通过选择“工程”|“部件”菜单项，打开“部件”对话框，并从“控件”选项卡的列表中选择"Microsoft Windows Common Controls 6.0"，如图9.5所示。单击“应用”按钮后，工具箱内会多出Toolbar、StatusBar、ProgressBar、ImageList等9个控件。

图　9.5　“部件”对话框

注意　必须在"Microsoft Windows Common Controls 6.0"之前打上钩，这样才是真正的选择。很多初学者在此都会犯错。

通过上面的步骤后，Toolbar控件被放到了工具箱中。接下来的内容，通过一个实例让读者感受工具栏的创建方法和应用方法。

【实例9.2】在实例9.1的基础上加入工具栏，并完成相应功能。工具栏中有5个快捷按钮，分别是“红色”、“绿色”、“蓝色”、“圆”、“矩形”。按钮实现的功能和对应菜单项的功能一致，如图9.6所示。

图　9.6　加入工具栏的程序界面

【实现步骤】下面列出完成实例的步骤。

（1）在工具箱里单击Toolbar控件，画到窗体的任何位置，Visual Basic会自动将Toolbar控件移到窗体顶部。再右击该对象，从快捷菜单中选择“属性”，打开“属性页”窗口，如图9.7所示。

图　9.7　工具栏“属性页”窗口

（2）选择“按钮”选项卡，单击“插入按钮”按钮，Visual Basic就会在工具栏（Toolbar控件）上显示一个按钮，但是没有任何图形和颜色。重复此步骤插入5个按钮。

（3）在Visual Basic工具箱里单击
ImageList控件，并将其放到窗体上的任何位置，再右击该对象，从快捷菜单中选择“属性”，进入“属性页”窗口，如图9.8所示。

图　9.8　ImageList控件的“属性页”窗口

（4）在“通用”选项卡内设置好要放到工具栏快捷按钮上的位图或图标的大小。此实例中选择16×16。

（5）选择“图像”选项卡，单击“插入图片”按钮，在选定图形对话框里选择想使用的位图或图标，然后单击打开按钮。重复以上步骤，在ImageList控件中加入5个位图或图标。如图9.9所示。本例用到的图标可以从随书光盘的相应目录中找到。

图　9.9　ImageList控件的“图像”选项卡

（6）单击“属性页”中“确定”按钮退出。

（7）打开Toolbar控件的“属性页”对话框，选择“通用”选项卡，在“图像列表”栏里选择刚才添加的ImageList控件的名称。

（8）选择“按钮”选项卡，这时“索引”栏内显示的是：1，代表当前按钮是第一个按钮。为了给其加入位图或图标，在当前选项卡中找到“图像”一栏。填入想放到该按钮上的ImageList控件中的位图序号。例如：填入1，代表将ImageList控件中第一个位图，即红色图标放到该按钮上。如图9.10所示。

图　9.10　Toolbar控件的“按钮”选项卡

（9）单击“索引”栏右边的“向右”按钮，“索引”栏内会显示：2。用步骤8的方法为其添加图片。重复此步骤为5个快捷按钮加上位图。如果现在就想运行程序，那也可以。只是快捷键不会起任何作用，它只是个装饰品而已。

技巧　在Toolbar控件的快捷按钮上想要放置图片，必须将Toolbar与一个ImageList链接起来。在上边的第（4）步，设置ImageList控件位图的大小必须先进行。否则，与Toolbar链接后，想修改其大小，那就几乎需要重新做一遍上边的工作了。

9.2.2　为工具栏按钮设置分组

有时编程人员会想将快捷按钮用其功能分组，中间加上分隔线，这样工具栏看起来就很完美了。方法如下：在需要分组的快捷按钮之间，插入一个新的按钮。其次，将其“样式”属性栏内值设置为"3-tbrSeparator"。读者可以自行挖掘“样式”属性栏内的其他值得效果。这里因为本书的篇幅有限不再细说。

在此，还有一个属性需要说明。它就是“工具提示文本”属性。“工具提示文本”是一种当把鼠标箭头放到快捷按钮上时弹出的黄色提示标签。有了“工具提示文本”的帮忙，用户很容易就能知道哪个按钮的功能是什么。

9.2.3　给工具栏按钮增加事件处理代码

接下来完善工具栏的功能，让快捷按钮起到应有的作用。

在编辑状态下，双击窗体上的Toolbar控件，打开代码编辑器窗口后，在该Toolbar控件的ButtonClick事件中写入如下代码。

01 Select Case Button.Index 'Index代表快捷按钮的索引号（序号）

02 Case 1

03 Call mnuRed_Click '调用mnuRed菜单项的单击事件

04 Case 2

05 Call mnuGreen_Click

06 Case 3

07 Call mnuBlue_Click

08 Case 4

09 Call mnuCircle_Click

10 Case 5

11 '此处填写调用“矩形”菜单项单击事件的代码，请读者自行完成

12 End Select

【代码说明】代码第01行判断Index参数值，其代表快捷按钮的索引号。根据不同的索引号，调用不同的单击事件。

9.2.4　美化工具栏

当读者做完以上的小实例之后，会觉得工具栏看上去有些别扭。原因就在于工具栏上的快捷按钮都像按钮控件一样凸起，而并非像其他标准Windows软件那样扁平。而且，当程序运行期间，鼠标经过快捷按钮时不会改变图案。接下来，解决这些问题。首先，改变快捷按钮凸起样式，让其变成扁平样式。方法如何。

（1）在图9.7的“通用”选项卡最下面的“样式”属性栏中选择"1-tbrFlat"。

（2）单击“应用”按钮，便可看到按钮成为扁平样式。

其次，鼠标经过时让快捷按钮上的图片改变的方法如下。

（1）在窗口中加入第二个ImageList控件，给其添加鼠标经过快捷按钮时显示的图片。

（2）在图9.7的“通用”选项卡中找到“热图像列表”一栏，从中选择第二个ImageList控件的名称，默认情况下第二个ImageList控件的名称为ImageList2。

（3）单击“应用”按钮，运行后就可以看到结果了。

说明　设置热图像的前提是将工具栏的样式变为"1-tbrFlat"。

9.2.5　Shell函数

在应用软件开发的过程中会遇到这样一种问题——调用外部可执行文件。例如，在开发一个工资管理系统时可能需要调用Windows自带的计算器；又比如，调用IE浏览器等。这时候，就会用到Shell函数，其功能就是调用外部可执行文件。Shell函数的格式如下。

Shell（命令字符串[，窗口类型]）

其中“命令字符串”是想要调用的可执行文件名，文件名包括路径。“窗口类型”是被调用的可执行文件的窗口样式，有6种选择，见表9.4。

Shell函数的返回值为：如果成功地执行了所要执行的文件，则它会返回程序的任务ID。任务ID是一个唯一的数值，用来代表正在运行的程序。若不成功，则会返回0。

【实例9.3】如图9.11所示，当用户单击工具栏中
快捷按钮时调用Windows的计算器，单击
快捷按钮时调用IE浏览器，并打开163免费邮箱的主页。界面制作用上几节讲述的方法即可，在此不再重复。相应代码如下。

图　9.11　实例9.3界面

01 Private Sub Toolbar1_ButtonClick(ByVal Button As MSComctlLib.Button)

02 Dim id

03 Select Case Button.Index

04 Case 1

05 id=Shell ("C:\WINDOWS\system32\calc.exe",1)

06 Case 2

07 id=Shell ("C:\Program Files\Internet Explorer\IEXPLORE.EXE http://mail.163.com",1)

08 End Select

09 End Sub

【代码说明】首先第03行判断按钮的索引号，然后Shell表示执行Windows的某个程序，其中第一个参数要求给出这个程序的绝对地址。

注意　Shell函数的第一个参数文件名必须带有扩展名。默认情况下，Shell函数是以异步方式来执行其他程序的。也就是说，用Shell启动的程序可能还没有完成执行过程，就已经执行到调用Shell函数之后的语句。

9.2.6　CoolBar控件

CoolBar控件用来创建类似于Microsoft Internet Explorer中所具有的工具栏。CoolBar控件是一个容器控件，可以在其上放置子控件，需要在应用程序窗口中放置多个工具栏时CoolBar控件很有用。使用CoolBar控件的步骤和方法如下。

（1）右击工具箱空白处，在弹出菜单中选择“部件”菜单项，打开“部件”对话框。

（2）从“控件”选项卡的列表中选择"Microsoft Windows Common Control-3 6.0"。

（3）单击“应用”按钮后，工具箱内会多出CoolBar控件，将其添加到窗体上。

（4）选中CoolBar控件，从工具箱中选择Toolbar控件并且在CoolBar中绘制它。

（5）右击CoolBar控件，在弹出的菜单中选择“属性”命令，打开其“属性页”窗口，选择“带区”选项卡。

（6）从“子”栏内选择刚才绘制的Toolbar控件的名称。应用后会发现漂浮在CoolBar上方的Toolbar控件很好地嵌入到CoolBar控件中。

如果希望在CoolBar控件中放入多个工具栏，基本上重复步骤4～6即可。唯一区别的是要注意改变“带区”选项卡内“索引”栏值。

说明　CoolBar控件的每一带区内只能放置一个控件。除工具栏以外，CoolBar控件中还能绘制其他大部分控件。如文本框、组合框等。

9.3　状态栏

状态栏与菜单栏、工具栏一样，也是标准Windows窗口必不可少的组件之一。状态栏主要用来显示应用程序的各种状态信息。状态栏可以说是由很多窗格组成的，每个窗格内可以显示不同的内容，其中可以是用户输入的文字，也可以是系统日期时间等。

9.3.1　设计状态栏

在上一节讲到导入扩展控件"Microsoft Windows Common Controls 6.0"后，工具箱内多出了9个控件。其中StatusBar就是要找的状态栏控件。下面用例子说明状态栏的简单用法。

【实例9.4】制作一个有两个窗格的状态栏，第一个窗格中显示“状态栏试验程序”，第二个窗格中显示“蓝色火焰工作室制作”。如图9.12所示。

图　9.12　简单状态栏

【实现步骤】

（1）将StatusBar控件绘制到窗体上，Visual Basic会自动将其放到窗体的最下方。

（2）右击状态栏选择“属性”菜单项。打开“属性页”窗口，并选择“窗格”选项卡。对于状态栏的设计大部分都在此选项卡内进行。如图9.13所示。

图　9.13　状态栏属性页窗口

（3）“索引”栏内显示的是窗格的索引号，代表当前正在设置的窗格。图9.10中，在“索引”栏内的值为数字1，代表当前设置的是第一个窗格。在“索引”栏下方的“文本”栏内输入汉字“状态栏试验程序”。当单击应用按钮时会发现第一个窗格内显示了刚输入的几个汉字。

（4）接下来，单击“插入窗格”按钮，然后单击“索引”栏后的向右
按钮。索引值会变为数字2，代表当前设置的是第二个窗格。在“文本”栏内输入“蓝色火焰工作室制作”。单击应用后，符合题目要求的状态栏就出现了。

9.3.2　在状态栏中显示当前日期时间

在状态栏中显示日期和时间也是状态栏应用的一个常用例子。接下来，讲述在状态栏中显示日期和时间的方法。改进实例实例9.4，使其第3个窗格内显示系统当前日期、第4个窗格内显示系统当前时间。

（1）在图9.10中，单击“插入窗格”按钮，然后单击“索引”栏后的向右
按钮。使索引值变为数字3。

（2）在“样式”栏内选择"6-sbrDate"值。应用后系统当前日期即可出现在状态栏第3个窗格内。

（3）再次单击“插入窗格”按钮，然后单击“索引”栏后的向右
按钮。使索引值变为数字4。

（4）在“样式”栏内选择"5-sbrTime"值。应用后系统当前时间即可出现在状态栏第4个窗格内。

注意　单击“插入窗格”按钮后，Visual Basic会自动将“索引”栏内的值变为数字1。所以，必须用向右
按钮选择当前被编辑窗格的索引号。

9.3.3　在状态栏中显示鼠标位置

在各种处理图片、图像的软件中，状态栏内经常会有鼠标位置的信息。例如，Windows自带的画图软件、制作二维动画的flash、制图软件AutoCAD等都在状态栏中显示鼠标位置信息。下面的例子将帮助读者完成这一功能。

【实例9.5】窗体中放置了一个图片框Picture1和一个状态栏StatusBar1，当程序运行后，鼠标在Picture1中移动时的坐标信息显示在StatusBar1的第3个窗格内，如图9.14所示。

图　9.14　实例9.5的窗口

首先，如果希望实时获得鼠标在Picture1中的移动位置，程序代码必须写在Picture1的MouseMove事件中。其次，将字符串或数字放入状态栏窗格内的代码格式如下。

[窗体.]状态栏.Panels(index)=字符串或数字

其中，窗体为包含状态栏的窗体名称，若为当前窗体可以省略，状态栏为当前要编辑的状态栏名称，index为窗格索引号。因此，本实例具体程序代码如下。

01 Private Sub Picture1_MouseMove(Button As Integer,Shift As Integer,X As Single,Y As Single)

02 Me.StatusBar1.Panels(3)="X:"＆X＆"Y:"＆Y '将鼠标的X，Y坐标信息显示在状态栏的第三窗格内

03 End Sub

【代码说明】代码第02行Me表示当前窗体，其中的Panels(3)表示状态栏中的第3个索引项。

【运行效果】实例运行效果如图9.14所示。

9.4　应用案例——编写类似Windows画图板的简单画图程序

【实例9.6】对于Windows自带的画图板软件，大多数读者都应该深有体会。本节，结合前面所学菜单栏、工具栏和状态栏的知识加上第8章的知识，带领读者完成类似Windows画图板的简单画图程序。程序界面如图9.15所示。

图　9.15　画图程序界面

首先，单击“文件”|“新建工程”菜单项，创建一个新的标准EXE工程。在窗体中加入图片框控件Picture1，在本实例中将在其上绘制图形。将Picture1的BackColor属性设置为白色。

9.4.1　设计菜单栏

设计菜单栏的步骤如下。

（1）单击Form1激活此窗体。将其Name属性改为：frmMain。

（2）单击工具栏中的“菜单编辑器”
按钮，打开菜单编辑器。

（3）根据表9.5设置菜单项属性，并确定退出菜单编辑器。

注意　“蓝色”菜单项复选属性前面的复选框内需要打上钩。这样，程序运行时“蓝色”菜单项之前便有黑色的钩出现。意思为当前画笔颜色为蓝色。

9.4.2　设计工具栏

完成了菜单栏的设计，接下来的篇幅，带领读者设计本实例的工具栏。

（1）单击“工程”|“部件”菜单项，打开“部件”对话框，并从“控件”选项卡的列表中选择"Microsoft Windows Common Controls 6.0"，保证在其前的复选框内打上钩，单击“确定”按钮退出。

（2）从工具箱内选择Toolbar控件，将其加入到frmMain窗体内。并使用其默认名称Toolbar1。

（3）右击工具栏TooBar1，从快捷菜单中选择“属性”，打开“属性页”窗口。

（4）选择“按钮”选项卡，单击“插入按钮”按钮。重复此步骤插入9个按钮。

（5）将第4个按钮和第8个按钮的“样式”属性改为"3-tbrSeparator"。

（6）从工具箱内选择ImageList控件加入到frmMain窗体内。并使用其默认名称ImageList1。

（7）右击ImageList1，从快捷菜单中选择“属性”，打开“属性页”窗口。

（8）在“通用”选项卡内设置图标大小为16×16。

（9）选择“图象”选项卡，使用“插入图片”按钮，在ImageList控件中加入7个位图，如图9.16所示。加完位图后单击确定按钮退出。（读者可以从随书光盘的相应文件夹中找到这些位图文件。）

图　9.16　ImageList1的“图像”选项卡

（10）右击TooBar1，选择快捷菜单中“属性”菜单项，打开“属性页”窗口。选择“通用”选项卡，在“图像列表”栏里选择ImageList1，如图9.17所示。

图　9.17　TooBar1的“属性页”选项卡

（11）选择“按钮”选项卡，给快捷按钮加位图文件。其属性值设置对照表9.6。这里注意，因为第4和第8按钮是分隔符所以不必加位图。选项卡中其他属性均取默认值即可。

（12）单击确定按钮退出后，就可以看到与图9.15一样的工具栏了。

技巧　设置快捷按钮5～7的“样式”属性为tbrButtonGroup。因此，这几个按钮就像word中的两端对齐、居中、右对齐等几个按钮一样，同一时刻只能按下其中的一个按钮。

9.4.3　处理菜单项的Click事件

设计好菜单栏、工具栏后，开始添加处理程序。在这一小节，逐个处理每一菜单项的Click事件。

（1）“新建”菜单项Click事件的代码如下。

Picture1.Picture=LoadPicture("") '给图片框加载空图片

（2）“打开”菜单项Click事件的代码如下。

01 Dim fname As String '此变量存放输入的图片文件名

02 fname=InputBox("请输入要打开的图片文件名称（包含路径和扩展名）","打开")

03 Me.Picture1.Picture=LoadPicture(fname)

说明　在此，读者会感觉输入带路径、扩展名的图片文件名很别扭，是否能像Windows一样，用一个打开对话框来选择文件。若想实现此功能请查看第10章10.4节的内容。

（3）因为保存图像要用到SavePicture语句，所以编写“保存”菜单项Click事件代码之前，要将图片框Picture1的AutoRedraw属性设置为真，只有这样SavePicture语句才能保存图片框内的图像。下面是“保存”菜单项Click事件代码。

01 Dim fname As String '此变量存放输入的图片文件名

02 fname=InputBox("请输入要保存的图片文件名称（包含路径和扩展名）","打开")

03 SavePicture Picture1.Image,fname '将Picture1上绘制的图像保存为fname文件

（4）“退出”菜单项Click事件的代码如下。

Unload Me '卸载窗体

蓝色、绿色菜单项的作用是改变画笔的颜色。因此，用户每次只能选一种颜色。为了让用户清楚自己选择的当前颜色是蓝色还是绿色，本实例中将在这两个菜单项前面打钩作标记。单击哪个菜单项，就会在其前面出现黑色的钩，另外一个菜单项的钩就会消失。还有，为了能将所选的颜色信息保存起来并能够传递给窗体的其他事件过程，在此要声明一个窗体级变量colornum，方法如图9.18所示。

图　9.18　通用声明区内的窗体级变量设置

（5）“蓝色”和“绿色”菜单项Click事件的代码分别如下。

01 Private Sub mnuBlue_Click()

02 mnuBlue.Checked=True '在蓝色菜单项前打钩

03 mnuGreen.Checked=False '去掉绿色菜单项前的钩子

04 colornum=vbBlue '将窗体级变量colornum设置为蓝色

05 End Sub

06

07 Private Sub mnuGreen_Click()

08 mnuGreen.Checked=True '在绿色菜单项前打钩

09 mnuBlue.Checked=False '去掉蓝色菜单项前的勾

10 colornum=vbGreen '给colornum赋值为绿色

11 End Sub

【代码说明】代码中的注释已经讲解得非常清楚，这是两个用来改变颜色的菜单事件。代码第01～05行完成选择蓝色菜单的功能，代码第07～11行完成选择绿色菜单的功能。

（6）“搜索帮助主题”菜单项Click事件的代码，请读者参考本书第18章的内容来完成。

（7）“关于”菜单项Click事件的代码如下。

MsgBox"版本"＆App.Major＆"."＆App.Minor＆"."＆App.Revision

9.4.4　处理工具栏的代码

编写完所有菜单项Click事件的处理代码后，接下来编写工具栏快捷按钮的处理事件。程序代码如下。

01 Private Sub Toolbar1_ButtonClick(ByVal Button As MSComctlLib.Button)

02 Select Case Button.Index

03 Case 1

04 Call mnuFileNew_Click '调用“新建”菜单项的click事件

05 Case 2

06 Call mnuFileOpen_Click '调用“打开”菜单项的click事件

07 Case 3

08 Call mnuFileSave_Click '调用“保存”菜单项的click事件

09 Case 5 'penshape变量用来存放所选形状的信息，其为窗体级变量,

10 penshape=1 '单击直线按钮时将penshape的值变为1

11 Case 6

12 penshape=2 '单击圆按钮时将penshape的值变为2

13 Case 7

14 penshape=3 '单击矩形按钮时将penshape的值变为3

15 Case 9

16 'call mnuHelpSearchForHelpOn_Click()

17 End Select

18 End Sub

【代码说明】代码第10～14行的penshape变量用来存放所选形状的信息，其为窗体级变量，以便在窗体的其他事件中调用并且区分画笔的形状。在窗体的通用声明区内，声明变量如下。

Dim penshape As Integer

9.4.5　编写绘制图形的代码

前期工作准备好以后，下面完成画图软件的关键部分——绘图。为了让画笔的颜色刚开始为蓝色，在窗体的Form_Load()事件中写入如下一行代码。

colornum=vbBlue

下面，以绘制直线为例详细讲解绘图的方法，其他图形的绘制可以参考绘制直线的方法来完成。绘制直线的方法类似于Windows画图板的直线绘制。

❑按下鼠标左键的一刹那，直线的起点被记录下来。

❑按住鼠标左键的同时移动鼠标，直线的长度和终点的位置会随着鼠标箭头的位置不断地变化，实现橡皮条的功能。

❑当松开鼠标左键的一刹那，得到直线的结束点，将直线固定的画在画板Picture1上。

分析：根据上面所描述的功能，绘图代码应当写在图片框Picture1的MouseDown、MouseMove、MouseUp 3个事件内。在Picture1中按下鼠标左键时，触发MouseDown事件。在其内应当添加记录鼠标当前位置X、Y坐标的语句。用oldx、oldy两个窗体级变量保存X、Y坐标。

oldx=X 'oldx，oldy为窗体级变量，给其赋值为鼠标当前的X，Y坐标值

oldy=Y

注意　编写上面的代码之前，要在窗体的通用声明区声明oldx、oldy两个变量为Single类型。

当鼠标在Picture1中移动时，会触发MouseMove事件，如果想完成橡皮条的功能，可以试着写入如下代码。

Picture1.Line (oldx,oldy)-(X,Y),colornum

运行程序后，在工具栏内单击“直线”快捷按钮。当把鼠标移动到画板Picture1上的时候，会发现虽然没有单击鼠标左键，但是跟随着鼠标的移动，产生了多条以画板的左上角为起始点的蓝色直线。当单击鼠标左键时，又会有多条直线以刚才单击左键的位置为起始点被绘制在画板上。如图9.19所示。

图　9.19　直线效果

目前程序存在的问题如下。

❑当鼠标进入画板内以后，还没有单击鼠标左键时直线就已经绘制了。

❑有多条直线被绘制，而不是期望的一条。

❑即使松开鼠标左键，直线还是跟着鼠标不断绘制，不能结束。现在，分析这三个问题产生的原因。

首先，第一个问题的原因是当鼠标进入画板内的一刹那触发了Picture1的MouseMove事件，并执行了画直线Line语句。又因为，这时还没来得急单击鼠标左键，没有给oldx、oldy两个变量赋予值，所以这两个变量取了默认值0。因此直线的起点为画板的左上角。解决问题的方法为在MouseMove事件的代码中加入一个标志位变量flag。变量flag为真时，执行画直线语句，为假时什么也不做。并且，在程序运行时初始化flag为假，当按下鼠标左键时将其变为真。

其次，第二个问题的原因也是当鼠标移动时触发了MouseMove事件。每一次鼠标的移动都会触发MouseMove事件并执行Line语句。鼠标移动多次所以在画板上绘制了多条直线。解决问题的方法为每一次移动鼠标时把移动鼠标前绘制的直线擦除掉。但是，又不能擦掉画板中原有的图形。所以，绘图时，画笔应该使用异或模式，这样，绘图时在一个位置画两个一模一样的图形就可以恢复这个位置上的原图。

最后，产生第三个问题的原因同样是因为鼠标只要移动就会执行MouseMove事件中的Line语句，所以直线一直跟着鼠标，并不断绘制直线。解决方法为在MouseUp事件中，将标志位变量flag设置为假即可。

下面列出关键程序代码供参考。

01 Dim colornum As Long

02 Dim penshape As Integer

03 Dim oldx As Single

04 Dim oldy As Single

05 Dim flag As Boolean

06

07 Private Function length(x1 As Single,y1 As Single,x2 As Single,y2 As Single) As Single

08 length=Round(Sqr((x1-x2)^2+(y1-y2)^2),1)

09 End Function

10

11 Private Sub Form_Load()

12 colornum=vbBlue

13 flag=False

14 End Sub

15

16 Private Sub Picture1_MouseDown(Button As Integer,Shift As Integer,X As Single,Y As Single)

17 oldx=X

18 oldy=Y

19 flag=True

20 End Sub

21

22 Private Sub Picture1_MouseMove(Button As Integer,Shift As Integer,X As Single,Y As Single)

23 Static x0 As Single 'x0，y0用来存储上一次的MouseMove事件的X，Y坐标

24 Static y0 As Single

25 Dim pmode As Long '用于保存Picture1以前的DrawMode

26 Dim pcolor As Long '用于保存Picture1以前的ForeColor

27

28 pmode=Picture1.DrawMode '保存Picture1以前的DrawMode

29 pcolor=Picture1.ForeColor '保存Picture1以前的ForeColor

30 Picture1.DrawMode=7 '设置为“异或”模式

31 Picture1.ForeColor=Picture1.BackColor Xor colornum '设置异或模式前景色

32 If flag=True Then

33 Select Case penshape

34 Case 1

35 Picture1.Line (oldx,oldy)-(x0,y0) '擦除上一次绘制的直线

36 Picture1.Line (oldx,oldy)-(X,Y) '重新绘制直线

37 Case 2

38 Picture1.Circle (oldx,oldy),length(oldx,oldy,x0,y0)

39 Picture1.Circle (oldx,oldy),length(oldx,oldy,X,Y)

40 Case 3

41 Picture1.Line (oldx,oldy)-(x0,y0),,B '擦除上一次绘制的直线

42 Picture1.Line (oldx,oldy)-(X,Y),,B '重新绘制直线

43 End Select

44 End If

45 x0=X

46 y0=Y

47 Picture1.DrawMode=pmode '还原Picture1以前的DrawMode

48 Picture1.ForeColor=pcolor '还原Picture1以前的ForeColor

49 End Sub

50

51 Private Sub Picture1_MouseUp(Button As Integer,Shift As Integer,X As Single,Y As Single)

52 flag=False

53 Select Case penshape

54 Case 1

55 Picture1.Line (oldx,oldy)-(X,Y),colornum

56 Case 2

57 Picture1.Circle (oldx,oldy),length(oldx,oldy,X,Y),colornum

58 Case 3

59 Picture1.Line (oldx,oldy)-(X,Y),colornum,B

60 End Select

61 End Sub

【代码说明】因为，程序中经常用到两点之间的距离。所以，在本程序中第08行编写了一个求两点间距离的函数length。程序中第13行的flag为窗体级变量，应当声明为Boolean类型。

9.4.6　处理状态栏

绘制图形已经没什么问题了。但是，绘制图形时感觉很不舒服，因为用户只能估计直线的距离、圆的半径和矩形的高度等。下面，完善画图程序的这一缺憾，把这些信息全部放在状态栏中。实现这一目的的方法如下。

（1）从工具箱中，将StatusBar控件绘制到窗体上。

（2）右击状态栏，选择“属性”菜单项。打开“属性页”窗口，选择“窗格”选项卡。

（3）使用“插入窗格”按钮插入窗格，使状态栏中最后变为4个窗格为止。每一窗格的属性设置见表9.7。除表中列出的属性以外其他属性均取默认值。

（4）单击“确定”按钮退出。

（5）在Picture1的MouseMove事件中添加如下语句（粗体显示）。效果如图9.20所示。

01 If flag=True Then

02 Select Case penshape

03 Case 1

04 Picture1.Line (oldx,oldy)-(x0,y0) '擦除上一次绘制的直线

05 Picture1.Line (oldx,oldy)-(X,Y) '重新绘制直线

06

07 StatusBar1.Panels(1)="长度："＆length(oldx,oldy,X,Y)

08 StatusBar1.Panels(4)="起：X:"＆Round(oldx,1)＆"，Y:"＆_

09 Round(oldy,1)＆"终：X:"＆Round(X,1)＆"，Y:"＆Round(Y,1)

10 Case 2

11 Picture1.Circle (oldx,oldy),length(oldx,oldy,x0,y0)

12 Picture1.Circle (oldx,oldy),length(oldx,oldy,X,Y)

13

14 StatusBar1.Panels(4)="圆心：X:"＆Round(oldx,1)＆"，Y:"＆_

15 Round(oldy,1)＆"半径R:"＆length(oldx,oldy,X,Y)

16

17 Case 3

18 Picture1.Line (oldx,oldy)-(x0,y0),,B '擦除上一次绘制的矩形

19 Picture1.Line (oldx,oldy)-(X,Y),,B '重新绘制矩形

20

21 StatusBar1.Panels(4)="高度："＆Round(Abs(Y-oldy),1)＆"宽度："＆Round(Abs(X-

22 oldx),1)

23 End Select

24 End If

【代码说明】代码第02～23行的Select Case语句，根据penshape变量的值来判断用户所选择的形状，然后根据不同的形状在状态栏显示不同的信息。

【运行效果】本例运行后，单击各按钮后的效果如图9.20所示。

图　9.20　最终运行效果

至此，本章综合实例——类似于Windows画图板的画图软件的制作基本完成。因为，本章篇幅的因素，其功能并不完善。读者可以继续模仿Windows画图板完善该程序。

9.5　小结

本章所讲的内容都是在Windows操作系统中经常碰到的控件，这些控件比按钮、文本框稍微复杂一些，都会牵扯到很多的事件和执行事件的方法，如单击菜单栏后要执行的操作等。通过本章的学习，读者首先要了解什么是菜单、状态栏和工具栏，然后掌握如何设计Windows窗体中的这些控件。希望读者在掌握这些技术的同时，对窗体开发有更深入的了解。

9.6　本章练习

1.菜单编辑器中的“标题”用于输入菜单名或命令名，即显示在菜单中的名称。例如，“文件”、“新建”、“打开”和“关闭”等。如果想在菜单中，将相关的操作分组使用分隔线分开，可以在“标题”框中输入一个连字符“-”，读者自己创建一个与Word中“文件”菜单相同的菜单。

2.如何为Toolbar控件添加图片？

3.状态栏中的Panel是通过索引号来获取的，那么索引号是从0开始，还是从1开始？

第10章　ActiveX控件

Visual Basic的控件分为两大类，分别是标准控件和ActiveX控件。默认情况下，在Visual Basic工具箱内的控件都是标准控件。而后来通过“部件”对话框添加进来的控件则都属于ActiveX控件，例如前面所学到的工具栏控件、状态栏控件、RichTextBox控件等。本章主要介绍关于ActiveX控件的相关知识。本章包括以下知识点。

❑ActiveX控件的添加方法。

❑使用Calendar控件实现日期的设置和获取。

❑调用Windows中的各种常见对话框。

通过本章的学习，了解一些比较高级的控件应用，熟练掌握各种对话框的事件和功能。

10.1　ActiveX控件概述

ActiveX控件可以使用各种程序设计语言来开发，所有开发出来的控件都要遵循统一的标准。ActiveX控件可以由系统提供，也可以由第三方软件生产者提供，还可以由用户自己开发。

第三方软件生产者开发的ActiveX控件通常以文件的形式存放，其扩展名为.ocx。有些ActiveX控件的发布因为了安装文件，所以安装后在Visual Basic的“部件”对话框（控件选项卡）中就可以看到这些控件的名称列表。例如，安装了用友公司生产的华表控件后，在“部件”对话框里就会自动多出该控件的名称列表，如图10.1中用方框选中的列表项。而窗体靠下的位置用椭圆选中的是该控件在计算机中实际的位置。

图　10.1　“部件”对话框

还有一些ActiveX控件因为没有做安装文件，所以必须通过手动操作才能将控件加入到“部件”对话框的列表中。操作方法为单击“部件”对话框中的“浏览”按钮，打开“添加ActiveX控件”对话框，如图10.2所示。通过该对话框选择要添加到Visual Basic中的ActiveX控件文件，单击“打开”按钮后即可将该控件名称添加到“部件”对话框中的列表内。接下来添加到工具箱中的方法与Visual Basic中其他控件的添加方法相同。如果第三方ActiveX控件是商业性控件，通常都会有详细的帮助文档和示例程序。通过这些文档和示例程序可以学习控件的属性、方法和事件。

图　10.2　“添加ActiveX控件”对话框

10.2　创建自己的控件

在开发应用程序时，如果Visual Basic中没有合适的控件能够使用，则可以采用两种方案解决这一问题。第一种方案是购买商业控件；第二种方案是自己动手创建一个ActiveX控件。本节将介绍如何自己动手创建及使用ActiveX控件。

10.2.1　手动创建ActiveX控件

创建ActiveX控件有两种方法：一是手动创建，二是利用Visual Basic提供的向导程序定制ActiveX控件。在开发应用程序时，经常会将开发单位标志或软件的标志放在软件的大部分窗口中。此时在每个窗口中一个一个地加入图像框，再设置其Picture属性添加图片文件会浪费很多不必要的时间和精力。

【实例10.1】下面通过设计一个能显示软件标志的控件，说明手动创建ActiveX控件的方法。操作步骤如下。

（1）启动Visual Basic后，在“新建工程”对话框中选择“ActiveX控件”图标，如图10.3所示。

图　10.3　“新建工程”对话框

（2）单击“打开”按钮，Visual Basic会生成一个新的工程并添加了一个空窗体，这个窗体和以往所见的窗体有所不同，它没有标题栏和明显的边框，默认名称也不是Form1，而是UserControl1，如图10.4所示。

图　10.4　UserControl1窗体

在创建ActiveX控件时，必须用到UserControl对象，因为在制作一个ActiveX控件时，需要把构成它的一些基本控件添加在UserControl对象上。实际上ActiveX控件就是由UserControl对象和放在其上的控件组成。

（3）在工程资源管理器中单击工程的名称，如图10.5中方框选中的区域。在属性窗口中将名称“工程1”改为"MyControl"。

图　10.5　工程资源管理器和属性窗口

（4）在工程资源管理器中再单击UserControl1窗体，这时属性窗口中的内容就会变为UserControl1窗体的属性，将其名称属性改为"Logo"。

（5）在窗体上画一个图像框Imag1，将其Stretch属性设置为True，并通过其Picture属性加入一幅图片，而后改变Imag1的位置，使其紧挨着窗体的顶边和左边，如图10.6所示。

图　10.6　设计界面一

（6）调整窗体的大小使其恰好包含住图像框Imag1，如图10.7所示。

图　10.7　设计界面二

（7）双击图像框Imag1，打开代码编辑器窗口，编写事件过程如下。

Private Sub Image1_Click()

MsgBox"X X X软件"＆vbCrLf＆"X X X软件公司制作",vbInformation+vbOKOnly

End Sub

（8）保存并运行程序，在有IE浏览器的机器上，会出现一个内部有刚才制作的控件的网页。在网页中单击该控件会弹出一个消息框，如图10.8所示。

图　10.8　IE浏览器中的运行结果

（9）测试结果正常后，就可以将ActiveX控件的程序进行编译，生成.ocx文件了。只有生成.ocx文件之后，其他程序才可以使用这个控件。方法是选择“文件”菜单下的“生成MyControl. ocx”菜单项。在打开的“生成工程”对话框中，选取好文件的路径即可。

10.2.2　使用自己创建的ActiveX控件

【实例10.2】使用自己创建的ActiveX控件的步骤如下。

（1）启动Visual Basic，并新建一个标准EXE工程。

（2）在工具箱中单击鼠标右键，在快捷菜单中选择“部件”菜单项。

（3）在打开的“部件”对话框“控件”选项卡中，使用“浏览”按钮从硬盘中找到并选择MyControl.ocx文件。这样就可以在控件列表中看到自己创建的ActiveX控件了。

（4）在名称列表框中选择该控件并在其前面的复选框中打勾，单击“确定”按钮后，工具箱中就会多出一个控件，如图10.9所示。

图　10.9　工具箱图

（5）同使用其他控件一样，将该ActiveX控件画到窗体上。

（6）运行程序，并单击该控件后如图10.10所示。

图　10.10　使用ActiveX控件的运行结果

10.2.3　使用向导定制控件

【实例10.3】使用向导创建ActiveX控件的操作步骤如下。

（1）启动Visual Basic后，在“新建工程”对话框中选择“ActiveX控件”图标，创建一个ActiveX工程。

（2）修改工程名称和窗体名称，并在窗体上使用图形和现有的控件绘制界面。例如，在窗体上加入两个标签，取其默认名称分别为Label1和Label2，如图10.11所示。

图　10.11　窗体上的两个标签

（3）选择“工程”|“添加用户控件”菜单项，在打开的“添加用户控件”对话框“新建”选项卡中选择“VB ActiveX控件界面向导”图标，如图10.12所示。

图　10.12　“添加用户控件”对话框“新建”选项卡

（4）单击“打开”按钮后，会出现“ActiveX控件接口向导”对话框的第一屏“介绍”。

（5）单击“下一步”按钮后，会出现第二屏“选定接口成员”，如图10.13所示。在第二屏内根据定制控件的需要为其挑选属性、方法和事件。窗口左边的列表提供了定制控件使用的标准属性、方法和事件。右边的列表内是已经为定制控件选好的属性、方法和事件。中间的4个箭头按钮用于在两个列表框之间移动列表项。

图　10.13　“ActiveX控件接口向导”对话框第二屏

（6）单击“下一步”按钮，进入第三屏，如图10.14所示。

图　10.14　“ActiveX控件接口向导”对话框第三屏

第三屏用来将自己定义的属性、方法和事件的名称添加到用户控件。例如，要给控件加上“标签一标题”和“标签二标题”两个属性，可以单击上图的“新建”按钮，此时会出现“添加自定义成员”对话框，在“类型”中选择“属性”选项，在“名称”栏中输入“标签一标题”，如图10.15所示，然后单击“确定”按钮。使用同样的方法将“标签二标题”属性加入到自定义成员中。

图　10.15　“添加自定义成员”对话框

（7）单击“下一步”按钮后，会出现第四屏——设置映射窗口，如图10.16所示。

图　10.16　“ActiveX控件接口向导”对话框第四屏

在第四屏中，将正在创建的ActiveX控件的具体属性、方法和事件与组成该控件的成员的属性、方法和事件联系（映射）起来。比如，从“公有名称”列表中选择“标签一标题”，然后在“控件”栏选择Label1，从“成员”栏选择Caption，如图10.17所示，这时ActiveX控件“标签一标题”属性和Label1控件的Caption属性就成为了一个概念。在应用程序中，设置该ActiveX控件的“标签一标题”属性，实际上就是在设置控件中的成员Label1的Caption属性。

图　10.17　映射“标签一标题”属性

（8）设置完映射后，单击“下一步”按钮，会出现设置默认属性的界面，设置好默认属性后再单击“下一步”按钮，在最后的提示“已完成”的界面中单击“完成”按钮，即可创建一个ActiveX控件。将该控件编译并生成.ocx文件后就可以使用了。

10.3　日历控件

在应用程序的输入界面中经常会遇到出生日期、生产日期、入住日期等需要输入日期的文本框。此时，怎样控制用户按照软件的日期格式输入日期成了一个让人头疼的问题。幸运的是Visual Basic为使用者提供了一种选择日期的控件——日历控件。该控件也是一个ActiveX控件，添加到工具箱中的方法是使用如前所述的方法打开“部件”对话框（“控件”选项卡），从列表中选择"Microsoft Calendar Controls 8.0"，确定后就可以在工具箱中看到多出来的日历控件的图标
。

10.3.1　通过日历控件获得日期

【实例10.4】编写程序，通过日历控件获得日期。

【实现步骤】操作步骤如下。

（1）在窗体中画一个日历控件，其默认名称为Calendar1，如图10.18所示。

图　10.18　实例10.4设计界面

（2）双击Calendar1打开代码编辑器窗口，编写如下代码。

01 Private Sub Calendar1_Click()

02 MsgBox"您选择的日期是："＆vbCrLf＆Me.Calendar1.Value

03 End Sub

【代码说明】代码第02行使用一个提示框输出用户选择的日期，其中用Calendar1.Value来获取日期值。vbCrLf用来换行。

【运行效果】运行程序，用鼠标单击日历控件中的某个日期，例如单击15日时的运行结果，如图10.19所示。

图　10.19　实例10.4运行结果

说明　Calendar控件的Value属性是用来获取被选择的日期（包含年月日）。除Value属性以外，该控件常用的属性还有Year、Month和Day，分别用来获取被选择日期的年、月和日。

10.3.2　日历控件独有的事件

Calendar控件有几个独有的事件，如NewYear、NewMonth等。当日历控件中的年份被更改时触发NewYear事件；NewMonth事件在日历控件中的月份被更改时触发。而更改日期时会先后触发两个事件分别是BeforeUpdate和AfterUpdate。

【实例10.5】编写程序，试验NewYear、NewMonth、BeforeUpdate和AfterUpdate四个事件。界面设计与实例10.4相同。

编写事件过程如下。

01 Private Sub Calendar1_AfterUpdate()

02 MsgBox"日期改变成功！"

03 End Sub

04

05 Private Sub Calendar1_BeforeUpdate(Cancel As Integer)

06 Dim x As Integer

07 x=MsgBox("您真的要改变日期么？",vbYesNo+vbQuestion)

08 If x=7 Then

09 Cancel=1 '撤销改变日期的操作

10 End If

11 End Sub

12

13 Private Sub Calendar1_NewMonth()

14 MsgBox"您选择了新的一月！"

15 End Sub

16

17 Private Sub Calendar1_NewYear()

18 MsgBox"您选择了新的一年！"

19 End Sub

【代码说明】第05～11行是Calendar控件的BeforeUpdate事件，其中第09行有一个参数Cancel，该参数的作用与窗体Unload事件中的Cancel参数相同，用来取消之前的操作。

【运行效果】本例与实例10.4的效果基本相同，这里不再给出。

10.4　通用对话框控件

Visual Basic的通用对话框CommonDialog控件提供了一组基于Windows的标准对话框界面。使用其可以减轻编程工作量。此通用对话框有6种类型：打开对话框（Open）、另存为对话框（Save As）、颜色对话框（Color）、字体对话框（Font）、打印对话框（Print）、帮助对话框（Help）等。通过设置CommonDialog控件的Action属性可以显示指定类型的对话框。此外，也可以使用控件的Show方法显示指定类型的对话框。Action属性和Show方法的具体取值说明见表10.1。

例如，

CommonDialog1.Action=1或CommonDialog1. ShowOpen

都可以显示“打开”对话框。

CommonDialog1.Action=4或CommonDialog1. ShowFont

可以显示“字体”对话框等。

Visual Basic的CommonDialog控件不是内部标准控件，因此必须手动添加到工具箱中。方法是：右击工具箱，选择“部件”菜单项打开“部件”对话框，从控件选项卡列表中选中"Microsoft Common Dialog Controls 6.0"前面的复选框，然后单击“确定”按钮。此时，在工具箱中便能看到Common Dialog控件的图标
。

10.4.1　使用“打开”对话框

【实例10.6】设计一个图片预览的程序，使用打开对话框定位图片位置。

界面设计如图10.20所示。

图　10.20　实例10.6界面设计

编写事件过程如下。

01 Private Sub cmdOpen_Click()

02 cd1.ShowOpen '显示打开对话框

03 img1.Picture=LoadPicture(cd1.FileName) '给图像框添加图片

04 End Sub

【代码说明】单击“打开”按钮后，代码第02行会显示打开对话框。代码第03行是加载指定位置的图片。

【运行效果】运行程序，单击“打开”按钮，此时便会显示一个Windows的“打开”对话框，如图10.21所示。

图　10.21　打开对话框

假设，通过该对话框选中了路径为"D:\SQBT\mori.jpg"的图片文件，单击“打开”按钮时，控件cd1的FileName属性的值就会被自动设置为字符串"D:\SQBT\mori.jpg"，并关闭对话框，之后程序继续向下执行第二条语句，此时cd1.FileName中因为有了"D:\SQBT\mori.jpg"，所以图像框中就会加载图片mori.jpg，运行结果如图10.22所示。

图　10.22　实例10.6运行结果

本例中的“打开”对话框会让用户感觉很不舒服，因为它不具有按图片类型筛选图片文件的功能。解决这个问题的方法是在设计阶段，右击CommonDialog控件cd1，从弹出菜单中选择“属性”菜单项，打开cd1的“属性页”对话框，如图10.23所示。

图　10.23　“属性页”对话框（“打开/另存为”选项卡）

在“打开/另存为”选项卡的“过滤器”栏中输入以下内容。

所有文件（*.*）|*.*|JPG文件（*.JPG）|*.JPG|bmp文件（*.bmp）|*.BMP

单击“确定”按钮退出，再次运行程序便可以发现“打开”对话框的“文件类型”下拉列表内出现了3个列表项，如图10.24所示，并可以使用它们筛选不同类型的文件。

图　10.24　打开对话框（文件类型下拉列表）

上面在“过滤器”栏中输入的内容里，将“所有文件（*.*）”、“JPG文件（*.JPG）”、“bmp文件（*.bmp）”等称为描述符，它们的作用只是起到提醒用户的目的，跟过滤文件没有任何关系。这些描述符将在“文件类型”列表中出现。另外将“*.*”、"*.JPG"、"*.BMP"称为过滤器，它们起到真正的过滤文件的作用。其通用格式如下所示。

描述符1|过滤器1|描述符2|过滤器2|……

说明：描述符与过滤器之间的竖杠“|”被称为管道符，在键盘上的位置为BackSpace键的左侧，“\”的上档键。

在如图10.23所示的“属性页”对话框中还可以设置对话框的标题、文件名栏中要显示的默认名称和对话框被打开时的初始路径。下面列出“打开/另存为”对话框的常用属性及解释。

10.4.2　使用“保存”对话框

“保存”对话框和“打开”对话框实际上是一种对话框，所以它们的属性设置和意义是一样的。10.3节介绍了通过“属性页”对话框设置CommonDialog控件属性的方法，下面介绍使用程序设置这些属性的方法。

【实例10.7】设计一个能够调用“保存”对话框的程序。“保存”对话框的要求如下。

（1）标题显示为“保存对话框试验”。

（2）默认保存路径为"D:\SQBT"。

（3）文件名栏中显示的默认文件名为“呵呵”。

（4）文件类型列表中能够选择mp3文件、mid文件和wma文件，默认显示mid文件。“保存”对话框的要求效果如图10.25所示。

图　10.25　实例10.7中的保存对话框

程序界面设计如图10.26所示。

图　10.26　实例10.7界面设计

编写的事件过程如下。

01 Private Sub Command1_Click()

02 cd1.DialogTitle="保存对话框试验"

03 cd1.InitDir="d:\sqbt"

04 cd1.FileName="呵呵"

05 cd1.Filter="mp3文件|*.mp3|mid文件|*.mid|wma文件|*.wma"

06 cd1.FilterIndex=2

07

08 cd1.ShowSave

09 End Sub

【代码说明】代码第02～06行是设置保存对话框的各个属性，如默认保存地址、保存对话框的标题等，第08行最关键，用来调用保存对话框。

【运行效果】本例的运行效果如图10.26所示。

10.4.3　使用“颜色”对话框

颜色对话框如图10.27所示。

图　10.27　颜色对话框

实例10.8】编写通过“颜色”对话框设置窗体背景颜色的程序。

本例界面由两个对象组成，分别为窗体Form1和CommonDialog控件cd1，编写事件过程如下。

01 Private Sub Form_Click()

02 cd1.ShowColor

03 Me.BackColor=cd1.Color

04 End Sub

【代码说明】代码第03行用到了CommonDialog控件的Color属性，该属性返回用户在“颜色”对话框中选择的颜色。

如前所述，Visual Basic是用数值代表颜色的，因此Color属性实际上返回的也是数字。在此可以做一个小实验。将上面的程序修改如下。

05 Private Sub Form_Click()

06 cd1.ShowColor

07 MsgBox cd1.Color

08 End Sub

通过该程序就可以知道哪个颜色由哪个数代表了，例如，0代表黑色、255代表红色、16777215代表白色等。

【运行效果】本例打开颜色对话框的效果如图10.27所示。

10.4.4　使用“字体”对话框

要显示字体对话框，首先应该设置Flag属性。该属性的取值及解释见表10.3。

例如下列语句

cd1.Flags=3

cd1.ShowFont

执行后，显示的“字体”对话框如图10.28所示。在这种对话框中不能设置下画线、字体颜色等字体效果。

图　10.28　字体对话框（无字体效果）

而如果将上面的语句改为如下语句。

cd1.Flags=3+256

cd1.ShowFont

显示的“字体”对话框中就会有字体效果的选项，如图10.29所示。

图　10.29　字体对话框（有字体效果）

在“字体”对话框中选择的字体会被FontName属性保存，选择的大小会被FontSize属性保存。例如，在“字体”中选择了“宋体”、在“大小”中选择了“36”，假设CommonDialog控件的名字叫cd1，则表达式如下。

cd1.FontName的值为“宋体”

cd1.FontSize的值为36

如果在“字形”中选择了“粗体”，则FontBold属性便会为True；而如果选择了斜体FontItalic属性便会为True；当选择“常规”，FontBold和FontItalic都会为False；而如果选择了“粗斜体”FontBold和FontItalic都会为True。

“效果”里的删除线和下划线对应FontUnderline和FontStrikethru两个属性，它们都是布尔型的属性。颜色对应color属性。

如果希望设置“字体”对话框的初始值，可以使用CommonDialog控件“属性页”对话框的“字体”选项卡，例如，设置默认字体为“宋体”、默认大小为72、默认字形为“粗体”的方法，如图10.30所示。

图　10.30　“属性页”对话框（字体选项卡）

10.4.5　使用“打印”对话框

关于“打印”对话框的常用属性，可以参考表10.4。

表中属性的初始值可以通过“属性页”对话框的“打印”选项卡进行设置。调用“打印”对话框的语句如下。

cd1.ShowPrinter或cd1.Action=5

10.4.6　对单击“取消”按钮的处理

所有类型的对话框中都有一个“取消”按钮。如果单击“取消”按钮，就应该提前结束事件过程（或者跳过执行某段代码），这样会避免出现错误。

例如实例10.5实际上存在一个错误：当单击“颜色”对话框中的“取消”按钮时，背景颜色不变才是正确的。然而实例10.5中窗体的背景颜色变成了黑色。原因是即使单击了“取消”按钮，程序依旧向下执行第02条语句，将默认颜色（黑色）赋值给了窗体的BackColor属性。下面修改实例10.5的这一问题，修改后的程序如下。

01 Private Sub Form_Click()

02 cd1.CancelError=True

03 On Error GoTo ErrHandler

04

05 cd1.ShowColor

06 Me.BackColor=cd1.Color

07

08 ErrHandler:

09 End Sub

【代码说明】程序中第02行用到了CommonDialog控件的CancelError属性，如果该属性被设置为True，当用户单击“取消”按钮时，就会产生一个错误。程序第03行就是捕获这一错误的语句，它的作用是出现错误马上跳转到第08行的ErrHandler标号处。这样一来，执行程序时就跳过了不该执行的语句。其他对话框的“取消”按钮的问题，也可以使用该方法解决。

10.5　小结

本章首先介绍了ActiveX控件，这是VB语言独有的一种控件，在其他语言中很少碰到。笔者曾经参与开发C#的项目，普通的程序都是由C#来开发，而一些专门的控件都是用VB生成，供C#开发人员调用。本章还介绍了日历控件和对话框控件。因为VB是Windows操作系统上的语言，所以可以轻松地调用Windows中的一些窗体或程序。

10.6　本章练习

1.复习一遍在VB工具箱中添加ActiveX控件的步骤。

2.说出5种Windows中常见的对话框，并在VB中实现它们。

3.Private Sub Calendar1_BeforeUpdate(Cancel As Integer)事件表示什么？其中Cancel参数有几个值？分别代表什么意义？

第11章　文件处理技术

文件是存放在外部介质上的数据集合。很多程序需要读写磁盘文件，所以涉及如何建立文件，如何从文件中读数据，如何向文件写数据等问题。本章将讨论与文件操作相关的内容。本章包括以下知识点。

❑数据的位、字节、文件的层次。

❑各种文件或文件夹控件的使用方法。

❑打开、读出、写入和关闭顺序文件、随机文件和二进制文件。

本章内容基本涵盖了VB程序设计中常用的文件及其相关操作知识。通过本章的学习，读者可以熟练应用文件系统控件进行文件的定位，进行文件读写操作，能够灵活应对实际软件开发中的文件处理操作。

11.1　文件的类型

在Visual Basic中根据文件的结构和访问方式，可将文件分为3类：顺序存取文件、随机存取文件和二进制存取文件。下面分别进行介绍。

11.1.1　顺序存取文件

顺序存取是将要保存的数据，按照先后顺序存入磁盘，读取时也是按照这个顺序读取。以顺序存取的方式保存数据的文件叫做顺序存取文件，也称顺序文件。顺序文件由很多记录组成，这些记录的长度可以是任意的。

顺序文件只提供第一个记录的存储位置，因此要查找一个数据，必须从文件头开始查找，直到找到目标为止。若要修改文件中的某些数据，只能将所有数据全部读入内存进行修改，改好后再重新写入磁盘，无法灵活地对文件进行数据的拼接、插入、修改、转移等操作。顺序型文件只适用于要求占用少量空间，不常用于进行数据修改和查找的文件。

11.1.2　随机存取文件

以随机存取方式存取的文件称为随机文件。随机文件的每条记录都占用相同的长度，类似于二维表格。每条记录前都有记录号，所以在读取文件时，只要给出记录号，就可以迅速找到该条记录，并将该记录读出；若对该记录作了修改，需要写到文件中时，也只要指出记录号，新记录将自动覆盖原有记录。所以，随机文件的访问速度快，读、写和修改都非常灵活。该类文件适合于数据库访问、快速查找、经常更新数据的文件，这种文件一般占用空间较大。

11.1.3　二进制存取文件

二进制文件中的数据均以二进制方式存储，存储单位是字节。它允许程序以任何方式组织和访问数据，可以把文件指针移到文件的任何地方，这类文件的灵活性最大，但编程量也最大。

11.2　顺序存取文件

相对于其他两种类型的文件，顺序存取文件的操作方法比较简单。本节将介绍打开/关闭顺序文件、读/写顺序文件的方法，并介绍了3个常用文件操作函数。

11.2.1　打开顺序文件

在对文件进行操作之前，必须打开文件，同时应该告之操作系统对文件进行读操作还是写操作，将数据存到什么地方。打开文件用Open语句。其使用语法如下。

Open 文件名 For 模式 [访问方式] [Lock] As [#]文件号 [Len=记录长度]

其中，文件名是指要打开的文件，可包含完整路径名。

模式是说明文件打开方式，对顺序文件而言，有三种模式。

❑Output（输出）：将数据从内存输出到文件（写文件）。

❑Input（输入）：将数据从文件调入到内存（读文件）。

❑Append（添加）：将数据添加在文件尾部。

技巧　Visual Basic中的输出（Output）、输入（Input）是站在内存的位置上所说的。可以将变量看作内存，将文件看作磁盘。这样，输出（Output）就可以理解为从内存（变量）向磁盘（文件）输出数据；输入（Input）可以理解为从磁盘（文件）向内存（变量）输入数据。

访问方式是说明打开文件所允许的操作，有三种方式。

❑Read：只读。

❑Write：只写。

❑ReadWrite：读写皆可，只适用于顺序文件的Append模式。

Lock参数是指明其他进程对打开文件所允许的操作，有三种方式。

❑Shared：可对此文件读写。

❑LockRead：不允许读此文件。

❑LockWrite：不允许写此文件。

文件号是一个1～255的整数，用于代表这个文件。新的文件号可用FreeFile()函数获得。

记录长度是小于32 767的整数。对顺序文件来说，它是指缓冲区分配的字符个数；对随机文件来说，它是文件中记录的长度。例如：

Open"c:\aaa.doc"For Input As #1 '以读取数据方式打开文件

Open"c:\bbb.dat"For Output As #2 '以写入数据方式打开文件

Open"c:\ccc.txt"For Append As #3 '以追加数据方式打开文件

11.2.2　写入顺序文件

写入顺序文件的命令有两种。

（1）第一种格式如下。

格式一：Print #文件号,[输出列表]

说明　如果省略输出列表参数，则写入一空行（逗号不能省略）。

例如：

01 Print #1,"TEST" '写入字符串数据

02 print #1, '写入空行

03 print #1,"zonel";tab;"zone2" '数据写入两个区

04 print #1,"Hello";"";"welcome!" '用空格分开2个字符串

05 print #1,spc(8) '写入八个空格

06 print #1,tab(5);99 '将数据99写在第5列

（2）第二种格式如下。

格式二：Write #文件号,[输出列表]

Write#的功能基本上与Print#语句相同，区别在于Write#是以紧凑格式存放，即在数据项之间插入逗号（,），并给字符串上加上双引号。

写文件时可以用Output和Append两种模式打开文件。以Output模式打开文件时若文件中已有数据，则删除旧数据，写入新数据（替换内容）。以Append模式打开文件与此很相似，二者的差别在于以Append模式打开文件时，如果该文件已有数据，Visual Basic并不删除旧数据，随后的输出命令把新数据追加到该文件尾部。

【实例11.1】例如下面的程序将在aaa.txt文件中以Output模式写入数据。

01 Open"aaa.txt"For Output As #1

02 Print #1,"写入文件试验，替换了文件中以前的内容"

03 Print #1, '写入一个空行

04 Print #1,"世界,";"";"你好！"

05 Close #1 '关闭文件

【代码说明】第01行要读者注意Output模式的格式，第02～04行分别写入数据，然后第05行关闭文件。

【运行效果】运行程序后aaa.txt文件中的内容如图11.1所示。

图　11.1　aaa.txt文件内容（Output模式和print#语句）

【实例11.2】再例如使用Append模式写入数据。

01 Open"aaa.txt"For Append As #1

02 write #1,"这是追加试验，在旧的内容中追加了数据"

03 Close #1

【代码说明】第01行要读者注意Append模式的格式，第02行插入数据，第03行关闭文件。

【运行效果】运行程序后aaa.txt文件中的内容如图11.2所示。

图　11.2　aaa.txt文件内容（Append模式和write#语句）

注意　使用write语句写入的内容上有双引号（＂＂）。

11.2.3　读取顺序文件

读取顺序文件的格式有三种。

（1）第一种格式如下。

格式一：Input #文件号,变量列表

该语句只能正确读取用Write语句写入数据的文件。

例如：使用Input语句将文件ccc.txt内的数据读入str1变量中。

01 Dim str1 As String

02 Open"c:\ccc.txt"For Input As #1

03 Input #1,str1

04 Close #1

（2）第二种格式如下。

格式二：Line Input #文件号,字符型变量

该语句一次只能读取一行数据（以回车换行为行尾，但读取的数据不包括回车符和换行符）。

例如：使用Line Input语句从系统文件boot.ini中一行一行地读取数据到文本框Text1中。

01 Dim inputdata As String

02 Text1.Text=""

03 Open cd1.FileName For Input As #1

04 Do While Not EOF(1)

05 Line Input #1,inputdata

06 Text1.Text=Text1.Text+inputdata+vbCrL

07 Loop

08 Close #1

注意　要在文本框中显示多行文本，必须设置其MultiLine属性为True。

（3）第三种格式如下。

格式三：Input$（读取的字符数，#文件号）

Input函数的返回值为字符串型，它能返回系统所能读出的字符，包括逗号、回车符、空白列、换行号、引号和前导空格等。

例如：使用Input函数将boot.ini的内容一次性的读入到文本框Text1中。

01 Text1.Text=""

02 Open"c:\boot.ini"For Input As #1

03 Text1.Text=Input(LOF(1),1)

04 Close #1

11.2.4　关闭顺序文件

打开文件进行了存取等操作之后一定要关闭文件。关闭文件的格式如下。

Close [#文件号1][，#文件号2]……

若Close语句后没有文件号，则关闭所有打开的文件。

11.2.5　文件操作函数

在Visual Basic中，无论哪种文件类型(随机、顺序、二进制)都会经常要用到下列函数。

1.LOF( )函数

以字节方式返回被打开文件大小。其调用语法如下。

LOF(文件号)

例如调用语句：

LOF(1)

的作用是，返回#1文件的长度。

2.LOC( )函数

返回被打开文件的当前位置。其调用语法如下。

LOC(文件号)

对于顺序位置的计算以128字节为单位，返回当前位置是第几个128字节；对于随机文件，它返回当前读写的记录号；对于二进制文件，返回当前的字节位置。

3.EOF( )函数

返回值指出读文件过程中是否到了文件末端。其调用语法如下。

EOF(文件号)

返回True，则表示到达文件末端；否则返回False。对于顺序文件，读写操作不能同时进行。每进行一次读或写操作，都必须重新打开文件，读或写完之后再关闭文件。

11.3　随机存取文件

在随机访问模式中，文件的存取是按记录进行操作的，每个记录都有记录号并且长度全部相同。所以无论是从内存向磁盘写数据，或是从磁盘读数据，都需要事先定义内存空间；而内存空间的分配是靠变量声明来进行的，所以不管是读操作还是写操作都必须事先在程序中定义变量。变量要定义成随机文件中一条记录的类型，一条记录又是由多个数据项组成的，每个数据项有不同的类型和长度。

因此在程序的变量说明部分采用用户自定义类型说明语句，首先定义记录的类型结构，然后再将变量声明成该类型，这样就为这个变量申请了内存空间用于存放随机文件中的记录。例如：

01 Type student

02 StudentName As String*10

03 Number As String*7

04 Score As String*3

05 End Type

06 dim stu1 as student '声明一个student类型的变量

11.3.1　随机文件的打开和关闭

打开文件仍用Open语句。其使用语法如下。

Open 文件名 For Random AS #文件号 [Len=记录长度]

文件以随机访问模式打开后，可以同时进行写入与读出操作。在Open语句中要指明记录的长度，记录的长度的默认值是128个字节，关闭文件仍然使用Close语句。

11.3.2　写入随机文件

向随机文件写数据用Put语句，其使用语法如下。

Put [#]文件号,[记录号],记录变量

其中#、记录号是可选的。该语句是将一个记录变量的内容写入所打开的磁盘文件中指定的记录位置处。记录号是大于1的整数，表示写入的是第几条记录；如果忽略记录号则表示在当前记录后插入一条记录。

例如：Put #1,stu1

表示将变量stu1的内容插入到文件号为#1的当前记录之后。

11.3.3　读取随机文件

使用Get语句从随机文件读取数据，其使用语法如下。

Get [#]文件号,[记录号],记录变量

其中，记录变量的数据类型必须同文件中记录的数据类型一致。该语句是从磁盘文件将一条由记录号指定的记录内容读入记录变量中。记录号是大于1的整数，表示对第几条记录进行操作，如果忽略记录号，则表示读出当前记录后的那一条记录。

例如：Get #1,3,stu1

表示把文件号为#1中第三条记录读到stu1变量中。

11.4　二进制存取文件

二进制访问模式与随机访问模式类似，读写语句也是GET和PUT，区别在于二进制模式的访问单位是字节，而随机模式的访问单位是记录。

在二进制访问模式中，可以把文件指针移到文件的任何地方。文件刚刚被打开时，文件指针指向第一个字节，以后将随着文件处理命令的执行而移动。二进制文件与随机文件一样，文件一旦打开，就可以同时进行读写。

11.4.1　二进制文件的打开和关闭

以二进制模式打开文件，其使用语法如下。

Open 文件名 For Binary As #文件号

可以看到，二进制文件访问中的Open与随机存取的Open不同，它没有指定Len部分。

11.4.2　二进制文件的读写操作

1.写数据操作

向二进制文件写数据的语法如下。

Put #文件号,[位置],变量

其中“位置”是按字节计数的读写位置。若默认，则文件指针从头到尾顺序移动。Put语句向文件写的字节数等于变量长度。

2.读数据操作

从二进制文件读数据的语法如下。

Get #文件号,[位置],变量

该语句从文件读出的字节数等于变量长度。

在二进制文件读写的过程中，常常用到Seek函数和Seek语句。

❑Seek()函数的使用语法如下。

Seek(文件号)

该函数用来返回当前文件指针的位置。

❑Seek语句的使用语法如下。

Seek(文件号,num)

该语句用来将文件指针定位到num字节处。

11.5　常用文件操作语句

Visual Basic给用户提供了一些文件操作语句。利用这些语句，可以复制、删除及重命名现有的文件，还可以建立新文件夹、删除旧文件夹等。

11.5.1　复制、删除及重命名文件

1.FileCopy语句

格式：FileCopy source ,destination

功能：复制一个文件。

说明：（1）参数source和destination分别表示要复制的源文件名和目标文件名。（2）FileCopy语句不能复制一个已打开的文件。

例：想要将C盘下的db1.mdb复制到D盘下。

FileCopy"c:\db1.mdb","d:\db1.mdb"

2.Kill语句

格式：Kill pathname

功能：删除文件。

说明：Pathname中可以使用通配符“*”和“？”。

例：想要删除C盘下所有扩展名为.txt的文件。

Kill"c:\*.txt"

3.Name语句

格式：Name oldpathname AS newpathname

功能：重新命名一个文件和目录。

说明：（1）NAME具有移动文件的功能。（2）在oldpathname和newpathname中不可以使用通配符“*”和“？”。（3）不能对一个已打开的文件使用NAME语句。

例：将C盘下"123.txt"重新命名为"ttt.txt"并将其移动到D:\SQBT文件夹下。

Name"c:\123.txt"As"d:\sqbt\ttt.txt"

例：将C盘下的"aa"文件夹重新命名为以当前日期命名的文件夹。

Name"c:\aa"As"c:\"＆Date

注意　使用Name语句只能移动文件，不能移动文件夹。

11.5.2　判断文件或文件夹是否存在

在编程时经常会需要判断文件或文件夹是否存在，然后根据结果编写随后的程序。Visual Basic中通常会使用Dir$函数解决判断是否存在的问题。

1.判断文件是否存在

例如，判断C盘下是否存在"123.txt"文件的方法如下。

01 Private Sub Command1_Click()

02 If Dir$("c:\123.txt")=""Then

03 MsgBox"该文件不存在!"

04 Else

05 MsgBox"文件存在！"

06 End If

07 End Sub

2.判断文件夹是否存在

例如判断C盘下是否存在"Downloads"文件夹的方法如下。

01 Private Sub Command1_Click()

02 If Dir$("c:\Downloads",vbDirectory)=""Then

03 MsgBox"该文件夹不存在!"

04 Else

05 MsgBox"文件夹存在！"

06 End If

07 End Sub

11.5.3　新建文件夹和删除文件夹

1.Mkdir语句

格式：MkDir path

功能：创建一个新的文件夹（目录）。

例：判断C盘下是否有"data"文件夹，如果没有就新建一个"data"文件夹。

If Dir$("c:\data",vbDirectory)=""Then

MkDir"c:\data"

End If

2.RmDir语句

格式：RmDir path

功能：删除一个存在的文件夹。

说明：Rmdir不能删除一个含有文件的目录。如要删除，则应先使用KILL语句删除所有文件。

例：删除C盘下刚建立的"data"文件夹（data为空文件夹）。

RmDir"c:\data"

11.6　应用案例——txt文件编辑器

【实例11.3】编程实现txt文件编辑器，运行效果如下。

❑可以打开浏览txt类型的文件。

❑可以将编辑的文本保存为txt类型的文件。

界面设计如图11.3所示。

图　11.3　实例11.3设计界面

各对象属性设置见表11.1。

编写事件过程如下。

01 Option Explicit

02

03 Private Sub Form_Load()

04 cd1.Filter="txt文件|*.txt"

05 End Sub

06

07 Private Sub mnuFileOpen_Click()

08 Dim inputdata As String

09 cd1.CancelError=True

10 On Error GoTo ErrHandler '如果出现错误，则跳转到ErrHandler标号处

11 cd1.ShowOpen '显示打开对话框

12 Text1.Text=""

13 Open cd1.FileName For Input As #1 '以读取方式打开文件

14 Do While Not EOF(1) '如果不到文件末尾，则继续循环

15 Line Input #1,inputdata '按行读取文件中的内容，并保存到inputdata变量中

16

17 Text1.Text=Text1.Text+inputdata+vbCrLf

18 Loop

19 Close #1 '关闭文件

20 ErrHandler:

21 End Sub

22

23 Private Sub mnuFileQuit_Click()

24 Unload Me '卸载窗体本身

25 End Sub

26

27 Private Sub mnuFileSave_Click()

28 Dim inputdata As String

29 cd1.CancelError=True

30 On Error GoTo ErrHandler

31 cd1.ShowSave '显示保存对话框

32 Open cd1.FileName For Output As #1 '以改写方式打开文件

33 Print #1,Text1.Text '将文本框内容写入到文件中

34 Close #1

35 ErrHandler:

36 End Sub

【代码说明】代码第04行首先限定打开对话框和保存对话框的文件类型。代码第07～21行实现了打开对话框的功能，代码第27～36行实现了保存对话框的功能。

【运行效果】本例的运行界面如图11.3所示。

11.7　小结

从实例来看，本章只在最后介绍了一个完整的案例，实现了打开对话框和保存对话框的功能。但全章介绍的其实就是这个功能，虽然看起来很简单，但本书只针对了一些简单数据，如文本文件。如果碰到比较复杂的数据，如图像的打开和保存，则需要深入学习11.4节的知识。希望读者通过本章的学习，了解一些文件操作，如打开、写入、读取、保存等。

11.8　本章练习

1.用于向文件内随机写入数据的语句是什么？

2.FlieListBox控件的Pattern属性的作用是什么？

3.如果在E盘VB文件夹下已存在Exercise.TXT数据文件。执行下列代码的结果是什么？

Open"E:\VB\Exercise.TXT"For Output As # 1

4.如果在E盘VB文件夹下不存在Exercise.TXT数据文件。执行下列代码的结果是什么？

Open"E:\VB\Exercise.TXT"For Intput As # 1

5.使用磁盘和目录的相关语句，以及文件的基本操作，在E:\下创建MyFile文件夹，并在该文件夹中创建MyTest.TXT文件，文件中写入Hello VB!内容。

第12章　数据库编程基础

在各行各业的数据处理应用中，都会用到数据库技术。数据库技术是针对大量数据存储和数据处理的一门技术。针对这一技术，有专门的数据库开发系统（如Access、FoxPro、SQL Server、Oracle等），在相应的操作系统平台上，为建立数据库管理系统提供了良好的软件开发环境。Visual Basic最初是为了能够比较容易地创建Windows应用程序而诞生的，但随着相关技术的不断发展和Visual Basic自身功能的完善，Visual Basic的应用范围也越来越广泛。其中，Visual Basic 6.0在数据库设计方面的强大功能，让许多编程人员爱不释手。本章包括以下知识点。

❑VB访问数据库的方式。

❑学习Access创建数据库和表的操作。

❑在SQL语句窗体中，使用SQL语言实现数据查询。

本章主要介绍Visual Basic 6.0中数据库设计方面的基本概念，举例介绍Access数据库的相关知识，并着重介绍SQL语言的使用。

12.1　数据库结构和数据访问方式

要学习数据库，首先应当明白数据库结构。简单地说，数据库由众多数据表组成。这些数据表之间的关系也反映在数据库内。每一个数据表都类似于一个二维表格，但是表格内填写什么类型的数据，数据长度是多少等都是有严格规定的。

Visual Basic有很多种访问数据库的方式，可以通过各类控件访问，也可以通过目前最流行的ADO数据库访问技术访问。这些方式各有优缺点，在使用时应当根据实际情况选择具体的访问方式。

12.1.1　基本概念

1.数据库（Database）

数据库是按照一定方式组织并存储的相关数据的集合，是数据库管理系统存储和处理的基本数据的组合。关系型数据库具有最小的冗余度、数据独立性、数据共享和安全可靠、保密性能好的特点。

例如，学校图书馆图书管理系统，主要任务是图书管理，与之相关的数据有书名、作者名、出版社名、出版时间、数量、存放位、借阅证号和读者名等，凡是与图书进馆、上架、节约有关的数据都要登记和存储，将这些数据进行有机组合，建立一个有关图书管理系统的数据库。

2.数据表（Table）

在关系型数据库中，数据以数据表的形式存放。数据表是数据库的基本单元，它是按照行和列排列起来的一些相关信息的集合。每个数据表中包含若干条记录，每个记录又包含若干个字段。一个数据库可由一个或多个数据表组成。

简单地说，数据表就是一个二维表格。例如：存储某学校的学生学籍信息的数据表12.1，就是一个二维表格。

3.记录（Record）

数据表中的“行”称为记录。例如，表12.1中共有9条记录。要注意，第1行“学号，姓名，……”不算记录。

4.字段（Field）

数据表中的“列”称为字段。每一个字段都有自己的字段名、数据类型、数据宽度等。例如表12.2中的“性别”字段，其字段名是“性别”，数据类型是“字符串”数据类型，数据宽度是两个字节。其中，字段名也被称为字段变量。

5.索引（Index）

为了提高访问数据库的效率，可以对数据库建立索引。索引是按照某个或几个字段建立的排序列表，用户可以通过不同的分类和索引条件把记录重新组合排列。在表12.1中，如果记录很多，在查询时，经常按照某种分类来排序，比如按照学号升序（由小到大）排列，即按照学号索引升序排列。

6.结构化查询语言SQL（Structured Query Language）

SQL是一种关系数据库语言，可用于定义查询对象，直接创建和操作Jet数据库以及操作客户/服务器数据库。

7.关系型数据库（Relation Database）

按照关系模型构造的数据库称为关系型数据库。关系——指满足一定规范要求的二维表。

在一个关系数据库中可以存放多个二维表，通过关键字可以建立表与表之间的联系。表与表之间除了一对一的联系外，还可能有一对多或多对多的联系。

关系型数据表的特点如下。

（1）每一个字段不可再分解，也不能有名字相同的字段。

（2）每一列中的数据都有相同的数据类型。

（3）表中没有内容完全相同的行（记录）。

8.关键字（Key）

关键字是用来区别两条记录的字段或字段组合。它分为主关键字和外部关键字，主关键字可对数据库表中的一条记录进行唯一标识。例如，表12.1中的学号就可以唯一标识一位同学的记录，因此可定义为主关键字。而外部关键字可将一条记录与其他数据表中的关键字联系起来。

9.数据库管理系统（DataBase Management System,DBMS）

数据库管理系统是操纵和管理数据库的系统软件。它是用来维护数据库并接收和完成用户程序或命令提出的访问数据库的各种请求。数据库管理系统的数据语言包括：数据定义语言（DDL）——建立所需的数据库（即设计库结构）；数据操作语言（DML）——对数据库进行查询和维护操作；数据控制语言（DCL）——控制数据的访问权限及事务管理。

数据库和数据表可以通过数据库管理系统软件来建立，如Access、Visual FoxPro、SQL Server、Sybase和Oracle等。在Visual Basic环境下可以直接建立Access数据库。

10.数据库系统（DataBase System,DBS）

数据库系统就是以数据库应用为基础的计算机系统。一个完整的数据库系统由数据库、数据库管理系统、数据库管理员和应用程序组成。数据库系统的组成也可以这样划分：硬件（计算机硬件设备）、软件（数据库管理系统、操作系统、开发工具、应用程序）和用户（应用程序设计员、终端用户、数据库管理员）。

12.1.2　Visual Basic数据库应用组成层次

Visual Basic数据库应用由三个部分组成：用户界面、数据库引擎和数据仓库。

1.用户界面和应用程序代码

用户界面包括显示数据及查看或更新数据的窗体，驱动这些窗体的是构成应用程序的代码，它包括请求数据库服务的数据访问对象和方法。这些服务请求不是直接针对数据库文件，而是向Jet数据库引擎提出的。由数据库引擎来执行对数据仓库请求的操作，并向应用程序返回所需要的结果。

2.数据库引擎

Jet数据库引擎被包括在一组动态链接库（DLL）文件中，运行时，这些文件被链接到Visual Basic应用程序中。数据库引擎把应用程序的请求翻译成对数据库文件或其他数据仓库的物理操作（如读取、写入、修改数据库等）。

3.数据仓库

数据仓库是包含数据库表的一个或多个文件（如多个.MDB文件），对其他形式的数据库，它可能是包含.Dbf文件或其他扩展名文件的目录。

12.1.3　Visual Basic数据访问方式

1.Visual Basic数据库访问方式

Visual Basic提供了多种访问数据库的方法：Data控件、ADO Data控件、DAO、RDO、ADO等。

❑Data控件是不需编程而能访问现存数据库的方法。

❑ADO Data控件是使用ADO（一种最新的访问数据的接口）快速创建数据绑定控件和数据提供者之间的连接。其中数据绑定控件可以是任何具有Datasource属性的控件；数据提供者可以是任何符合OLE DB规格的源。

❑DAO（数据访问对象）是全面控制数据库的完整编程接口，用户可用这些对象的属性和方法来实现对数据库的操作，如创建数据库，定义数据库表、字段和索引以及建立表间的关系，记录定位或查询等。

❑RDO（远程数据对象）是全面控制远程ODBC数据库的完整编程接口。

❑ADO（ActiveX数据对象）是访问OLE DB的应用程序接口，是最新的、最灵活的，也是最简单的接口。OLE DB是数据访问的低级接口，它能处理任何类型的数据，而不管其格式。

2.Visual Basic可以访问的数据库

Visual Basic可以访问以下3类数据库。

❑Jet数据库：数据库由Jet引擎直接生成和操作，不仅灵活而且速度快。Visual Basic默认的数据库格式为Microsoft Office Access格式，扩展名为.MDB。Visual Basic和Access数据库管理系统共享Jet数据库引擎。

❑ISAM数据库：用索引顺序访问方法（ISAM）数据库有几种不同的形式，如Dbase、FoxPro、Text Files和Paradox。在Visual Basic中可以生成和操作这些数据库。

❑ODBC数据库：开放式数据库连接，这些数据库包括遵守ODBC标准的客户/服务器数据库，如Microsoft SQL Server、Oracle、Sybase等，Visual Basic可以使用任何支持ODBC标准的数据库。

12.2　Access数据库

Access是Microsoft Office办公软件系列中一个极为重要的组成部分。是目前最流行的桌面数据库管理系统之一。Access是一种关系型数据库管理系统，它为用户提供直观的开发环境及工具，无须编写程序代码，利用可视化操作能够快速构建数据库应用程序。是一种使用方便、功能完善的数据库开发工具。

12.2.1　启动Access

在已经安装了Access的系统中，建立或打开一个数据库之前，应首先启动Access数据库管理系统软件。启动Access的方法有多种。

❑在Windows XP操作系统中，单击“开始”|“程序”|"Microsoft Office 2003"|"Microsoft Office Access 2003"菜单项，可以启动Access程序。如图12.1所示。

图　12.1　启动Access方法一

❑在桌面上如果有Access的快捷图标，双击Access快捷图标，可以启动Access程序。如图12.2所示。

图　12.2　Access程序图标

❑在计算机中双击已建立的Access数据库文件的同时，可以启动Access程序。

12.2.2　建立数据库

【实例12.1】利用Access软件建立数据库的步骤如下。

（1）启动Access2003或其他版本。

（2）选择“文件”|“新建”菜单项，从右侧的“新建文件”面板中，选择“空数据库”，如图12.3所示。

图　12.3　建立数据库窗口

（3）通过“文件新建数据库”对话框，给数据库文件起一个名字，并将文件保存到合适的位置。例如，起名为“学生数据库”，此时会出现如图12.4所示的数据库窗口。

图　12.4　数据库窗口

12.2.3　建立数据表

【实例12.2】建立数据库后，应当在数据库中建立数据表，具体操作步骤如下。

（1）在图12.4内，双击“使用设计器创建表”选项，打开“表设计器”窗口，并在其内按照图12.5所示的内容设计表结构（字段属性均取默认值）。

图　12.5　Access表设计器

注意　“设计表结构”是在设计字段的属性。例如，设计“字段名称”、“数据类型”、“字段大小”等。而并非是录入具体的记录内容。

（2）设计完表结构之后，直接单击“表设计器”窗口中的“关闭”按钮。此时，会跳出一个确认保存的提示对话框，从中选择保存，并在之后出现的“另存为”对话框中，输入表名称，例如："student"，如图12.6所示。

图　12.6　“另存为”对话框

（3）单击“确定”按钮，完成数据表的建立。此时，数据库窗口内会多出一个"student"项，如图12.7所示。

图　12.7　数据库窗口

（4）到此为止，实际上只建立了一个空数据表。接下来，给"student"表添加记录内容。在数据库窗口中双击"student"项，打开编辑表记录的窗口，按照图12.8所示的内容添加记录。

图　12.8　编辑表记录的窗口

（5）添加完记录之后，直接关闭窗口即可，Access会自动为用户保存所做的工作。

经过上述步骤后，在数据库中建立了一个"student"数据表，而且给其添加了9条记录。可以使用上面的方法在一个数据库中建立多个数据表。

为了给下面的章节作准备，在“学生数据库”中，再建立一个存放学生考试成绩的数据表"score"。结构设计如表12.3所示。

建立好空数据表之后，请参照表12.4所示的内容，给数据表添加记录。

12.3　SQL语言基础知识

SQL是结构化查询语言Structured Query Language的缩写。它是一种通用的关系型数据库的编程语言，SQL是一组命令的集合，包括关系型数据库的建立、维护和查询等操作，已发展成为数据库的标准接口，被广泛使用。

12.3.1　SQL语言简介

最早的SQL标准是1986年10月由美国ANSI（American National Standards Institute）公布的。随后，ISO（International Standards Organization）于1987年6月也正式采纳它为国际标准，并在此基础上进行了补充，到1989年4月，ISO提出了具有完整性特征的SQL，并称为SQL89。由此SQL在不断完善中得到越来越广泛的使用，现在各有关数据库语言都采用标准（ANSI）SQL格式。

1.SQL语言的特点

SQL语言具有如下主要特点。

（1）SQL是一种一体化语言，它提供了一系列完整的数据定义、数据查询、数据操纵和数据控制等方面的功能。用SQL可以实现数据库生命周期中的全部活动，包括简单地定义数据库和表的结构，实现表中数据的录入、修改、删除、查询和维护，数据库重构、数据库安全控制等一系列操作要求。

（2）SQL语言是一种高度非过程化的语言，它没有必要一步步地告诉计算机“如何”去做，而只需要描述清楚用户要“做什么”，SQL语言就可以将要求交给系统，自动完成全部工作。

（3）SQL语言非常简洁。虽然SQL语言功能很强，但它只有为数不多的几条命令，表12.5给出了分类的命令动词。另外SQL的语法也非常简单，它很接近英语自然语言，因此容易学习和掌握。

（4）SQL语言可以嵌入到程序设计语言中以程序方式使用。在Visual Basic中可以直接使用。

2.SQL语言的组成

SQL语言由命令、子句、运算符和统计函数组成。

（1）命令

①数据定义命令

❑CREATE：建立新的数据表、字段和索引表。

❑DROP：从数据库删除数据表或索引。

❑ALTER：增加或修正字段属性。

②数据操作命令

❑SELECT：找出满足条件的记录。

❑INSERT：增加记录或合并多个数据表。

❑UPDATE；更正满足条件的记录。

❑DELETE：删除满足条件的记录。

（2）子句

❑FROM：指定数据表。

❑WHERE：设置条件。

❑GROUP BY：设置分组。

❑HAVING：设置分组条件。

❑ORDER BY：设置输出的顺序。

（3）运算符

逻辑运算符：AND（与）、OR（或）、NOT（非）

关系运算符：＜（小于）、＜=（小于等于）、＞（大于）、＞=（大于等于）、=（等于）、＜＞（不等于）、BETWEEN（设置范围）、LIKE（通配设置）、IN（集合设置）。

（4）统计函数

❑AVG：求指定条件的平均。

❑COUNT：求指定条件的数量。

❑SUN：求指定条件的和。

❑MAX：求指定条件的最大值。

❑MIN：求指定条件的最小值。

12.3.2　SELECT语句基础

SELECT语句的主要功能是查询数据库中满足条件的记录，并不会改变数据库中的数据。查询出来的记录或放在一个记录集中，或生成一个新的记录集，或产生一个报表。SQL的SELECT语句基本形式由SELECT-FROM-WHERE查询块组成，多个查询块可以嵌套执行。SELECT-SQL最基本的语法格式如下。

SELECT*FROM ＜表名＞

说明：

（1）SELECT子句后的“*”，代表查询全部字段。在这个位置也可以指定要查询的字段名列表。

（2）FROM子句后的“表名”，指定要查询的数据表。

（3）SELECT语句中不区分大小写。

下面用两个简单实例说明SELECT语句的用法。

【实例12.3】查询student数据表中的所有内容。

SELECT*FROM student

说明　本例的代码都很简短，除非特殊关键词出现，这里都省略了【代码说明】和【运行效果】标识。

【实例12.4】从student表中，只查询“姓名”和“专业”两个字段的所有记录内容。

SELECT姓名,专业FROM student

注意　字段名之间要有逗号分隔。

12.3.3　在Access中运行SELECT语句

了解了编写SELECT语句的基本方法之后，应当找一个环境试验其效果。几乎所有的数据库软件中，都可以直接运行SELECT语句。在此，仍然选择Access作为本书SELECT语句的试验环境。

【实例12.5】使用Access试验SELECT语句的具体操作步骤如下。

（1）打开前面所建立的“学生数据库”数据库文件。

（2）在数据库窗口中，单击左侧“对象”面板中的“查询”按钮，如图12.9所示。

图　12.9　数据库窗口

（3）在右侧子窗口中，双击“在设计视图中创建查询”项，出现如图12.10所示的“选择查询”设计视图窗口界面。单击“显示表”对话框中的“关闭”按钮，将其关闭。

图　12.10　“选择查询”窗口（设计视图）

（4）选择“视图”|“SQL视图”菜单项，设计视图会切换成SQL视图状态，如图12.11所示。

图　12.11　“选择查询”窗口（SQL视图）

（5）在SQL视图内，可以直接编写SELECT语句。例如，在白色的文本区内输入如下语句。

SELECT 姓名,专业 FROM student

（6）选择“查询”|“运行”菜单项。这时，会看到如图12.12所示的查询结果。

图　12.12　查询结果

读者可以使用上面所述的方法，试验下面章节中将要讲述的更复杂的SELECT语句。

12.3.4　WHERE子句

SELECT语句后，其实还可以设置查询的条件。查询条件由WHERE子句指定。其格式如下。

SELECT*FROM ＜表名＞ WHERE 查询条件

WHERE子句后的“查询条件”是一个查询表达式。下面通过实例说明WHERE子句的用法。

【实例12.6】在student数据表中，查询姓名为“赵欣”的学生，并显示其完整信息。编写SELECT语句如下。

SELECT*FROM student WHERE 姓名="赵欣"

【代码说明】表达式：

姓名="赵欣"

就是所谓的查询表达式，表达式中的“姓名”是"student"数据表中的字段名，因为该字段是文本类型的字段，所以应当与字符串常量比较，因此在“赵欣”两个字上加了双引号。

【运行效果】该SELECT语句在Access中执行后的结果如图12.13所示。

图　12.13　实例12.6执行结果

【实例12.7】在student数据表中，查询在1982年1月1日之前出生的学生，并显示其完整信息。编写SELECT语句如下。

SELECT*FROM student WHERE 出生日期＜#1982-01-01#

【代码说明】因为“出生日期”是日期/时间类型的字段，所以应当与日期型的常量比较，因此在“1982-01-01”的两边加了井号（#）。

【运行效果】该SELECT语句在Access中执行后的结果如图12.14所示。

图　12.14　实例12.6执行结果

12.3.5　ORDER BY子句

使用SELECT语句时，有时希望查询到的记录按某种顺序显示，即排序显示。ORDER BY子句就可以实现这一要求。

其格式为：

SELECT*FROM ＜表名＞ [WHERE 查询条件] ORDER BY 字段名 [ASC或DESC]

其中，“ORDER BY字段名”后，如果写了"ASC"，则表示升序显示查询结果；而写了"DESC"，则表示降序显示查询结果；如果省略则表示升序显示。

【实例12.8】查询student表中所有学生的信息，并将结果按出生日期字段升序显示。

SELECT*FROM student ORDER BY 出生日期

【运行效果】该SELECT语句在Access中执行后的结果，如图12.15所示。

图　12.15　实例12.8执行结果

如果要对出生日期进行降序排列，只要在字段名之后，加上"DESC"即可，例如下面的语句。

SELECT*FROM student ORDER BY 出生日期 DESC

有时，在按某个字段进行排序时，会出现排序值相等的问题。这时，应当设置第二个排序字段。当第一个排序字段值相等时，用第二个排序字段值排序。

【实例12.9】查询score数据表中所有的记录，并按课名升序、成绩降序显示查询结果。

SELECT*FROM score ORDER BY 课名 ASC,成绩 DESC

【运行效果】该SELECT语句在Access中执行后的结果，如图12.16所示。

图　12.16　实例12.9执行结果

12.3.6　SQL用于计算的几个函数

SQL不仅具有一般的查询数据的功能，而且还有计算查询的功能。SQL用于计算查询的系统函数有COUNT（计数）、SUM（求和）、AVG（求平均）、MAX（最大值）和MIN（最小值）。下面通过几个实例，介绍上述几个计算函数的用法。

【实例12.10】查询出student表中，生日最小（出生日期值最大）的学生的出生日期。

SELECT MAX(出生日期) FROM student

【运行效果】该SELECT语句在Access中执行后的结果如图12.17所示。

图　12.17　实例12.10执行结果1

读者应该从图中看到了一个很不舒服的临时字段名"Expr1000"。改变这个临时字段名的方法是将上面的SELECT语句改写成如下的形式。

SELECT MAX(出生日期) as 最小生日 FROM student

该语句执行后的结果如图12.18所示。其中，"as"的作用是给字段起一个临时的名字。

图　12.18　实例12.10执行结果2

【实例12.11】在数据表student中，查询1982年1月1日之前出生的学生人数。

SELECT count(*) as 人数 FROM student WHERE 出生日期＜#1982-01-01#

【运行效果】该语句执行后的结果如图12.19所示。

图　12.19　实例12.11执行结果

【实例12.12】查询高等数学的平均成绩。

SELECT AVG(成绩) as 高数平均成绩 FROM score WHERE 课名='高等数学'

【运行效果】该语句执行后的结果如图12.20所示。

图　12.20　实例12.12执行结果

12.3.7　添加数据记录

SQL语言，不仅有前面所介绍的SELECT语句，经常使用的还有INSERT、DELETE和UPDATE三种操作数据表的语句。其中，INSERT语句用来向数据表添加记录；DELETE语句用来删除数据表中的记录；UPDATE语句用来更新数据表中的记录。这一小节中，将介绍INSERT语句的使用方法，12.3.8节和12.3.9节将介绍DELETE和UPDATE语句的使用方法。

INSERT语句的格式如下。

INSERT INTO＜表名＞[＜字段名1＞[,＜字段名2＞,…]VALUES（＜表达式1＞[,＜表达式2＞…]

说明：在指定的表中添加一条新记录，其值为VALUES后面的表达式的值。当需要插入表中所有字段的数据时，“表名”后面的字段名可以省略，但插入数据的格式必须与表的结构完全吻合；若只需要插入表中某些字段的数据，就需要列出插入数据的字段，当然相应表达式的数据位置会与之对应。

【实例12.13】在student表中插入一条新记录。

INSERT INTO student(学号,姓名) VALUES ('0108015','高丽雅')

【运行效果】该语句执行后，student数据表的内容如图12.21所示，在数据表的最下面添加了一条新记录。

图　12.21　实例12.13执行结果

12.3.8　修改数据记录

UPDATE语句用来更新数据表中的记录，即修改记录。UPDATE语句的格式如下。

UPDATE＜表名＞SET＜字段1＞=＜表达式1＞[,＜字段2＞=＜表达式2＞…][WHERE＜条件表达式＞

说明如下。

（1）＜表名＞：指定要修改数据记录所在的表名。

（2）SET＜列名＞=＜表达式＞：指定被修改的字段及该字段的新值。如果省略WHERE子句，则该字段每一行都用同样的值修改。

（3）WHERE＜条件表达式＞：指明将要修改数据的记录。即更新表中符合条件表达式的记录。

【实例12.14】将score数据表中，所有“计算机基础”课的成绩提高3%。

UPDATE score SET 成绩=成绩*1.03 WHERE 课名="计算机基础"

【运行效果】该语句执行后，score数据表的内容如图12.22所示，在数据表中5条记录的内容被更新。

图　12.22　实例12.14执行结果

12.3.9　删除数据记录

用SQL语言删除记录的命令格式如下。

DELETE FROM ＜表名＞ [WHERE ＜条件表达式＞]

说明如下。

（1）＜表名＞：指定要删除的记录所在的表名。

（2）WHERE：指明只对满足条件的记录进行删除。

【实例12.15】删除student数据表中，姓名为“王丽”的记录。

DELETE FROM student WHERE 姓名="王丽"

【运行效果】该语句执行后，student数据表的内容如图12.23所示，在数据表中已经找不到“王丽”的记录了。

图　12.23　实例12.15执行结果

12.3.10　多表联合查询

在SQL查询中，经常要对多个表进行查询，多表联合查询是SQL语言查询的基本操作之一。多表查询实现的方法有多种，可以解决各种复杂的关联查询。

1.联接查询

联接查询，简单地说就是将两个或多个数据表联接起来进行查询。数据表联接的必要条件是各表中要有共同的字段。例如，student和score两个数据表，因为有共同的字段“学号”，因此能够进行联接。

【实例12.16】下面的SELECT语句联接了以上两个表，并显示了两个表的全部记录。

SELECT*FROM student,score WHERE student.学号=score.学号

【运行效果】该语句执行后的结果如图12.24所示。

图　12.24　联接查询结果

其实，上面的SELECT语句是一个典型的联接数据表的方法。其FROM子句后，应当列出将要联接的所有表名，表名之间用逗号分开。其WHERE子句后，要写联接表达式。下面看两个联接查询的实例。

【实例12.17】查找学生“李宁”的所有课程的成绩。

【实例分析】如果只从数据表student中查询，因为没有“成绩”字段，所以无法查到成绩。而如果单独从数据表score中查询，因为没有“姓名”字段，所以更不能完成任务。因为上述原因，应当使用联接查询的方法解决问题，即将student和score两个数据表联接起来，然后进行查询。

SELECT*FROM student,score WHERE student.学号=score.学号 AND 姓名="李宁"

【运行效果】该语句执行后的结果如图12.25所示。

图　12.25　实例12.17执行结果

【实例12.18】查找计算学生“李宁”的总分。

SELECT sum(成绩) as 李宁的总分 FROM student,score WHERE student.学号=score.学号 AND 姓名="李宁"

【运行效果】该语句执行后的结果如图12.26所示。

图　12.26　实例12.18执行结果

2.嵌套查询

嵌套查询，简单地说就是在一个SELECT语句里又包含了另一个SELECT语句。下面用一个实例说明嵌套查询。

【实例12.19】查询“大学语文”成绩大于80分的学生姓名。

SELECT 姓名 FROM student WHERE 学号 IN (SELECT 学号 FROM score WHERE 成绩＞80 AND 课名="大学语文")

【运行效果】该语句执行后的结果如图12.27所示。

图　12.27　实例12.19执行结果

在上面的SELECT语句的WHERE子句里，又出现了一个SELECT语句，所以是嵌套查询。嵌套查询理论上允许多层嵌套，但是嵌套的层次太多，很难让人理解其含义。

12.4　小结

有些读者在学习数据库知识时比较发怵，其实没有关系，在VB语言的数据操作中，并没有涉及太复杂的数据挖掘技术，而是一些简单的数据操作，如增加、删除、修改和查询等。本章12.2节介绍了Access数据库的一些基础知识，而12.3节介绍的是SQL的一些常见操作，这些一般应用在SQL Server数据中，具体要选择什么数据库，要根据程序的数据量和安全性决定。

12.5　本章练习

1.Visual Basic数据访问方式有几种？

2.说出3个以上SQL用于计算的函数，并说明它们的用途？

3.请分析下列代码的功能是什么？

'声明数据库路径变量

Public strDBPath As String

Public strDBName As String

Public strDataTable As String

'声明打开的数据库对象变量

Public FermDB As Database

'声明打开的数据库对象记录集

Public rstRecordSet As Recordset

Private Sub Form_Load()

Set FermDB=DBEngine.Workspaces(0).OpenDatabase(strDBPath＆"\"＆strDBName＆".mdb",True)

Set rstRecordSet=FermDB.OpenRecordset(strDataTable)

rstRecordSet.Move 5

End Sub

第13章　数据控件编程

Visual Basic为用户提供了多种访问数据库的方法，大致可以分为两大类，分别是通过控件访问数据库和通过数据库访问技术（接口）访问数据库。本章将介绍使用控件访问数据库的方法。本章包括以下知识点。

❑使用Data控件访问数据库、查询数据和对数据库中数据进行修改等操作。

❑详细介绍了数据绑定技术。

❑使用ADO Data控件实现数据查询。

掌握了技术数据绑定，数据记录的查询等数据库编程技术，读者就能够编写很多中级的数据库程序了。

13.1　Data控件

Data控件可用来访问数据库。如果只是简单地操作数据库，使用Data控件是最方便不过的了。因为该控件可以和文本框等类型的控件绑定，这样就减少了编写程序这一步，取而代之的则是仅仅设置一些属性就可以了。Data控件在工具箱中的图标是
。下面介绍关于Data控件的相关内容。

13.1.1　Data控件简单应用

Data控件可以和很多控件进行绑定，绑定后不用写任何代码就可以浏览或修改数据表中的记录。下面，用一个简单的实例说明Data控件的这种用法。

【实例13.1】使用Data控件，浏览student数据表的内容。

说明　student表是在第12章建立的“学生数据库”数据库中的表。

【实现步骤】具体操作步骤如下。

（1）新建一个标准EXE工程。

（2）在其默认的窗体Form1中，按照图13.1绘制1个Data控件、2个标签和2个文本框。

图　13.1　实例13.1界面

（3）参照表13.1，设置Data控件Data1的属性。

（4）参照表13.2，设置文本框控件Text1与Text2的属性。

【运行效果】运行程序，结果如图13.2所示。可以通过单击Data控件两边的箭头浏览其他记录。

图　13.2　实例13.1运行结果

说明　上面的实例通过Data控件和文本框的绑定，无需编写代码就实现了浏览数据记录的功能。通过上面的程序也能修改数据记录。例如，在图13.2的窗口中将“王刚”改为“王刚刚”后，student数据表中相应的记录也会被改变为“王刚刚”。

13.1.2　Data控件的主要属性

Data控件最主要的属性就是前面实例中所用到的3个属性——Connect、DatabaseName和RecordSource。此外，还有一些其他属性，具体属性说明如下。

1.设置数据库的类型

Connect属性用于设置所要连接的数据库的类型。例如，实例13.1中将Connect属性设置为"Access2000"，是在告诉系统要连接的数据库是Access2000类型的数据库。

2.设置要连接的数据源

DatabaseName属性用于设置Data控件的数据源，即决定Data控件连接到哪个数据库上。该属性除了可以在属性窗口中设置外，还可以通过程序设置，例如：

Data1.DatabaseName="D:\VB\学生数据库.mdb"

3.设置要连接的数据表

设置好数据源之后，应当设置要连接的具体的数据表。RecordSource属性用来设置数据表。

说明　当程序运行时改变RecordSource属性值后，必须用Refresh方法使改变生效，以重建Recordset对象。

4.屏蔽修改功能

在13.1.1节的实例中，通过程序可以更改数据表中的内容，有时应当屏蔽该功能，因为用户很可能会在无意中修改数据库的内容。

要屏蔽修改功能可以使用ReadOnly属性。该属性默认值为False，表示数据可以被修改；将其改为True，则屏蔽了修改功能。

13.1.3　Data控件的主要方法

1.Refresh方法

在DatabaseName、ReadOnly、Connect及RecordSource等属性值发生改变时，可使用该方法来打开或重新打开数据库，以使设置生效，并重建Recordset记录集对象。

语法为：Data1.Refresh

2.UpdateControls方法

将Data控件记录集中的当前记录填充到某个数据绑定控件。

3.UpdateRecord方法

将修改的记录内容保存到数据库中，同时避免引发Validate事件。

语法为：Data1.UpdateRecord

说明　该方法与Recordset对象的Update方法类似，不同的是使用该方法可以避免引发Validate事件。

13.1.4　Data控件的常用事件

1.Reposition事件

当用户单击Data控件上的箭头按钮，或在应用程序中使用了Move或Find方法时，一个新记录成为当前记录之后，都会引发Reposition事件。

注意　当加载一个Data控件时，Recordset对象的第一条记录成为当前记录，也会触发该事件。

语法为：Data1_Reposition()

2.Validate事件

该事件由记录改变触发，包括记录指针的改变或记录内容的改变。并且，该事件发生在当前记录改变之前，也发生在应用Update方法、Delete方法和Close方法等操作之前。

语法为：Data1_Validate(Action As Integer,Save As Integer)

其中，Action参数和Save参数的取值及含义如表13.3和表13.4所示。

Action参数的返回值为整型值，用来标识Validate事件由什么操作引发，利用这个返回值即可根据不同操作编写相应的程序代码；Save参数的返回值为布尔型，可利用这个返回值来判断被连接的数据绑定控件的记录内容是否已被修改了。例如下面的代码。

01 Private Sub Data1_Validate(Action As Integer,Save As Integer)

02 Dim x As Integer

03 If Save=True Then

04 x=MsgBox("要保存修改的部分吗？",vbYesNo)

05 If x=7 Then

06 Save=False

07 Data1.UpdateControls '将控件中的数据恢复到没修改以前

08 End If

09 End If

10 End Sub

13.1.5　Recordset（记录集）对象

Recordset对象是Data控件的子对象，它由0条或多条记录组成，可以将它理解成是一个临时数据表。Recordset对象拥有自己的方法，通过这些方法可以对数据库进行各种灵活的操作，例如，设置当前记录、查询记录、添加记录和删除记录等。Recordset对象也有自己的属性，通过其属性可获取当前记录的位置、当前记录的内容等。下面介绍Recordset对象的常用方法和属性。

1.Recordset对象的方法

Recordset对象有很多的方法，使用这些方法可以对数据库进行各种各样的操作。

（1）MoveFirst、MoveNext、MovePrevious和MoveLast方法

这4个方法的功能分别与Data控件从左到右的4个箭头按钮的功能相同。具体格式如下。

Data1.Recordset.MoveFirst '移动到首记录

Data1.Recordset.MoveNext '移动到下一条记录

Data1.Recordset.MovePrevious '移动到上一条记录

Data1.Recordset.MoveLast '移动到最后一条记录

【实例13.2】在实例13.1的窗体上加入4个按钮，如图13.3所示，编程实现，窗体中4个按钮的功能与Data控件4个箭头按钮的功能相同。

图　13.3　实例13.2界面

编写事件过程如下。

01 Private Sub Command1_Click()

02 Data1.Recordset.MoveFirst

03 End Sub

04

05 Private Sub Command2_Click()

06 Data1.Recordset.MovePrevious

07 End Sub

08

09 Private Sub Command3_Click()

10 Data1.Recordset.MoveNext

11 End Sub

12

13 Private Sub Command4_Click()

14 Data1.Recordset.MoveLast

15 End Sub

【代码说明】本程序中存在一个错误，当到了数据表最后一条记录时，如果再单击“下一条”按钮，则执行第10行时会出现“无当前记录”的错误（“上一条”按钮其实也存在这一问题）。解决错误的方法是在执行MoveNext语句之前，应当判断是否到达数据表末尾，然后根据判断结果执行不同的语句。例如：

16 Private Sub Command3_Click()

17 If Not Data1.Recordset.EOF Then '判断是否到达数据表末尾

18 Data1.Recordset.MoveNext

19 Else

20 MsgBox"已经到数据表末尾！"

21 End If

22 End Sub

【代码说明】程序中第17行的EOF是Recordset对象的属性，将在后面讲解。

【运行效果】案例的运行效果如图13.3所示。

（2）FindFirst、FindLast、FindNext和FindPrevious方法

这些方法的功能分别是自首记录开始向下查询匹配的第一个记录、自尾记录开始向上查询匹配的第一个记录、自当前记录开始向下查询匹配的第一个记录和自当前记录开始向上查询匹配的第一个记录。具体使用方法如下。

Data1.Recordset.FindFirst string

Data1.Recordset.FindLast string

Data1.Recordset.FindNext string

Data1.Recordset.FindPrevious string

其中string为查询条件字符串，比如要查找学生“乌兰”，则查询语句可以写为：

Data1.Recordset.FindNext"姓名='乌兰'"

（3）AddNew、Edit、Delete和Update方法

❑AddNew方法：用于添加一个新记录。添加记录后必须用Update方法进行保存结果。

❑Edit方法：将当前记录放入拷贝缓冲区，以改变信息进行编辑记录的操作，和AddNew方法一样，必须使用Update方法保存修改的结果。

❑Delete方法：用于删除一个记录，一旦使用了该方法，记录就永远消失不可恢复。

注意　删除当前记录后，该记录内容仍将显示在数据绑定控件中。因此，每使用一次Delete方法后必须用MoveNext方法或MovePrevious方法来改变当前记录。

❑Update方法：将数据缓冲区的内容保存到记录集对象中，即更新数据表。

【实例13.3】在实例13.2的基础上再加入查找、添加和删除记录的功能。界面设计如图13.4所示。

图　13.4　实例13.3界面

本实例，程序清单如下。

01 Option Explicit

02

03 Private Sub Command1_Click()

04 Data1.Recordset.MoveFirst

05 End Sub

06

07 Private Sub Command2_Click()

08 If Data1.Recordset.BOF Then

09 Data1.Recordset.MovePrevious

10 Else

11 MsgBox"已经到数据表顶端！"

12 End If

13 End Sub

14

15 Private Sub Command3_Click()

16 If Not Data1.Recordset.EOF Then '判断是否到达数据表末尾

17 Data1.Recordset.MoveNext

18 Else

19 MsgBox"已经到数据表末尾！"

20 End If

21 End Sub

22

23 Private Sub Command4_Click()

24 Data1.Recordset.MoveLast

25 End Sub

26

27 Private Sub Command5_Click()

28 Dim str As String

29 str=InputBox("请输入要查找的学生姓名：")

30 Data1.Recordset.FindNext"姓名='"＆str＆"'" '向后查找

31 End Sub

32

33 Private Sub Command6_Click()

34 Data1.Recordset.AddNew '添加新记录

35 End Sub

36

37 Private Sub Command7_Click()

38 Data1.Recordset.Delete '删除当前记录

39 Data1.Refresh '刷新

40 End Sub

41

42 Private Sub Command8_Click()

43 Dim str As String

44 str=InputBox("请输入要查找的学生姓名：")

45 Data1.Recordset.FindPrevious"姓名='"＆str＆"'" '向前查找

46 End Sub

【代码说明】代码第08行通过BOF关键字判断是否是数据的开头，因为如果已经是开头，则不能再执行打开“上一条”记录的命令。代码第16行通过EOF判断是否是数据的结尾，如果是结尾，则不能执行打开“下一条”记录的命令。

【运行效果】案例的运行效果如图13.4所示。

（4）Close方法

该方法用来关闭指定的记录集。

2.Recordset对象的属性

（1）Fields属性：该属性是记录集的字段的集合。通常用于获取或设置记录集中的数据。例如：

MsgBox Data1.Recordset.Fields(2)

会在消息框中显示记录集中当前记录的第三个字段的内容。

（2）BOF属性：返回当前记录指针是否指向数据表的开头，是一个逻辑值，True代表指向数据表的开头。

（3）EOF属性：返回当前记录指针是否指向数据表的末尾，是一个逻辑值，True代表指向数据表的末尾。

（4）Bookmark属性：设置或返回记录集中当前记录指针的书签。可以用Bookmark属性重新定位记录集指针。

（5）Nomatch属性：用Find方法查找记录时，如果没有找到匹配的记录，则该属性为True。

（6）AbsolutePosition属性：返回记录集中当前记录的序号位置，它是从0开始计数的。另外，该属性只支持动态集类型或快照类型的记录集。

13.1.6　数据绑定控件

Data控件具有链接数据库、建立数据源及移动数据记录指针等功能，但不能显示和编辑数据库中的数据。一些控件与Data控件进行数据绑定后可实现这一功能，这些控件被称为数据绑定控件。所谓绑定，是指通过设置数据绑定控件的相应属性，使之与Data控件联系起来，可以在数据绑定控件中显示数据库中相应记录的数据，同时还可以对这些数据进行编辑。在数据绑定控件里改变的数据，当记录指针移动到其他记录时，这些改变会自动地写入到数据库中。

一般情况下，具有"Data Source"和"Data Field"属性的控件都是数据绑定控件。

❑DataSource属性：指定要绑定的数据源（即哪个Data控件）。

❑DataField属性：指定要绑定数据源中的哪个字段。

每一数据绑定控件对数据的处理都具有不同的功能，常用的数据绑定控件有文本框、标签、复选框、列表框、组合框、图像框、图片框、DBlist、DBCombo和DBGrid等。

13.1.7　Data控件应用案例

【实例13.4】设计如图13.5所示的操作“用户登记表”数据表的程序。

图　13.5　实例13.4设计界面

1.创建数据库与数据表

（1）打开Access，新建一个空白数据库，取其默认名为db1.mdb。

（2）使用设计器设计一个“用户登记表”数据表，如图13.6所示。

图　13.6　“用户登记表”结构

（3）打开“用户登记表”数据表，添加如图13.7所示的记录数据。

图　13.7　“用户登记表”内容

（4）将数据库取名为db1，并保存到D:\VB路径下。

2.界面设置

程序中各对象属性设置如表13.5所示。

3.编写事件过程

01 Option Explicit

02

03 Private Sub cmdAdd_Click()

04 Data1.Recordset.AddNew

05

06 cmdDele.Enabled=False

07 cmdUpdate.Enabled=True

08 cmdFind.Enabled=False

09

10 txtID.SetFocus

11 End Sub

12

13 Private Sub cmdDele_Click()

14 Dim x As Integer

15

16 x=MsgBox("真的要删除当前记录吗",vbYesNo,"信息提示")

17 If x=6 Then

18 Data1.Recordset.Delete '删除当前记录

19 Data1.Recordset.MoveNext '让下一条记录变为当前记录

20 If Data1.Recordset.EOF Then '如果到达数据表末尾

21 Data1.Recordset.MoveFirst

22 If Data1.Recordset.BOF Then

23 Data1.Caption="数据表为空"

24 cmdDele.Enabled=False

25 cmdFind.Enabled=False

26 End If

27 End If

28 End If

29 End Sub

30

31 Private Sub cmdFind_Click()

32 Dim str As String

33 str=InputBox("请输入想要查找人的用户姓名","查找")

34 If str=""Then Exit Sub

35 Data1.Recordset.FindFirst"name='"＆str＆"'"

36 If Data1.Recordset.NoMatch Then '如果没有查到记录

37 MsgBox"没有查到符合条件的记录！",vbOKOnly,"信息提示"

38 End If

39

40 End Sub

41

42 Private Sub cmdUpdate_Click()

43 If optMan.Value=True Then

44 Data1.Recordset.Fields(2)="男"

45 Else

46 Data1.Recordset.Fields(2)="女"

47 End If

48 Data1.Recordset.Update

49

50 Data1.Recordset.Bookmark=Data1.Recordset.LastModified

51 cmdUpdate.Enabled=False

52 cmdDele.Enabled=True

53 cmdFind.Enabled=True

54 55 End Sub

56

57 Private Sub Data1_Reposition()

58 If Not Data1.Recordset.EOF Then '如果没有到达数据表末尾

59 If Data1.Recordset.Fields(2)="男"Then '如果第3个字段的内容为“男”

60 optMan.Value=True

61 Else

62 optWomen.Value=True

63 End If

64 Data1.Caption="记录："＆(Data1.Recordset.AbsolutePosition+1)

65 End If

66 End Sub

67

68 Private Sub Form_Initialize()

69 If Data1.Recordset.EOF And Data1.Recordset.BOF Then '检测记录集是否为空

70 Data1.Caption="数据表为空"

71 cmdFind.Enabled=False

72 cmdDele.Enabled=False

73 Else

74 Data1.Recordset.MoveFirst '指向第一个记录

75 End If

76 cmdUpdate.Enabled=False

77 End Sub

【代码说明】代码第03～11行是完成添加按钮的功能，此时查找按钮和删除按钮不可用；代码第13～29行是删除按钮的功能，删除时，代码第16行给出一个对话框提示用户是否真的要删除；代码第31～40行实现查找按钮的功能；代码第33行通过用户输入的内容获取要查找的用户名；代码第44～55行实现数据的更新；代码第57～66行是更改要显示的逻辑字段的内容；代码第68～77行是一个初始化事件，用来判断数据是否为空，如果不为空显示第一条记录。

【运行效果】实例的运行效果如图13.5所示。

13.2　ADO Data控件

ADO Data控件（简称为ADO控件）与前面介绍的Data控件相似。但因为ADO的适应性更广，因此建议用户使用ADO Data控件来创建新的数据库应用程序。使用ADO Data控件，可以利用Microsoft ActiveX Data Objects（ADO）快速建立数据绑定控件和数据提供者之间的连接。

ADO Data控件可以实现以下功能。

❑连接一个本地数据库或远程数据库。

❑打开一个指定的数据库表，或定义一个基于结构化查询语言（SQL）的查询、存储过程或该数据库中的表的视图的记录集合。

❑将数据字段的数值传递给数据绑定控件，可以在这些控件中显示或更改这些数值。

❑添加新的记录，或根据更改显示在绑定的控件中的数据来更新一个数据库。

要使用ADO Data控件，可右击工具箱，在弹出式菜单中选择“部件”菜单项，并在“部件”对话框中选择"Microsoft ADO Data Control 6.0"，如图13.8所示。单击“确定”按钮，就可以在Visual Basic中的工具箱内看到它。通过数据控件可以直接对记录集进行访问，移动记录指针，不需要编写代码即可实现对数据库的操作。

图　13.8　“部件”对话框

13.2.1　ADO Data控件的属性

ADO Data控件的常用属性如下。

1.Align属性

该属性用来把数据控件摆放在窗体的特定位置，有5个可选的位置。

❑vbAlignNone：可以用鼠标指针拖动控件到窗口的任何位置。

❑vbAlignTop：将控件放到窗口的顶端。

❑vbAlignBottom：将控件放到窗口的底部。

❑vbAlignLeft：将控件放到窗口的最左边。

❑vbAlignRight：将控件放到窗口的最右边。

2.BOFAction和EOFAction属性

当移动数据库记录指针时，如果记录指针移动到BOF或EOF位置后，再向前或向后移动记录指针将发生错误。BOFAction和EOFAction属性指定当发生上述错误时，数据控件采取什么样的操作。

BOFAction属性有两个可选常量。

❑adDoMoveFirst：移动记录指针到第一个记录。

❑adStayBOF：移动记录指针到记录的开始。记录指针移动到记录的开始位置时将引发数据控件的Validate事件和Reposition事件，这时可编写程序代码确定要执行的操作。

EOFAction属性有三个可选常量。

❑adDoMoveLast：移动记录指针到最后一个记录。

❑adStayEOF：移动记录指针到记录的结尾，同样可利用它所引发的事件编写程序代码。

❑adDoAddNew：当记录指针移动到文件尾部时，引发数据控件的Validate事件，然后自动执行AddNew方法添加新记录，并在新记录上引发Reposition事件。

3.ConnectionString属性

ConnectionString属性用来建立到数据源的连接信息。由于Visual Basic的ADO对象模型可以链接不同类型的数据库，所以在使用ADO Data控件时也能够通过ConnectionString属性来设置链接不同的数据源。

在设计时，可以首先将Connection String属性设置为一个有效的连接字符串，也可以将ConnectionString属性设置为定义连接的文件名。该文件是由“数据链接”对话框产生的。

设置ConnectionString属性的步骤如下。

（1）选中ADO Data控件，在属性窗口中找到其Connection-String属性，单击最右侧的“...”按钮，出现如图13.9所示的“属性页”对话框。

图　13.9　ADOData控件的“属性页”对话框

❑如果已经创建了一个Microsoft数据链接文件（.UDL），选择“使用Data Link文件”单选按钮，并单击“浏览”按钮，找到计算机上的文件。

❑如果使用DSN，则单击“使用ODBC数据源名称”连接，并从列表中选择一个DSN，或单击“新建”按钮创建一个。

❑如果想创建一个连接字符串，选择“使用连接字符串”单选按钮，单击“生成”按钮，然后在“数据链接属性”对话框创建一个连接。

例如，想创建一个与Access2000数据库的连接时，选择如图13.10所示的"Microsoft Jet 4.0 OLE DB Provider"选项。单击“下一步”按钮后出现如图13.11所示的“连接”选项卡的内容，在“选择或输入数据库名称”栏内直接输入或使用左侧的“...”按钮选择数据库名称，并单击“确定”按钮。

图　13.10　“数据链接属性”对话框（“提供程序”选项卡）　　　　　　　　　　　　　　　图　13.11　“数据链接属性”对话框（“连接”选项卡）

技巧　在“连接”选项卡中可以使用“测试连接”按钮，测试与数据库是否连接成功。如果连接成功则会显示连接成功消息框。

（2）单击“确定”按钮之后，ConnectionString属性被下面的字符串填充。

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\VB\db1.mdb;Persist Security Info=False

在运行时，可以动态地设置ConnectionString更改数据库。

4.RecordSource属性

RecordSource属性可用来设置要链接的表或者SQL查询语句。可以在属性窗口中将RecordSource属性设置为一个SQL语句。例如：

SELECT*FROM用户登记表WHERE tele="0471-123456"

5.CommandType属性

CommandType属性用于指定RecordSource属性的取值类型。可直接在属性窗口中CommandType属性框右边的下拉列表中选择需要的类型，其取值如下。

❑adCmdUnknown：默认值。CommandText属性中的命令类型未知。

❑adCmdTable：将CommandText作为其列全部由内部生成的SQL查询返回的表格的名称进行计算。

❑adCmdText：将CommandText作为命令或存储过程调用的文本化定义进行计算。

❑adCmdStoreProc：将CommandText作为存储过程名进行计算。

6.UserName属性

UserName属性指定用户的名称，当数据库受密码保护时，需要指定该属性。该属性可以在ConnectionString中指定。如果同时提供一个ConnectionString属性以及一个UserName属性，则ConnectionString中的值将覆盖UserName属性的值。

7.Password属性

Password属性指定密码，在访问一个受保护的数据库时指定密码是必需的，和Provider属性与UserName属性类似，如果在ConnectionString属性中指定了密码，将覆盖在该属性中指定的值。

8.ConnectionTimeout属性

该属性用来设置等待建立一个连接的时间，以秒为单位。连接超时，则返回一个错误。

13.2.2　ADO Data控件的方法

ADO Data控件的常用方法如下。

1.AddNew方法

用于在ADO Data控件的记录集中添加一条新记录。其使用语法如下。

Adodc1.Recordset.AddNew

其中Adodc1是一个ADO Data控件的名字。在添加语句之后，应该给相应的各个字段赋值，然后调用UpdateBatch方法保存记录，或者调用CancelUpdate方法取消保存。

2.Delete方法

用于在ADO Data控件的记录集中删除当前记录。其使用语法如下。

Adodc1.Recordset.Delete

3.MoveFirst、MoveLast、MoveNext和MovePrevious方法

用于在ADO Data控件的记录集中移动记录。MoveFirst、MoveLast、MoveNext和MovePrevious方法分别代表移到首记录、最后一条记录、下一条记录和上一条记录。其使用语法如下。

Adodc1.Recordset.MoveFirst

Adodc1.Recordset.MoveLast

Adodc1.Recordset.MoveNext

Adodc1.Recordset.MovePrevious

4.CancelUpdate方法

用于取消ADO Data控件的记录集中添加或编辑操作，恢复修改前的状态。其使用语法如下。

Adodc1.Recordset.CancelUpdate

5.UpdateBatch方法

用于保存ADO Data控件的记录集中添加或编辑操作。其使用语法如下。

Adodc1.Recordset.UpdateBatch

13.2.3　ADO Data控件的事件

ADO Data控件的常用事件如下。

1.WillMove和MoveComplete事件

WillMove事件在当前记录的位置即将发生变化时触发，如使用ADO Data控件上的按钮移动记录位置时发生。MoveComplete事件在位置改变完成时触发。

2.WillChangeField和FieldChangeComplete事件

WillChangeField事件是当前记录集中当前记录的一个或多个字段发生变化时触发。而FieldChange-Complete事件则是当字段的值发生变化后触发。

3.WillChangeRecord和RecordChangeComplete事件

WillChangeRecord事件是当记录集中的一个或多个记录发生变化前产生的。而RecordChange-Complete事件则是当记录已经完成后触发。

13.2.4　应用案例——ADO控件与select语句联合应用

【实例13.5】设计如图13.12所示的“用户登记表”查询程序。本例中的“用户登记表”是在13.1.7节创建的数据表。效果如下所示。

图　13.12　实例13.5界面

❑初始运行时使用ADO Data控件的向前、向后按钮可以浏览数据表中所有的记录。标签lblResult中显示数据表中所有记录的个数。

❑在查询文本框txtSQL中输入select语句并按下Enter后，将查询到的结果的第一条记录显示在被绑定的控件内，其他记录可以用向后按钮浏览。并在标签lblResult中显示查询到的记录个数。

程序中各对象属性设置见表13.6，对象的其他属性均取默认值。

编写事件过程如下。

01 Option Explicit

02

03 Private Sub Adodc1_MoveComplete(ByVal adReason As ADODB.EventReasonEnum,ByVal pError As

04 ADODB.Error,adStatus As ADODB.EventStatusEnum,ByVal pRecordset As ADODB.Recordset)

05 Adodc1.Caption="记录："＆Adodc1.Recordset.AbsolutePosition

06 End Sub

07

08 Private Sub Form_Load()

09 If Not Adodc1.Recordset.EOF Then

10 lblResult.Caption="目前记录集中共有："＆Adodc1.Recordset.RecordCount＆"条记录"

11 Else

12 lblResult.Caption="目前记录集中共有：0条记录"

13 End If

14

15 End Sub

16

17 Private Sub txtSQL_KeyPress(KeyAscii As Integer)

18 If KeyAscii=13 Then

19 Adodc1.RecordSource=txtSQL.Text

20 Adodc1.Refresh

21 lblResult.Caption="本次查询共查询到："＆Adodc1.Recordset.RecordCount＆"条记录"

22 End If

23

24 End Sub

【代码说明】代码第08～15行是窗体加载事件，其中主要是判断查询数据的记录条数。代码第17～24行是判断用户是否按下键盘上的Enter键，如果是，则根据查询文本框内的语句重新查询数据，并显示查询记录的条数。

【运行效果】本例的运行效果如图13.12所示。

13.3　两种表格控件的用法

已往所学习的控件都无法处理以表格形式显示的数据。DataGrid和MSHFlexGrid控件能够显示包含若干行和列的表格数据。

13.3.1　DataGrid控件

DataGrid控件是ActiveX控件，所以要先将其添加到工具箱中，然后再使用。添加的方法为在工具箱上单击右键，从弹出菜单中选择“部件”菜单项，打开“部件”对话框，从ActiveX控件列表中选择"Microsoft DataGrid Control 6.0"，如图13.13所示。单击“确定”按钮后，DataGrid控件的图标
出现在工具箱中，用户可以像使用内部控件一样将它绘制在窗体中。

图　13.13　“部件”对话框

DataGrid控件主要用来与ADO Data控件绑定。下面通过一个例子说明DataGrid控件与ADO Data控件绑定的方法。

【实例13.6】编写程序，试验DataGrid控件与ADO Data控件绑定，界面设计如图13.14所示。运行效果如下所示。

图　13.14　实例13.6界面设计

❑能够以表格的形式浏览数据表内容。

❑能够更改记录的内容。

❑能够添加新记录。

具体操作步骤如下。

1.设置Adodc1的属性

（1）首先设置ConnectionString属性的值为：

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\VB\db1.mdb;Persist Security Info=False

（2）其次设置CommandType属性为：2——adCmdTable。

（3）最后设置RecordSource属性为：用户登记表。

2.设置DataGrid1的属性

（1）在DataGrid1控件上右击，在弹出菜单中选择“属性”菜单项。在打开的“属性页”对话框中选择“允许添加”右侧的复选框，如图13.15所示。选中该项后，程序运行时，表格的最后会出现一条空行，可以通过该行向数据库添加内容。

图　13.15　“属性页”对话框

技巧　在“属性页”对话框中还可以选择“允许删除”。但是，即使选中该项，在程序运行时也可能不允许删除，而且程序会变得很不稳定。因此，建议不要使用“允许删除”项。

（2）设置DataGrid1的DataSource属性为：Adodc1。

编写事件过程：

01 Private Sub Adodc1_MoveComplete(ByVal adReason As ADODB.EventReasonEnum,ByVal pError As

02 ADODB.Error,adStatus As ADODB.EventStatusEnum,ByVal pRecordset As ADODB.Recordset)

03 Adodc1.Caption="记录："＆Adodc1.Recordset.AbsolutePosition

04 End Sub

【代码说明】代码第02行只是设置了Adodc1控件的标题。

【运行效果】运行程序，效果如图13.16所示。表格中第4条记录的内容为通过表格新添加的内容。

图　13.16　实例13.6运行结果

13.3.2　MSHFlexGrid控件

与DataGrid控件相比MSHFlexGrid控件的功能更多。但令人遗憾的是MSHFlexGrid控件不能像DataGrid控件那样可以直接通过表格添加新记录或者直接修改内容。

MSHFlexGrid控件也是ActiveX控件，所以也需要先要将其添加到工具箱中，然后再使用。添加的方法为打开“部件”对话框，从ActiveX控件列表中选择"Microsoft Hierarchical FlexGrid Control 6.0"。MSHFlexGrid控件在工具箱中的图标为
。在窗体中的外观如图13.17所示。

图　13.17　MSHFlexGrid控件

1.设置表格的行列数

MSHFlexGrid控件的行列数默认为2行2列，其中1行1列为固定行、固定列（参考图13.17）。

改变表格的行列数一般有两种方法。一种为在设计阶段更改，另一种为在运行阶段通过程序更改。在知道表格需要多少行、多少列的时候可以在设计阶段设置行列数；而在不知道表格有多少行列时，则必须通过程序设置行列数。比如，要将数据库表中的内容显示到表格中时，在程序设计阶段，编程人员不会知道表格的行列数，因此只能在程序中设置。

（1）通过“属性页”窗口设置行列数

右击窗体上的MSHFlexGrid控件，从弹出菜单中选择“属性”菜单项，即可打开该控件的“属性页”对话框，如图13.18所示。

图　13.18　“属性页”对话框

该窗口中除了可以设置行列数以外，还可以设置用户选择模式（行选择、列选择、单元选择）、是否允许用户调整大小（行高、列宽）等选项。

（2）通过程序设置行列数

通过程序设置行列数的方法可以为：

MSHFlexGrid1.Rows=5

MSHFlexGrid1.Cols=6

其中，Rows属性用来设置表格的总行数，Cols属性设置总列数（包括固定行、列数）。上面两行语句的作用为设置表格的行列数为5行5列。

在数据库编程中，因为字段通常是固定的，所以表格的列数也可以是固定数，但记录是不固定的，因此要将数据库表中的内容全部显示到表格中时，设置行数的更有效的一种方法为：

MSHFlexGrid1.Rows=MSHFlexGrid1.Rows+1

通常，会把上面的语句放在循环中使用。

2.在单元格中显示数据

要在单元格中显示预定的数据，可以使用MSHFlexGrid控件的Text属性或TextMatrix属性。

（1）Text属性

使用Text属性在单元格中显示数据时，必须先设置行号和列号，需要注意的是MSHFlexGrid控件的行号和列号都是从0开始。例如，下面的程序使表格的第3行第3列的单元格中显示字符串"abcd"。

01 Private Sub Form_Load()

02 MSHFlexGrid1.Row=2

03 MSHFlexGrid1.Col=2

04 MSHFlexGrid1.Text="abcd"

05 End Sub

其中Row和Col属性分别为表格当前行号和列号。除了可以设置行号、列号以外，还可以通过这两个属性获取当前行号、列号。例如：

06 Private Sub MSHFlexGrid1_Click()

07 MsgBox"行号："＆MSHFlexGrid1.Row＆vbCrLf＆"列号："＆MSHFlexGrid1.Col

08 End Sub

上面程序的运行结果如图13.19所示（单击了第1行、第1列的单元格）。

图　13.19　显示当前行号、列号

（2）TextMatrix属性

TextMatrix属性实际上是一个二维数组。例如，要在表格的第4行第2列单元格中显示数字100的语句为：

MSHFlexGrid1.TextMatrix(3,1)=100

【实例13.7】将二维数组的内容显示到表格中。界面设计如图13.20所示。

图　13.20　实例13.7界面设计

编写事件过程如下。

01 Private Sub Form_Load()

02 '将总行数、总列数设置为0

03 MSHFlexGrid1.Rows=0

04 MSHFlexGrid1.Cols=0

05 End Sub

06

07 Private Sub cmdShow_Click()

08 Dim i As Integer

09 Dim j As Integer

10 Dim a(8,5) As Integer

11

12 '下面的代码给二维数组a赋值

13 For i=0 To 8

14 For j=0 To 5

15 a(i,j)=i+j

16 Next

17 Next

18

19 '将表格的总列数设置为数组a的第二维的上界值+1，因为数组下标从0开始

20 Me.MSHFlexGrid1.Cols=UBound(a,2)+1

21

22 '下面的代码将数组a的内容显示到表格中

23 For i=0 To 8

24 Me.MSHFlexGrid1.Rows=Me.MSHFlexGrid1.Rows+1

25 For j=0 To 5

26 Me.MSHFlexGrid1.TextMatrix(i,j)=a(i,j)

27 Next

28 Next

29 End Sub

【代码说明】代码第03～04行设置控件的总行数、总列数；代码第10～17行定义了一个二维数组，并且为这个数组赋值；代码第23～28行通过遍历的方式将数组a的内容显示到表格中。

【运行效果】最终运行结果如图13.21所示。

图　13.21　实例13.7运行结果

3.设置单元格的格式

MSHFlexGrid控件允许用户对其单元格格式进行设置。下面用一个实例说明在程序中设置单元格格式的方法。

【实例13.8】编程试验设置MSHFlexGrid控件的单元格格式。界面为在窗体Form1中绘制一个MSHFlexGrid1。编写事件过程如下。

01 Private Sub Form_Load()

02 With MSHFlexGrid1

03 .Row=2

04 .Col=2

05 .CellAlignment=4

06 .CellBackColor=vbGreen

07 .CellFontBold=True

08 .CellFontSize=9

09 .Text="1212"

10 End With

11 End Sub

【代码说明】代码第02行的With语句的作用是为了少写几个对象名，要注意的是在With…End With之间使用对象的属性时属性前面的“点”（.）不可省略。代码第05行的CellAlignment属性用来设置当前单元格的对齐方式，其对齐方式共有10种。具体值和含义见表13.7。

CellBackColor属性用来设置当前单元格的背景颜色；CellFontBold属性用来设置当前单元格文字的加粗；CellFontSize属性用来设置当前单元格的字号。MSHFlexGrid控件还有很多其他用来设置单元格格式的属性，在此就不再一一列举，使用时可以参考Visual Basic的MSDN联机帮助系统。

【运行效果】实例13.8的运行结果如图13.22所示。

图　13.22　实例13.8的运行结果

4.与ADO Data控件绑定

MSHFlexGrid控件也可以与ADO Data控件绑定。绑定后只能浏览数据表内容，不能用其直接修改、添加和删除与之绑定的数据库表内容，而只能通过编写相应的程序来实现。绑定的具体方法为将MSHFlexGrid控件的DataSource属性设置为ADO Data控件的名称即可。

MSHFlexGrid控件还有一个Recordset对象属性，使用该对象属性可以直接将内存缓冲区中的数据集显示到MSHFlexGrid控件内。具体用法在本书后面的章节中通过实例说明。

13.4　小结

本章继续第12章的内容，通过VB与数据库连接，实现数据的读取。第12章我们介绍的是一些操作数据的基础语句，如Select查询等，而本章很少看到这些语句，都是通过一些控件来完成，如第13节介绍的两个控件，都可以在显示数据同时，还实现了数据的添加、删除、更新等功能。希望读者通过本章的学习，对Windows窗体下的数据库应用程序开发有一定的了解。

13.5　本章练习

1.使用TextBox控件和Data控件实现数据绑定时，设置步骤是什么？

2.简述为什么在程序运行的时候，不能动态给Data控件的DatabaseName属性赋值实现数据绑定？

3.使用ADO Data控件实现数据的新增、删除、修改。

第14章　ADO数据库编程

ADO数据库编程是目前最为流行的一种数据库编程方案。因为，使用ADO可以很容易与各种类型的数据库连接，而且其数据存取功能也是包罗万象，吸取了各种数据库访问对象的精华部分。本章将介绍ADO数据库编程方面的内容。本章包括以下知识点。

❑数据访问对象可以在程序运行时创建数据库，以及对数据库进行访问等操作。

❑Connection对象和RecordSet对象的使用。

❑数据的一些高级操作技巧。

通过本章的学习，读者可以掌握数据绑定及数据记录的动态查询、数据库的动态创建等技术。掌握了这些数据库编程技术，读者能够编写很多高级的数据库程序。

14.1　ADO介绍

ADO的全称为Active Data Objects，即ActiveX数据对象。ADO是微软新的通用数据存取框架，它包含了数据库访问对象（DAO）、远程数据对象（RDO）及几乎所有其他数据存取方式的全部功能。用户可以利用ADO连接Access、SQL Server、Oracle及其他的数据源。

14.1.1　认识ADO

当今，编写数据库应用程序时，大部分程序员都会采用ADO数据库编程技术。第13章讲述的ADO Data控件的用法就是在程序中实现ADO的最简便的方法。

ADO被设计用来同一个叫做OLE DB Provider的新的数据访问层协同工作。OLE DB是一个底层的数据访问接口，用它可以访问各种数据源，包括传统的关系型数据库、电子邮件系统及自定义的商业对象等。微软目前正积极推广ADO+OLE DB技术，并计划用OLE DB取代ODBC。

ADO将所有强大的功能都封装起来，展现给用户的仅仅是一个简单易用的界面，通过应用程序不仅可以访问单用户的Access数据库，而且还可以访问SQL Server、Oracle等复杂的客户/服务器系统的数据库，及其他任何提供OLE DB或ODBC机制的数据库。其模型如图14.1所示。

图　14.1　ADO模型

ADO的具体优点如下。

❑ADO具有高度的灵活性，它可以使用相同的编程模式连接到不同的数据提供者，而不用理会给定提供者的特定特性。

❑较低的内存占用率。

❑具有远程数据服务（RDS）功能，通过RDS可以在一次往返过程中将数据从服务器移动到客户端应用程序或Web页，并在客户端对数据进行处理后将更新结果返回服务器。

❑同传统的数据对象层次（DAO和RDO）不同，ADO可以独立创建。可以只创建一个Connection对象，然后由多个独立的Recordset对象来使用它。

❑ODBC本身是以SQL Server、Oracle等关系数据库作为访问对象，而OLE DB则不仅限于此，它还可以对电子邮件、文本文件、复合文件、数据表等各种各样的数据通过统一的接口进行存取。

14.1.2　ADO编程模型

下面结合实际生活中的例子说明ADO的编程模型。在此将应用程序比作超市，数据库比作仓库。那么ADO编程模型如下。

（1）超市和仓库之间必须建立合作关系。而应用程序和数据库之间也必须先建立连接。这个连接使用Connection对象实现。

（2）超市定制计划，比如出库计划（从仓库拿什么货物）、销毁计划（销毁过期货物）等。而应用程序中需要编写SQL语句，比如查询哪些数据、删除哪些数据等。

（3）实施计划，假设实施的是出库计划，首先要有运货的卡车，让卡车带上出库单到达仓库，根据计划从仓库把货物装载到卡车上，然后返回超市。而应用程序中，如果执行的是Select语句，首先要创建Recordset对象，该对象将根据Select查询条件从数据库中提取数据，并返回给程序。Recordset对象好比卡车。

（4）超市将对卡车运回来的货物进行上架、销售等操作。而应用程序通过Recordset对象对数据进行修改、删除、增加等操作。

（5）货物出库或入库后更改仓库的货物清单。而数据库要更新数据。

（6）当不再使用仓库时，超市与仓库解除合作关系。而应用程序和数据库也应该结束连接。

14.1.3　在工程中引用（添加）ADO

如果要在程序中使用ADO，首先必须要引用相应的类型库。引用的方法如下。

（1）选择“工程”菜单下的“引用”菜单项。

（2）在打开的“引用”对话框中选择"Microsoft ActiveX Data Objects 2.8 Library"，如图14.2所示。如果列表中没有"Microsoft ActiveX Data Objects 2.8 Library"，可以使用"Microsoft ActiveX Data Objects 2.6 Library"或更低版本的类型库。

图　14.2　“引用”对话框

（3）单击“确定”按钮，即可在程序中使用ADO了。

注意　引用ADO类型库和添加ActiveX控件不同，前者不会在工具箱上添加任何控件。

测试ADO是否引用成功的方法为在程序的随便一个位置输入

dim cn as

时在as后面的自动显示的类型列表中如果有ADODB，即证明引用成功，否则表明引用未成功，如图14.3所示。

图　14.3　数据类型列表（ADODB）

14.2　访问数据库

ADO有两个非常重要的核心对象，分别是Connection对象和Recordset对象。本节将介绍Connection对象。Connection对象用于建立和数据源的连接。在C\S结构中，该对象实际上是表示了与服务器的实际网络连接。建立和数据库的连接是访问数据库的第一步，ADO打开连接的主要方法是通过Connection对象来连接数据库。

14.2.1　使用Connection对象连接Access数据库

例如，要连接一个Access2000数据库的操作步骤如下。

（1）在工程中引用ADO（使用14.1.3节的方法）。

（2）在程序中添加类似下面的代码：

01 Dim cn As ADODB.Connection

02 Set cn=New ADODB.Connection

03 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

其中，第1条语句定义了一个ADO连接对象cn；第2条语句创建并初始化连接对象cn，在此因为cn是对象变量，因此初始化语句前要加上Set关键字；第3条语句使用Connection对象的Open方法连接数据库。Open方法后面的连接字符串分为两部分。其中第一部分：

PROVIDER=Microsoft.Jet.OLEDB.4.0

用来表明连接的是一个Access2000数据库。连接字符串的第二部分：

Data Source=d:\sqbt\vb\db1.mdb;

用来指定一个具体的数据库。如果数据库设置了密码，则必须在字符串的后面加上数据库的密码。例如假设db1数据库的密码为“123”则连接字符串应该写为：

"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;Jet OLEDB:Database Password=123;"

上面的第3条语句也可以使用Connection对象的ConnectionString属性完成，代码如下：

cn.ConnectionString="PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"cn.Open

【实例14.1】编写程序，使用Connection对象连接"d:\sqbt\vb\db1.mdb"数据库。连接成功则返回成功信息，连接失败则返回失败。本例中的db1.mdb是在13.1.6节创建的数据库。界面设计如图14.4所示，其中按钮的名称为cmdConnect。

图　14.4　实例14.1设计界面

编写如下事件过程。

01 Private Sub cmdConnect_Click()

02 Dim cn As ADODB.Connection

03 Set cn=New ADODB.Connection

04 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

05 If cn.State=adStateOpen Then

06 MsgBox"连接成功！"

07 Else

08 MsgBox"连接失败！"

09 End If

10 cn.Close

11 Set cn=Nothing

12 End Sub

【代码说明】代码第05行用到了Connection对象的State属性。该属性说明当前Connection对象是已经建立还是关闭。它是只读属性，其取值有两个，具体说明见表14.1。

本例中第10行还用到了Connection对象的Close方法，该方法用来关闭连接。程序中最后一条语句：

Set cn=Nothing

的作用是释放Connection对象cn的内存空间。

【运行效果】本例的运行界面如图14.4所示。

14.2.2　使用Connection对象连接其他数据库

如前所述，使用ADO不仅可以连接Access数据库，还可以连接其他类型的数据库。下面介绍连接几个常用数据库的方法。

1.连接Visual FoxPro数据库

连接Visual FoxPro数据库的写法类似于下面的代码。

cn.Open"PROVIDER=MSDASQL;DRIVER={Microsoft Visual Foxpro Driver};"＆_

"SourceDB=d:\\data\\;SourceType=DBF"

其中，连接字符串的开头"PROVIDER=MSDASQL;"可以被省略。

2.连接SQL Server数据库

连接SQL Server数据库的写法类似于下面的代码。

cn.Open"PROVIDER=MSDASQL;DRIVER={SQL Server};"＆_

"SERVER=myserver;DATABASE=pubs;UID=sa;PWD=123"

3.连接Sybase数据库

连接Sybase数据库的写法类似于下面的代码。

cn.Open"PROVIDER=MSDASQL;DRIVER={Sybase System 11};"＆_

"SRVR=serveru;DSN=;DB=dataa;UID=sa;PWD=dqwe;PERSIST SECURITY INFO=False"

4.连接Oracle数据库

连接Oracle数据库的写法类似于下面的代码。

cn.Open"PROVIDER=MSDASQL;DRIVER={Microsoft ODBC for Oracle};"＆_

"SERVER=Webserver;DSN=;databasename=aa;databasefile=d:\\aa\\;UID=sqbt;PWD=123"

14.2.3　使用Connection对象连接ODBC数据源

虽然微软提倡使用PROVIDER方式连接数据库，但是目前仍然有一部分编程人员使用ODBC数据源的方法连接数据库。在网络编程中如果使用Access数据库，为了防止数据库被下载破译，使用ODBC数据源可以隐藏数据库的路径。下面介绍这种方法的使用。

1.创建ODBC数据源

使用ODBC连接数据库时，首先必须创建ODBC数据源。创建ODBC数据源的操作步骤如下。

（1）单击“开始”|“控制面板”|“管理工具”|“数据源（ODBC）”命令，在“ODBC数据源管理器”中选择“文件DSN”选项卡，如图14.5所示。

图　14.5　“ODBC数据源管理器”（“文件DSN”选项卡）

（2）在“文件DSN”选项卡中，单击“添加”按钮，打开“创建新数据源”对话框。

（3）在“创建新数据源”对话框中，选择正确的数据源驱动程序，例如，给Access数据库创建数据源，应该选择"Microsoft Access Driver（*.mdb）"项，如图14.6所示。而要给Oracle数据库创建数据源，应该选择"Microsoft ODBC for Oracle"项。

图　14.6　“创建新数据源”对话框1

（4）单击“下一步”按钮，在输入框中键入新建的文件数据源的名称，比如"MyODBC"，如图14.7所示。

图　14.7　“创建新数据源”对话框2

（5）再单击“下一步”按钮，会出现完成界面，从中单击“完成”按钮后，即可创建一个数据源。

（6）单击“完成”按钮，弹出“ODBC Microsoft Access安装”对话框，如图14.8所示。

图　14.8　“ODBC Microsoft Access安装”对话框

（7）在“ODBC Microsoft Access安装”对话框中，单击“选择”按钮，在打开的“选择数据库”对话框中选择"d:\sqbt\vb\"下的db1.mdb数据库，如图14.9所示。

图　14.9　“选择数据库”对话框

（8）在“选择数据库”对话框中，单击“确定”按钮退出。

（9）在“ODBC Microsoft Access安装”对话框中，单击“确定”按钮退出。此时，将会看到“ODBC数据源管理器”的“文件DSN”选项卡列表中出现了一个"MyODBC"列表项，如图14.10所示。单击“确定”按钮，完成ODBC的全部配置。

图　14.10　“文件DSN”选项卡（列表中有数据源名称MyODBC）

2.编写连接ODBC数据源的代码

连接ODBC数据源的写法类似于下面的代码。

cn.Open"FileDSN=MyODBC.dsn"

假设数据库有用户名和密码，分别为aa和123时，其连接代码如下。

cn.Open"FileDSN=MyODBC.dsn;UID=aa;PWD=123"

14.2.4　应用案例——使用Connection执行SQL

使用Connection对象执行SQL语句时要用到其Execute方法。下面的实例使用Execute方法完成向数据表插入记录、删除记录和修改记录，并将每次的操作结果反映到表格内。

【实例14.2】编写程序，使用Connection对象执行SQL语句。界面设计如图14.11所示。

图　14.11　实例14.2界面设计

操作步骤如下。

（1）在工程中引用ADO。选择“工程”|“引用”命令，在“引用”对话框中选择"Microsoft ActiveX Data Objects 2.8 Library"。

（2）将ADO Data控件和MSHFlexGrid控件添加到工具箱中。选择“工程”|“部件”菜单项，在“部件”对话框ActiveX控件列表中选择"Microsoft ADO Data Control 6.0"和"Microsoft Hierarchical FlexGrid Control 6.0"两项。

（3）按照图14.11所示，将所有控件绘制到窗体上。

（4）设置Adodc1的ConnectionString属性如下。

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\SQBT\vb\db1.mdb;Persist Security Info=False

说明：本例中的D:\SQBT\vb\db1.mdb是在13.1.6节创建的数据库。

（5）设置Adodc1的RecordSource属性如下。

select*from 用户登记表

（6）将MSHFlexGrid1的DataSource属性设置为：Adodc1（绑定）。

（7）编写事件过程如下。

01 Option Explicit

02 Private Sub cmdDelete_Click()

03 Dim cn As ADODB.Connection

04 Set cn=New ADODB.Connection

05 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

06 '下面一行语句从数据表中删除名叫“马六”的人

07 cn.Execute"delete from用户登记表where name='马六'"

08 cn.Close

09 Set cn=Nothing

10 Adodc1.Refresh '刷新控件

11 End Sub

12

13 Private Sub cmdInsert_Click()

14 Dim cn As ADODB.Connection

15 Set cn=New ADODB.Connection

16 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

17 '下面一行语句向数据表中插入一条新记录

18 cn.Execute"insert into用户登记表(id,name,sex,tele,address) values('999','马六','男','0472-222222','包

19 头市')"

20 cn.Close

21 Set cn=Nothing

22 Adodc1.Refresh

23 End Sub

24

25 Private Sub cmdUpdate_Click()

26 Dim cn As ADODB.Connection

27 Set cn=New ADODB.Connection

28 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

29 '下面一行语句将数据表中名叫“马六”的人的姓名改为“马小六”

30 cn.Execute"update用户登记表set name='马小六' where name='马六'"

31 cn.Close

32 Set cn=Nothing

33 Adodc1.Refresh

34 End Sub

【代码说明】代码第07行使用Execute方法执行了一条删除语句，代码第18行使用Execute方法执行了一条添加语句。代码第30行执行了一条更新语句。不要忘记用代码第31行来关闭连接。

【运行效果】运行结果如图14.12所示。

图　14.12　实例14.2运行结果

通过本例可知Execute方法的格式如下。

Connection对象.Execute SQL字符串

其中，SQL字符串通常主要是Insert、Delete和Update等语句。

14.3　使用RecordSet对象操作记录

在数据库编程中，人们通常会用RecordSet对象变量存放查询数据。然后通过该变量对查询到的数据进行各种各样的操作。不仅如此，使用RecordSet对象变量还可以向数据库表添加新记录、修改记录等。实际上可以认为RecordSet对象是一个存放二维表格的变量，该表格是相对应的被查询数据库表的一个子集。

14.3.1　使用RecordSet对象执行Select查询

1.创建RecordSet对象变量

在用到RecordSet对象变量之前应该创建它。例如创建一个rs变量的语句如下。

Dim rs As ADODB.Recordset

Set rs=New ADODB.Recordset

其中，第1条语句用来声明一个RecordSet对象变量，这时还没有创建。第2条语句用来创建RecordSet对象变量。

2.使用Open方法执行Select语句

使用Open方法可以执行Select查询语句，并将查询结果返回给RecordSet对象变量。Open方法的使用类似于下面的语句。

rs.Open"select*from用户登记表where sex='男'",cn,adOpenForwardOnly,adLockOptimistic

其中，rs是RecordSet对象变量。Open方法的后面有4个参数，分别说明如下。

❑第一个参数是Select查询语句，必须将其放在双引号中，实际上它是一个字符串。

❑第二个参数cn是一个Connection对象变量，它用来指引rs到指定的数据库取数据。

❑第三个参数告诉ADO在记录集上使用何种类型的数据库游标。ADO能使用多种类型的游标，表14.2列出了各种可能的选项及其含义。

❑第4个参数指定锁方式，可能的取值类型及其含义见表14.3。

14.3.2　显示RecordSet对象的查询结果

用Open方法执行Select查询语句后，会将查询结果存放到RecordSet对象变量中，接下来是怎样利用这些查询结果。最简单的用法是将其显示出来，供用户查看。

要显示查询结果，必须用到控件。例如用文本框显示rs中当前记录的name字段变量的值，可以用如下两种语句。

Text1.Text=rs("name")

或

Text1.Text=rs.Fields(1)

第二种语句使用了记录集的Fields属性。该属性是一个一维数组，用来存放当前记录的各字段的信息。比如rs.Fields(0)代表当前记录第1个字段的值；rs.Fields(3)代表当前记录第4个字段的值等。

技巧　其实rs.Fields(i)（i为0、1、2…等整数）实际上是rs.Fields(i).Value的简写，从这里看得出Fields属性，不仅仅是属性，它本身其实就是一个对象。Fields(i)是Field对象，Fields是Field对象的集合对象，这两个对象各有各的属性和方法。例如，Fields对象有append方法（rs.Fields.Append…），用来添加字段；Field对象有Name属性，用来获取字段名称等。Field对象以Fields(i)的形式存在。

说明　记录指针指向的记录为当前记录。RecordSet对象变量中只有一个当前记录。这样，二维表格的行就被确定了下来，此时再用字段就可以确定二维表格中具体的单元格了。如图14.13所示，当前记录为第一条记录时，如果确定了字段（列）为name字段时，单元格就被确定了下来，其内容肯定为“张三”。

图　14.13　RecordSet记录集

【实例14.3】编写程序，将RecordSet对象变量中查询到的结果显示出来。

界面设计如图14.14所示。窗体Form1中有一个列表框List1。

图　14.14　实例14.3界面设计

首先在工程中引用ADO，选择“工程”|“引用”菜单项，在“引用”对话框中选择"Microsoft ActiveX Data Objects 2.8 Library"，单击“确定”按钮。其次编写事件过程如下。

01 Option Explicit

02

03 Private Sub Form_Load()

04 Dim cn As ADODB.Connection

05 Dim rs As ADODB.Recordset

06 Dim sql As String

07

08 Set cn=New ADODB.Connection

09 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

10 '获取“用户登记表”中的所有记录

11 sql="select*from用户登记表"

12 Set rs=New ADODB.Recordset

13 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

14 Do While Not rs.EOF

15 List1.AddItem rs("name")

16 rs.MoveNext

17 Loop

18 rs.Close

19 cn.Close

20 Set rs=Nothing

21 Set cn=Nothing

22 End Sub

【代码说明】本例中使用到了RecordSet对象的EOF属性和MoveNext方法。其中，第14行的EOF属性为当数据表的记录指针指向了数据表的末尾时才会为True，其他情况下均为False。第16行的MoveNext方法用来将记录指针向下移动，如果本例中忘记了使用MoveNext方法移动指针，那么Not rs.EOF就会永远为True，从而将会造成死循环。

【运行效果】本例运行结果如图14.15所示。

图　14.15　实例14.3运行结果

说明　本例中的D:\SQBT\vb\db1.mdb是在13.1.6节创建的数据库。

14.3.3　应用案例——向数据表添加记录

使用RecordSet对象添加记录，要用到其AddNew方法。要使用该方法，应当在程序中写入类似于下面的语句。

......

rs.AddNew

rs("ID")="0008"

rs("name")="杨八"

rs("sex")="女"

...

...

rs.Update

...

其中，第1条语句：

rs.AddNew

的作用是在rs记录集中添加一条空白记录，此时记录指针会自动指向该空白记录，即该空白记录为当前记录。第2、3、4条语句的作用为将相应数据放到当前记录的ID、name和sex三个字段中。最后一条语句：

rs.Update

的作用是更新数据表，将新添加的记录真正的加入到数据表中。如果不使用Update语句，即使使用了AddNew方法添加了数据，数据库中的数据仍然不会变化。

【实例14.4】使用AddNew方法，编写一个能向“用户登记表”添加记录的程序。“用户登记表”为在13.1.6节创建的db1数据库中的数据表。

界面设计如图14.16所示。本程序最终功能要求如下。

图　14.16　实例14.4界面设计

❑窗体出现的时候，“男”单选按钮的状态为被选择状态（默认单选按钮）。

❑单击“添加”按钮时，如果文本框的输入内容为空，则给出提示，不让继续添加。

❑添加记录时，判断将要添加记录的用户ID值在数据库表中有无重复。如果有则给出提示，不让添加，否则可以添加。

❑添加成功后，将所有文本框全部清空，并将光标定位在第一个文本框内。

程序中各对象属性设置如表14.4所示。

写代码前，首先在工程中引用ADO。选择“工程”|“引用”菜单项，在“引用”对话框中选择"Microsoft ActiveX Data Objects 2.8 Library"，单击“确定”按钮。

其次，编写事件过程如下。

01 Option Explicit

02

03 Private Sub cmdAdd_Click()

04 Dim cn As ADODB.Connection

05 Dim rs As ADODB.Recordset

06 Dim sql As String

07 '判断文本框是否为空

08 If Trim(txtID.Text)=""Then

09 MsgBox"用户ID不能为空！",vbOKOnly+vbExclamation,"警告"

10 Exit Sub

11 End If

12 If Trim(txtName.Text)=""Then

13 MsgBox"姓名不能为空！",vbOKOnly+vbExclamation,"警告"

14 Exit Sub

15 End If

16 If Trim(txtTele.Text)=""Then

17 MsgBox"联系电话不能为空！",vbOKOnly+vbExclamation,"警告"

18 Exit Sub

19 End If

20 If Trim(txtAddress.Text)=""Then

21 MsgBox"地址不能为空！",vbOKOnly+vbExclamation,"警告"

22 Exit Sub

23 End If

24

25 Set cn=New ADODB.Connection

26 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

27

28 '查找数据表中字段ID值等于txtID文本框中输入值的记录

29 sql="select*from用户登记表where ID='"＆Trim(txtID.Text)＆"'"

30 Set rs=New ADODB.Recordset

31 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

32 '判断是否到达数据表末尾，如果到达则记录集为空，没有重复ID。

33 If rs.EOF Then

34

35 '____________添加新记录______________

36 rs.AddNew

37 rs("ID")=Trim(txtID.Text)

38 rs("name")=Trim(txtName.Text)

39 If optMan.Value=True Then

40 rs("sex")="男"

41 Else

42 rs("sex")="女"

43 End If

44 rs("tele")=Trim(txtTele.Text)

45 rs("address")=Trim(txtAddress.Text)

46 rs.Update

47 MsgBox"记录添加成功！",vbOKOnly+vbInformation,"提示"

48

49 '清空所有文本框中内容，并将光标定位到第一个文本框内

50 txtID.Text=""

51 txtName.Text=""

52 txtTele.Text=""

53 txtAddress.Text=""

54 txtID.SetFocus

55 Else

56 MsgBox"该ID已经存在！请使用其他ID！",vbOKOnly+vbExclamation,"警告"

57 txtID.Text=""

58 txtID.SetFocus

59 End If

60 rs.Close

61 cn.Close

62

63 Set rs=Nothing

64 Set cn=Nothing

65 End Sub

66

67 Private Sub Form_Load()

68 '初始化单选按钮的状态

69 optMan.Value=True

70 End Sub

【代码说明】代码第08～23行是判断用户的输入是否为空，如果是空则弹出提示对话框。代码25～61行打开连接，并读取数据。代码第63～64行清空对象。代码第69行初始化单选按钮。

【运行效果】运行效果如图14.17所示。

图　14.17　实例14.4运行效果

执行添加后，想要查看添加结果，可以使用Access软件打开“用户登记表”数据表，查看其内容的变化。执行添加前后的“用户登记表”中的内容，可以参考图14.18a和图14.18b。

图　14.18　执行添加前后对比

说明　使用AddNew方法时，新记录应该被添加在数据表的末尾。而图14.18b中新添加记录没有在数据表的末尾显示的原因是“用户登记表”曾经用ID做过索引，因此数据表显示的时候按照ID值升序排序了。

注意　使用Access查看“用户登记表”时，应该及时刷新，或者每次运行程序后都要重新打开数据表查看，这样才能看到正确的结果。

14.3.4　应用案例——从数据表删除记录

删除记录要用到Delete方法。该方法的格式类似于下面的语句。

rs.Delete

使用Delete方法，只能将当前记录删除（只有一条记录）。所以使用该方法前，应当将记录指针定位到要删除的记录上。使用Select语句可以获得将要删除的记录集，然后可以使用Delete方法一条一条地删除记录集中的记录。下面的实例说明了Delete方法的用法。

【实例14.5】删除“用户登记表”中的所有女性用户。

界面设计如图14.19所示，窗体Form1中有一个名称为cmdDelete的按钮。

图　14.19　实例14.5设计界面

首先，在工程中引用ADO，然后编写代码如下。

01 Private Sub cmdDelete_Click()

02 Dim cn As ADODB.Connection

03 Dim rs As ADODB.Recordset

04 Dim sql As String

05 Dim x As Integer

06

07 x=MsgBox("您确定删除么？",vbYesNo+vbQuestion,"提示")

08 If x=vbYes Then 'vbYes是常量，其值为6

09 Set cn=New ADODB.Connection

10 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

11 '获取"用户登记表"中的所有女性用户记录

12 sql="select*from用户登记表where sex='女'"

13 Set rs=New ADODB.Recordset

14 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

15 Do While Not rs.EOF

16 rs.Delete

17 rs.MoveNext

18 Loop

19 MsgBox"删除成功！",vbOKOnly+vbInformation,"提示"

20 rs.Close

21 cn.Close

22 Set rs=Nothing

23 Set cn=Nothing

24 End If

25 End Sub

【代码说明】执行删除前，使用Access软件打开“用户登记表”数据表，查看其内容后关闭。执行删除后再次打开“用户登记表”时会发现所有的女性用户的记录全部被删除。

在具体编写删除程序时，只要将本例中第12行的语句，即SQL字符串

sql="select*from用户登记表where sex='女'"

更改一下即可，其他代码均可以不变。例如要删除数据表中所有天津市的用户时，仅仅将SQL字符串改为

sql="select*from用户登记表where address='天津市'"

就可以完成删除所有天津市的用户的功能。

【运行效果】本例的运行效果如图14.19所示。

14.3.5　应用案例——修改数据表记录

想要修改记录内容时，首先要让记录指针指向该条记录（与Delete方法中相同），然后使用赋值语句和Update方法更新即可。例如使用下面的两条语句，可以将当前记录的name字段的内容更新为“汤姆”。

rs("name")="汤姆"

rs.Update

【实例14.6】编写一个程序，实现修改数据表记录的功能。本例中使用的数据库表，仍是13.1.6节创建的db1数据库中的“用户登记表”数据表。界面设计如图14.20所示。本程序最终功能要求如下。

图　14.20　实例14.6界面设计

❑窗体出现时，MSHFlexGrid表格控件中显示“用户登记表”中所有的记录，“确定修改”按钮不可用，“修改区域”中的“用户ID”后的文本框不可用，即不能修改用户ID。

❑单击表格中某行时，将该行的信息显示到下面的“修改区域”中，并将“确定修改”按钮设置为可用。

❑在下面的“修改区域”中修改完记录内容，并单击“确定修改”按钮后，数据表中相应记录的内容被修改，与此同时表格中的数据马上被刷新为新内容。

程序中各对象属性设置如表14.5所示。

首先，在工程中引用ADO，其次编写事件过程如下。

01 Option Explicit

02 Sub ShowGridData()

03 Dim cn As ADODB.Connection

04 Dim rs As ADODB.Recordset

05 Dim sql As String

06

07 Set cn=New ADODB.Connection

08 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

09 '查询"用户登记表"中的所有记录

10 sql="select*from用户登记表"

11 Set rs=New ADODB.Recordset

12 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

13 '将记录集内容显示到MSHFlexGrid1中

14 Set MSHFlexGrid1.Recordset=rs

15 MSHFlexGrid1.Refresh

16 rs.Close

17 cn.Close

18

19 Set rs=Nothing

20 Set cn=Nothing

21 End Sub

22

23 Private Sub cmdCancel_Click()

24 Unload Me

25 End Sub

26

27 Private Sub cmdOk_Click()

28 Dim cn As ADODB.Connection

29 Dim rs As ADODB.Recordset

30 Dim sql As String

31

32 Set cn=New ADODB.Connection

33 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=d:\sqbt\vb\db1.mdb;"

34 '查询要修改的记录

35 sql="select*from用户登记表where ID='"＆txtID.Text＆"'"

36 Set rs=New ADODB.Recordset

37 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

38 If Not rs.EOF Then

39 rs.Fields(0)=Trim(txtID.Text)

40 rs.Fields(1)=Trim(txtName.Text)

41 If optMan.Value=True Then

42 rs.Fields(2)="男"

43 Else

44 rs.Fields(2)="女"

45 End If

46 rs.Fields(3)=Trim(txtTele.Text)

47 rs.Fields(4)=Trim(txtAddress.Text)

48 rs.Update

49 MsgBox"数据修改成功！",vbOKOnly+vbInformation,"提示"

50 End If

51 rs.Close

52 cn.Close

53 Call ShowGridData

54

55 Set rs=Nothing

56 Set cn=Nothing

57

58 End Sub

59

60 Private Sub Form_Load()

61 MSHFlexGrid1.SelectionMode=flexSelectionByRow

62 cmdOk.Enabled=False

63 Call ShowGridData

64 End Sub

65

66 Private Sub MSHFlexGrid1_Click()

67 cmdOk.Enabled=True

68 With MSHFlexGrid1

69 .Col=1

70 txtID.Text=.TextMatrix(.Row,.Col)

71 txtName.Text=.TextMatrix(.Row,.Col+1)

72 If Trim(.TextMatrix(.Row,.Col+2))="男"Then

73 optMan.Value=True

74 Else

75 optWomen.Value=True

76 End If

77 txtTele.Text=.TextMatrix(.Row,.Col+3)

78 txtAddress.Text=.TextMatrix(.Row,.Col+4)

79 End With

80 End Sub

【代码说明】本例中，因为多次使用到了将记录集内容显示到MSHFlexGrid控件中的功能，所以笔者将此功能独立出来，编写为一个通用过程——代码第02～21行的ShowGridData()。程序中第68～79行还用到了With…End With语句。该语句的作用是在With…End With之间，引用With后面对象（例如MSHFlexGrid1）的属性或方法时可以省略该对象名，但是要注意不能省略属性或方法前的“.”（点）。例如语句

txtID.Text=MSHFlexGrid1.TextMatrix(MSHFlexGrid1.Row,MSHFlexGrid1.Col)

使用With…End With之后可以写为：

With MSHFlexGrid1

txtID.Text=.TextMatrix(.Row,.Col)

End With

使用这种方法在程序中可以少写很多次较长的对象名，这会给编写应用程序节省很多的时间和精力。

【运行效果】程序最后运行效果如图14.21所示。

图　14.21　实例14.6运行效果

本例中表格的使用方法用于添加、删除记录时也会得到很好的效果。

14.4　小结

本章深入学习了数据库应用程序的开发过程，这里主要介绍的是ADO编程模型，虽然在很多.NET框架中，已经出现了ADO.NET编程模型，但在VB程序中，大多数公司还是使用ADO模型。本章介绍了ADO模型中的很多操作对象，如数据库对象Connection、操作记录的对象RecordSet等。通过ADO提供的对象，可以方便地操作数据库中的数据记录。

14.5　本章练习

1.在工程中引用ADO的步骤是什么？

2.使用Connection对象连接ODBC数据源的步骤是什么？

3.练习使用RecordSet对象实现数据的新增、删除、修改。

第15章　网络编程技术

使用Visual Basic不仅可以编写出单机程序，也可以编写出基于网络的程序。本章将介绍使用Winsock控件编写客户/服务器程序，使用WebBrowser控件制作浏览器，使用Internet Transfer控件下载网页等方面的内容。本章包括以下知识点。

❑使用Winsock控件实现本地计算机与远程计算机之间的数据交换。

❑使用Internet Transfer控件实现文件下载等功能。

❑使用WebBrowser控件实现网页浏览功能。

通过本章的学习，读者能够使用Winsock、Internet Transfer和WebBrowser三种网络控件编写网络应用程序。读者甚至能够通过简单的代码编写自己的网页浏览器。

15.1　网络基础

使用Visual Basic编写网络程序之前，首先应当了解一些在编程时会用到的网络知识。例如，IP地址、端口和协议。

15.1.1　IP地址

计算机网络中相连的每一台计算机至少有一个网卡。对于局域网（LAN）而言，通常是使用以太网、令牌环或FDDI网卡。如果是一个移动用户或没有直接接入网络的家庭用户，则可使用调制解调器与网络相连。

对于一个基于TCP/IP网路中的计算机，无论是以太网、令牌环网、FDDI或调制解调器的连接，均由一个称为IP地址的32位数作为唯一标识。这个数通常写成一组由“．”号分隔的4个十进制数，例如222.39.165.116。

连入一个网络的每台计算机都必须有一个唯一的IP地址，如果与两个或两个以上的网络连接，那么每个网络连接者必须赋予该连接唯一的IP地址。拥有一个以上IP地址的计算机被称为多宿主机。

15.1.2　端口

端口（port）是计算机输入输出信息的接口。例如，个人计算机一般都有串口（serial port），它是加载在I/O设备上的一个物理接口。计算机连入通信网络或Internet也需要一个端口，但这些端口不是物理端口，而是一个由16位数标识的逻辑端口。这个端口号是TCP/IP协议的一部分，通过这些端口信息可以进行输入和输出。在Internet上的每个计算机都有10000个逻辑端口号，其中1024以内的端口号几乎都分配给了特殊服务的功能。因此，如果要建立新的应用，不应该使用这个范围内的端口号。

15.1.3　协议

在讨论网络的连接机制时，还需要说明在网络上传输哪种数据类型、怎样解释这些数据类型以及怎样请求传输这些数据，这些都是通过网络协议规定的。协议是描述数据交换时必须遵循的规则和数据格式。网络协议中有两个非常重要的协议，分别是TCP协议和UDP协议。

TCP协议是一个面向连接的协议，允许从一台机器发出的字节流无差错地发往网络中的其他机器。TCP还要处理流量控制，以避免快速发送方向低速接收方发送过多数据而使接收方无法处理。UDP是一个不可靠的、无连接协议，用于不需要TCP的排序和流量控制能力而是自己完成这些功能的应用程序。这种协议主要适合传输语音或影像。

15.2　Winsock控件

WinSock控件能够通过UDP协议（用户数据报协议）或TCP协议（数据传输协议）连接到远程计算机并进行数据交换。使用这两种协议可以开发复杂的通信应用程序和实用程序。要使用Winsock控件，首先应将其添加到工具箱中，方法为：右击工具箱，选择“部件”菜单项，在打开的“部件”对话框控件列表中选择"Microsoft Winsock Control 6.0"项，单击“确定”按钮后Winsock控件就会被添加到工具箱中，其在工具箱中的图标为
。Winsock控件在运行状态下不可见。

15.2.1　Winsock控件的常用属性

1.Protocol属性

设置使用的协议（TCP或UDP），其取值及含义如表15.1所示。

该属性可以在属性窗口中设置，也可以在程序中设置，例如下面的语句可以在程序中设置使用协议：

Winsock1. Protocol=sckTCPProtocol

2.RemoteHost属性

指定要连接的远程主机的名称或IP地址（字符串型），例如：

Winsock1. RemoteHost="192.168.0.1"

3.RemotePort属性

设置或返回要连接的应用程序的远程端口号，在设置端口号时应尽量设置成大于1024。

4.RemoteHostIP属性

返回实际连接的远程计算机的IP地址（字符串型）。可以是客户端IP，也可以是服务器IP。在连接成功后，对于客户端该属性为服务器IP；对于服务器该属性为客户端IP。当使用UDP协议时，在DataArrival（数据到达）事件出现之后，属性包含了发送UDP数据的计算机的IP地址。

5.LocalHostName属性

返回本地计算机名。在设计时是只读的，而且是不可用的。

6.LocalPort属性

该属性用于设置或返回Winsock控件使用的本地端口。对服务器应用程序来说，这是用于侦听的本地端口号，一定要设置；对于客户应用程序来说，该属性指定发送数据的本地端口，可以不设置，由Winsock控件随机指定。

7.LocalIP属性

该属性用于返回本地主机的IP地址（字符串型）。在设计时是只读的，而且是不可用的。

8.State属性

该属性用于返回Winsock控件的当前状态，其取值及含义如表15.2所示。

15.2.2　Winsock控件的常用方法

1.Connect方法

使用TCP协议时，用于建立与远程计算机的连接，该方法在编写客户端程序时使用。

格式：Object. Connect [remoteHost,remotePort]

2.Accept方法

接收一个新的连接请求。该方法仅在TCP型服务器应用程序中的ConnectRequest事件过程中使用。

格式：Object. Accept requestID

其中，requestID参数是ConnectRequest事件传递过来的请求号。

3.Listen方法

TCP连接时，用于创建套接字并设置为侦听模式。该方法仅适用于TCP连接。

格式：Object. Listen

4.SendData方法

用于将数据发送给远程计算机。

格式：Object. SendData data

其中，data参数是要发送的数据。

5.GetData方法

用于获取从网络传送给Winsock控件的数据，该方法通常与DataArrival事件一起使用。

格式：Object. GetData data,[type,][maxlen]

其中，data参数用来存放传过来的数据，一般为变量；type参数指定数据类型；maxlen指定字符串最大长度或字节数。

6.Close方法

用于关闭TCP连接。

格式：Object. Close

15.2.3　Winsock控件的常用事件

1.Connect事件

该事件通常在编写客户端程序时使用。当与服务器的连接操作完成时被触发。通常在此事件里写入连接成功提示、返回服务器IP等。例如：

Private Sub Winsock1_Connect()

MsgBox"成功连接:"＆Winsock1.RemoteHostIP

End Sub

2.ConnectionRequest事件

该事件只能用于编写服务器程序。当远程计算机请求连接时被触发，在该事件中往往使用Accept方法接受新请求的连接。例如：

Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)

...

Winsock1.Accept requestID

...

End Sub

3.DataArrival事件

当新的数据到达时触发，该事件的bytesTotal参数指明了新到达数据的总字节数。

4.SendComplete事件

当完成一个发送操作时触发。

5.Error事件

当后台处理中出现任何错误时被触发。

6.Close事件

当远程计算机断开连接时被触发。

上面列举了Winsock控件的属性、方法和事件。下面用一个实例说明Winsock控件的用法。

【实例15.1】编写程序，试验Winsock控件的用法。

【实现步骤】具体操作步骤如下。

（1）新建一个标准EXE工程，在其内使用“工程”|“添加窗体”菜单项，向工程添加一个新窗体Form2。加上新建工程时默认自带的窗体Form1，工程中一共有两个窗体。

（2）右击工具箱，在弹出的快捷菜单中选择“部件”菜单项，在打开的“部件”对话框控件列表中选择"Microsoft Winsock Control 6.0"项，单击“确定”按钮，将Winsock控件添加到工具箱中。

（3）在Form1窗体中，加入一个标签、一个按钮和一个Winsock控件。将按钮的名称属性改为"cmdShow2"，Caption属性改为“显示客户端窗口”；将Winsock控件的名称属性改为"wskServer"，Protocol属性改为"0-sckTCPProtocol"；将标签的Caption属性设置为空。如图15.1所示。

图　15.1　Form1窗体

（4）在Form2窗体中，加入一个按钮和一个Winsock控件。将按钮的名称属性改为"cmdConn"，Caption属性改为“与服务器连接”；将Winsock控件的名称属性改为"wskClient"，Protocol属性改为"0-sckTCPProtocol"。如图15.2所示。

图　15.2　Form2窗体

（5）打开Form1窗体的代码窗口，编写事件过程如下。

01 Private Sub cmdShow2_Click()

02 Form2.Show

03 End Sub

04

05 Private Sub Form_Load()

06 wskServer.LocalPort=8888 '设置侦听端口

07 wskServer.Listen '侦听开始

08 End Sub

09

10 Private Sub wskServer_ConnectionRequest(ByVal requestID As Long)

11 '如果当前连接状态是非关闭状态，则关闭连接

12 If wskServer.State＜＞sckClosed Then wskServer.Close

13 wskServer.Accept requestID '接受连接请求

14 Label1.Caption="有客户端连入本主机！"

15 End Sub

（6）打开Form2窗体的代码窗口，编写事件过程如下。

16 Private Sub cmdConn_Click()

17 wskClient.Connect '向服务器发送连接请求

18 cmdConn.Enabled=False '将按钮设置为不可用

19 End Sub

20

21 Private Sub Form_Load()

22 wskClient.RemoteHost="127.0.0.1" '设置要连接的服务器的IP地址

23 wskClient.RemotePort=8888 '设置要连接的服务器正在监听的端口

24 End Sub

【代码说明】代码第05～08行是窗体加载事件，这里非常关键的是第06～07行设置端口并开始侦听。运行程序后，程序执行的情况如下。

（1）随着Form1窗体的加载，自动执行其Load事件内的代码，设置了端口，并开始侦听。

（2）单击Form1中的按钮后，Form2窗体会出现，并执行其Load事件内的代码，设置将要连接的服务器的IP地址和服务器正在侦听的端口。

（3）单击Form2中的按钮，执行其Click事件内的代码，通过Winsock控件wskClient向服务器发送连接请求。

（4）Form1中的winsock控件wskServer，得到连接请求后，便触发了其ConnectionRequest事件。执行了事件内的语句。首先，判断当前连接状态，如果是非关闭状态，则关闭连接。然后接受传递过来的连接请求，成功建立连接。

【运行效果】运行结果如图15.3和图15.4所示。

图　15.3　Form1窗体运行结果

图　15.4　Form2窗体运行结果

15.2.4　Winsock编程模型

1.基于TCP的模型

TCP协议允许创建和维护与远程计算机的连接。连接两台计算机就可彼此进行数据传输。这两台计算机中将运行服务器应用程序的机器称做服务器，运行客户端程序的称为客户机。

（1）如果创建服务器应用程序，就应设置一个侦听端口（LocalPort属性）并调用Listen方法侦听在这个端口上的传入信息。当客户计算机传来要求连接的信息时就会发生ConnectionRequest事件。为了完成连接，可调用ConnectionRequest事件内的Accept方法。

（2）如果要创建客户应用程序，就必须知道服务器的计算机名或者IP地址，然后给RemoteHost属性设置值，还要知道服务器程序在哪个端口上侦听，并给RemotePort属性设置这个端口，最后调用Connect方法连接服务器。

建立连接后，任何一方计算机都可以收发数据。为了发送数据，可调用SendData方法。当接收数据时会发生DataArrival事件。调用DataArrival事件内的GetData方法就可获取数据。基于TCP的连接模型，如图15.5所示。

图　15.5　基于TCP的连接模型

说明　图中白色格子代表计算机的逻辑端口，两个黑色格子代表客户端程序和服务器程序正在使用的端口。

从图15.5中可以看出，如果客户端程序不知道服务器的IP就找不到这台机器，即使找到了机器如果不知道在哪个端口上侦听，也不会连接成功。

2.基于UDP的模型

UDP协议是一个无连接协议，跟TCP的操作不同，计算机并不建立连接。另外UDP应用程序可以是客户机，也可以是服务器。

为了传输数据，首先要设置客户计算机的LocalPort属性。然后，服务器计算机只需将RemoteHost设置为客户计算机的Internet地址，并将RemotePort属性设置为跟客户计算机的LocalPort属性相同的端口，并调用SendData方法来发送信息。于是，客户计算机使用DataArrival事件内的GetData方法来获取已发送的信息。

说明　这里所说的客户机与服务器只是为了区分机器、它们的角色可以互换，而并非是TCP中的严格意义上的客户机与服务器。

15.2.5　应用案例——简易聊天程序

【实例15.2】编写一个两台机器可以互相发信息聊天的程序。

分析：两台机器中的一台应为服务器，另一台应为客户端。因此，需要编写两段程序，分别实现服务器的功能和客户端的功能。编程步骤如下。

1.服务器端程序设计

（1）启动Visual Basic，新建一个标准EXE工程，将默认窗体名称改为frmServer。

（2）选择“工程”菜单下的“工程1属性”菜单项，在打开的“工程1-工程属性”对话框中将“工程名称”栏中的内容改为"Server"，并单击“确定”按钮。如图15.6所示。

图　15.6　“工程1-工程属性”对话框

（3）右击工具箱，选择“部件”菜单项，在打开的“部件”对话框控件列表中选择"Microsoft Winsock Control 6.0"项，单击“确定”按钮将Winsock控件添加到工具箱。

（4）在窗体frmServer上，按照图15.7绘制控件。

图　15.7　服务器程序界面设计

（5）各对象属性设置见表15.3。

（6）编写事件过程如下。

01 Option Explicit

02 Private Sub Form_Load()

03 tcpServer.LocalPort=8888

04 tcpServer.Listen

05 lblState.Caption="目前没有客户端连入！"

06 End Sub

07

08 Private Sub tcpServer_ConnectionRequest(ByVal requestID As Long)

09 If tcpServer.State＜＞sckClosed Then tcpServer.Close

10 tcpServer.Accept requestID

11 lblState.Caption="有客户端连入本主机！IP:"＆tcpServer.RemoteHostIP

12 End Sub

13

14 Private Sub tcpServer_DataArrival(ByVal bytesTotal As Long)

15 Dim strData As String

16 tcpServer.GetData strData

17 txtOutput.Text=strData＆vbCrLf＆txtOutput.Text

18 End Sub

19

20 Private Sub txtSendData_KeyPress(KeyAscii As Integer)

21 If KeyAscii=13 Then

22 If tcpServer.State=sckConnected Then

23 tcpServer.SendData"服务器说："＆txtSendData.Text

24 txtOutput.Text="服务器说："＆txtSendData.Text＆vbCrLf＆txtOutput.Text

25 txtSendData.Text=""

26 Else

27 MsgBox"目前没有连接的客户端！"

28 End If

29 End If

30 End Sub

【代码说明】代码第03～04行设置端口号并开始侦听。第11行向服务器发出请求。代码第21行捕获键盘的输入，如果是回车键，则判断连接状态，如果是打开，则可以输出结果。

（7）将工程保存为Server.vbp，将窗体保存为frmServer.frm。

（8）选择“文件”菜单中的“生成Server.exe”，生成一个exe可执行文件。

2.编写客户端程序

（1）重新启动Visual Basic，新建一个标准EXE工程，将默认窗体名称改为frmClient。

（2）选择“工程”菜单下的“工程1属性”菜单项，在打开的“工程1-工程属性”对话框中将“工程名称”栏中的内容改为"Client"，并单击“确定”按钮。

（3）使用前面的方法将Winsock控件添加到工具箱。

（4）在窗体frmClient上，按照图15.8绘制控件。

图　15.8　客户端程序界面设计

（5）各对象属性设置见表15.4。

（6）编写事件过程如下。

01 Option Explicit

02

03 Private Sub Form_Load()

04 tcpClient.RemoteHost="127.0.0.1"

05 tcpClient.RemotePort=8888

06 End Sub

07

08 Private Sub cmdConnect_Click()

09 tcpClient.Connect

10 End Sub

11

12 Private Sub tcpClient_Connect()

13 MsgBox"成功连接服务器！"＆vbCrLf＆_

14"IP为："＆tcpClient.RemoteHostIP

15 End Sub

16

17 Private Sub tcpClient_DataArrival(ByVal bytesTotal As Long)

18 Dim strData As String

19 tcpClient.GetData strData

20 txtOutput.Text=strData＆vbCrLf＆txtOutput.Text

21

22 End Sub

23

24 Private Sub txtSendData_KeyPress(KeyAscii As Integer)

25 If KeyAscii=13 Then

26 If tcpClient.State=sckConnected Then

27 tcpClient.SendData"客户端说："＆txtSendData.Text

28 txtOutput.Text="客户端说："＆txtSendData.Text＆vbCrLf＆txtOutput.Text

29 txtSendData.Text=""

30 Else

31 MsgBox"目前没有连接服务器，请单击“连接”按钮连接！"

32 End If

33 End If

34 End Sub

【代码说明】Form_Load事件中的第04行IP地址"127.0.0.1"代表本机IP，如果服务器和客户端程序在同一台机器上运行时可采用此IP。如果要在不同机器上分别运行服务器、客户端程序时，IP应改写为运行服务器程序的机器的具体IP。

（7）将工程保存为Client.vbp，将窗体保存为frmClient.frm。

（8）选择“文件”菜单中的“生成Client.exe”，生成一个exe可执行文件。

3.测试程序

（1）首先，双击Server.exe，运行服务器端程序。

（2）再双击Client.exe，运行客户端程序。

（3）单击客户端程序中的“连接”按钮，会出现一个消息框，如图15.9所示。

图　15.9　客户端程序运行界面

（4）在客户端界面文本框中输入文本并按下Enter键后即可在服务器窗体中看到发送的信息。在服务器窗体中输入文本按下Enter键后也可在客户端窗体中看到内容。效果如图15.10和图15.11所示。

图　15.10　客户端程序运行效果

图　15.11　服务器程序运行效果

本例也可以在两台不同机器上进行测试。需要修改的就是客户端程序中Winsock控件的RemoteHost属性。将其设置为另一台机器的IP即可。例如服务器的IP假设为“222.39.165.188”时，客户端程序设置RemoteHost属性的语句如下。

tcpClient.RemoteHost="222.39.165.188"

获得机器IP的方法可以为：单击Windows的“开始”|“运行”菜单项。在“运行”对话框中键入cmd，并按下Enter键。在打开的“控制台”窗口（MS DOS窗口）下，输入ipconfig即可看到本机的IP地址，如图15.12所示。

图　15.12　“控制台”窗口下执行ipconfig命令的结果

本实例只是实现了两台机器之间通信，如果想让多台机器与服务器通信，应当在服务器程序中放置多个Winsock控件，每一个客户端对应一个服务器中的Winsock控件。关于详细内容，读者可以参考本书第22章的综合应用案例。

15.3　制作自己的浏览器

因为Windows应用程序不是Web程序，它不能直接调用IE来浏览网页，但是VB提供了WebBrowser控件支持制作浏览器，本节就来介绍这个控件的用法。

15.3.1　WebBrowser控件

要制作自己的浏览器，在Visual Basic中可以使用WebBrowser控件。将该控件添加到工具箱的方法为：打开“工程”|“部件”菜单项，在“部件”对话框中选择"Microsoft Internet Controls"项。其在工具箱中的图标为
。Web Browser控件的使用方法如下。

（1）Navigate方法

该方法用于将网页显示到WebBrowser控件中。其用法类似于：

WebBrowser1.Navigate"www.google.com"

（2）GoBack方法

该方法用于将网页返回到前一页。其用法类似于：

WebBrowser1.GoBack

（3）GoForward方法

该方法用于在最近使用过的网页中前进一页。其用法类似于：

WebBrowser1.GoForward

（4）GoHome方法

该方法用于显示在Internet Explorer中设定的主页。其用法类似于：

WebBrowser1.GoHome

（5）Stop方法

该方法用于停止在WebBrowser控件中显示网页。其用法类似于：

WebBrowser1. Stop

（6）Refresh方法

该方法用于刷新在WebBrowser控件中显示的网页。其用法类似于：

WebBrowser1.Refresh

15.3.2　设计浏览器界面

【实例15.3】使用WebBrowser控件制作属于自己的浏览器。界面设计如图15.13所示。

界面设计操作步骤如下。

（1）首先，将WebBrowser控件、工具栏控件和ImageList控件添加到工具箱中：打开“工程”|“部件”菜单项，在“部件”对话框中选择"Microsoft Internet Controls"和"Microsoft Windows Common Controls 6.0"两项。

（2）使用工具箱中的控件，按照图15.13绘制界面。

图　15.13　实例15.3“浏览器”界面设计

注意　文本框txtAddress是在工具栏Toolbar1中绘制的。

（3）右击控件ImageList1，选择快捷菜单中的“属性”菜单项，在打开的“属性页”对话框的“通用”选项卡中选择好图标的尺寸。

（4）在“图像”选项卡中，使用“插入图片”按钮，插入5个图像文件，如图15.14所示。

图　15.14　ImageList1的“属性页”窗口

（5）右击工具栏控件Toolbar1，选择快捷菜单中的“属性”，在打开的“属性页”对话框的“通用”选项卡中，设置“图像列表”栏为ImageList1。使用“按钮”选项卡，在工具栏上添加5个按钮，并将ImageList1中的5幅图像分别放到这5个按钮上。

（6）将文本框txtAddress的Text属性设置为“请在此输入地址”，将窗体Form1的Caption属性设置为“我的浏览器”，并将Form1的Icon属性设置为自己喜欢的图标。

15.3.3　编写代码实现浏览器

下面编写浏览器的事件过程。

01 Option Explicit

02

03 Private Sub Form_Load()

04 lstAddress.AddItem"mail.163.com"

05 lstAddress.AddItem"www.yahoo.com"

06 lstAddress.AddItem"www.temee.com"

0 7 End Sub

08

09 Private Sub Form_Resize()

10 '判断窗体状态如果不是最小化则执行里面的语句

11 If WindowState＜＞vbMinimized Then

12 lstAddress.Height=ScaleHeight-Toolbar1.Height-80

13 WebBrowser1.Height=lstAddress.Height

14 WebBrowser1.Width=ScaleWidth-lstAddress.Width-80

15 End If

16 End Sub

17

18 Private Sub lstAddress_Click()

19 WebBrowser1.Navigate Trim(lstAddress.Text)

20 End Sub

21

22 Private Sub txtAddress_GotFocus()

23 '使文本框中的文字被选中

24 txtAddress.SelStart=0

25 txtAddress.SelLength=Len(txtAddress.Text)

26 End Sub

27

28 Private Sub txtAddress_KeyPress(KeyAscii As Integer)

29 If KeyAscii=13 Then '按回车键则执行下面的语句

30 WebBrowser1.Navigate Trim(txtAddress.Text)

31 End If

32 End Sub

33

34 Private Sub Toolbar1_ButtonClick(ByVal Button As MSComctlLib.Button)

35 Select Case Button.Index

36 Case 1

37 WebBrowser1.GoBack

38 Case 2

39 WebBrowser1.GoForward

40 Case 3

41 WebBrowser1.GoHome

42 Case 4

43 WebBrowser1.Stop

44 Case 5

45 WebBrowser1.Refresh

46 End Select

47 End Sub

【代码说明】本例中代码第09～16行使用到了窗体的Resize事件，该事件在窗体的大小被改变时发生。在上面的代码中体现了使用这一事件可以让控件的大小随着窗体大小而变化。

本例结合第14章所学的知识，将常用网址和访问过的网址放入数据库中，如果调用时也从该数据库中调用的话，效果会更好。

15.4　下载网页

使用Visual Basic的Internet Transfer控件，可以将网页下载到本机上浏览。本节将介绍使用Internet Transfer控件下载网页的编程方法。

15.4.1　Internet Transfer控件

Internet Transfer控件能够使用Internet上应用最广泛的协议HTTP和FTP下载文件。HTTP主要用于从互联网中的服务器上传输HTML文档。当在浏览器中以"http://"开始键入一个Internet地址时，就在告诉服务器，想要打开的是一个具有HTML格式代码的文档，此时浏览器可以理解并显示这种代码。Internet Transfer控件还可以用这个协议从Internet的服务器上下载网页。

FTP主要用于从特殊服务器，比如FTP服务器或FTP站点传输二进制文件或文本文件。可以通过服务器名的前缀"ftp:/"识别FTP服务器。一般的公司都会利用其FTP站点传输.zip（已压缩）格式的工程文件以及其他二进制文件，比如动态链接库(.dll)和可执行文件(.exe)等。Internet Transfer控件还可以用来管理下载和上传等FTP操作。

Internet Transfer控件是一个ActiveX控件，将该控件添加到工具箱的方法为：“工程”|“部件”菜单项，“部件”对话框中选择"Microsoft Internet Transfer Control 6.0"项。其在工具箱中的图标为
。

1.Internet Transfer控件的常用方法

（1）OpenURL方法

该方法以同步方式连接到远程服务器上，打开并下载一个完整的文档。文档以变体型返回。该方法完成时，URL的各种属性（以及该URL的一些部分，如协议）将被更新，以符合当前的URL。使用该方法的语句类似于：

Text1.Text=Inet1.OpenURL("www.yahoo.com.cn")

其中，Inet1是Internet Transfer控件的默认名称。上面语句的作用是将www.yahoo.com.cn的主页内容下载下来复制给了Text1文本框。

（2）Execute方法

以异步方式连接远程服务器，向远程服务器发送服务请求并得到服务结果，该方法无返回值。使用该方法的语句类似于：

Inet1.Execute"FTP://ftp.microsoft.com","GET aa.txt D:\sqbt\aa.txt"

该语句的作用是将ftp.microsoft.com下的aa.txt下载到本机D:\sqbt\文件夹下，并起名为aa.txt。该方法的第二个参数中，除了可以用GET运算符下载文件以外，还可以用DELETE、DIR等多种运算符。如果读者想要深入研究可以查看MSDN联机帮助文档。

2.Internet Transfer控件的事件

Internet Transfer控件只有一个事件——StateChanged事件。该事件当连接状态发生改变时被触发。StateChanged事件有一个参数State，该参数的取值及其含义如表15.5所示。

15.4.2　实现下载网页

【实例15.4】编写一个程序，实现从任意网站下载网页的功能。

界面设计如图15.15所示。

图　15.15　实例15.4界面设计

程序中各对象属性设置如表15.6所示。

编写事件过程如下。

01 Option Explicit

02 Private Sub cmdSave_Click()

03 cd1.Filter="HTML files (*. HTML;*. HTM)|*.html;htm"

04 '显示“保存”对话框

05 cd1.ShowSave

06 If cd1.FileName＜＞""Then

07 Open cd1.FileName For Output As #1

08 '将字符串保存到文件中

09 Print #1,txtHtml.Text

10 Close #1

11 End If

12 End Sub

13

14 Private Sub Inet1_StateChanged(ByVal State As Integer)

15 If State=icConnected Then '如果已与主机连接成功

16 '将地址框变为可用，将鼠标指针还原

17 txtAddress.Enabled=True

18 Screen.MousePointer=0

19 ElseIf State=icError Then '如果与主机通信时出现了错误

20 MsgBox"找不到网页或网页不存在！",vbOKOnly+vbExclamation,"提示"

21 txtAddress.Enabled=True

22 Screen.MousePointer=0

23 End If

24 End Sub

25

26 Private Sub txtAddress_KeyPress(KeyAscii As Integer)

27 On Error GoTo erroropen

28 Dim strAddress As String

29 If KeyAscii=13 Then

30 strAddress=txtAddress.Text

31 '将地址框变为不可用，将鼠标指针变为沙漏

32 txtAddress.Enabled=False

33 Screen.MousePointer=11

34 '把网页的内容复制到文本框中

35 txtHtml.Text=Inet1.OpenURL(strAddress)

36 End If

37 Exit Sub

38 erroropen:

39 MsgBox"Error opening URL",,Err.Description

40 End Sub

【代码说明】在输入网址的文本框中输入网址，并回车。此时，会执行代码第26～40行的txtAddress_KeyPress事件中IF语句的代码。为了不让用户继续操作地址框，同时让用户知道程序仍在继续执行，所以使用第33行语句将屏幕鼠标指针变为了沙漏状。语句中使用的Screen是Visual Basic中的屏幕对象。而后，使用第35行语句将指定网址的网页内容复制到了文本框txtHtml中。这时，会看到文本框中显示的是网页HTML代码，并不是通常所看到的网页，如图15.16所示。

图　15.16　实例15.4运行界面

因为，打开网址有可能会出现不可预料的错误，所以，在txtAddress_KeyPress事件中，使用了错误处理语句：

On Error GoTo erroropen

当遇到错误时，就将程序流程跳转到erroropen标号处执行。本例中还使用到了Visual Basic的Err对象。关于错误处理语句和Err对象的详细内容，读者可以参考第17章的内容。

（1）当使用OpenURL方法连接远程主机成功或者通信出错时，就会触发Inet1_StateChanged事件，并根据State参数的值，执行其内的不同语句。

（2）网页被下载下来之后，当单击“保存为HTML”按钮之时就会触发cmdSave_Click事件，跳出“另存为”对话框，通过该对话框设置好网页文件的保存位置和名称。

（3）使用IE浏览器打开保存后的网页，会发现已经不是刚才的HTML代码，而是一个正常的网页文件。

技巧　用上面的程序下载一个较长的网页，保存后会发现网页的下半部分会丢失。原因是Visual Basic的普通文本框有字数限制。因此要想得到最佳效果，应当使用RichTextBox控件替换显示网页内容的普通文本框（界面中第二个文本框）。

【运行结果】本例的效果如图15.15所示。

15.5　小结

网络编程技术并不是VB开发程序的重点，但很多应用程序要求在局域网中操作，所以读者就必须了解本章的内容。15.1节概述了网络编程的一些基础知识，要掌握这门技术，就必须知道什么是端口、什么是协议、网络之间的IP代表什么？本章15.2节介绍了Winsock控件，这是网络开发的关键控件，不管是VB还是VC，Winsock都对网络编程有非常重要的意义。本章最后学习了如何自己来开发一个简单浏览器。

15.6　本章练习

1.IP地址是什么？局域网内是否有IP地址？

2.如何使用Winsock控件编写一个服务器程序，使得多个客户端与该服务器连接？

提示　可以使用Load方法动态加载Winsock控件，生成控件数组实现服务器与多个客户端连接。具体使用方法是在窗体上添加一个Winsock控件，并将其Index属性值设置为0。然后在程序中需要连接的地方，动态加载该控件。

3.假设brwWebBrowser为WebBrowser控件。下列代码的作用是什么？

Private Sub brwWebBrowser_TitleChange(ByVal Text As String)

Me.Caption=brwWebBrowser.LocationName

End Sub

第16章　多媒体编程

随着计算机技术的飞速发展和计算机的普及，多媒体和人们的休闲生活已经融为一体，在Visual Basic中，通过使用一些编程技巧或多媒体控件可以开发出具有鲜明特点的多媒体程序。本章主要包括以下知识点。

❑多媒体基本知识。

❑使用Windows MediaPlayer控件进行多媒体编程的基本方法。

❑使用Shock wave Flash控件播放动画的多媒体编程技术。

通过本章的学习，读者可以熟练掌握Windows MediaPlayer和Shock wave Flash等多媒体控件的编程技术。读者甚至能够使用这些控件轻松编写音频和视频播放程序。

16.1　文字处理特效

在编程时，有时希望自己的界面里有些动态的文字，用于提高界面的感观效果。下面介绍两种动态文字特效的制作方法。

16.1.1　缩放文字特效

【实例16.1】编程实现循环缩放文字特效。效果为：窗体出现后，文字从小变到大，到了一定尺寸后，再重复刚才从小变大的效果，并不断循环。

界面设计如图16.1所示，窗体Form1中有一个图片框Picture1，在图片框中放置了一个标签Label1和一个Timer控件Timer1。为简单起见，各对象名称和其他属性均取默认值。

图　16.1　实例16.1设计界面

编写事件过程如下。

01 Private Sub Form_Load()

02 label1.Caption="缩放文字效果！"

03 label1.FontSize=1

04 label1.AutoSize=True

05 Timer1.Interval=200

06 End Sub

07

08 Private Sub Timer1_Timer()

09 If label1.FontSize＜=100 Then

10 label1.FontSize=label1.FontSize+10

11 Else

12 label1.FontSize=1

13 End If

14 End Sub

【代码说明】本例第08～14行使用了Timer事件，在每隔Inerval设置的时间之后就发生一次原理，不断地改变标签内文本的字号，从而达到了文字缩放的特效。其中，代码第04行将标签的AutoSize属性设置为True，可以让标签的大小随着内部文字的大小而变化。

【运行效果】最后运行效果如图16.2所示。

图　16.2　实例16.1运行效果

16.1.2　滚动文字特效

【实例16.2】编程实现滚动文字特效。效果为：开始时，文字从右边界出现向左边界移动，当全部文字移过左边界消失后，再重新从右边界出现向左边界移动，并不断循环。本例界面设计与实例16.1的界面设计相同。

编写事件过程如下。

01 Private Sub Form_Load()

02 label1.Caption="这是滚动文字效果！"

03 label1.FontSize=16

04 label1.Left=Picture1.Width

05 Timer1.Interval=10

06 End Sub

07

08 Private Sub Timer1_Timer()

09 If label1.Left+label1.Width＜=0 Then

10 label1.Left=Picture1.Width

11 Else

12 label1.Left=label1.Left-10

13 End If

14 End Sub

【代码说明】同实例16.1一样，本例代码第08～14行也使用了Timer控件的Timer事件。通过不断修改标签的Left属性得到滚动文字的效果。

【运行效果】运行效果如图16.3所示。

图　16.3　实例16.2运行效果

16.2　音频、视频处理

使用Visual Basic可以制作播放音乐和视频的软件，本节将给读者介绍几个制作这类软件的控件，并举例说明如何使用这些控件。

16.2.1　多媒体控件

多媒体控件是一个ActiveX控件。因此，必须将其手动添加到工具箱中。方法为选择“工程”|“部件”菜单项，在“部件”对话框的列表项中选择"Microsoft Multimedia Control 6.0"，单击“确定”按钮，如图16.4所示。

图　16.4　“部件”对话框

多媒体控件在工具箱中的图标为
，通常称其为Multimedia MCI控件。Multimedi MCI控件管理媒体控制接口(MCI)设备上的多媒体文件的记录与回放。实际上，这种控件就是一组按钮，它被用来向诸如声卡、MIDI序列发生器、CD-ROM驱动器、视频CD播放器和视频磁带记录器及播放器等设备发出MCI命令。MCI控件还支持Windows (*.avi)视频文件的回放。

在设计时，将Multimedia MCI控件添加到一个窗体上，它在窗体中的外观如图16.5所示，组成该控件的按钮的功能与家用录音机、录像机相似。

图　16.5　Multimedia MCI控件

控件中每个按钮的作用分别为：向前、下一个、播放、暂停、后退、前进一步、停止、记录和弹出。

【实例16.3】编程实现，控制播放mp3歌曲。界面如图16.6所示，所有控件的属性均取默认值。

图　16.6　实例16.3设计界面

编写事件过程如下。

01 Private Sub Form_Load()

02 MMControl1.FileName=App.Path＆"\sailing.mp3" '设置要播放的歌曲

03 MMControl1.Command="open" '打开多媒体设备

04 Label1.Caption=""

05 Label2.Caption=""

06 End Sub

07

08 Private Sub Form_Unload(Cancel As Integer)

09 MMControl1.Command="Close"

10 End Sub

11

12 Private Sub MMControl1_StatusUpdate()

13 Label1.Caption="歌曲当前位置为："＆Str(MMControl1.Position)

14 Label2.Caption="歌曲的总长度为："＆Str(MMControl1.Length)

15 End Sub

【代码说明】在本段代码中，Form_Load事件内的第02行的作用是指定将要播放的mp3歌曲文件；第03行用来打开多媒体设备，如果不写该语句，Multimedia MCI控件的按钮就不可用。代码第08～10行的Form_Unload事件用来关闭多媒体设备。代码第12～15行的Multimedia MCI控件的StatusUpdate事件与Timer控件的Timer事件很相似，它在每隔UpdateInterval属性设置的时间之后就发生一次。UpdateInterval属性的默认值为1000，所以StatusUpdate事件每隔1秒钟就会执行一次。

【运行效果】运行效果如图16.7所示。

图　16.7　实例16.3运行结果

16.2.2　使用Windows Media Player控件播放影音文件

Windows Media Player控件也是一个ActiveX控件。将该控件添加到工具箱中的方法为：选择“工程”|“部件”菜单项，在“部件”对话框的列表项中选择"Windows Media Player"，单击“确定”按钮。该控件在工具箱中的图标为
。

注意　该控件其实就是Windows操作系统中的Windows Media Player播放器的引用。所以，操作系统版本不一样或者Windows Media Player播放器版本不一样都会得到不同外观和性能的Windows Media Player控件。

Windows Media Player控件的常用属性/方法和其解释见表16.1，其中WMP1是在窗体上绘制的Windows Media Player控件的名称。

【实例16.4】使用WindowsMediaPlayer控件播放电影（mpeg）文件。

界面设计：在窗体中绘制一个WindowsMediaPlayer控件，将其名称属性改为WMP1。

编写程序如下。

01 Private Sub Form_Load()

02 WMP1.settings.autoStart=True

03 WMP1.URL=App.Path＆"\813.mpg"

04 End Sub

【代码说明】其中，第02行的作用为使Windows Media Player控件自动播放指定的文件，如果将语句改为

WMP1.settings.autoStart=False

则不会自动播放文件。第03行为指定具体要播放的影音文件。

【运行效果】运行效果如图16.8所示。

图　16.8　实例16.4运行结果

16.2.3　使用Real Audio控件播放RM文件

使用上面介绍的两种控件并不能播放网上广为流行的RM格式的文件。因此，接下来给读者介绍一种能够播放RM格式文件的控件——Real Audio控件。将该控件添加到工具箱中的方法是在“部件”对话框的列表项中，选择"Real Player ActiveX Control Library"，并单击“确定”按钮，如图16.9所示。Real Audio控件在工具箱中的图标为
。下面用一个实例说明该控件的基本用法。

图　16.9　“部件”对话框

【实例16.5】使用Real Audio控件播放RM格式的视频文件。界面设计如图16.10所示。运行效果如下。

图　16.10　实例16.5设计界面

❑刚开始单击界面中的“播放”按钮可以播放指定的RM格式的视频文件，并将该按钮上的标题文字改为“暂停”。

❑单击“暂停”按钮，可以暂停播放的文件，并将按钮标题改为“播放”。依此类推，可以使用这个按钮不断暂停或继续播放视频文件。

❑使用“停止”按钮可以停止播放文件。

编写程序如下。

01 Private Sub Form_Load()

02 RealAudio1.AutoStart=False

03 RealAudio1.Source=App.Path＆"\舞蹈比赛.rm"

04 End Sub

05 Private Sub cmdPlay_Click()

06 If cmdPlay.Caption="播放"Then

07 cmdPlay.Caption="暂停"

08 Else

09 cmdPlay.Caption="播放"

10 End If

11 RealAudio1.DoPlayPause

12 End Sub

13 Private Sub cmdStop_Click()

14 Me.RealAudio1.DoStop

15 cmdPlay.Caption="播放"

16 End Sub

【代码说明】第02行设置播放器不自动播放视频文件（默认属性值）。如果将该属性设置为True，可以使播放器自动播放指定的文件。第03行给播放器指定了要播放的文件。第05～12行代码中，IF语句的作用是切换cmdPlay按钮的标题文字。第11行语句的作用是在继续播放和暂停两个功能之间进行切换。代码第13～16行是最后“停止”按钮的代码，其中第15行语句的作用是将cmdPlay按钮的标题改为“播放”，因为该按钮的标题目前是“暂停”。

【运行效果】最终运行效果如图16.11所示。

图　16.11　实例16.5运行效果

16.2.4　应用案例——制作My Player播放器

【实例16.6】编制一个能够播放mp3、wav、avi、wma和mpeg等类型文件的My Player播放器。界面设计如图16.12所示。

图　16.12　实例16.6设计界面

My Player播放器的功能如下。

❑可以用“打开文件”按钮，从计算机中随意选择以上列举的类型文件进行播放。

❑可以使用其他自制的按钮控制播放器。具体控制为：静音/取消静音、显示歌曲长度（时间）、播放/暂停、停止。

❑可以使用“媒体库”|“添加影音文件”菜单项，将自己喜欢的影音文件添加到媒体库中，并同时显示到列表框内。

❑可以使用“媒体库”|“删除影音文件”菜单项，将媒体库中的文件删除，同时从列表框内删除。

❑窗体启动时，列表框内显示所有媒体库中的文件列表。

❑在列表框中单击右键时，“媒体库”菜单会以弹出式菜单的形式出现。

❑双击列表框中的列表项时，播放该列表项对应的影音文件。

❑如果找不到要播放的影音文件时（从原来的位置移动或者删除了影音文件），给出“没有找到影音文件！”的提示。

编写程序的具体操作步骤如下。

1.界面部分

（1）添加菜单。单击工具栏中的“菜单编辑器”
按钮，打开菜单编辑器。按照表16.2设置菜单及其属性。

（2）选择“工程”|“部件”菜单项，从“部件”对话框的列表内选中"Windows Media Player"和"Microsoft Common Dialog Control 6.0"两项，并单击“确定”按钮。

（3）在窗体中绘制两个框架控件Frame1和Frame2。将Frame1的Caption属性设置为空白，Frame2的Caption属性设置为“我的媒体库”。将这两个框架控件的BackColor属性（背景颜色）设置为“突出显示”，如图16.13所示。将Frame2的ForeColor属性（前景色）设置为“突出显示文本”。

图　16.13　BackColor属性

（4）在上面的框架Frame1内，绘制一个Windows Media Player控件，并将其名称改为WMP1；再绘制5个按钮，按照表16.3设置其名称和Caption属性。

（5）在窗体的任意位置绘制一个CommonDialog控件，将其名称改为cd1。

（6）在下面的框架Frame2内绘制一个列表框，使用其默认名称List1。

（7）将工程文件和窗体文件全部保存到“D:\VB\播放器\”下。

2.数据库部分

本例中，使用Access数据库保存被添加的影音文件，即媒体库就是数据库。考虑到实际应用并不是把真正的影音文件本身放到数据库内，而是将影音文件的路径保存到了数据库中，下面介绍建立数据库和具体数据表的步骤。

（1）打开Access软件，新建一个空数据库，给数据库文件取名为"MyLike"，并将其保存到“D:\VB\播放器\”下。

（2）使用设计器创建一个数据表。表中只设置一个字段“路径”，将其数据类型设置为“文本”，字段大小设置为“200”，如图16.14所示。并将表名设置为path。

图　16.14　数据表path的表结构设计

3.程序部分

（1）选择“工程”|“引用”菜单项，在打开的“引用”对话框中选择"Microsoft ActiveX Data Objects 2.8 Library"项。

（2）编写5个按钮的单击事件如下。

01 Private Sub cmdOpenFile_Click()

02 cd1.Filter="音乐文件(*.mp3;*.wav)|*.mp3;*.wav|"＆_

03 "视频文件(*.avi;*.wma)|*.avi;*.wma|"＆_

04 "电影文件(*.mpeg)|*.mpeg;*.mpg|"＆_

05 "所有文件(*.*)|*.*|"

06 '显示"打开"对话框

07 cd1.ShowOpen

08 If cd1.FileName＜＞""Then

09 WMP1.URL=cd1.FileName

10 Call cmdPlay_Click '调用了“播放”按钮的单击事件过程

11 End If

12 End Sub

13

14 Private Sub cmdQuiet_Click()

15 If cmdQuiet.Caption="静音"Then

16 WMP1.settings.mute=True

17 cmdQuiet.Caption="取消静音"

18 Else

19 WMP1.settings.mute=False

20 cmdQuiet.Caption="静音"

21 End If

22 End Sub

23

24 Private Sub cmdGetLength_Click()

25 MsgBox"文件长度为："＆WMP1.currentMedia.durationString

26 End Sub

27

28 Private Sub cmdPlay_Click()

29 If cmdPlay.Caption="播放"Then

30 WMP1.Controls.play

31 cmdPlay.Caption="暂停"

32 Else

33 WMP1.Controls.pause

34 cmdPlay.Caption="播放"

35 End If

36 End Sub

37

38 Private Sub cmdStop_Click()

39 WMP1.Controls.stop

40 cmdPlay.Caption="播放"

41 End Sub

【代码说明】上述代码看起来虽然多，但都很简单，对应着5个按钮的单击事件。如代码第28～36行是播放按钮，在单击此按钮后，需要修改按钮的Caption属性。

（3）编写“添加影音文件”和“删除影音文件”两个菜单项的事件。

42 Private Sub mnuAdd_Click()

43 Dim cn As ADODB.Connection

44 Dim rs As ADODB.Recordset

45 Dim sql As String

46 Dim X As Integer

47

48 cd1.Filter="音乐文件(*.mp3;*.wav)|*.mp3;*.wav|"＆_

49 "视频文件(*.avi;*.wma)|*.avi;*.wma|"＆_

50 "电影文件(*.mpeg)|*.mpeg;*.mpg|"＆_

51 "所有文件(*.*)|*.*|"

52 cd1.CancelError=True

53 '显示"打开"对话框

54 cd1.ShowOpen

55 If cd1.FileName＜＞""Then

56 Set cn=New ADODB.Connection

57 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source="＆App.Path＆"\MyLike.mdb;"

58 sql="select*from path"

59 Set rs=New ADODB.Recordset

60 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

61 '添加记录

62 rs.AddNew

63 rs.Fields(0)=cd1.FileName

64 '更新记录

65 rs.Update

66 MsgBox"添加成功！",vbOKOnly+vbInformation,"提示"

67 List1.AddItem cd1.FileName '向列表框添加内容

68 rs.Close

69 cn.Close

70 Set rs=Nothing

71 Set cn=Nothing

72 End If

73 End Sub

【代码说明】该事件过程中，代码第48～51行首先设置了通用对话框的Filter属性，用来过滤对话框中显示的文件。然后代码第54行显示了“打开”对话框。如果在“打开”对话框内选取了影音文件，则使用Connection对象变量cn连接数据库，并使用Recordset对象rs将影音文件的完整名称（含路径和扩展名）添加到数据表内。同时也将影音文件的完整名称添加到了列表框内。

74 Private Sub mnuDelete_Click()

75 Dim cn As ADODB.Connection

76 Dim rs As ADODB.Recordset

77 Dim sql As String

78 Dim X As Integer

79

80 X=MsgBox("您确定删除么？",vbYesNo+vbQuestion,"提示")

81 If X=vbYes Then

82 Set cn=New ADODB.Connection

83 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source="＆App.Path＆"\MyLike.mdb;"

84 sql="select*from path where路径='"＆List1.Text＆"'"

85 Set rs=New ADODB.Recordset

86 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

87 '删除当前记录

88 rs.Delete

89 MsgBox"删除成功！",vbOKOnly+vbInformation,"提示"

90 List1.RemoveItem List1.ListIndex '从列表框删除所选项

91 rs.Close

92 cn.Close

93 Set rs=Nothing

94 Set cn=Nothing

95 End If

96 End Sub

【代码说明】上述代码针对菜单中删除影音文件的操作，为了确保删除了正确文件，代码第80行先弹出对话框提示信息。如果用户确认要删除，则连接数据库，通过代码第88行执行删除操作，然后通过代码第90行从列表中删除这个影音文件。

（4）编写窗体的加载事件如下。

97 Private Sub Form_Load()

98 '将媒体库中的影音文件名称添加到列表框内

99 Dim cn As ADODB.Connection

100 Dim rs As ADODB.Recordset

101 Dim sql As String

102 Dim X As Integer

103

104 Set cn=New ADODB.Connection

105 cn.Open"PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source="＆App.Path＆"\MyLike.mdb;"

106 sql="select*from path"

107 Set rs=New ADODB.Recordset

108 rs.Open sql,cn,adOpenForwardOnly,adLockOptimistic

109 Do While Not rs.EOF

110 List1.AddItem rs.Fields(0)

111 '让记录指针向下移动

112 rs.MoveNext

113 Loop

114 rs.Close

115 cn.Close

116 Set rs=Nothing

117 Set cn=Nothing

118 End Sub

【代码说明】这段程序中，代码第109～113行使用了一个循环将数据表path中所有的记录都添加到了列表框内。

（5）编写列表框的事件过程如下。

119 Private Sub List1_DblClick()

120 WMP1.URL=List1.Text

121 WMP1.Controls.play

122 cmdPlay.Caption="暂停"

123 End Sub

124 Private Sub List1_MouseUp(Button As Integer,Shift As Integer,X As Single,Y As Single)

125 If Button=2 Then '如果单击的是右键

126 PopupMenu mnuMTK

127 End If

128 End Sub

【代码说明】双击列表框某项时，将该项指定为播放器要播放的文件，然后播放，并将cmdPlay按钮的标题改为“暂停”。这里不能像cmdOpenFile_Click事件过程中那样，直接调用cmdPlay_Click事件。

（6）编写WindowsMediaPlayer控件的Error事件：

129 Private Sub WMP1_Error()

130 MsgBox"没有找到影音文件！",vbOKOnly+vbExclamation,"提示"

131 End Sub

【代码说明】当指定的URL不存在，而使用Controls.play方法播放文件时，代码第129～131行的事件会被触发。因为，当用户将影音文件的路径添加到了数据库中后，有可能通过Windows操作挪动或者删除这些文件，因此编写该事件过程是必要的。

【运行效果】最终运行效果如图16.15所示。

图　16.15　实例16.6运行结果

16.3　使用Shockwave Flash控件播放动画

Shockwave Flash控件用于在应用程序中播放Flash文件（扩展名为.swf）。在使用该控件前，应将其添加到工具箱中，在“部件”对话框的控件列表内选择"Shockwave Flash"，并单击“确定”按钮，如图16.16所示。Shockwave Flash控件在工具箱中的图标为
。

图　16.16　“部件”对话框

【实例16.7】编程试验Shockwave Flash控件的用法。界面设计如图16.17所示。

图　16.17　实例16.7界面设计

编写事件过程如下。

01 Private Sub Form_load()

02 SF1.Movie=App.Path＆"\环保徽.swf"

03 HScroll1.Max=SF1.TotalFrames

04 End Sub

05 Private Sub cmdPlay_Click()

06 SF1.Play

07 End Sub

08

09 Private Sub cmdStop_Click()

10 SF1.Stop

11 End Sub

12 Private Sub HScroll1_Change()

13 SF1.FrameNum=HScroll1.Value

14 End Sub

【运行效果】代码第02行是ShockwaveFlash控件的Movie属性，指定要播放的Flash文件；代码第03行的TotalFrames属性用来获取要播放的Flash文件的总帧数。代码第05～11行的Play与Stop方法用于播放和暂停Flash文件。代码第13行的FrameNum属性用于指定Flash文件具体的帧。代码第12～14行的事件过程通过改变滚动条的值，可以查看Flash文件的任何一帧。

【运行效果】运行效果如图16.18所示。

图　16.18　实例16.7运行结果

16.4　小结

对音乐非常感兴趣的读者可能比较喜欢本章，因为这是一个多媒体编程的技术章节。第16.1节介绍了几个比较炫的文字特效。16.2节主要讲解音频和视频处理技术，其中讲解了两个控件：Windows Media Player和Real Audio，它们都是用来播放多媒体文件的。16.3节则介绍了Shockwave Flash控件，并学习用其播放动画的技术。

16.5　本章练习

1.如何将多媒体控件添加到VB工具箱内？

2.如何设置Multimedia MCI控件中的某些在程序用或不用的按钮不可见？

提示　可以从Multimedia MCI控件的属性对话框的“自定义”项中打开该控件的“属性页”对话框，设置某些不用的按钮不可见。对于某个按钮，如果设置其不可见，则即使设置其有效，在程序设计和运行时都不能使用。因为，该按钮不会显示在按钮组中。

3.假设mmcMedia为Multimedia MCI控件。则下列代码的作用是什么？

mmcMedia.hWndDisplay=Me.hWnd

第17章　程序调试与错误处理

在编程过程中，经常会遇到各种各样的错误。这些错误可能是编程时人为的失误，也可能是计算机运行时的异常错误，又或许是后期软件使用者的错误操作导致的错误等。本章包括以下知识点。

❑VB程序中有三种常见错误类型，分别是编辑错误、运行时错误和逻辑错误。

❑常用的程序调试手段是设置断点。

❑排错方法。

❑错误处理程序的步骤分别是捕获错误、错误处理和退出错误处理例程。

为了尽量保证软件的正确性，掌握好的程序调试方法是非常重要的。当然，不论程序员的工作多么仔细，测试工作多么周密，成功地通过调试、测试的程序在运行中仍旧有可能会出现错误。错误处理程序就是针对运行中的错误而编写的对错误进行处理的程序。

17.1　程序的错误类型

程序在编写代码或调试运行中，屏幕上将出现一些出错的提示信息，这些信息按照出错的类型可分为编译错误、实时错误和逻辑错误三种。

17.1.1　编译错误

编译错误发生在编辑时，是用户输入代码时，违反语句的语法规定而引起的错误。例如：关键字拼写错误、变量名定义错和表达式错误等。这类错误的表现形式是系统将出错行代码变成红色高亮度显示，并拒绝接收新的输入。

17.1.2　运行时错误

运行时错误是指应用程序在运行期间执行了非法操作所发生的错误。例如，除法运算中除数为零，访问文件时文件夹或文件找不到，给数值变量赋予了字符串值等。这种错误只有在程序运行时才能被发现。

17.1.3　逻辑错误

逻辑错误使程序运行时得不到预期的结果。这种程序没有语法错误，也能运行，但却得不到正确的结果。例如，在一个算术表达式中，把乘号“*”写成了加号“+”，条件语句的条件写错，循环次数计算错误等。通常，调试程序过程中所花的大部分时间和精力都在逻辑错误上。

17.2　错误排错方法

当程序中出现错误时，应当寻找到出错的地方，并对其进行修改。作为编程人员必须掌握排除错误的各种方法和技巧。因为，即使是非常熟练的编程人员也会在编写应用程序时犯一些错误，而此时掌握好的排错方法就会变得非常重要了。

17.2.1　编译错误排错与实时错误排错

1.编译错误排错

编译错误排错要按照系统的提示，检验语法错误，并重新输入语句。当输入正确后，系统将红色高亮度显示的错误变成正常的语句显示。

编译方式还可设置为自动语法检测，在系统的“工具”菜单中，选择“选项”命令，在“编辑器”选项卡中，选中“自动语法检测”复选框，即可自动编译检测语法错误。

2.实时错误排错

当运行之后，如发生实时错误，系统将弹出对话框，提示出错信息。例如，下面的代码运行后将会出现如图17.1所示的实时错误对话框。

图　17.1　实时错误对话框

01 Private Sub Command1_Click()

02 Dim x As Integer

03 x="asdfas"

04 End Sub

错误对话框中给出了4个按钮，处理的方法应当是先按“调试”按钮，此时会出现图17.2所示的调试状态的界面，用户可以根据代码窗口中黄色箭头的指向，修改错误的地方，然后再按下F5键，继续执行程序。

图　17.2　调试状态

当按下“结束”按钮时，Visual Basic系统将回到编辑状态，用户可根据提示信息，对程序进行较大范围的修改。然后重新运行程序。

如果用户对系统给出的提示信息，不能准确定位出错位置，则可按下“帮助”钮，根据系统给出的帮助信息，继续进行排错处理。

17.2.2　逻辑错误调试方法

逻辑错误是指执行程序后，得不到所期望的结果。这类错误往往是程序逻辑缺陷所引起的。例如：程序的逻辑判断条件不正确、循环的次数不对、进入死循环等错误，或是不能按照运算式得到结果等。这类错误往往需要用户进行调试才能排除。

通常解决逻辑错误的方法如下：

（1）监视窗口；

（2）设置断点；

（3）跟踪调试。

逻辑错误调试时，可打开“调试”窗口，选择菜单“视图”|“工具栏”中的“调试”菜单项，屏幕上将出现如图17.3所示的“调试”工具栏，使用“调试”工具栏中的命令按钮可以排除逻辑错误。

图　17.3　“调试”工具栏

1.用监视窗口检查

通过对变量和表达式监视，达到逻辑判断的目的。监视表达式值的变化可以控制程序的执行、转移和中断情况。

例如，在图17.4中的代码里，想要监视x＜19这个表达式的变化。

图　17.4　代码窗口

操作步骤如下。

（1）选择“调试”菜单中的“添加监视”菜单项，屏幕上将出现如图17.5所示的调试对话框。在表达式栏中输入x＜19。其他选项的设置与图中的选项一致。单击“确定”按钮退出。

图　17.5　“添加监视”对话框

（2）在代码窗口中，给图17.6所示的语句上添加一个断点。方法是单击语句左边框的区域，单击一下会出现一个紫红色的圆点，表明设置好了断点，程序运行到这里时会自动停顿。如果要删除断点，在紫红色圆点上单击即可删除。

图　17.6　添加断点

（3）运行程序，当在InputBox中输入12时的监视窗口如图17.7a所示、而输入28时的监视窗口如图17.7b所示。

图　17.7　监视窗口

2.逐行逐句监视过程运行

逐行逐句地对过程进行监视，可以得知错误出现的位置。这种排错方法比较适用于分支处理或多重循环等情况。例如使用调试工具栏的“逐行”命令按钮，可以一条语句一条语句的监视程序的运行。具体操作方法是每次按一下“逐行”命令按钮，语句就会向下执行一条，然后停顿。在这种状态下，可以将鼠标放到需要知道其值的变量上停顿一小会儿，就会获知变量的值，如图17.8所示。让鼠标在x上停顿几秒后，弹出一个米黄色的提示框，里面显示的是执行黄色背景的语句前的变量x的值。

图　17.8　“逐行”执行代码

3.设置断点

前面已经讲过关于断点的一些内容。断点是人为设置的暂停点，除前面讲过的设置断点的方法外，在程序代码窗口中，将光标放在需要设置断点的位置，单击调试工具栏的“切换断点”按钮也可在程序中切换设置断点。当运行之后，程序遇到断点即停，使用上面介绍的鼠标停顿的方法，用户可监视变量情况和通过程序运行的位置判断逻辑功能是否正确。

17.3　错误处理程序

错误处理程序的作用是对程序运行期发生的错误操作进行响应和处理。当程序发生错误时，错误处理程序能够捕获错误，并按程序中所设计的处理方法对错误进行处理。

17.3.1　Err对象

程序运行中出现问题时，当前运行程序的错误信息就会在Err对象中反映出来。Err是Visual Basic在运行期间的对象，包含了最新的错误信息。利用这些错误信息，可以了解系统错误的原因，也可以编写有针对性的错误处理程序。程序运行后，Err对象就存在，出现错误时，有关信息被写到相应的属性中，直到遇到On Error语句，并且在使用Exit Sub等语句退出一个过程后，Err对象中的属性值才被清除。Err对象的常用属性见表17.1。

需要说明的是，Err对象中保存的是最新的错误信息，且这些信息能够被清除。若需要使用这些错误信息，应及时保存。

17.3.2　捕获错误

在程序运行中，应及时捕获出现的错误，并予以相应的提示或处理。如果没有及时捕获错误，会导致程序终止运行。在实际的应用系统中，应该有错误处理程序。

捕获错误程序有时又称为错误陷阱，实际上是在出现错误时，将程序的执行流程引导到错误处理程序，由错误处理程序针对具体的错误进行处理。

捕获错误的基本方法是在程序的开始放置错误转移命令，相当于激活错误处理程序。具体的操作语句主要有On Error GoTo和On Error Resume Next。

1.On Error GoTo

当出现错误时，使用On Error GoTo语句将程序的执行流程转移到指定的代码行。On Error GoTo的语法格式如下。

On Error GoTo 标号

其中，“标号”是与On Error GoTo语句在同一个过程中的行标注。

【实例17.1】编写程序，试验On Error GoTo语句。在窗体Form1上绘制一个按钮将其名称属性改为cmdOK。编写事件过程如下。

01 Private Sub cmdOK_Click()

02 On Error GoTo ErrZero

03 Dim x As Integer

04 Dim y As Integer

05 x=9

06 y=0

07 Print 9/y

08 Exit Sub

09 ErrZero:

10 MsgBox"错误内容为："＆Err.Description

11 End Sub

【代码说明】本程序因为除数为0，所以肯定会出错，由于程序中第02行使用了On Error GoTo语句，所以出现错误时，将程序的流程转到了第09行的ErrZero：标号处，并从该标号处开始，向下执行剩下的语句。ErrZero后面的第10行语句就是错误处理程序。

【运行效果】运行结果如图17.9所示。

图　17.9　实例17.1运行结果

2.On Error Resume Next

On Error Resume Next语句的作用是不中断程序的运行，也不转移程序的执行流程，而是忽略出现错误的代码行，执行导致错误的下一条语句。这是错误处理的最简单的方法，但也是最危险的方法。因为错误没有被纠正，也没有提示，所以程序继续运行。这个错误可能给后续的操作留下隐患。例如，在执行连接数据库时操作失败，在这样的情况下，若执行On Error Resume Next语句，忽略连接数据库的语句，继续执行其下面的语句，而由于数据库没有连接成功，所有与该数据库有关的操作都不能正常进行。由于使用忽略错误的On Error Resume Next语句有一定的危险性，因此建议尽量不要使用On Error Resume Next语句。

17.3.3　编写错误处理程序

捕获错误后，应当根据被捕获的错误，编写相应的错误处理程序。在实例17.1中，语句标号"ErrorZero:"之后的代码就是一段简单的错误处理程序，该例是对出现的错误给出简单的提示。

编写错误处理程序的目的有三个：一是防止程序异常的中断运行；二是如果可能，在程序运行中纠正某些错误，使程序继续运行；三是将发生的错误通知用户，以便用户纠正错误。

【实例17.2】编写一个备份数据库文件的程序。在窗体Form1上绘制一个按钮将其名称属性改为cmdBak。

分析：备份数据库文件，其实就是将数据库文件复制一份。复制文件可以使用前面介绍的FileCopy语句。编写如下事件过程。

01 Private Sub cmdBak_Click()

02 FileCopy App.Path＆"\data\Hotel.mdb",App.Path＆"\bak\Hotel.mdb"

03 MsgBox"导出工程成功！",vbInformation+vbOKOnly,"提示"

04 End Sub

【代码说明】这段程序在各项设置都正确的情况下，运行会完全正常。但是，有可能出现下面的问题。

❑将Hotel.mdb文件无意中删除了，因此找不到要备份的数据库源文件。这时会出现如图17.10所示的错误。

图　17.10　错误窗口1

❑数据库文件Hotel.mdb正在使用中，此时不能被复制。这时会出现如图17.11所示的错误。

图　17.11　错误窗口2

❑程序中使用的data文件夹或者bak文件夹不存在。这时会出现如图17.12所示的错误。

图　17.12　错误窗口3

由于，在使用程序时有可能会出现上面的问题。所以，必须加入错误捕获语句，并编写相应的错误处理程序。改进后的程序如下。

05 Private Sub cmdBak_Click()

06 On Error GoTo ErrCopy

07 FileCopy App.Path＆"\data\Hotel.mdb",App.Path＆"\bak\Hotel.mdb"

08 MsgBox"数据库已成功备份！",vbInformation+vbOKOnly,"提示"

09 Exit Sub

10 ErrCopy:

11 Select Case Err.Number

12 Case 53

13 MsgBox"数据库文件不存在！请恢复数据库或者联系开发人员！"

14 Case 70

15 If MsgBox("数据库正在使用，请关闭后再备份！",vbRetryCancel)=vbRetry Then

16 Resume

17 End If

18 Case 76

19 MsgBox"程序中使用的文件夹不存在！"

20 Case Else

21 MsgBox"错误号码为："＆Err.Number＆"错误信息："＆Err.Description

22 End Select

23 End Sub

【代码说明】本例的错误处理程序是第11～22行，其根据错误号码（Err.Number），执行了不同的分支语句。如，当数据库文件找不到时，会发生53号错误。因为程序的开头第06行写了On Error GoToErrCopy语句，所以执行流程就会转到第10行的ErrCopy标号处，并向下执行。又因为错误号码是53，因此会执行Case 53分支下的语句，弹出消息框，内部显示“数据库文件不存在！请恢复数据库或者联系开发人员！”。其中，Case 70分支下的第15～17行语句的作用是，先弹出一个带有“重试”和“取消”按钮的消息框。如果单击“重试”按钮，则执行Resume语句。该语句的功能是重新执行引起错误的语句，即重新执行FileCopy。

Resume语句有三种用法，分别如下。

❑Resume：Resume的作用是重新执行引起错误的语句。例如，对于复制正在使用中的数据库时发生的“拒绝的权限”错误，在执行了关闭数据库的操作后，错误会被纠正，可以重新执行复制操作，不必结束程序的运行。因此在手动关闭数据库之后，执行Resume语句，程序的执行流程会返回到FileCopy语句上。

❑Resume Next：当程序中发生错误时，忽略该错误语句，继续执行下一条语句。如果发生错误的语句与程序中其他剩余语句没关系，则可以使用Resume Next语句。

❑Resume标号：当发生错误时，忽略错误行，并跳转到“标号”指明的语句继续执行。若“标号”为0，则表示中止程序的执行。

除上述列出的错误之外，程序在运行中还会有其他不可预知的错误，凡是这类错误，都由Case Else分支统一处理，用系统提供的Err.Number和Err.Description给出提示。

【运行效果】这里只是演示如何捕获错误，并不算真正完整的案例，所以不再给出效果。

需要说明的一点是，在程序正常运行时，不应执行错误处理程序的代码，即不执行标号ErrCopy之后的语句。因此，在该语句之前应该有退出过程的语句Exit Sub。

技巧　如果想知道某种错误的错误号码是多少，可以在MSDN的“搜索”选项卡中搜索字符串“可捕获的错误”即可。

17.3.4　集中错误处理程序

在实际的商业应用系统中，错误处理程序是必不可少的。只给一个过程添加错误处理程序感觉很简单，但给所有过程都添加错误处理程序，工作量则是可想而知的。解决这个问题的方法是建立一个集中错误处理的子程序。在所有的过程中，如果出现错误，都调用该子程序，并用它来集中处理错误。例如，将上面的实例改为使用集中错误处理程序处理错误。

01 Sub GoErr()

02 Select Case Err.Number

03 Case 53

04 MsgBox"数据库文件不存在！请恢复数据库或者联系开发人员！"

05

06 Case 70

07 If MsgBox(Err.Description,vbRetryCancel)=vbRetry Then

08 Resume

09 End If

10 Case 76

11 MsgBox"程序中使用的文件夹不存在！"

12 Case Else

13 MsgBox"错误号码为："＆Err.Number＆"错误信息："＆Err.Description

14 End Select

15 End Sub

16 Private Sub cmdBak_Click()

17 On Error GoTo ErrCopy

18 FileCopy App.Path＆"\data\Hotel.mdb",App.Path＆"\bak\Hotel.mdb"

19 MsgBox"数据库已成功备份！",vbInformation+vbOKOnly,"提示"

20 Exit Sub

21 ErrCopy:

22 Call GoErr

23 End Sub

【代码说明】第01～15行的GoErr()就是一个集中错误处理程序。当编写大型商用软件时，使用这种方法会给编程人员节省大量的时间。除此之外，第一次完整地编写了集中错误处理的子程序后，以后的项目中可以直接引用，不必重复编写。

17.4　小结

很多书都把程序与错误处理放在前面的章节讲，因为初学者刚开始容易犯一些小错误，很有必要先学习程序调试技巧。因为VB程序相对来说比较简单，而且都是界面操作，所以本书把这些技术放在这里讲解。通过本章的学习，希望读者能够学会一些错误处理的技巧。大多数技术人员都知道，没有完美的程序，只要是程序编写就会出现错误，但错误可大可小。学完本章后，希望读者编写的程序错误越少越好。

17.5　本章练习

1.信息错误的类型一般分为三种，都有哪三种？

2.遇到On Error语句时，程序是显示错误，还是按照语句指示的步骤继续进行？

3.为什么在使用Set Next Statement后，执行的结果不正确？

提示　Set Next Statement功能用于人为指定下一条需要执行的语句，而跳过中间的某些程序。因此，使用Set Next Statement功能可能没有执行到某些程序才导致执行结果不准确，可以使用Run To Cursor功能尝试一下。

4.下列错误捕获程序代码的作用是什么？

Private Sub cmdCaculate_Click()

'错误捕获

On Error GoTo 0

'除法运算

txtConclution.Text=txtDivideNum1.Text/txtDivideNum2.Text

End Sub

第18章　建立并使用帮助文件

帮助文件对于一个完整的商业软件来说是必不可少的。它应当帮助用户挖掘软件的全部功能。帮助文件写得越细越好，越简单易懂越好。本章将介绍帮助文件的建立方法和在程序中如何调用帮助文件的方法。本章包括以下知识点。

❑创建帮助文件。

❑HTML Help Workshop工具的使用。

❑帮助文件的索引。

通过本章的学习，我们能够为程序创建一个帮助文档。

18.1　建立帮助文件

一个完整的帮助文件，应当由目录文件、主体文件和项目文件组成。下面分别介绍这三类文件的建立方法和连接方法。虽然有很多商业软件或者共享软件可以帮助用户建立帮助文件，但在本章中，将使用Visual Basic自带的工具建立帮助文件。

当完整地安装了Visual Basic之后，在Windows开始菜单的Visual Basic工具集中可以找到"Help Workshop"项。如果开始菜单中没有该项，可以从Visual Basic中的安装光盘里找。下面以给本书第16章中制作的播放器建立帮助文件为例说明建立帮助文件的方法。

18.1.1　建立目录文件

建立帮助文件的第一步，应当是建立目录（Contents）文件。该文件使用"Help Workshop"工具建立。具体操作步骤如下。

（1）启动"Help Workshop"，界面如图18.1所示。

图　18.1　Help Workshop界面图

（2）选择界面中的"File"|"New"菜单项后，会出现一个"New"（新建）对话框，从其列表中选择"Help Contents"，并单击"OK"按钮，如图18.2所示。

图　18.2　"New"对话框

（3）单击"OK"按钮后，会出现如图18.3所示的目录文件编辑界面。在"Default filename"（默认文件名）栏中输入"MyPlayer"，在"Default title"（默认标题）栏中输入“我的播放器”。

图　18.3　目录文件编辑界面

（4）单击图18.3中的"Add Above"按钮，在打开的"Edit Contents Tab Entry"对话框中，选择"Heading"单选按钮，并在"Title"栏中输入文字“关于MyPlayer播放器”，并单击"OK"按钮，如图18.4所示。这就好比在设计树型控件中的根结点。

图　18.4　"Edit Contents Tab Entry"对话框

（5）在图18.3的界面中，单击"Add Below"按钮，仍会出现图18.4的"Edit Contents Tab Entry"对话框，只是这次从中选择"Topic"单选按钮。并在"Title"栏中输入文字“MyPlayer是什么”；在"TopicID"栏中输入"ID_What"，其他两个栏不用输入文字。单击"OK"按钮后，界面如图18.5所示。这就好比在刚才的根结点下，创建了一个子结点。

图　18.5　目录文件编辑界面

技巧　"Add Below"按钮用来在所选项的下面插入创建的目录，而"Add Above"按钮用来在所选项的前面插入创建的目录。

（6）按照步骤5的方法，使用"Add Below"按钮和"Edit Contents Tab Entry"对话框，在“关于MyPlayer播放器”结点（目录）下，根据表18.1的内容创建子结点。

（7）单击"Add Below"按钮，在"Edit Contents Tab Entry"对话框中，选择"Heading"单选按钮，并在"Title"栏中输入“初学者快速起步”，并单击"OK"按钮。这在编辑界面中又创建了一个根结点目录。

（8）再单击"Add Below"按钮，在对话框中，选择"Heading"，并在"Title"栏中输入“如何打开要播放的影音文件”，并单击"OK"按钮。此时，编辑界面如图18.6所示。

（9）在选中“如何打开要播放的影音文件”项的前提下，使用图18.6中的"MoveRight"按钮。让其向右缩进，使其成为“初学者快速起步”根结点目录的子结点。这类似于菜单编辑器的用法。读者可以继续按照步骤5的方法，给“如何打开要播放的影音文件”项下建立子结点。

图　18.6　目录文件编辑界面

（10）使用工具栏中的“保存”快捷按钮，将目录文件保存到“D:\VB\播放器\”下，并命名为"MyPlayer"，其扩展名为".cnt"。至此，目录文件创建完毕。

18.1.2　编写主题文件

建立完目录文件后，可以开始编写主题（Topic）文件了，主题文件其实就是帮助文件中的含有具体帮助内容的文件。主题文件可以使用微软的Word字处理软件建立。具体操作步骤如下。

（1）启动Word软件，在文档区第一行键入文字“MyPlayer是什么”，将文字的字号设置为三号字，并加粗。

（2）从第二行开始键入下面的文字，并在文字的后面插入一幅播放器的图片。

MyPlayer是一种自制的播放器软件。它可以播放多种类型的影音文件，例如可以播放mp3、wav、avi、wma、mpeg等类型文件。界面如图所示。

（3）将光标放到第一行文字“MyPlayer是什么”的前面，选择菜单栏中的“插入”|“引用”|“脚注和尾注”菜单项。

（4）在打开的“脚注和尾注”对话框中，找到“自定义标记”栏，在其内键入“#”号，并单击“插入”按钮，如图18.7所示。

图　18.7　“脚注和尾注”对话框

（5）在Word文档编辑窗口内，在脚注文本区键入"ID_What"，"ID_What"是刚才在创建目录文件时给“MyPlayer是什么”主题设置的TopicID。Word中显示的内容如图18.8所示。

图　18.8　Word中显示的内容

到此为止，已经成功地建立了第一个主题页面。下面再建立两个主题页面，用于演示帮助文件中主题页面之间的切换。

说明　脚注文本区在Word文档页面的最下面。

（6）在Word中，按Ctrl+Enter快捷键在页面中加入一个换页符，强制建立一个新的页面。这样的好处是一个主题就可以占一个独立的页面。

（7）在第二个页面的第一行键入“MyPlayer的局限”，与建立第一个页面相同，将文字的字号设置为三号字，并加粗。

（8）从第二行开始键入下面的文字：

MyPlayer播放器不能播放RM格式的文件，主要局限于mp3、wav、avi、wma、mpeg等类型文件。而且不能自动连续地播放媒体库中的影音文件。

（9）将光标放到第一行文字“MyPlayer的局限”的前面，按照步骤3、4、5的方法插入一个脚注，并在脚注文本区键入"ID_Limit"。

（10）按照上面的方法，建立第三个主题页面。页面内容如下。

感谢

首先，感谢用户使用MyPlayer播放器。该播放器由于是教学用例，所以很多功能并不完善，希望用户多提意见。

并在插入的脚注文本区键入"ID_Thanks”。

（11）将文档保存到“D:\VB\播放器\”下，并命名为"MyPlayer"，注意一定将其扩展名设置为".RTF"。

18.1.3　建立项目文件

编写完主题文件，并将其保存为RTF格式的文件后就可以创建一个项目文件了。项目文件将会把目录文件和主题文件关联起来，并可以将其编译生成可用的帮助文件。具体操作步骤如下。

（1）启动"Help Workshop"，选择界面中的"File"|"New"菜单项，在"New"对话框中的列表中选择"Help Project"，并单击"OK"按钮。

（2）单击"OK"按钮后，会出现图18.9所示的项目文件编辑界面。在"Help File"栏中输入"MyPlayer.hlp"。

图　18.9　项目文件编辑界面

（3）单击窗口右侧的"Options"按钮，打开"Options"对话框，在"Help title"栏中输入文字“我制作的第一个帮助文件”，如图18.10所示。

图　18.10　"Options"对话框（General选项卡）

（4）单击"Compression"选项卡，从选项卡中选择"Custom"单选按钮，并选择其下的"Hall Compression"选项，如图18.11所示。

图　18.11　"Options"对话框（"Compression"选项卡）

（5）选择"Files"选项卡，在"Help File"栏中输入"MyPlayer.hlp"，如图18.12所示。

图　18.12　"Options"对话框（"Files"选项卡）

（6）单击图18.12中方框选中的"Change"按钮，打开"Topic Files"对话框，如图18.13所示。

图　18.13　"Topic Files"对话框

（7）单击对话框右侧的"Add"按钮，并从“打开”对话框中选择18.1.2节创建的主题文件MyPlayer.rtf。这时对话框内的"Topic files"下的列表框中将会出现MyPlayer.rtf列表项。单击"Topic Files"对话框中的"OK"按钮。

（8）单击图18.12中的"Browse"按钮，从“打开”对话框中选择在18.1.1节创建的目录文件MyPlayer.cnt。

（9）单击“确定”按钮，关闭"Options"对话框。

（10）在如图18.9所示的项目文件编辑界面中，单击"Windows"按钮。

（11）在打开的"Create a window"对话框"Create a window named:"栏中输入文字“主窗口”。并单击"OK"按钮，如图18.14所示。

图　18.14　"Create a window"对话框

（12）单击"OK"按钮后，会出现"Window Properties"对话框，单击"Buttons"选项卡，并选中"Browse"复选框，如图18.15所示。这会将"Browse"（浏览）按钮加入到帮助文件的窗口中。使用"Color"选项卡还可以设置帮助文件中标题区和文本区的背景颜色。

图　18.15　"Window Properties"对话框

（13）单击“确定”按钮，退出"Window Properties"对话框。

（14）单击项目文件编辑界面中的"Map"按钮，打开"Map"对话框，如图18.16所示。通过该对话框可以设置上下文相关性。

图　18.16　"Map"对话框

（15）单击右侧的"Add"按钮，打开"Add Map Entry"对话框，连续加入表18.2的主题和上下文标识符，在"Topic ID"栏中输入相应的Topic ID，在"Mapped numeric value"栏中输入上下文标识符，请参考图18.17。

图　18.17　"Add Map Entry"对话框

（16）单击"Map"对话框中的"OK"按钮，关闭"Map"对话框。此时，项目文件编辑界面如图18.18所示。

图　18.18　项目文件编辑界面

（17）单击图18.18中右下角的"Save and Compile"按钮，保存并编译项目文件。此时，编译窗口会最小化。最后"Help Workshop"窗口中的编译结果如图18.19所示。

图　18.19　"Microsoft Help Workshop"窗口中的内容

（18）在Windows桌面上，双击“我的电脑”图标，打开"D:\VB"目录时，会发现该目录下多出了一个“播放器MYPLAYER． hlp”文件，如图18.20所示。

图　18.20　"D:\VB"目录

（19）将“播放器MYPLAYER.hlp”文件移动到“播放器”文件夹内，并将其改名为"MYPLAYER.hlp"。如果前面的操作全部正确，双击该文件后，应当能够打开前面制作的帮助文件，可以用鼠标双击目录结点，查看下级目录结点或主题文件列表，如图18.21所示。

图　18.21　帮助文件界面

（20）双击帮助窗口中的主题文件名时，会打开相关主题文件，例如双击“MyPlayer是什么”主题文件名时，会打开如图18.22所示的“MyPlayer是什么”主题文件。

图　18.22　“MyPlayer是什么”主题文件

18.2　连接应用程序和帮助文件

制作完帮助文件之后，应当将应用程序和帮助文件连接起来，下面用一个实例说明连接应用程序和帮助文件的方法。

【实例18.1】设计一个如图18.23所示的程序，当选择“帮助”|“MyPlayer是什么”菜单项时，打开前面制作的帮助文件的“MyPlayer是什么”主题文件。其他两个菜单项的作用同“MyPlayer是什么”菜单项。

图　18.23　实例18.1运行界面

（1）打开菜单编辑器，按照表18.3的内容设计菜单。

（2）选择“工程”|“工程1属性”菜单项，在打开的“工程1-工程属性”对话框中，将“帮助文件名”栏设置为前面创建的MYPLAYER.HLP，如图18.24所示。

图　18.24　“工程1-工程属性”对话框

（3）选择“工程”|“部件”菜单项，从“部件”对话框的列表中选择"Microsoft Common Dialog Control 6.0"项，并单击“确定”按钮，将CommonDialog控件添加到工具箱中。

（4）在窗体上绘制一个CommonDialog控件，将其名称属性设置为：cdHelp。

（5）编写程序如下。

01 Option Explicit

02 Private Sub Form_Load()

03 cdHelp.HelpFile=App.HelpFile

04 End Sub

05

06 Private Sub mnuHelpWhat_Click()

07 cdHelp.HelpContext=1000

08 cdHelp.HelpCommand=cdlHelpContext

09 '显示帮助对话框

10 cdHelp.ShowHelp

11 End Sub

12

13 Private Sub mnuHelpLimit_Click()

14 cdHelp.HelpContext=2000

15 cdHelp.HelpCommand=cdlHelpContext

16 cdHelp.ShowHelp

17 End Sub

18

19 Private Sub mnuHelpThanks_Click()

20 cdHelp.HelpContext=3000

21 cdHelp.HelpCommand=cdlHelpContext

22 cdHelp.ShowHelp

23 End Sub

【代码说明】本段代码中第07行、14行和第20行出现的语句

cdHelp.HelpContext=1000或2000或3000

中的1000、2000和3000是在创建帮助文件时，在"Map"对话框中设置的上下文标识符。

【运行效果】本例效果如图18.23所示。

18.3　HTML Help Workshop工具的使用

目前，大多数软件的帮助系统已经不再是WinHelp帮助系统，而采用的是HTML格式的帮助系统，例如Visual Basic的MSDN就是一个HTML格式的帮助系统。制作这一类的帮助文件可以使用Visual Basic安装光盘中自带的HTML Help Workshop工具。

18.3.1　HTML Help Workshop的安装与启动

在使用HTML Help Workshop之前，应当先将其安装。打开Visual Basic安装光盘，找到"HTMLHELP"文件夹，并将其打开，其内应该有一个名为HTMLHELP.exe的安装文件，双击运行安装文件后根据安装向导的提示安装即可。但是，可惜这个版本是英文版的软件，在转换WinHelp主题文件的中文字符时可能会成为乱码，因此本书使用的是从Internet上搜索到的中文版本。

安装完成后，打开“开始”菜单选择“程序”|"HTML Help Workshop"|"HTML Help Workshop"菜单项即可打开HTML Help Workshop工具，其界面如图18.25所示。

图　18.25　"HTML Help Workshop"主界面

18.3.2　将帮助文件转换为HTML格式

使用HTML Help Workshop可以直接制作帮助文件，但是前面已经制作了WinHelp系统的帮助文件，所以在此直接将其转换成HTML格式的帮助文件即可。接下来，将前面制作的WinHelp帮助文件转换成HTML格式的帮助文件。具体操作步骤如下。

（1）启动HTML Help Workshop。

（2）选择“文件”|“新建”菜单项。

（3）从“新建”对话框的列表中选择“方案”列表项，并单击“确定”按钮。

（4）这时会出现一个“新建方案”向导对话框，如图18.26所示，将其内的“转换WinHelp方案”复选框选中。

图　18.26　“新建方案”对话框

（5）单击“下一步”按钮后，会出现如图18.27所示的“来源和目标方案文件”对话框，在其内要指定将要转换的WinHelp系统的源文件和转换成HTML后的目标文件的路径。

图　18.27　“来源和目标方案文件”对话框

（6）在上面的栏中，通过“浏览”按钮选择输入本章前面建立的帮助项目文件MyPlayer.hpj。

（7）在下面的栏中，也可通过其下方的“浏览”按钮，选择好保存路径，并将HTML文件命名为htmlMyPlayer。

（8）单击“下一步”按钮，将出现如图18.28所示的“完成”界面。在其内单击“完成”按钮即可结束WinHelp系统的转换过程。

图　18.28　完成界面

转换结束后，可以将文件直接编译。方法是：使用“文件”|“编译”菜单项即可编译。不过为了让帮助系统更加完整，下面继续建立帮助系统的索引文件。

18.3.3　为帮助系统建立索引文件

18.3.2节中将WinHelp系统转换为HTML格式的文件之后，会出现如图18.29所示的界面。

图　18.29　HTML Help Workshop界面

接下来，建立索引文件，索引文件是一些可以用来快速遍历主题的关键字清单。建立索引文件的具体操作步骤如下。

（1）单击图18.29中的“索引”选项卡。会跳出一个“没有指定索引”对话框，从中选择“创建一个新索引文件”选项，并单击“确定”按钮，如图18.30所示。

图　18.30　“没有指定索引”对话框

（2）此时，会出现“另存为”对话框，在其内将索引文件命名为"Index"，并保存。

（3）打开“索引”选项卡后，左侧有11个竖排的按钮。单击第2个“插入关键字”按钮
。

（4）单击“插入关键字”按钮后，会出现“索引项目”对话框，如图18.31所示。

图　18.31　“索引项目”对话框（“常规”选项卡）

（5）在“常规”选项卡的“关键字”栏内输入关键字，并使用其下方的“添加”按钮指定与该关键字相关联的主题文件，然后单击“确定”按钮。重复步骤4、5可以加入多个关键字。至此索引文件可以说已经建立完毕。

说明　关键字是用户可能用到的并与一个或多个帮助主题文件关联在一起的字、词或短语，索引文件（.hhk）也是一个HTML文件，它包含若干个关键字，当用户打开chm文件后，单击索引标签并输入一个关键字后，chm文件将显示与这个关键字有关的主题的列表，可以非常方便地找到相关主题。

（6）选择并打开“方案”选项卡。

（7）单击“方案”选项卡左侧按钮中，最下面的按钮
，保存并编译文件。

编译器将处理项目中的所有文件并建立一个包含帮助系统的压缩HTML文件。当编译器工作结束后，将看到在HTML Help Workshop主界面右侧的编译日志文件，如图18.32所示。

图　18.32　HTML Help Workshop界面（编译日志）

18.3.4　解决中文乱码问题

编译完成后，找到HTML格式的帮助文件（扩展名为.chm），双击将其打开。会出现如图18.33所示的MyPlayer播放器的帮助文档的界面。

图　18.33　MyPlayer播放器的帮助文档

这时会发现在右侧的窗口内显示的并不是原来的帮助主题文件内容，而是一堆乱码。原因是主题文件中的中文字符在转换的时候没有被正确的识别。如果出现了这类问题，可以用下面的方法来解决。

（1）启动HTML Help Workshop。

（2）打开刚才保存的方案文件（项目文件）htmlMyPlayer.hhp。

（3）选择并打开右侧的“目录”选项卡，双击具体的主题内容，此时右侧窗口内会出现与此主题相关联的网页文件的HTML源代码，如图18.34所示。

图　18.34　HTML源代码

（4）将HTML源代码中用矩形选中的

charset=Windows-1252

改为

charset=gb-2312

说明　charset=gb-2312在网页代码中的意思是当前网页要采用简体中文字符集。

（5）将其他变成乱码的文件都用上面的方法修改，并保存方案文件。

（6）重新编译后即可解决乱码的问题。

其实，在制作帮助文档时，如果对HTML语言熟悉，可以直接使用HTML Help Workshop工具制作帮助文件。

18.4　小结

很多初级程序员都不一定深入地学习过本章，因为在一个项目组中，有专门的人负责制作帮助文件，很多其他语言的开发人员也是用这个Help Workshop工具来制作帮助文件。为了让用户可以更详细地了解软件，一个项目或者产品完成后，帮助文件必不可少。希望读者认真学习本章，对整个项目的流程有个完整的了解。

18.5　本章练习

1.如何解决帮助工具中的中文乱码问题？

2.如果我们用Word制作的帮助文件，是否能将其转化为HTML格式显示呢？

3.自己动手制作一个简单的帮助文档。

第19章　Visual Basic与Excel联合编程

使用Visual Basic编写管理信息系统类的软件时，经常需要将最终结果以没有规律而且复杂的报表的形式显示出来。虽然Visual Basic中自带了处理报表的一些工具，但是用起来很不方便，所以进行报表编程时经常会用到Excel类型库和Excel软件。本章将介绍Visual Basic和Excel联合运用的方法。本章包括以下知识点。

❑如何通过VB来创建Excel文件。

❑使用OLE控件操作Excel文件。

❑使用VB对Excel文件进行操作。

通过本章的学习，可以熟练地应用Excel的各种操作。

19.1　Excel基础

在编写报表程序之前，首先应当用Excel软件制作好报表的模板文件。然后才使用程序向该模板文件输出数据。所以编程人员必须会使用Excel软件才可以。下面简单地介绍一下Excel的相关知识。

19.1.1　Excel简介

Excel的全称是Microsoft Office Excel。它是Microsoft Office（微软办公自动化）套装软件的成员之一，主要用于电子表格的处理。由于其强大的表格处理能力，用户非常喜欢用其制作各种复杂的表格和各类图表。因为，介绍Excel软件已经远远超出了本书的范围，所以在此不再多说。

在进行报表编程时，通常先利用Excel软件制作好复杂表格的样式，然后在程序中使用代码实现向表格输出数据，最后利用Excel软件的打印等功能处理报表。另外Excel超群的计算能力也可以省掉程序设计人员编写的很多计算的代码。

19.1.2　创建Excel文件

Excel的文件都以工作簿文件的形式存放在硬盘上。这些工作簿文件的扩展名为".xls"。每个工作簿文件内包含一个或多个工作表。所以，在制作表格之前首先应当建立工作簿文件。建立工作簿文件的操作方法如下（以Microsoft Office Excel 2003举例）。

（1）启动"Microsoft Office Excel 2003"。在Windows XP系统中，单击“开始”|“所有程序”|"Microsoft Office"|"Microsoft Office Excel 2003"菜单项，即可启动Excel软件，如图19.1所示。

图　19.1　“开始”菜单

（2）启动成功后Excel会自动建立一个名为Book1.xls的工作簿文件。该文件内有三个默认的工作表，分别为Sheet1、Sheet2和Sheet3。Sheet1为默认的当前工作表。

（3）在工作表中可以制作表格，也可以制作图表。制作完成后使用“文件”|“保存”菜单项保存工作簿文件。这样在硬盘上就创建了一个工作簿文件。

19.2　使用OLE控件调用Excel

在程序中可以直接使用工具箱中的OLE控件调用Excel文件。这一节将介绍如何使用OLE控件调用Excel文件的方法。

19.2.1　“插入对象”对话框的使用

在程序中调用Excel文件可以使用“插入对象”对话框，也可以使用程序代码实现。下面用一个实例说明使用“插入对象”对话框调用Excel文件的方法。

【实例19.1】在Visual Basic中使用OLE控件，显示Excel工作表的内容。操作方法如下。

（1）首先，使用Excel创建一个工作簿文件，在Sheet1工作表中随意输入一些内容，最后将工作簿保存到"D:\VB\Excel\"文件夹下，并命名为"a123.xls"。

（2）启动Visual Basic，在默认窗体Form1上绘制一个OLE控件。

（3）绘制结束的一刹那，会跳出一个“插入对象”对话框，在对话框中选择“由文件创建”单选按钮，并使用浏览按钮选择刚才创建的a123.xls工作簿文件，本例中还需要选中“链接”复选框，如图19.2所示。

图　19.2　“插入对象”对话框

（4）单击“确定”按钮，关闭“插入对象”对话框后，在窗体中即可看到Excel文件的内容，如图19.3所示。

图　19.3　实例19.1界面

【运行效果】运行程序，不仅可以在窗体中浏览a123.xls的工作表内容，而且当用鼠标双击OLE控件时，Excel会被启动，a123.xls工作簿文件会在Excel软件内被打开。原因是在“插入对象”对话框中，选择了“链接”复选框，并且保持了OLE控件的AutoActivate属性的默认值2-Doubleclick。

说明　在“插入对象”对话框中，如果不选择“链接”复选框，双击OLE控件时会在应用程序的窗体上出现Excel软件的一部分菜单，不会单独启动Excel。这种打开方式叫做嵌入式打开，而上面的选择“链接”复选框的打开方式叫做链接式打开。

19.2.2　编写程序在OLE控件中显示文件

1.CreateEmbed方法

使用CreateEmbed方法可以在OLE控件中以嵌入方式打开Excel文件。例如下面的语句可以在OLE控件中打开a123.xls。

OLE1.CreateEmbed"D:\VB\Excel编程\a123.xls"

其中，OLE1是OLE控件的实例。

2.CreateLink方法

使用CreateLink方法可以在OLE控件中以链接方式打开Excel文件。例如下面的语句：

OLE1.CreateLink"D:\VB\Excel编程\a123.xls"

3.SourceDoc属性和Action属性

使用SourceDoc属性和Action属性也能实现以上两个方法的功能。例如，要以嵌入式打开Excel文件的代码是：

OLE1.SourceDoc="D:\VB\Excel编程\a123.xls"

OLE1.Action=0

要以链接式打开Excel文件的代码是：

OLE1.SourceDoc="D:\VB\Excel编程\a123.xls"

OLE1.Action=1

【实例19.2】制作一个Excel文件查看器。界面设计如图19.4所示。

图　19.4　实例19.2设计界面

编写程序如下。

01 Private Sub cmdBrow_Click()

02 cd1.Filter="Excel文件(*.xls)|*.xls|所有文件(*.*)|*.*|"

03 cd1.ShowOpen

04 If cd1.FileName＜＞""Then '判断是否指定了具体的文件

05 If optLink.Value=True Then

06 OLE1.CreateLink cd1.FileName '以链接方式打开文件

07 ElseIf optEmbed.Value=True Then

08 OLE1.CreateEmbed cd1.FileName '以嵌入方式打开文件

09 End If

10 End If

11 End Sub

12

13 Private Sub Form_Load()

14 optEmbed.Value=True

15 End Sub

【代码说明】代码第05～09行通过If判断语句，判断用户选择的是链接方式还是嵌入方式。代码第06行是以链接方式打开文件，代码第08行是以嵌入方式打开文件。

【运行效果】运行程序后的界面如图19.4所示。

19.3　针对Excel的程序设计

如果仅是显示Excel文件的内容，则使用前面介绍的OLE控件的方法即可。但是，在实际编程时可能要对Excel文件进行各种各样的操作。例如，向Excel文件输出数据、获取Excel文件中的内容、控制打印Excel文件等。从这一节开始，将介绍针对Excel的具体编程方法。

19.3.1　在程序中引用Excel类型库

在编写操作Excel文件的程序时，首先应当引用Excel类型库。根据计算机上安装的Excel软件的版本的不同，类型库的版本也不一样。引用Excel类型库的具体操作方法如下。

（1）选择“工程”|“引用”菜单项，在“引用”对话框的列表中选择"Microsoft Excel 11.0 Object Library"项，如图19.5所示。

图　19.5　“引用”对话框

（2）单击“确定”按钮，关闭“引用”对话框。

说明　本书使用的是Excel的2003版本，因此类型库的版本为11.0。

19.3.2　操作Excel对象

1.程序中使用到的Excel对象

要通过程序代码操作Excel文件时，必须在程序中使用Excel对象。而且要以变量的形式使用Excel对象。经常使用到的Excel对象主要有Application（应用）、Workbook（工作簿）和Worksheet（工作表）等三个对象。其中，应当首先创建Application对象，然后通过Application对象操作Workbook对象，再通过Workbook对象操作Worksheet对象。这三个对象的关系如图19.6所示。从图中可以看出Application对象是顶层对象，Workbook对象是其子对象，Worksheet对象则是Workbook的子对象。

图　19.6　对象关系图

2.通过程序操作Excel对象

在程序中要用上面提到的三种类型的对象变量时，首先应该声明这些变量。即使Visual Basic中不要求声明，但最好是对其进行声明。声明语句如下。

Dim xlApp As Excel.Application

Dim xlBook As Excel.Workbook

Dim xlSheet As Excel.Worksheet

声明之后首先需要创建Application对象，其语句是：

Set xlApp=CreateObject("Excel.application")

创建了Application对象之后，就可以使用其新建或者打开工作簿文件了。使用Application对象新建工作簿的语句是：

Set xlBook=xlApp.Workbooks.Add

在使用Add方法新建工作簿之后，工作簿的当前状态为打开状态。而使用Application对象打开一个已经存在的Excel工作簿文件的语句类似于：

Set xlBook=x.Workbooks.Open("D:\VB\Excel编程\a123.xls")

在新建或者打开一个工作簿文件后，默认当前工作表为第一个工作表。例如，如果是新建的工作簿，默认会有名称为Sheet1、Sheet2和Sheet3的三个工作表。其中，Sheet1为当前工作表。此时对工作表进行的任何操作只会影响当前工作表Sheet1。如果想操作Sheet2工作表，则必须让Sheet2变成当前工作表，下面的语句将Sheet2变成了当前工作表。

Set xlSheet=xlBook.Worksheets("sheet2")

或者

Set xlSheet=xlBook.Worksheets(2)

使用Worksheets对象的Add方法，可以新建工作表，例如：

xlBook.Worksheets.Add Count:=3,Before:=Sheets(1)

该语句在第一个工作表之前加入了3个新的工作表。下面用一个简单的实例说明Excel三种对象的用法。

【实例19.3】编程试验Excel三种对象的用法。试验的界面设计为：在Form1窗体上绘制一个Command1按钮。编写程序如下。

01 Option Explicit

02 Private Sub Command1_Click()

03 Dim xlApp As Excel.Application

04 Dim xlBook As Excel.Workbook

05 Dim xlSheet As Excel.Worksheet

06

07 Set xlApp=CreateObject("Excel.application")

08 '创建新工作簿

09 Set xlBook=xlApp.Workbooks.Add

10 Set xlSheet=xlBook.Worksheets("sheet2")

11 '向当前工作表的第一行第一列的单元格输出数据

12 xlSheet.Cells(1,1)="abcdefg"

13 '将工作簿文件保存为d:\aaa.xls

14 xlBook.SaveAs FileName:="d:\aaa.xls"

15 xlBook.Close '关闭工作簿文件

16 xlApp.Quit '结束Excel对象

17 Set xlApp=Nothing '释放xlApp对象的内存空间

18 End Sub

【代码说明】代码第03～06行创建了三个Excel对象。代码第07行创建一个Excel程序。第09～14行创建一个工作簿并创建一个工作表，然后在工作表的第一行第一列的单元格中输出数据。第16行非常关键，一定要及时结束Excel对象，因为会占用很多进程。

【运行效果】运行程序，并单击Command1按钮后，在D:盘下会自动生成一个aaa.xls的Excel工作簿文件。打开该文件，查看Sheet2工作表时，其结果如图19.7所示。

图　19.7　aaa.xls的内容

19.3.3　向工作表输出数据

上一小节的实例19.3中使用了语句xlSheet.Cells(1,1)="abcdefg"，向当前工作表输出了字符数据。其中，Cells是工作表的属性，可以用其向工作表输出数据。接下来介绍几种输出数据的属性。

1.Cells属性的应用

Cells属性的格式为：Cells(行号,列号)，程序中用其代表具体的单元格。例如，给第3行、第4列的单元格赋值为数字20的语句：

xlSheet.Cells(3,4)=20

使用Cells属性不仅可以给单元格赋值，也可以给单元格设置公式。例如，下面的语句给A3（第3行、第1列）单元格设置公式：

xlSheet.Cells(3,1).Formula="=Sum(B1:B3)"

其中，Sum(B1:B3)为Excel的公式，其作用是得到B1、B2和B3三个单元格内容的和。用Cells属性还可以设置单元格的字体、字号和字形等。例如下面的语句分别设置了单元格的字体、字号和粗体。

xlSheet.Cells(3,1).Font.Name="华文彩云"

xlSheet.Cells(5,2).Font.Size=20

xlSheet.Cells(6,9).Font.Bold=True

如果要设置表格中所有单元格的字体为“宋体”，字号为“16”号时，应当编写语句如下：

xlSheet.Cells.Font.Name="宋体"

xlSheet.Cells.Font.Size=16

Cells属性的行号和列号可以使用变量，例如Cells(i,j)。这使得对Excel单元格编写循环的程序成为可能。

2.Rows属性和Columns属性的应用

有时希望在程序中设置工作表的整行或整列。Rows属性用来设置整行，而Columns属性用来设置整列。例如下面的语句将设置工作表的第1行字体为加粗、20号字。

xlSheet.Rows(1).Font.Bold=True

xlSheet.Rows(1).Font.Size=20

表19.1举例说明了使用Rows和Columns属性的一些对行和列的引用。

3.Range属性的应用

使用Range属性既可以设置单个单元格，也可以设置一个单元格区域。例如将单元格区域A1:D5的字体设置为加粗，并给区域内的单元格全部赋值为0的语句如下。

xlSheet.Range("A1:D5").Font.Bold=True

xlSheet.Range("A1:D5")=0

表19.2举例说明了使用Range属性的一些引用方法。

19.3.4　打印工作表

数据表中有了需要的数据后，接下来最重要的工作可能就是打印了。要打印工作表中的内容，需要用到的是Worksheets对象的PrintOut方法，该方法的格式如下。

Worksheets对象.PrintOut(From,To,Copies,Preview,ActivePrinter,PrintToFile,Collate,PrToFileName)

PrintOut方法后面参数的意义分别如下。

❑From：可选。打印的开始页号。如果省略该参数，将从起始位置开始打印。

❑To：可选。打印的终止页号。如果省略该参数，将打印至最后一页。

❑Copies：可选。要打印的份数。如果省略该参数，将只打印一份。

❑Preview：可选。如果该值为True，则Microsoft Excel打印指定对象之前进行打印预览。如果该值为False（或者省略此参数），则立即打印该对象。

❑ActivePrinter：可选。设置活动打印机的名称。

❑PrintToFile：可选。如果该值为True，则打印输出到文件。如果没有指定PrToFileName，则Microsoft Excel将提示用户输入要输出文件的文件名。

❑Collate：可选。如果该值为True，则逐份打印每份副本。

❑PrToFileName：可选。如果PrintToFile为True，则本参数指定要打印到的文件名。

例如下面的语句用来直接打印当前工作表的全部页面：

xlSheet.PrintOut

从当前工作表的第5页开始打印到第20页的语句为：

xlSheet.PrintOut 5,20

将当前工作表打印2份的语句为：

xlSheet.PrintOut ,,2

打印当前工作表之前先进行打印预览的语句为：

xlSheet.PrintOut ,,,True

将当前工作表的第10页到第15页，打印5份，并且先进行打印预览的语句为：

xlSheet.PrintOut 10,15,5,True

【实例19.4】编程实现打印d:\bbb.xls文件，要求打印2份工作表的前5页，并在打印前显示打印预览的结果。编写程序如下。

01 Private Sub Form_Click()

02 Dim xlApp As Excel.Application

03 Dim xlBook As Excel.Workbook

04 Dim xlSheet As Excel.Worksheet

05

06 Set xlApp=CreateObject("Excel.application")

07 Set xlBook=xlApp.Workbooks.Open("d:\aaa.xls")

08 Set xlSheet=xlBook.Worksheets("sheet2")

09 '++++下面的代码用来打印和打印预览+++++

10 xlApp.Visible=True '让对象可见

11 xlSheet.PrintOut 1,5,2,True

12 xlApp.Visible=False

13 '+++++++++++++++++++++++++++++++++++

14 xlBook.Close

15 xlApp.Quit

16 Set xlApp=Nothing

17 End Sub

【代码说明】代码中第10行的作用是让Excel对象可见，只有设置了该项才能看到预览结果，并在预览后应当让对象不可见，这样就避免了Excel工作簿在程序以外显示出来。

【运行效果】打印预览窗口如图19.8所示。通过该窗口的“设置”按钮，用户可以进行打印页面设置、页边距设置、工作表中显示网格、显示行号和列标等操作。

图　19.8　打印预览窗口

19.3.5　获取工作表数据

获取工作表数据时，只能一个单元格一个单元格的获取，不能获取一个区域、一整行或一整列的数据。因此获取数据的方法有两种，一种是使用Cells属性，例如，将第03行第3列中的数据显示到消息框中的语句如下。

MsgBox xlSheet.Cells(3,3)

另一种为使用Range属性，例如，将B4单元格的内容显示到消息框中的语句如下。

MsgBox xlSheet.Range("b4")

要获取某一区域的数据时，在程序中应当使用循环，例如，将单元格区域A1:B5中的数据显示到消息框中的程序段如下。

01 For i=1 To 5

02 For j=1 To 2

03 str1=str1＆xlSheet.Cells(i,j)

04 Next j

05 str1=str1＆vbCrLf '连接换行符，起到换行的作用

06 Next i

07 MsgBox str1

下面用一个完整的实例演示获取工作表数据的方法。

【实例19.5】设计如图19.9所示的界面，当单击“显示”按钮时，在MSHFlexGrid1表格控件中显示工作表的A1:H15单元格区域的内容。

【实现步骤】操作步骤如下。

（1）选择“工程”|“引用”菜单项，在“引用”对话框的列表中选择"Microsoft Excel 11.0 Object Library"项，并单击“确定”按钮。

（2）选择“工程”|“部件”菜单项，在打开的“部件”对话框控件列表中选择"Microsoft Hierarchical FlexGrid Control 6.0"，将MSHFlexGrid控件添加到工具箱内。

（3）按照图19.9在窗体上绘制好控件，并将按钮的名称属性改为cmdShow；将Caption属性改为显示。

图　19.9　实例19.5设计界面

（4）编写事件过程如下。

01 Private Sub Form_Load()

02 Me.MSHFlexGrid1.Rows=16

03 Me.MSHFlexGrid1.Cols=9

04 End Sub

05

06 Private Sub cmdShow_Click()

07 Dim xlApp As Excel.Application

08 Dim xlBook As Excel.Workbook

09 Dim xlSheet As Excel.Worksheet

10 Dim i As Integer

11 Dim j As Integer

12

13 Set xlApp=CreateObject("Excel.application")

14 Set xlBook=xlApp.Workbooks.Open("d:\bbb.xls")

15 Set xlSheet=xlBook.Worksheets("sheet2")

16 With MSHFlexGrid1

17 For i=1 To 15

18 For j=1 To 8

19 .TextMatrix(i,j)=xlSheet.Cells(i,j)

20 Next j

21 Next i

22 End With

23 xlBook.Close

24 xlApp.Quit

25 Set xlApp=Nothing

26 End Sub

【代码说明】在代码第01～04行的Form_Load事件过程内设置MSHFlexGrid1控件的总行数和总列数为16和9，因为考虑到MSHFlexGrid1有固定行和固定列，而且其行和列的下标是从0开始。代码第16～22行中应注意Excel工作表单元格的下标是从1开始。

【运行效果】最后运行结果如图19.10所示。

图　19.10　实例19.5运行结果

19.4　小结

本章是VB开发中非常重要的一章，因为很多人用VB来计算Excel表数据，或者直接在Office中使用VBA或VB语言开发应用程序。通过本章的学习，读者应该能了解在VB开发中，Excel都包括哪些对象，如何创建和应用这些对象，如何将它们与实际的Excel表关联。

19.5　本章练习

1.以下代码是什么意思？

OLE1.CreateLink"D:\VB\Excel编程\a123.xls"

2.在程序中引用Excel类型库的步骤是什么？

3.以下代码是什么意思？

xlSheet.Cells(3,1).Font.Name="华文彩云"

xlSheet.Cells(5,2).Font.Size=20

xlSheet.Cells(6,9).Font.Bold=True

第20章　使用动态链接库和API

在用Visual Basic进行应用程序的编制时，虽然Visual Basic能够完成几乎所有的Windows应用程序，不管是图形处理、多媒体应用还是网络编程都可以胜任，而且开发起来非常快捷方便。但是，对于一些特殊的要求，仅仅用Visual Basic提供的功能还不能实现，比如访问操作系统、管理内存等要用Visual Basic实现就很困难了。此时，解决问题最好的方法就是使用Windows操作系统自带的Windows API函数。本章包括以下知识点。

❑了解什么是DLL。

❑学习实用API查看器。

❑API函数的应用。

本章的知识点虽然属于高级编程，但经常会在程序中遇到，所以读者一定要好好学习。

20.1　动态链接库（DLL）简介

使用Visual Basic编写应用程序时，偶尔会遇到仅使用Visual Basic的控件没办法解决，或者解决起来很费劲的问题。此时可以有三种选择。

❑利用其他程序设计语言（例如C++语言）编写代码，再从Visual Basic中调用此类程序。

❑可通过OLE技术调用另一个应用程序。

❑可以调用Windows操作系统内置的Windows API函数。

说明　Windows API是一组文件的集合，这些文件被称为DLL。其中包含了数千个能被程序员从其应用程序中调用的程序。Windows操作系统的大量功能是由这些DLL文件实现的。

因为C++语言编程的细节超出了本书讨论的范畴，因此下面仅讨论DLL。当然OLE自动化技术是另一种很不错的实现方法，前面的章节中曾经讲过一些这方面的内容。

DLL是Dynamic Link Library的缩写，意为动态链接库。在Windows中，许多应用程序并不是一个完整的可执行文件，它们被分割成一些相对独立的动态链接库，即DLL文件，放置在系统中。当执行某一个程序时，相应的DLL文件就会被调用。一个应用程序可有多个DLL文件，一个DLL文件也可能被几个应用程序所共用。

如果希望复用他人的程序或希望使程序更小，则DLL非常有用。Windows操作系统则大量地使用了这种方法，Windows可以说是由多个小程序（Applets）和DLL构成。这些小程序被反复使用，例如“打开对话框”和“保存对话框”。读者可能已经注意到了，这两个对话框外形相似，因为它们使用了小程序。对话框由DLL调用，因此它能在很多程序中应用。

许多初学者把Windows API想象成既难懂又难用的“黑匣子”。但实际上，API只是DLL的集合，它们提供了Widows系统的众多功能。API很像一个编译后的工具箱，用户只需要知道要做什么、从何处得到以及如何得到。

说明　组成API的大量DLL存储在Windows/System的子目录下。其他的DLL也常驻在Windows目录或其他子目录中。

当前多数的API是32位版本（Win32API），它们用32位的代码编写而成的。与16位的API相比，Win32API能处理更多的信息。为了保证向下兼容性，老的程序仍可调用16位形式的API。可以简单地把API想象成带有文档说明的函数，用户可以使用和访问它。

20.2　理解DLL调用规范

调用DLL略显复杂。幸而，DLL有很好的说明文档，并且在Visual Basic中提供了有关说明。为了在用户应用程序中使用DLL，应在代码中调用相应函数之前加以声明。

程序需要告诉Visual Basic如何调用DLL，即需要声明过程及其定义的参数。完成上述步骤后，用户程序便可实际地调用函数。“函数声明”可以使Visual Basic编译器知道如何传递数据到函数（或从函数输出）。Windows API是功能强大且实用的函数集合，但它也非常脆弱。传递参数的类型错误便可能引起程序阻塞、停止，甚至使整个系统崩溃，这常常造成很大危害，特别是没有存盘的时候。

技巧　在使用DLL并试验之前，应保存工作成果。否则，一旦DLL调用出错，可能使系统崩溃并丢失工作成果。

调用Windows API的第一步是，决定调用哪个API函数。确定了函数名之后，将其链接到用户程序。所有这些都是通过声明语句完成的。在声明了API函数之后，可以把它当做Visual Basic自己内部的函数使用。例如，一个API函数的声明语句如下。

Private Declare Function FindWindow Lib"user32.dll"Alias"FindWindowA"_

(ByVal lpClassName As String,ByVal lpWindowName As String) As Long

上面的命令好像很复杂，但实际上非常简单，需要了解的仅仅是DLL的调用规范。对于不同的API函数，其声明语句有几点相似之处。下面，逐个介绍它们，以便理解实际发生的操作。

1.设置API作用范围

API的作用范围与Visual Basic中编写的过程的作用范围类似。可以使用Public关键字，使函数在整个程序中可用。也可以使用Private关键字，使函数仅仅在调用的窗口模块中可用。在上面的声明语句中，Private关键字使该API函数只在本模块中有效。

2.声明DLL

Declare Function语句告诉Visual Basic，将常驻在user32.dll文件中的FindWindow函数与当前应用程序链接起来。Alias（别名）关键字定义函数在DLL中引用时使用的别名。Alias经常用来处理16位API与32位API之间的兼容性。除此之外，还有以下两种情况下使用Alias。

❑当外部过程名与某个关键字重名时。

❑当动态链接库的过程名与同一范围内的公用变量、常数或其他过程的名称相同时。

3.理解参数类型

API函数的参数可以按值和按地址两种方式定义。如果按值传递变量，则使用ByVal关键字。如果是按地址传递，则使用ByRef关键字。这与定义Visual Basic内部通用过程的形参没什么两样。后者保证与C或C++语言兼容，因为多数API函数是用上述语言编写的。

4.使用返回码

返回码用于声明语句的最后设置函数的返回类型。因为无法看到许多API函数的运行状态，于是返回一个代码指示函数执行成功或失败以及其原因，这是非常重要的，因为使用API函数时，用户程序以函数为基础。如果API程序因不详的原因失败，而用户程序又因连续执行而没有检查返回码，则程序无法说明故障点或崩溃的原因。

Windows API与用户函数或Sub过程很相似，但也有所不同。API函数是其他人已经编好的，用户的责任仅在于保证函数声明正确并且传递了正确的变量类型。如果在此掉以轻心，则很容易造成系统崩溃或系统挂起。

20.3　API查看器

在了解了关于DLL的声明基础后，现在再介绍一个快捷方法。Visual Basic带有一个称为API浏览器的实用程序，如图20.1所示。

图　20.1　API浏览器

API浏览器能读取大多数文件，这些文件包含了许多Windows API中使用的函数声明，变量类型声明和常量声明。浏览器可以帮助编程者快速查找要调用的函数并把相应的语句插入到Visual Basic应用程序中。例如，使用API浏览器查找"FindWindow"函数，以及将其声明语句复制到Visual Basic中的操作步骤如下。

（1）从“开始”菜单的Visual Basic程序组中运行API文本浏览器（API Text Viewer）。

（2）在浏览器中，选择“文件”|“加载文本文件”菜单项。

（3）在出现的对话框内，选择"WIN32API"，并单击“打开”按钮，此时API浏览器的“可用项”列表框内会出现API函数名的清单。

（4）保证“API类型”栏内是“声明”，然后在其下方的“键入您要查找的内容的开头几个字母”栏内键入"FindWindow"，这时“可用项”中的API函数清单内容会根据输入的字母不断地变化，通常不用输入全部字符时，要找的函数名就会显示在“可用项”的清单中了。

（5）选择“可用项”列表框内的"FindWindow"项，并在“声明范围”中，选择“私有”项。然后单击右侧的“添加”按钮。此时，API浏览器将自动把"FindWindow"函数的声明语句填入“选定项”列表框内，如图20.2所示。

图　20.2　API浏览器中的"FindWindow"函数

（6）单击右侧的“复制”按钮就可以将声明语句复制到剪贴板中，然后打开Visual Basic的代码窗口，在相应的位置上粘贴即可。最后关闭API浏览器。

20.4　在应用程序中使用API函数

前面，介绍了API的一些基础知识。下面将用几个实例说明在Visual Basic中使用API函数的具体方法。

20.4.1　判断窗体是否已经显示

【实例20.1】调用"FindWindow"API函数，编写一个能够判断窗体是否已经显示的程序。

【实现步骤】操作步骤如下。

（1）新建一个Visual Basic的标准EXE工程。

（2）使用“工程”|“添加窗体”菜单项，添加一个新窗体。

（3）将默认窗体Form1的Caption属性改为“第一个”，将新添加的Form2窗体的Caption属性改为“第二个”。

（4）在Form1上绘制一个按钮Command1，将其Caption属性改为“显示Form2”。

（5）使用API浏览器将"FindWindow"API函数的声明语句复制到剪贴板中。

（6）双击Form1窗体，打开其代码编辑器窗口，在“通用声明区”粘贴"FindWindow"函数的声明语句，如图20.3所示。

图　20.3　“通用声明区”中的"FindWindow"函数声明

（7）继续在Form1的代码窗口中，编写事件过程如下。

01 Public Function IsShow(FormName As String) As Boolean

02 Dim x As Long

03 '调用FindWindow函数

04 x=FindWindow(vbNullString,FormName)

05 If x＜＞0 Then

06 IsShow=True

07 Else

08 IsShow=False

09 End If

10 End Function

11

12 Private Sub Command1_Click()

13 If IsShow("第二个")=False Then

14 Form2.Show

15 Else

16 MsgBox"第二个窗体已经被打开！"

17 End If

18 End Sub

【代码说明】应当给第04行的"FindWindow"函数的第2个参数传递指定窗体的标题，函数返回值如果是0，则表示该指定窗体没有显示，否则该窗体已经显示。

本例中，代码第01～10行特意写了一个IsShow函数，在其内调用了"FindWindow"API函数，这种函数通常被称为包装函数，该包装函数根据"FindWindow"函数的值可以返回True和False两种结果。在Command1_Click事件中调用的就是该包装函数。

【运行效果】当第一次单击Form1中的Command1按钮时，显示Form2窗体；如果不关闭Form2，单击Command1时，就会出现“第二个窗体已经被打开！”的提示，如图20.4所示。

图　20.4　实例20.1运行结果

20.4.2　获取鼠标在屏幕上的位置

【实例20.2】编程实现，每隔一秒钟，读取一次鼠标在整个显示器屏幕上的位置。

【实现步骤】操作步骤如下。

（1）首先，通过查看API手册后，决定使用"GetCursorPos"函数。

（2）其次，启动API浏览器，通过该浏览器复制"GetCursorPos"函数的声明语句。

（3）启动Visual Basic，新建一个标准EXE工程。

（4）使用“工程”|“添加模块”菜单项，添加一个新的模块文件。

（5）打开模块的代码窗口，将"GetCursorPos"函数的声明语句粘贴进去，具体代码如下。

01 Public Type POINTAPI

02 x As Long

03 y As Long

04 End Type

（6）在Form1窗体上绘制一个Timer控件，并将其Interval属性设置为1000。

（7）双击Timer控件，打开代码窗口，编写程序如下。

05 Private Sub Timer1_Timer()

06 Dim p As POINTAPI

07 retnum=GetCursorPos(p) '用变量p获取鼠标的位置

08 Print p.x,p.y

09 End Sub

【代码说明】代码第01～04行定义了一个自定义数据类型POINTAPI，然后第06行声明一个此类型的变量，代码第07行用此变量获取鼠标的位置。

【运行效果】运行程序后，结果如图20.5所示。

图　20.5　获取鼠标屏幕位置的结果

20.4.3　最前窗体（顶层窗体）

【实例20.3】编程实现，将一个窗体设置为最前窗体，即保持在所有窗体之前的窗体。例如，腾讯公司的QQ登录界面，会一直保持在所有窗体的前面。

【实现步骤】编写最前窗体的操作步骤如下。

（1）首先，通过查看API手册后，决定使用"SetWindowPos"函数。

（2）其次，启动API浏览器，通过该浏览器复制"SetWindowPos"函数的声明语句。

（3）启动Visual Basic，新建一个标准EXE工程。

（4）使用“工程”|“添加模块”菜单项，添加一个新的模块文件。

（5）打开模块的代码窗口，将"SetWindowPos"函数的声明语句粘贴进去。

（6）打开Form1窗体的代码窗口，编写如下程序。

01 Private Sub Form_Load()

02 retnum=SetWindowPos(Me.hwnd,-1,600,200,100,100,＆H40)

03 If retnum=0 Then

04 MsgBox"顶层窗口设置失败！",vbOKOnly+vbExclamation

05 End If

06 End Sub

【代码说明】通过API手册可以知道代码第02行SetWindowPos函数的各个参数的意义。代码第03行判断函数执行是否正确。如果不正确则执行第04行提示错误信息。

【运行效果】运行程序后，结果如图20.6所示。

图　20.6　顶层窗口运行结果

注意　Visual Basic不能验证传递到API函数的值是否正确。如果传递了错误的值，函数就将出错，并可能因此导致Visual Basic应用程序停止运行。这样就不得不重新加载和启动应用程序。在测试API过程时千万要小心，一定要经常保存自己的工作。

20.5　小结

本章简单地说明了如何在Visual Basic中调用API函数，用Windows API动态链接库可以使程序如虎添翼，更加专业化，满足编程人员更高的要求。经常使用VB编程的开发人员，更善于使用这些API函数，而初学者也应该学会如何使用它们，以及如何为它们搭配正确的参数。

20.6　本章练习

1.如何打开API查看器？

2.API函数的别名和真名的区别是什么？什么场合使用别名？

3.下列代码的作用是什么？单击"Yes"或"No"按钮，Answer的值分别为多少？

'-----------------------------------mdlCommom.bas----------------------------------------

'声明MessageBox按钮和图标类型

Public Const MB_YESNO=＆H4＆

Public Const MB_ICONQUESTION=＆H20＆

'声明MessageBox函数

Public Declare Function MessageBox Lib"USER32"Alias"MessageBoxA"(ByVal hWnd As Long,ByVal lpText As String,ByVal lpCaption As String,ByVal wType As Long) As Long

'-----------------------------------frmMain.frm----------------------------------------

Private Sub Form_Load()

Dim Answer As Integer

Answer=mdlCommon.MessageBox(Me.hWnd,"确定删除数据库？",_

"删除数据库",mdlCommon.MB_ICONQUESTION Or mdlCommon.MB_YESNO)

End Sub

第21章　编译与制作安装文件

完成了应用程序的编码，并且在Visual Basic的环境中全部测试通过后，接下来的任务就是编译与发布了。用户拿到开发好的软件后，首先接触的就是安装文件。安装文件是给用户的第一印象，如果安装文件出了问题，用户安装不了软件，那么不管软件的功能多强大，最后都会被用户否定。所以制作一个好的安装文件是非常重要的。本章包括以下知识点。

❑介绍程序编译的细节。

❑介绍两个安装软件制作安装文件的方法。

❑学会为自己的程序制作安装文件。

21.1　编译项目

编写完应用程序后，首先应当将其编译，生成可执行文件，让其脱离Visual Basic的集成开发环境。下面用一个实例详细的介绍编译项目的细节。

【实例21.1】编写程序，并试验将其编译成可执行文件。

【实现步骤】操作步骤如下。

（1）新建Visual Basic的标准EXE工程，将其工程名称改为——MyFirstApp。方法为：选择“工程”|“工程1属性”菜单项，在打开的“工程属性”对话框内，找到“工程名称”栏，在其内键入"MyFirstApp"即可。

（2）将默认窗体Form1的Caption属性改为“编译与安装的测试”，并给其Icon属性设置一个图标文件，例如本例中设置的图标文件为
。在窗体上绘制一个按钮Command1，为了简单，只将其Caption属性改为“成功了”，其他属性均取默认值，如图21.1所示。

图　21.1　实例21.1界面设计

（3）编写Command1的事件过程如下。

01 Private Sub Command1_Click()

02 MsgBox"真的成功了！"

03 End Sub

（4）选择“文件”|“生成MyFirstApp.EXE”菜单项，打开如图21.2所示的“生成工程”对话框。

图　21.2　“生成工程”对话框

（5）单击对话框右下角的“选项”按钮，则打开“MyFirstApp-工程属性”对话框，该对话框有两个选项卡，分别为“生成”和“编译”，下面章节将分别介绍它们。

21.2　工程属性的设置

本节主要介绍两种选项卡的设置：“生成”选项卡和“编译”选项卡。两个都是用来设置工程的属性，是一种全局设置的选项卡。

21.2.1　“生成”选项卡设置

“生成”选项卡，如图21.3所示，在其内可以修改工程的各种选项，用于决定建立什么样的可执行文件。可以修改的选项有版本号、版本信息和其他信息。“生成”选项卡分成4个部分：版本号、应用程序、版本信息、命令行和条件编译参数文本区。下面分别介绍这4个部分。

图　21.3　“工程属性”对话框“生成”选项卡

1.版本号

版本号部分包含3个文本框和一个复选框。在这3个文本框内分别设置主版本号、次版本号和修订号。当为应用程序设置版本号时，通常都是从版本1.0.0开始——表示第一次生成的第一个版本。如果选择了“自动升级”复选框，则每次运行工程的“生成工程”菜单项时，“修正”文本框中的值便自动加1。

包含在项目中的版本号和修订号是很重要的。使用其可以识别可执行文件是不是最新版本。这在当用户寻求帮助时是非常重要的。

另外，选取“自动升级”复选框的同时，应当将程序的每次修改过程都记录下来。甚至可以将修改说明存储在数据库中，并按版本号和修订号进行排序，这样对以后需要为产品提供技术支持时将非常有用。

2.应用程序

应用程序部分包含“标题”和“图标”两个框。“标题”包含应用程序名，在本例中键入文本"MyFirstApp"。“图标”允许从工程中已有的图标中，选择可执行文件的图标，在此就将Form1窗体的图标设置为可执行文件的图标。

3.版本信息

版本信息部分的操作方法为：先选择左侧“类型”列表框中的信息类型，其次将该信息类型的注明键入右侧的“值”文本框中。通过它们可以在程序中嵌入法律说明和公司信息。在“类型”中选择“产品名称”，在右侧的“值”中键入"ABCD"。

技巧　保证在应用程序中包含版权注意事项，它会告诉使用者，本程序版权的合法拥有者。

4.命令行参数

为使应用程序执行某种特殊的功能，可以在命令行参数后的文本框内输入值。例如，有些程序允许在程序名后指定文件名，程序将自动装入所指定的文件。本例中不对其进行设置。

5.条件编译参数

利用条件编译可使Visual Basic只编译程序中特定的语句，通常用于将一个应用程序编译并运行在多种平台上的情况下，或者生成最终版本并希望删除程序内的所有调试语句之时。例如，

01 ......

02 #If debugVar Then

03 MsgBox"内存溢出" '调试语句

04 #End If

05 ......

当在生成最终可执行文件时，由于不再需要程序中大量的调试语句，因此在条件编译参数后面的文本框中键入：

debugVar=0

即可。当程序编译时会根据条件编译参数的值debugVar选择性的编译程序内的语句。如果debugVar为真，则编译其内的语句，而如果为假，则跳过这段语句不进行编译。这里给debugVar赋值为0等价于赋值为假。

21.2.2　“编译”选项卡设置

“编译”选项卡包含编译应用程序时可用的各种选项，如图21.4所示。

图　21.4　“工程属性”对话框“编译”选项卡

选项卡内各项的解释如下。

1.编译为P-代码

使用本选项时，Visual Basic把应用程序编译成一种解释性的语言，称为伪代码。Visual Basic以前的版本都采用这种方案，以伪代码（P-CODE）编译的程序在运行时需要一个运行时解释器，例如VBRUN300.DLL或VBRUN500.DLL，伪代码会使程序的可执行文件小一些，但它必须提供解释器的动态链接库和其他组建。另外，每条命令在执行之前必须先解释成机器代码。例如，如果运行一个伪代码的应用程序，则运行模块VBRUN500.DLL将逐行解释伪代码。如果这些代码能被识别，则可将其编译成机器代码并执行它。执行结果值将再转化为伪代码，以便下一条命令执行。这意味着，每行代码在执行前需进行双重转换，即它需被转换回去，而本机代码在运行时不需要任何转换。

但这并不能证明伪代码一无是处，它在应用程序接口及与处理器无关的方面工作的很好。以数据库引擎为基础的数据库程序能很好地使用伪代码。简单的实用程序，例如数据转换实用程序，被编译成伪代码时工作得也很好。另外，编译成伪代码所花费的时间很短，所以如果匆忙地开发出应用程序，且执行速度不是关键因素，则可将其编译成伪代码。

2.编译为本机代码

选择本选项，则Visual Basic会把用户程序编译成机器代码，这样程序运行时就不需要上面提到的运行解释器了。如果选择本选项编译应用程序，则可以选择下列编译选项。

❑代码速度优化：通过指示编译器在速度和大小之间首选速度，使.exe和.dll文件的速度最快。编译器可以减少许多功能上跟机器码序列相同的构造。在某些情况下，这种不同提供了用大小来换取速度的权衡交换。

❑代码大小优化：通过指示编译器在速度和大小之间首选大小，来使.exe和.dll文件最小。编译器可以减少许多功能上跟机器码序列相同的构造。如果不选中这个选项，代码可能就会大一些，但是速度会更快一些。

❑无优化：无优化地编译。如果选择了该项，程序的编译时间将会缩短一些。

❑针对Pentium Pro(TM)优化：优化所创建的代码，优化方向是使之更有利于Pentium Pro(TM)处理器来处理。如果在程序中使用了这个选项，则意味着仅仅是为Pentium Pro(TM)处理器所创建的代码。用此选项产生的代码仍然可以在更早期的处理器上运行，但是工作起来性能不是很好。

❑创建符号化调试信息：在可执行文件或者DLL文件中产生符号调试信息。用此选项创建的可执行文件可以用Visual C++或者具有CodeView风格调试信息的调试器来调试。设置这个选项将为可执行文件产生一个带有符号信息的.pdb文件。

❑高级优化：显示“高级优化”对话框。

3.DLL数据地址

给程序设置一个基地址，从而覆盖.dll文件的默认位置（在0 X 10,000,000处）。操作系统首先试图在指定的或者默认的位置加载一个程序。如果没有足够空间，那么系统将重新定位该程序。

现在，已经了解所有可用的编译器选项，可以使应用程序更适合自己的要求了。对大多数应用程序，需要设置“生成”选项卡的选项。可能直到应用程序需要测试和调试时，才需要设置“编译”选项卡中的选项。

在设置完应用程序所需的所有参数后，单击“确定”按钮关闭“工程属性”对话框，回到“生成工程”对话框上，确认“文件名”内的文本为"MyFirstApp"后单击“确定”按钮建立可执行文件。

21.3　使用Package＆Deployment Wizard制作安装文件

将应用程序编译成可执行文件（EXE）或ActiveX控件后，便可以脱离Visual Basic的集成开发环境，像其他可执行文件那样，用鼠标双击其图标时能够独立的执行。接下来的工作是把这些文件和应用程序的辅助文件放到安装包里制作安装文件。

21.3.1　启动Package＆Deployment Wizard

Visual Basic为用户提供了一个制作安装包的工具——Package＆Deployment Wizard。如果完整地安装了Visual Basic，则从“开始”菜单内的Visual Basic工具集中可以找到打开Package＆Deployment Wizard的菜单项。如果“开始”菜单中没有该项，可以打开Visual Basic的安装光盘，并从"COMMON\TOOLS\VB\PDWIZARD"路径下找到该工具的安装文件，并将其安装到机器上。

启动向导后，首先会出如图21.5所示的对话框。使用其可以指定需要制作安装文件的Visual Basic工程文件，还可以使用其选择安装程序的生成方式（包括软盘安装、硬盘安装、Internet下载的安装和脚本化的安装）。

图　21.5　“打包和展开向导”对话框

21.3.2　制作安装文件

接下来将制作前面创建的MyFirstApp工程的安装文件。操作步骤如下。

（1）如果Visual Basic是打开的，首先将其关闭。然后启动Package＆Deployment Wizard。

（2）单击图21.5中的“浏览”按钮，选择保存好的MyFirstApp的工程文件（MyFirstApp.vbp）。

（3）单击“打包”按钮，进行打包，这时会显示如图21.6所示的“包类型”对话框。在“包类型”列表内选择“标准安装包”，并单击“下一步”按钮。

图　21.6　“打包和展开向导-包类型”对话框

（4）在出现的“打包文件夹”对话框内，选择好将要建立的安装文件包的保存位置，如图21.7所示。单击“下一步”按钮。

图　21.7　“打包和展开向导-打包文件夹”对话框

（5）接下来，出现的是“包含文件”对话框，在其内列出了需要放入安装包内的所有文件列表，如图21.8所示。如果该列表中没有列出程序需要的附加文件时，必须使用“添加”按钮，将这些附加文件添加到列表中，例如帮助文件、数据库文件等。

图　21.8　“打包和展开向导-包含文件”对话框

（6）单击“下一步”按钮后，出现“压缩文件选项”对话框，如图21.9所示。在其内可以设置将安装文件压缩为一个整体文件，还是压缩成大小相同的多个文件块。如果要建立软盘安装文件，考虑到软盘容量的限制应当使用“多个压缩文件”选项。

图　21.9　“打包和展开向导-压缩文件选项”对话框

（7）再单击“下一步”按钮，出现“安装程序标题”对话框，如图21.10所示。在其内可以设置安装程序的标题。

图　21.10　“打包和展开向导-安装程序标题”对话框

（8）单击“下一步”按钮，会出现如图21.11所示的“启动菜单项”对话框，通过该对话框可以改变应用程序在开始菜单中的位置和在菜单中显示的文字。

图　21.11　“打包和展开向导-启动菜单项”对话框

（9）再次单击“下一步”按钮，图21.12显示了“安装位置”对话框，在此可以指定当用户运行安装文件时，将安装程序的源文件安装（复制）到硬盘的哪些位置。其中，App.Path代表用户指定的安装位置；WinSysPath代表用户计算机Windows\system32文件夹等。

图　21.12　“打包和展开向导-安装位置”对话框

（10）设置好各项内容后，单击“下一步”按钮，图21.13显示了“共享文件”对话框，使用其可以将安装文件包中的源文件设置为共享文件，这对于发布ActiveX控件很有用，因为ActiveX控件就是为了让其他程序调用的。

图　21.13　“打包和展开向导-共享文件”对话框

（11）单击“下一步”按钮，会出现如图21.14所示的“已完成”对话框，在其内可以指定安装脚本，该脚本记录了所有的设置选项。因为Package＆Deployment Wizard将在下一次生成安装程序时，去寻找这个脚本，所以该脚本是很重要的。

图　21.14　“打包和展开向导-已完成”对话框

（12）最后单击“完成”按钮，完成建立安装包的过程。向导程序将把所需要的组件全部放进一个程序包中。然后将这个包发布给用户就可以了。

全部完成后，向导程序将显示一个报告，说明向导程序做了哪些工作，以及安装程序的更新信息等，如图21.15所示。

图　21.15　“打包报告”对话框

虽然Package＆Deployment Wizard可以制作安装文件，但是其功能却很少，就连安装文件中的画面都修改不了，所以并不能满足开发人员制作完美的安装文件的要求。

目前，制作安装文件的工具有很多，最强大的专业级工具要数Install Shield。但是Install Shield功能虽然强大，但其体积过于庞大，而且用其制作安装文件时需要一些专业知识，这对于初学者很不方便，因此，下面将给读者介绍一种操作简单且功能强大的制作安装文件的工具——Installer VISE。

21.4　使用Installer VISE制作安装文件

Installer VISE是一个在Mac机上非常有名的安装程序制作软件，现在已经移植到PC平台上，具有易学易用、功能强大的特点。比如有可以随意修改安装画面，提供手工和自动化的安装程序制作功能，自动检测DirectX 6并执行安装，可以将多个安装程序打包放在一个可执行文件中，自动生成序列号等功能。由于Installer VISE的功能很多，本书的篇幅又非常有限，因此本节只介绍Installer VISE的最基本的功能。

21.4.1　安装Installer VISE

Installer VISE可以从Internet上直接搜索其安装文件并下载安装。需要注意的是软件版本有两种，一种是运行在Windows操作系统上的安装文件，另一种是运行在Mac操作系统上的安装文件。提供链接下载的中文网站内基本上都是支持Windows的版本。

获得Installer VISE的安装文件之后，就可以进行安装了。双击运行安装文件后，就可以看到安装向导的第一屏，如图21.16所示。

图　21.16　安装第一屏

等待半秒钟后，安装向导自动显示第二屏，如图21.17所示，根据安装向导的提示可以完成Installer VISE的安装，由于过程并不复杂，所以在此不再详述。

图　21.17　安装第二屏

21.4.2　启动Installer VISE

安装好Installer VISE之后，打开“开始”菜单，并选择“程序”|"Installer VISE 3.6.1"|"Installer VISE"菜单项后即可启动该软件。启动后即可看到其主界面和欢迎屏幕如图21.18所示。

图　21.18　Installer VISE主界面

21.4.3　制作安装文件

下面用Installer VISE给本书16.2.4节建立的MyPlayer播放器制作一个安装文件。具体步骤如下。

1.添加所有源文件

建立安装文件的第一步是将所有需要添加到安装包内的文件找出来。这部分工作由Installer VISE做一部分，开发发布人员做一部分。具体如下。

（1）关掉图21.18中的“欢迎屏幕”。

（2）选择"Tools"（工具）菜单下的"Visual Basic Project Wizard"（Visual Basic工程向导），会打开"Visual Basic Project Wizard"对话框，如图21.19所示。

图　21.19　"Visual Basic Project Wizard"对话框

（3）在Product栏内输入产品的名称，例如在其内键入"MyPlayer"，即将产品命名为"MyPlayer"。

（4）在Defalut栏内设置默认安装目录，其中变量"%ProgramFilesDir%"代表将默认安装目录设置为用户计算机的"ProgramFiles"文件夹，本例暂时不对其进行更改。

技巧　上面在设置目录时，不应当直接写为C:\Program-Files，因为，有些用户可能将操作系统安装在D盘或者其他盘内，这时其ProgramFiles目录的全址可能是D:\ProgramFiles。

（5）在Visual Basic Project栏内应当输入将要给其制作安装包的Visual Basic应用程序的工程文件。例如，本例中通过其后的"Browse"（浏览）按钮，将MyPlayer应用程序的工程文件（.vbp）选中，此时，其后的"Scan Project"（扫描工程）按钮，从不可用变为可用。

（6）单击"Scan Project"按钮，等待1～2秒钟后，会在Required files（需要的文件）列表内出现一些带有复选框的文件列表项，通常不用更改其值。

（7）单击"OK"按钮，关闭"Visual Basic Project Wizard"对话框。主界面内会出现将要添加到安装包内的源文件清单，如图21.20所示。

图　21.20　扫描后的源文件清单

（8）查看源文件清单后发现缺少一部分应用程序执行时需要的文件。例如，缺少可执行文件MyPlayer.exe、数据库文件MyLike.mdb和帮助文件的3个文件（.cnt、.rtf、.hlp）。所以这几个文件要由开发发布人员手动添加。

（9）在源文件清单列表区域空白处单击鼠标右键，在弹出的快捷菜单内选择"Add"|"Files"菜单项。

（10）在如图21.21所示的"Add Files"（添加文件）对话框内，从左上侧的"File and folder names"列表框中按住Ctrl键的同时选取需要添加的多个文件，并单击"Add"按钮，将选中的文件列表添加到对话框下面的"Items to add"列表框内，最后单击"OK"按钮退出对话框。

图　21.21　"Add Files"（添加文件）对话框

（11）回到主界面中，会发现刚才添加的文件已经出现在源文件清单内了。

2.添加桌面快捷方式

当用户安装软件后，会在桌面上出现启动应用程序的快捷方式，所以，在制作安装包时应当将这类快捷方式设置好。

（1）首先，使用上面的方法添加一个图标文件到文件清单内，为快捷方式准备图标。

（2）在主界面空白处单击右键，并选择"Add"|"Shorcut"菜单项，这时会出现"Shortcut Properties"（快捷方式属性）对话框，如图21.22所示。

图　21.22　"Shortcut Properties"对话框（"General"选项卡）

（3）在Name（名称）栏内输入快捷方式的名称，本例中输入"MyPlayer"。

（4）在Target file（目标文件）栏内设置快捷方式的目标文件，即双击快捷方式的时候要调用的具体文件。单击下拉列表后带有两个点的按钮
，打开"Select File from Archive"(从文档中选择文件)对话框，从列表内选择目标文件“工程1.exe”，并单击"OK"按钮，如图21.23所示。

图　21.23　"Select File from Archive"对话框

说明　在"Select File from Archive"对话框列表内的文件清单就是主界面上的文件清单。

（5）通过Icon（图标）栏设置快捷方式的图标。使用
按钮，选取第一步添加到文件清单内的图标文件。

（6）选择"Shortcut Properties"对话框的"Advanced"（高级）选项卡，在Install栏内选择"Desktop Folder"（桌面文件夹）项，如图21.24所示。

图　21.24　"Advanced"选项卡

（7）关闭对话框，回到主界面后文件清单如图21.25所示，清单最下面便是刚才添加的快捷方式。

图　21.25　主界面文件清单

3.设置安装程序的界面和默认安装路径

安装程序本身也应当有一个特色的界面，这样既可以宣传软件本身，又会让用户在安装的过程中感觉很舒服。设置步骤如下。

（1）选择"Screens"（屏幕）菜单，其内的每个菜单项都对应一个安装过程中的窗口界面，菜单项前有钩（√）则表明该窗口界面将在安装过程中出现，如果没钩则表明该窗口界面不会出现。对于出现的窗口可以设置其画面和内容。

（2）选择"Screens"菜单下的"Welcome Screen"（欢迎屏幕）菜单项。打开如图21.26所示的"Welcome Screen"对话框。

图　21.26　"Welcome Screen"对话框

说明　如果不选择对话框左上角的"Show this dial"（显示该对话框）复选框，则该窗口界面在安装过程中不会出现。

（3）编写欢迎屏幕中显示的文字内容。首先，取消选择"Welcome Screen"对话框右上角的"Use default text"复选框，文字区域就会从灰色变成白色，在这种状态下就可以编写自己的欢迎词了。其实通常这里的文字都不用更改，因为这段文字已经成为标准了。当然，制作中文安装文件时应当将这段文字翻译成汉语。

注意　编写完欢迎词后，千万不要选择"Use default text"复选框，这会让你刚才的工作付诸东流。

（4）使用对话框中图片下方的"Set"按钮，可以改变该窗口界面中显示的图片。设置完窗口中显示的文字和图片后单击"OK"按钮关闭该对话框。

（5）选择"Screens"菜单下的"Select Install Directory"菜单项，将会打开"Select Install Directory"对话框。该对话框窗口界面的修改与前面介绍的方法相同。特殊的一点是在这个对话框中可以设置默认安装路径。例如要将默认安装路径设置为系统的Program Files下的MyPlayer文件夹时，应当在对话框底部的"Default destination directory（win95"栏内输入"%ProgramFilesDir%\MyPlayer\"，如图21.27所示。

图　21.27　"Select Install Directory"对话框

"Screens"菜单下的其他窗口的设置方法大致都与上述方法相同，这里不再重复。

4.建立安装包

在完成前面介绍的内容之后，接下来就可以建立安装包了。

（1）选择"File"|"Installer Properties"（安装程序属性）菜单项，打开如图21.28所示的"Installer Properties"对话框，在其Product栏内输入“MyPlayer播放器”，其他选项均取默认值。

图　21.28　"Installer Properties"对话框（"General"选项卡）

（2）单击“确定”按钮，关闭对话框。

（3）选择"File"|"Build Installer"（建立安装程序）菜单项，会出现"Save Installer As"（保存安装程序为）对话框，通过该对话框将安装包保存起来。这就是最后要发布给用户的安装文件。

21.4.4　发布安装文件

不论用哪种工具建立的安装包，最后都要发布给用户。发布之前应该有两项工作必须完成，如下。

❑检测安装程序是否被病毒感染。这点很重要，如果安装程序内有病毒，所有的杀毒软件将会提示用户删除它，这会让多数用户放弃安装软件。事实上，即使杀毒软件查不出来病毒，对用户负责也是开发发布人员最起码的职业道德。

❑在不同的机器、不同版本的操作系统上进行安装测试，还要特别注意测试的机器上绝对不能安装Visual Basic。

完成了上述两项检查工作后，具体的发布方法可以是制作安装光盘，即把安装文件包刻录到光盘上，或者也可以采用直接上传到Internet上的具体网站，然后让用户直接登录该网站下载。

21.5　小结

本章介绍了两种制作安装文件的方法：Package＆Deployment Wizard和Installer VISE，第一种方式是VB语言默认安装时自带的安装向导工具；第二种方式是目前比较流行的安装工具，从Mac操作系统上移植过来。不管读者使用哪一种工具，都可以方便地制作安装程序。如果为了方便，建议大家使用VB语言自带的安装向导。

21.6　本章练习

1.Installer VISE安装和Package＆Deployment Wizard的区别?

2.分别用两个工具制作一个安装文件。

第22章　学生考试系统

学生考试系统是管理信息系统（MIS）的一种，使用计算机实现考试过程。该系统还能够自动评阅试卷，保存学生答题结果和学生考试成绩的查询。本章包括以下知识点。

❑ADODB对象在数据库操作中的应用。

❑使用SQL查询实现系统登录。

❑通过数据绑定技术显示试题，简化数据库操作。

❑Adodc控件的Move方法的使用。

通过本章的学习，读者可以综合使用ADODB对象、SQL查询和数据绑定等技术，快速实现小型信息管理系统的开发。熟练使用这些重要技术，也有助于实现中大型管理系统的开发。

22.1　程序功能简介

学生考试系统主要用在考试比较频繁的学校。尤其在高校，很多任课教师同时教授不同的课程和多个班级，每次考试都要命题、印刷试卷、评阅试卷和成绩录入计算机等烦琐的操作。计算机技术的普及和发展改善了传统的卷面考试的烦琐和不足。学生考试系统可以改善考试的过程，节省考试的准备时间和纸张资源。

教师只要事先将题目录入数据库。考试时，只要学生登录考试系统，就能直接在计算机上完成考试过程。提交试卷后，系统能够自动评阅试卷，节省教师阅卷的环节，也避免了阅卷过程的偶然错误，保证成绩的公平公正。而且，学生能够很快获知考试成绩。同时，系统能够准确地记录学生的每题答案和得分，并能够将学生的答题记录和成绩保存到数据库中，方便查询和保存。

本章所要介绍的学生考试系统主要包含学生登录、注销登录、开始考试和成绩查询等功能。学生信息被保存在数据库中，具有考试资格的考生只要登录考试系统就能打开试卷。试卷的试题是教师事先准备和按照一定的格式录入数据库的。本章学生考试系统只提供单选试题，其余类型的客观题的处理方式与此类似。学生答题完毕就提交试卷。提交时，系统会给出提示，一旦确定提交，考试答案将被正式录入考试系统的数据库，不能进行修改。提交试卷以后，学生可以查询自己的得分，以及每道试题的正确答案和自己的试题答案。考试成绩将被保存到数据库中。

22.2　设计思路和结构

登录学生考试系统时，使用SQL方法查询数据库中符合条件的学生信息。如果用户名和密码正确，就登录该系统，并能够进行考试。反之，则不能登录系统。学生信息表中已经存储了学生信息。学生登录以后，就可以开始参加考试。考试试题也存储在数据库中，这些试题通过Adodc控件绑定到答题界面的相应控件中显示给考生。学生可以通过单击选择按钮来试题作答。答题完毕后，提交试卷，系统将学生答题记录保存起来，并自动评阅试卷。试卷一旦提交将无法修改答案，提交之前，系统会给出提示信息。学生提交试卷之后，就可以查询考试成绩。系统将详细显示每道试题、试题的正确答案、学生选择的答案、试题分值、卷面总分以及学生得分等信息。

学生考试系统主要包括主界面、登录界面、答题界面和成绩查询界面。主界面是一个MDI窗体，其菜单栏包含系统登录、注销登录、退出系统、开始答题和查询成绩等操作。答题界面和成绩查询界面的BorderStyle属性为0，即两个窗体没有边框。同时，这两个窗体的MDIChild属性值为True，即为主界面的子窗体。表22.1为学生考试系统程序的主要对象和属性。

22.3　程序设计步骤

程序设计分为5个模块进行，分别为主界面程序、SQL查询模块程序、系统登录模块程序、答题模块程序和成绩查询模块程序。

22.3.1　主界面程序

【实例22.1】主界面程序主要负责进入系统登录界面、系统注销、进入答题界面和成绩查询界面等操作。参考代码如下所示。

'----------------------------------MDIMain.frm----------------------------------

01 Option Explicit

02

03 Private Sub MDIForm_Load()

04 '初始化登录变量

05 blnLogin=True

06 End Sub

07

08 Private Sub MDIForm_Unload(Cancel As Integer)

09 End

10 End Sub

11

12 '考试成绩查询

13 Private Sub mnuExamScoreFind_Click()

14 '声明ADODB.Recordset对象变量

15 Dim objRecordset As ADODB.Recordset

16 '声明SQL字符串

17 Dim strSQL As String

18

19 '查找用户名是否已经考过

20 strSQL="select*from Students where StudentID='"＆strStudentID＆"'"

21 '获得符合输入的用户记录集

22 Set objRecordset=ExecuteSQL(strSQL)

23 '如果得分不为-1，表明已经考过

24 If objRecordset.Fields(3)=-1 Then

25 MsgBox"该学生尚未参加考试",vbExclamation,"操作无法完成"

26 Exit Sub

27 End If

28

29 '释放对象

30 Set objRecordset=Nothing

31

32 '显示成绩窗体

33 frmExamResult.Show

34 End Sub

35

36 '开始考试

37 Private Sub mnuExamStart_Click()

38 '声明ADODB.Recordset对象变量

39 Dim objRecordset As ADODB.Recordset

40 '声明SQL字符串

41 Dim strSQL As String

42

43 '查找用户名是否已经考过

44 strSQL="select*from Students where UserName='"＆UserName＆"'"

45 '获得符合输入的用户记录集

46 Set objRecordset=ExecuteSQL(strSQL)

47 '如果得分不为-1，表明已经考过

48 If objRecordset.Fields(3)＜＞-1 Then

49 MsgBox"试卷已经提交，请选择查询成绩",vbExclamation,"操作无法完成"

50 Exit Sub

51 End If

52 '释放对象

53 Set objRecordset=Nothing

54

55 '显示答题窗体

56 frmAnswer.Show

57 End Sub

58

59 Private Sub mtExit_Click()

60 End

61 End Sub

62

63 '学生登录

64 Private Sub mnuSysLogin_Click()

65 frmLogin.Show

66

67 If blnLogin=True Then

68 mnuSysLogin.Enabled=False

69 mnuSysLogout.Enabled=True

70 mnuExamManage.Visible=True

71 End If

72 End Sub

73

74 '退出登录

75 Private Sub mnuSysLogout_Click()

76 '初始化登录变量

77 blnLogin=True

78

79 mnuSysLogin.Enabled=True

80 mnuSysLogout.Enabled=False

81 mnuExamManage.Visible=False

82 End Sub

【代码说明】代码第05行初始化登录变量，默认是已经登录。代码第14～30行判断用户名是否已经考过。代码第43～53行也是判断用户名是否已经考过。两个判断后都打开的是不同的窗体。代码第75～82行注销登录用户。

说明　使用ADODB对象之前，要先添加其引用。从“项目”菜单中选择“引用”子菜单项，从打开的“引用”对话框中选择"Microsoft ActiveX Data Objects 2.5 Library"。否则，程序运行时，系统会产生编译错误。

22.3.2　SQL查询模块

【实例22.2】SQL查询模块程序主要用于声明一些全局变量和实现SQL查询等数据库操作。该模块的主体部分为一个执行SQL查询的全局函数ExecuteSQL。通过该函数确认学生的登录名和密码，以及其他类似的查询功能。参考代码如下所示。

'----------------------------------mdlSQL.bas----------------------------------

01 Option Explicit

02

03 Public strSQL As String

04 Public UserName As String

05

06 Public strStudentID As String

07 Public blnLogin As Boolean

08 Public varAnswer()

09 Public sngSum As Single

10

11 Public Function ExecuteSQL(ByVal strSQL As String) As ADODB.Recordset

12 On Error GoTo ErrorTrap

13

14 '声明ADODB.Connection对象变量

15 Dim objConnection As ADODB.Connection

16 '声明ADODB.Recordset对象变量

17 Dim objRecordset As ADODB.Recordset

18 '声明一个存放SQL查询条件的数组

19 Dim strArray() As String

20

21 '定义新连接

22 Set objConnection=New ADODB.Connection

23 '创建新连接

24 objConnection.ConnectionString=ConnectString

25 '打开数据库

26 objConnection.Open

27

28 'Split函数返回一个下标从零开始的一维数组，包含指定数目的子字符串

29 strArray=Split(strSQL)

30 If InStr("INSER,DELETE,UPDATE",UCase(strArray(0))) Then

31 '执行SQL查询

32 objConnection.Execute strSQL

33 Else

34 '打开记录集

35 Set objRecordset=New ADODB.Recordset

36 objRecordset.Open Trim(strSQL),objConnection,adOpenKeyset,adLockOptimistic

37 Set ExecuteSQL=objRecordset

38 End If

39

40 '释放对象

41 Set objRecordset=Nothing

42 Set objConnection=Nothing

43 Exit Function

44

45 '出错处理

46 ErrorTrap:

47 Set objRecordset=Nothing

48 Set objConnection=Nothing

49 End Function

50

51 '连接字符串赋值函数

52 Public Function ConnectString() As String

53 ConnectString="provider=Microsoft.Jet.OLEDB.4.0;Data source="_

54＆App.Path＆"/ExaminationSystem.mdb"

55 End Function

【代码说明】代码第11～49行定义了一个函数ExecuteSQL，其参数是一个符合T-SQL标准的字符串。代码第52～54行是获取数据库连接字符串的函数。这里数据访问模式是"Microsoft.Jet.OLEDB.4.0"。

22.3.3　系统登录模块

【实例22.3】系统登录模块用于判断输入的用户名和密码是否符合数据库中的相关记录。该模块也是正式进入考试环节的先决条件，只有已经将信息录入数据库的考生才具备考试资格。系统登录模块使用SQL查询用户名和密码。查询结果是由SQL查询模块中的函数返回的。参考代码如下所示。

'----------------------------------frmLogin.frm----------------------------------

01 Option Explicit

02

03 '登录

04 Private Sub cmdLogin_Click()

05 '声明ADODB.Recordset对象变量

06 Dim objRecordset As ADODB.Recordset

07 '声明SQL字符串

08 Dim strSQL As String

09

10 '查找用户名和密码

11 strSQL="select*from Students where UserName='"＆Trim(txtUserName.Text)＆"'"

12 '获得符合输入的用户记录集

13 Set objRecordset=ExecuteSQL(strSQL)

14 '如果输入的用户不存在或密码不正确

15 If objRecordset.EOF=True Or Trim(txtPassword.Text)＜＞objRecordset.Fields(2) Then

16 MsgBox"用户名错误！",vbExclamation+vbOKOnly,"警告"

17 txtUserName.SetFocus

18 txtUserName.SelStart=0

19 txtUserName.SelLength=Len(txtUserName.Text)

20 Exit Sub

21 End If

22 '从数据库中获得用户名

23 UserName=objRecordset.Fields(1)

24 '从数据库中获得用户学号

25 strStudentID=objRecordset.Fields(0)

26

27 '登录成功

28 blnLogin=True

29

30 '卸载登录窗体

31 Unload Me

32 End Sub

33

34 '退出登录

35 Private Sub cmdQuit_Click()

36 Unload Me

37 End Sub

38

39 '输入用户名

40 Private Sub txtUserName_KeyPress(KeyAscii As Integer)

41 '输入回车表示输入结束，密码框获得焦点

42 If KeyAscii=13 Then

43 txtPassword.SetFocus

44 End If

45 End Sub

46

47 '输入密码

48 Private Sub txtPassword_KeyPress(KeyAscii As Integer)

49 '输入回车表示输入结束，登录按钮获得焦点

50 If KeyAscii=13 Then

51 cmdLogin.SetFocus

52 End If

53 End Sub

【代码说明】代码第13行调用了前面设计好的ExecuteSQL函数，代码第15～21行判断输入的用户或密码是否正确。代码第25行的变量strStudentID保存的是登录用户的学号。代码第42行和第50行都是获取键盘的按键值，13表示用户按下回车键。

【运行效果】学生考试系统的登录界面如图22.1所示。

图　22.1　学生考试系统的登录界面

22.3.4　答题模块

【实例22.4】答题模块使用数据绑定技术显示数据库中的试题，该模块是学生进行考试的界面。学生只要操作该界面上的按钮，就可以进行答题操作。参考代码如下所示。

'----------------------------------frmAnswer.frm----------------------------------

01 Option Explicit

02

03 '移动到第一题

04 Private Sub cmdMoveFirst_Click()

05 Dim i As Integer

06

07 If Not adoQuestions.Recordset.BOF Then

08 adoQuestions.Recordset.MoveFirst

09

10 '显示选项

11 For i=0 To 3

12 optChoice.Item(i).Caption=adoQuestions.Recordset.Fields(i+2)

13 Next

14 End If

15 End Sub

16

17 '移动到最后一题

18 Private Sub cmdMoveLast_Click()

19 Dim i As Integer

20

21 If Not adoQuestions.Recordset.EOF Then

22 adoQuestions.Recordset.MoveLast

23

24 '显示选项

25 For i=0 To 3

26 optChoice.Item(i).Caption=adoQuestions.Recordset.Fields(i+2)

27 Next

28 End If

29 End Sub

30

31 '移动到后一题

32 Private Sub cmdMoveNext_Click()

33 Dim i As Integer

34

35 If Not adoQuestions.Recordset.EOF Then

36 adoQuestions.Recordset.MoveNext

37

38 If Not adoQuestions.Recordset.EOF Then

39 '显示选项

40 For i=0 To 3

41 optChoice.Item(i).Caption=adoQuestions.Recordset.Fields(i+2)

42 Next

43 Else

44 adoQuestions.Recordset.MovePrevious

45 End If

46 End If

47 End Sub

48

49 '移动到前一题

50 Private Sub cmdMovePrev_Click()

51 Dim i As Integer

52

53 If Not adoQuestions.Recordset.BOF Then

54 adoQuestions.Recordset.MovePrevious

55

56 If Not adoQuestions.Recordset.BOF Then

57 '显示选项

58 For i=0 To 3

59 optChoice.Item(i).Caption=adoQuestions.Recordset.Fields(i+2)

60 Next

61 Else

62 adoQuestions.Recordset.MoveNext

63 End If

64 End If

65 End Sub

66

67 '提交试卷

68 Private Sub cmdSubmit_Click()

69 Dim i As Integer

70 '声明ADODB.Recordset对象变量

71 Dim objRecordset As ADODB.Recordset

72 '声明SQL字符串

73 Dim strSQL As String

74

75 '将试卷答案保存到文件中

76 If MsgBox("一旦提交，将无法修改答案。确定提交？",vbYesNo,"提交")=vbYes Then

77 Open App.Path＆"\"＆strStudentID＆"_"＆UserName＆".txt"For Output As #1

78 For i=1 To adoQuestions.Recordset.RecordCount

79 Write #1,varAnswer(i)

80 Next

81 Close #1

82 Else

83 Exit Sub

84 End If

85

86 Me.Hide

87 sngSum=0

88 If Not adoQuestions.Recordset.BOF Then

89 adoQuestions.Recordset.MoveFirst

90 End If

91 '评判试卷

92 For i=1 To adoQuestions.Recordset.RecordCount

93 If varAnswer(i)=adoQuestions.Recordset.Fields(6) Then

94 sngSum=sngSum+adoQuestions.Recordset.Fields(7)

95 End If

96 If Not adoQuestions.Recordset.EOF Then

97 adoQuestions.Recordset.MoveNext

98 End If

99 Next

100

101 '将分数保存到数据库中

102 '查找学号

103 strSQL="select*from Students where StudentID='"＆strStudentID＆"'"

104 '获得符合输入的用户记录集

105 Set objRecordset=ExecuteSQL(strSQL)

106 '从数据库中获得用户名

107 objRecordset.Fields(3)=sngSum

108 objRecordset.Update

109

110 '释放对象

111 Set objRecordset=Nothing

112

113 Unload Me

114 End Sub

115

116 Private Sub Form_Load()

117 Dim i As Integer

118

119 '设置连接的数据库

120 adoQuestions.ConnectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="_

121+App.Path+"\ExaminationSystem.mdb;Persist Security Info=False"

122 adoQuestions.RecordSource="SELECT*FROM Questions"

123 '将数据库中的数据输出到窗体控件中

124 adoQuestions.Refresh

125

126 '重定义答案数组大小

127 ReDim varAnswer(1 To adoQuestions.Recordset.RecordCount)

128

129 '显示选项

130 For i=0 To 3

131 optChoice.Item(i).Caption=adoQuestions.Recordset.Fields(i+2)

132 Next

133 End Sub

134

135 Private Sub optChoice_Click(Index As Integer)

136 varAnswer(adoQuestions.Recordset.Fields(0))=optChoice.Item(Index).Caption

137 End Sub

【代码说明】代码第07～14行从数据库获取第一个考题，并绑定到optChoice控件中。通过第07行的.Recordset.BOF来获得第一行记录。代码第35行通过Recordset.EOF来获取最后一条记录。代码第50～65行是向上翻页，也就是移动到前一题，这已经是第一题，则无法再向前翻页，所以第53行需要根据.Recordset.BOF来判断是否移动到了第一题。代码第68～114用来判断考题并提交试卷。代码第116～132是初始化窗体时需要做的一些事情，包括创建数据库连接等。

【运行效果】学生考试系统的答题界面如图22.2所示。

图　22.2　答题界面

学生提交答题结果之后，系统先将答题结果存储在创建文本文件中，然后评阅试卷，并将学生的成绩直接存储到学生信息数据库中。在未参加考试之前，数据库中成绩字段的值设置为-1，考试结束后，将更新这一结果。同时，该字段可用于判断学生是否参加过该课程的考试。

22.3.5　成绩查询模块

【实例22.5】成绩查询模块在试卷提交之后才能打开，该模块主要用于显示学生的考试结果，同时显示考生的每题答案和正确答案，便于考生对照分析。参考代码如下所示。

'----------------------------------frmExamResult.frm----------------------------------

01 Option Explicit

02

03 '移动到第一题

04 Private Sub cmdMoveFirst_Click()

05 Dim i As Integer

06

07 If Not adoQuestions.Recordset.BOF Then

08 adoQuestions.Recordset.MoveFirst

09

10 '显示本题学生答案和得分

11 lblAnswer.Item(1).Caption=varAnswer(Val(adoQuestions.Recordset.Fields(0)))

12 If varAnswer(Val(adoQuestions.Recordset.Fields(0)))=lblAnswer.Item(0).Caption Then

13 lblScore.Item(1).Caption=lblScore.Item(0).Caption

14 Else

15 lblScore.Item(1).Caption=0

16 End If

17 End If

18 End Sub

19

20 '移动到最后一题

21 Private Sub cmdMoveLast_Click()

22 Dim i As Integer

23

24 If Not adoQuestions.Recordset.EOF Then

25 adoQuestions.Recordset.MoveLast

26

27 '显示本题学生答案和得分

28 lblAnswer.Item(1).Caption=varAnswer(Val(adoQuestions.Recordset.Fields(0)))

29 If varAnswer(Val(adoQuestions.Recordset.Fields(0)))=lblAnswer.Item(0).Caption Then

30 lblScore.Item(1).Caption=lblScore.Item(0).Caption

31 Else

32 lblScore.Item(1).Caption=0

33 End If

34 End If

35 End Sub

36

37 '移动到后一题

38 Private Sub cmdMoveNext_Click()

39 Dim i As Integer

40

41 If Not adoQuestions.Recordset.EOF Then

42 adoQuestions.Recordset.MoveNext

43

44 If Not adoQuestions.Recordset.EOF Then

45 '显示本题学生答案和得分

46 lblAnswer.Item(1).Caption=varAnswer(Val(adoQuestions.Recordset.Fields(0)))

47 If varAnswer(Val(adoQuestions.Recordset.Fields(0)))=lblAnswer.Item(0).Caption Then

48 lblScore.Item(1).Caption=lblScore.Item(0).Caption

49 Else

50 lblScore.Item(1).Caption=0

51 End If

52 Else

53 adoQuestions.Recordset.MovePrevious

54 End If

55 End If

56 End Sub

57

58 '移动到前一题

59 Private Sub cmdMovePrev_Click()

60 Dim i As Integer

6 1

62 If Not adoQuestions.Recordset.BOF Then

63 adoQuestions.Recordset.MovePrevious

64

65 If Not adoQuestions.Recordset.BOF Then

66 '显示本题学生答案和得分

67 lblAnswer.Item(1).Caption=varAnswer(Val(adoQuestions.Recordset.Fields(0)))

68 If varAnswer(Val(adoQuestions.Recordset.Fields(0)))=lblAnswer.Item(0).Caption Then

69 lblScore.Item(1).Caption=lblScore.Item(0).Caption

70 Else

71 lblScore.Item(1).Caption=0

72 End If

73 Else

74 adoQuestions.Recordset.MoveNext

75 End If

76 End If

77 End Sub

78

79 Private Sub cmdOK_Click()

80 Unload Me

81 End Sub

82

83 Private Sub Form_Load()

84 Dim i As Integer

85 Dim sngTotalSum As Single

86 '声明ADODB.Recordset对象变量

87 Dim objRecordset As ADODB.Recordset

88 '声明SQL字符串

89 Dim strSQL As String

90

91 '设置连接的数据库，题库

92 adoQuestions.ConnectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="_

93 +App.Path+"\ExaminationSystem.mdb;Persist Security Info=False"

94 adoQuestions.RecordSource="SELECT*FROM Questions"

95 '将数据库中的数据输出到窗体控件中

96 adoQuestions.Refresh

97

98 '重定义答案数组大小

99 ReDim varAnswer(1 To adoQuestions.Recordset.RecordCount)

100

101 '计算试卷总分

102 sngTotalSum=0

103 For i=1 To adoQuestions.Recordset.RecordCount

104 sngTotalSum=sngTotalSum+adoQuestions.Recordset.Fields(7)

105 adoQuestions.Recordset.MoveNext

106 Next

107 adoQuestions.Recordset.MoveFirst

108 lblTotalScores.Item(0)=sngTotalSum

109

110 '重定义答案数组大小

111 ReDim varAnswer(1 To adoQuestions.Recordset.RecordCount)

112

113 '读取学生答案

114 Open App.Path＆"\"＆strStudentID＆"_"＆UserName＆".txt"For Input As #1

115 For i=1 To adoQuestions.Recordset.RecordCount

116 Input #1,varAnswer(i)

117 Next

118 Close #1

119 '显示学生答案

120 lblAnswer.Item(1).Caption=varAnswer(1)

121 If varAnswer(1)=lblAnswer.Item(0).Caption Then

122 lblScore.Item(1).Caption=lblScore.Item(0).Caption

123 Else

124 lblScore.Item(1).Caption=0

125 End If

126

127 '查找学生总分

128 strSQL="select*from Students where StudentID='"＆strStudentID＆"'"

129 '获得的用户记录集

130 Set objRecordset=ExecuteSQL(strSQL)

131 '显示学生总分

132 lblTotalScores.Item(1).Caption=objRecordset.Fields(3)

133 End Sub

【代码说明】成绩查询模块与答题模块的某些功能是相似的。例如，试题的显示也是通过Adodc控件的数据绑定技术来实现的。此外，试题的浏览方式与答题模块相似。不同的是，该模块读取试卷提交程序中所保存的学生答题结果，并将其显示在相应的控件中。

【运行效果】学生考试系统的成绩查询界面如图22.3所示。

图　22.3　成绩查询界面

22.4　本章常见问题

1.为什么运行程序的时候编译错误，系统会提示ADODB对象所在的语句行“用户定义类型未定义”？

因为在使用ADODB对象之前，没有添加其引用。应该从“引用”对话框中选择"Microsoft ActiveX Data Objects 2.5 Library"。否则，程序运行的时候，就会产生上述的编译错误。

2.如何使用ADODB.Recordset绑定DataGrid控件？

先声明ADODB.Recordset对象变量，然后打开数据库，最后将该记录集对象变量绑定到DataGrid控件即可。参考代码如下所示。

01 '声明ADODB.Connection对象变量

02 Dim objConnection As ADODB.Connection

03 '声明ADODB.Recordset对象变量

04 Dim objRecordset As ADODB.Recordset

05

06 '定义新连接

07 Set objConnection=New ADODB.Connection

08 '创建新连接

09 objConnection.ConnectionString="provider=Microsoft.Jet.OLEDB.4.0;Data source="_

10＆App.Path＆"/ExaminationSystem.mdb"

11 '打开数据库

12 objConnection.Open

13

14 '打开记录集

15 Set objRecordset=New ADODB.Recordset

16 objRecordset.Open strSQL,objConnection,adOpenKeyset,adLockOptimistic

17 Set Datagrid1.DataSource=objRecordset

其中，strSQL为要绑定的数据库路径及数据库名，Datagrid1为DataGrid控件。

22.5　小结

本章所介绍的学生考试系统是一个简单的程序，其功能也只有系统登录、答题和成绩查询等。但是，程序中使用的SQL查询、数据库绑定、Adodc控件方法的使用等技术，第23章中将被广泛采用。读者尤其需要注意的SQL查询技术的使用，该技术将简化烦琐的数据库查询操作。可以通过编写SQL查询的全局函数，实现多种条件的查询，其基本的查询结构都是一样的。有了这一章的基础，第23章将介绍学生信息管理系统，该系统比较复杂，实现的功能也较多。

第23章　学生信息管理系统

学生信息管理系统也是管理信息系统MIS的一个常用的和典型的应用实例。该系统集成了很多实用的学生信息管理功能，包括班级管理、档案管理和成绩管理等。本章包括以下知识点。

❑使用ADODB对象进行数据库操作。

❑SQL查询在系统登录查询和其他功能查询中的应用。

❑Adodc控件的在数据库操作中的使用。

❑复杂管理系统的设计方法。

通过本章的学习，读者可以进一步熟悉和掌握SQL查询技术和数据绑定技术的使用，并具备复杂管理信息系统MSI的开发能力。

23.1　程序功能简介

学生信息管理系统是用于管理学生综合信息的应用管理软件。该系统具有系统管理、班级管理、档案管理、课程管理和成绩管理等功能，是典型的管理信息系统MIS。

系统管理包含三个主要功能，分别是添加用户、修改密码和重新登录。其中，添加的用户分为三个权限，即高级用户、普通用户和只读用户。班级管理主要用于实现班级浏览、添加班级和班级查询。学生档案管理主要负责学生档案的添加、浏览和查询。课程管理负责设置不同课程的教材，以及不同专业的各学期课程。成绩管理主要用于设置考试类型、录入学生考试成绩和学生成绩的浏览等功能。

23.2　设计思路和结构

与学生考试系统一样，登录学生信息管理系统时，也使用SQL方法查询登录用户的信息是否合法。如果用户名和密码正确，就登录该系统，并能进行相应权限的操作。反之，则不能登录系统。用户登录信息、班级信息、学生档案、基本课程、班级课程、考试类型和考试成绩等都存储在已经创建好的数据库中。通过不同的界面或操作，来访问或修改这些数据库。

除了管理用户数据库，数据库中的其余数据表分别是学生档案、班级、基本课程、班级课程、考试类型和成绩。各数据表在程序各功能模块之间有着复杂的逻辑关系，通过数据表将各功能联系在一起。本系统的基本设计思路和结构主要分为两条线。第一条是用户的操作，包括档案管理、班级管理、课程管理和成绩管理，可以称为实线。第二条是在用户操作背后的上述数据库管理，可以称为虚线。只有将这两条线结合起来才能实现本系统设计。两条线的结合如下所示。

（1）通过档案管理将学生档案添加到档案数据表中。

（2）通过班级管理将档案数据表中的学生分到各班级中。

（3）通过基本课程设置操作，将所有需要教授的课程及其教材添加到课程数据表中。

（4）通过班级课程设置操作，将班级数据表和基本课程数据表中的数据信息处理后添加到班级课程数据表中。

（5）通过考试类型设置操作，将所遇到的考试类型添加到数据表中。

（6）只有设置了班级课程和考试类型，才能通过成绩添加操作将学生的考试成绩添加到成绩数据表中。

（7）成绩浏览操作将成绩数据表和学生档案数据表结合起来完整地显示学生成绩。

学生信息管理系统由一个MDI窗体作为主界面，除登录界面之外，其余操作界面都设置为子窗体。主窗体菜单栏提供了进入系统管理、班级管理、档案管理、课程管理和成绩管理操作界面的命令。表23.1为学生信息管理系统程序的各窗体对象和属性。

23.3　程序设计步骤

学生信息管理系统程序主要分为7个模块，分别为主界面程序模块、SQL查询模块、系统管理模块、班级管理模块、档案管理模块、课程管理模块和成绩管理模块。各个模块由相应的若干子模块组成。下面分别介绍其程序设计步骤。

23.3.1　主界面程序

【实例23.1】主界面操作程序提供了进入各管理操作界面的相关操作。在进入这些管理操作界面之前，都要判断用户的权限，以进入添加新用户的操作界面为例，参考代码如下所示。

'----------------------------------mdiMain.frm----------------------------------

01 Option Explicit

02

03 Private Declare Function SetWindowPos Lib"user32"(ByVal hwnd As Long,ByVal hWndInsertAfter As

04 Long,ByVal X As Long,ByVal Y As Long,ByVal cx As Long,ByVal cy As Long,ByVal wFlags

05 As Long)As Long

06

07 '添加新用户

08 Private Sub mnuSysAddUser_Click()

09 strUserManage=UserManage(1)

10 If strUserManage="False"Or strUserManage="ReadOnly"Then

11 MsgBox"对不起，你没有管理本功能的权限，请与管理员联系！",vbInformation+vbOKOnly,"警12告"

13 Exit Sub

14 End If

15 '显示添加新用户界面

16 frmAddNewUser.Show

17 frmAddNewUser.ZOrder 0

18 End Sub

【代码说明】代码第09～13行首先判断用户是否具备权限，如果没有，则直接通过第13行退出程序。如果有，则调用第16行代码显示新用户界面。进入其余管理操作界面的程序代码与此类似。

【运行效果】图23.1为学生信息管理系统的主界面。

图　23.1　学生信息管理系统的主界面

23.3.2　SQL查询模块

【实例23.2】SQL查询模块提供了公用的查询函数，用于查询登录用户信息和其他程序模块中涉及的很多信息查询。此外，该模块还提供了用户权限管理程序，该程序也是通过SQL查询实现的。实例23.2用户权限管理的参考代码如下所示。

'----------------------------------mdlSQL.bas----------------------------------

01 Option Explicit

02

03 Public blnModifyDoc As Boolean

04 Public blnModifyClass As Boolean

05 Public strSQL As String

06 Public blnFindDoc As Boolean

07 Public blnFindClass As Boolean

08 Public UserName As String

09 Public strUserManage As String

10

11 '用户权限管理

12 Public Function UserManage(ByVal intUsrClass As Integer) As String

13 On Error GoTo ErrorTrap

14

15 '声明SQL字符串

16 Dim strSQL As String

17 '声明ADODB.Connection对象变量

18 Dim objConnection As ADODB.Connection

19 '声明ADODB.Recordset对象变量

20 Dim objRecordset As ADODB.Recordset

21

22 '定义新连接

23 Set objConnection=New ADODB.Connection

24 '创建新连接

25 objConnection.ConnectionString=ConnectString

26 '打开数据库

27 objConnection.Open

28 '打开记录集

29 Set objRecordset=New ADODB.Recordset

30

31 '查询高级用户

32 strSQL="select Administration from Users where UserName='"＆UserName＆"'"

33 objRecordset.Open Trim(strSQL),objConnection,adOpenKeyset,adLockOptimistic

34 '如果没查询到当前用户

35 If objRecordset.EOF=True Then

36 MsgBox"非法用户！",vbExclamation+vbOKOnly,"警告"

37 UserManage="nothing"

38 Exit Function

39 End If

40 '高级用户

41 If objRecordset.Fields(0)="Y"Then

42 UserManage="Administration"

43 Exit Function

44 End If

45 '关闭记录集

46 objRecordset.Close

47

48 '查询只读用户

49 strSQL="select ReadOnly from Users where UserName='"＆UserName＆"'"

50 objRecordset.Open Trim(strSQL),objConnection,adOpenKeyset,adLockOptimistic

51 '只读用户

52 If objRecordset.Fields(0)="Y"Then

53 UserManage="ReadOnly"

54 Exit Function

55 End If

56

57 '普通用户

58 Select Case intUsrClass

59 '权限

60 Case 1

61 strSQL="select Weight1 from Users where UserName='"＆UserName＆"'"

62 Case 2

63 strSQL="select Weight2 from Users where UserName='"＆UserName＆"'"

64 Case 3

65 strSQL="select Weight3 from Users where UserName='"＆UserName＆"'"

66 Case 4

67 strSQL="select Weight4 from Users where UserName='"＆UserName＆"'"

68 End Select

69

70 '打开记录集

71 Set objRecordset=New ADODB.Recordset

72 objRecordset.Open Trim(strSQL),objConnection,adOpenKeyset,adLockOptimistic

73 '权限

74 If objRecordset.Fields(0)="Y"Then

75 UserManage="True"

76 Else

77 UserManage="False"

78 End If

79

80 '释放对象

81 Set objRecordset=Nothing

82 Set objConnection=Nothing

83 Exit Function

84

85 '出错处理

86 ErrorTrap:

87 Set objRecordset=Nothing

88 Set objConnection=Nothing

89 End Function

注意　创建的对象变量在使用结束后，要及时释放，否则会占用内存空间。

【代码说明】用于查询登录信息等功能的全局函数与第22章中的ExecuteSQL函数是相似的，只是查询语句中的条件变换一下。因此，ExecuteSQL函数在上述程序中省略了。代码第23～46行创建数据库连接并查询用户权限，如果查询过程中出现错误，则转到第86行后面的错误处理代码。

23.3.3　系统管理模块

系统管理模块主要包括添加用户、修改密码和重新登录三个子功能模块。

1.添加用户模块

【实例23.3】只有已经注册成为系统用户后才具备添加其他用户的权限。添加用户和权限选择的参考代码如下所示，程序中省略了输入是否合法的判断程序。

'----------------------------------frmAddNewUser.frm----------------------------------

01 Option Explicit

02

03 Private Sub cmdOK_Click()

04 Dim blnWeight As Boolean

05 Dim i As Integer

06 Dim j As Integer

07 '声明ADODB.Recordset对象变量

08 Dim objRecordset As ADODB.Recordset

09

10 '判断，普通用户必须至少设置一个权限

11 blnWeight=False

12 If optUserClass(2).Value=True Then

13 For i=0 To 3

14 If chkWeight(i).Value=1 Then

15 blnWeight=True

16 Exit For

17 End If

18 Next

19

20 If blnWeight=False Then

21 MsgBox"普通用户至少要有一项权限！",vbExclamation+vbOKOnly,"警告"

22 Exit Sub

23 End If

24 End If

25

26 '查询用户是否存在

27 strSQL="select*from Users where UserName='"＆Trim(txtNewUser(0).Text)＆"'"

28 Set objRecordset=ExecuteSQL(strSQL)

29 '如果用户存在

30 If objRecordset.EOF=False Then

31 MsgBox"已存在该用户！",vbExclamation+vbOKOnly,"警告"

32 txtNewUser(0).SetFocus

33 txtNewUser(0).SelStart=0

34 txtNewUser(0).SelLength=Len(txtNewUser(0).Text)

35 Exit Sub

36 End If

37

38 '如果用户不存在，添加新用户

39 strSQL="select*from Users"

40 Set objRecordset=ExecuteSQL(strSQL)

41 objRecordset.AddNew

42 objRecordset.Fields(0)=Trim(txtNewUser(0).Text)

43 objRecordset.Fields(1)=Trim(txtNewUser(1).Text)

44 '设置新用户权限

45 For i=0 To 2

46 If optUserClass(i).Value=True Then

47 Select Case i

48 '高级拥护

49 Case 0

50 objRecordset.Fields("Administration")="Y"

51 '只读用户

52 Case 1

53 objRecordset.Fields("ReadOnly")="Y"

54 '普通用户

55 Case 2

56 '普通用户权限

57 For j=0 To 3

58 If chkWeight(j).Value=1 Then

59 Select Case j

60 Case 0

61 objRecordset.Fields("Weight1")="Y"

62 Case 1

63 objRecordset.Fields("Weight2")="Y"

64 Case 2

65 objRecordset.Fields("Weight3")="Y"

66 Case 3

67 objRecordset.Fields("Weight4")="Y"

68 End Select

69 End If

70 Next j

71 End Select

72 End If

73 Next

74 '更新用户数据库

75 objRecordset.Update

76 '添加

77 MsgBox"用户添加成功！",vbExclamation+vbOKOnly,"提示"

78 txtNewUser(0).Text=""

79 txtNewUser(1).Text=""

80 txtNewUser(2).Text=""

81 End Sub

82

83 '用户类型选择

84 Private Sub optUserClass_Click(Index As Integer)

85 Dim i As Integer

86

87 '权限设置

88 If Index＜＞2 Then

89 For i=0 To 3

90 chkWeight(i).Enabled=False

91 Next i

92 Else

93 For i=0 To 3

94 chkWeight(i).Enabled=True

95 Next

96 End If

97 End Sub

【代码说明】代码第26～36行判断用户是否存在，代码第45～73行判断用户的权限，0是高级用户，1是只读用户，2是普通用户。

【运行效果】图23.2为学生信息管理系统的添加用户操作界面。

图　23.2　学生信息管理系统的添加用户操作界面

2.修改密码模块

修改密码模块比较简单，其设计思想是，判断旧密码是否正确，然后判断新密码和确认密码是否一致。如果判断结果都是正确的，那么就将新的密码保存到数据库中用户信息中。图23.3为学生信息管理系统的修改密码操作界面，其程序代码省略。

图　23.3　学生信息管理系统的修改密码操作界面

3.重新登录模块

重新登录界面与打开系统时的登录界面其实是同一个界面。选择重新登录时，程序先卸载主界面，然后再显示登录界面。图23.4为学生信息管理系统的登录界面。其程序代码与第22章的登录程序的代码相同，本章省略系统登录程序。

图　23.4　学生信息管理系统的登录界面

23.3.4　班级管理模块

班级管理模块主要包括班级浏览、添加班级和班级查询三个子功能模块。

1.班级浏览模块

通过TreeView控件将数据库中的所有班级信息罗列出来。双击某个子结点，可以将该班级的详细信息显示在表格中。同时，该模块还提供了班级信息的修改、查询和删除操作。修改操作是将用户修改后的信息保存到数据库中相应的位置，与添加班级共用同一个操作界面。查询操作是打开查询界面，实际上就是班级查询。执行删除操作的时候，需要将该班所有学生的档案和成绩等信息一起删除，然后更新数据库，并刷新界面上的数据表。

【实例23.4】删除操作和TreeView控件中班级显示等程序代码如下所示，其余操作的程序代码省略。

'----------------------------------frmClassBrower.frm----------------------------------

01 Option Explicit

02

03 '删除

04 Private Sub cmdDelete_Click()

05 '声明ADODB.Recordset对象变量

06 Dim objRecordset As ADODB.Recordset

07

08 strUserManage=UserManage(2)

09 If strUserManage="ReadOnly"Then

10 MsgBox"对不起，你是只读用户不能删除记录，请与管理员联系！",vbInformation+vbOKOnly,"警11告"

12 Exit Sub

13 End If

14

15 If Trim(flgClassData.TextMatrix(flgClassData.Row,1))=""Then

16 MsgBox"你还没有选择记录！",vbOKOnly+vbExclamation,"警告"

17 Exit Sub

18 End If

19

20 If MsgBox("确定要删除班级为"＆Trim(flgClassData.TextMatrix(flgClassData.Row,2))＆"的记录吗？

21",vbOKCancel+vbExclamation,"警告")=vbOK Then

22 strSQL="delete*from Documents where班级='"＆

23 Trim(flgClassData.TextMatrix(flgClassData.Row,2))＆"'"

24 Set objRecordset=ExecuteSQL(strSQL)

25 '删除在成绩表中的所有该班级的记录

26 strSQL="select*from Scores where学号in (select from Documents where班级='"＆

27 Trim(Me.flgClassData.TextMatrix(flgClassData.Row,2))＆"')"

28 Set objRecordset=ExecuteSQL(strSQL)

29 '删除该班级的记录

30 strSQL="delete*from Classes where班级='"＆

31 Trim(flgClassData.TextMatrix(flgClassData.Row,2))＆"'"

32 Set objRecordset=ExecuteSQL(strSQL)

33 End If

34

35 '显示表格中班级名称

36 ShowClassTitle

37 '显示班级数据

38 ShowClassData

39 End Sub

40

41 '使用TreeView控件显示班级名称

42 Public Sub ClassTree()

43 Dim objNode As Node

44 Dim objRecordset As ADODB.Recordset

45 Dim objSubRecordset As ADODB.Recordset

46 Dim strSQL As String

47 Dim strClass As String

48

49 trvClass.Nodes.Clear

50

51 '显示根结点

52 strClass="年级"

53 trvClass.LineStyle=tvwRootLines

54 '查询年级

55 strSQL="select distinct年级from Classes order by年级"

56 Set objRecordset=ExecuteSQL(strSQL)

57 '查询班级

58 strSQL="select distinct年级,班级from Classes order by年级,班级"

59 Set objSubRecordset=ExecuteSQL(strSQL)

60

61 objRecordset.MoveFirst

62 '将班级信息添加到TreeView控件中

63 Do Until objRecordset.EOF

64 objSubRecordset.MoveFirst

65 Set objNode=trvClass.Nodes.Add(,,strClass,objRecordset.Fields(0))

66

67 Do While Not objSubRecordset.EOF

68 If objSubRecordset.Fields(0)=objRecordset.Fields(0) Then

69 Set objNode=trvClass.Nodes.Add(strClass,tvwChild,,objSubRecordset.Fields(1))

70 End If

71 objSubRecordset.MoveNext

72 Loop

73

74 strClass=strClass＆"1"

75 objRecordset.MoveNext

76 Loop

77

78 objSubRecordset.Close

79 objRecordset.Close

80 Set objRecordset=Nothing

81 Set objSubRecordset=Nothing

82 End Sub

83

84 '双击TreeView控件显示班级具体信息

85 Private Sub trvClass_DblClick()

86 On Error GoTo ErrorTrap

87

88 If trvClass.SelectedItem.Index=0 Then

89 flgClassData.Clear

90 Exit Sub

91 End If

92

93 strSQL=trvClass.Nodes.Item(trvClass.SelectedItem.Index)

94 strSQL="select*from Classes where年级='"＆Trim(strSQL)＆"' or班级='"＆Trim(strSQL)＆"' order

95 by年级,班级"

96

97 Me.ShowClassTitle

98 Me.ShowClassData

99 Exit Sub

100

101 ErrorTrap:

102 flgClassData.Clear

103 End Sub

【代码说明】程序中，第36行的ShowClassTitle和第38行的ShowClassData过程是负责显示表格MSHFlexGrid控件中第一行的标题和刷新表格中班级信息数据。其操作方法都是将标题或SQL查询到的数据按顺序添加到表格中，相对比较容易。

【运行效果】图23.5为学生信息管理系统的班级浏览界面。

图　23.5　学生信息管理系统的班级浏览界面

2.添加班级模块

【实例23.5】添加班级模块可以完成两个功能，其一是添加班级信息到数据库，其二是在修改原有班级信息，并将其保存到数据库中。其参考代码如下所示。

'----------------------------------frmClassModify.frm----------------------------------

01 Option Explicit

02 Dim strSQL As String

03

04 Private Sub cmdOK_Click()

05 Dim i As Integer

06 Dim objRecordset As ADODB.Recordset

07

08 '如果为修改班级信息

09 If blnModifyClass=False Then

10 strSQL="select*from Classes where班级='"＆Trim(txtClass(1).Text)＆"'"

11 Set objRecordset=ExecuteSQL(strSQL)

12

13 If objRecordset.EOF=False Then

14 MsgBox"已经存在该班级的设置,不能重复！",vbOKOnly,"警告"

15 txtClass(1).SetFocus

16 txtClass(1).SelStart=0

17 txtClass(1).SelLength=Len(txtClass(1).Text)

18 objRecordset.Close

19 Exit Sub

20 End If

21 End If

22

23 '如果为添加班级信息

24 strSQL="delete*from Classes where班级='"＆Trim(txtClass(1).Text)＆"'"

25 Set objRecordset=ExecuteSQL(strSQL)

26 strSQL="select*from Classes"

27 Set objRecordset=ExecuteSQL(strSQL)

28 objRecordset.AddNew

29 '添加数据至数据库

30 For i=0 To 6

31 If txtClass(6).Text=""Then

32 txtClass(6).Text="无"

33 End If

34

35 objRecordset.Fields(i)=txtClass(i).Text

36 Next

37 '更新数据库

38 objRecordset.Update

39 End Sub

【代码说明】代码第10行从数据库中查询班级信息，如果存在，则执行代码第13～20行的设置。代码第23～38行可在数据库中添加或更新班级信息。

【运行效果】图23.6为学生信息管理系统的添加班级界面，可以将该界面和班级浏览界面同时打开，方便信息对照。

图　23.6　学生信息管理系统的添加班级界面

3.班级查询模块

班级查询程序比较简单，只要输入年级、班级和专业三个查询条件中的任何一个，就能实现查询功能。查询过程仍然使用SQL实现。图23.7为学生信息管理系统的班级查询界面。该模块的程序代码省略。

图　23.7　学生信息管理系统的班级查询界面

23.3.5　档案管理模块

档案管理模块包括学生档案添加、浏览和查询三个子功能模块。添加档案模块可以实现登记档案入数据库和修改档案两个功能。图23.8为学生信息管理系统的档案添加界面。

图　23.8　学生信息管理系统的档案添加界面

档案添加模块与添加班级模块的程序代码结构是一样的。档案浏览模块与班级浏览模块的程序代码结构也是一样的。图23.9为学生信息管理系统的档案浏览界面，其中的数据表格也使用MSHFlexGrid控件实现。

图　23.9　学生信息管理系统的档案浏览界面

档案查询模块与班级查询模块的程序代码结构也是一样的。图23.10为学生信息管理系统的档案查询界面。

图　23.10　学生信息管理系统的档案查询界面

档案管理模块的程序与班级管理模块的程序结构是一样的，只是查询的条件和查询的字段有所不同。此处，省略档案管理模块的程序。

23.3.6　课程管理模块

课程管理模块有基本课程设置和班级课程设置两个模块。基本课程设置主要用于设置各门课程的教材，包括修改数据库中已有课程的教材，增加新教材和删除旧教材。课程和教材的修改、添加和删除与班级修改、添加和删除的操作方法是相同的。

【实例23.6】下面给出课程添加的程序参考代码，其中省略了用户权限和输入合法性判断语句。

'----------------------------------frmDocModify.frm----------------------------------

01 Option Explicit

02 Dim strSQL As String

03

04 '添加课程

05 Private Sub cmdAdd_Click()

06 strUserManage=UserManage(4)

07

08 '添加课程

09 If cmdAdd.Caption="确定"Then

10 '使用Adodc控件连接数据库

11 adoCourse.RecordSource="select*from Courses where课程名称='"＆Trim(txtCourseName.Text)

12 ＆"'"

13 adoCourse.Refresh

14 '如果不存在数据记录

15 If adoCourse.Recordset.EOF=False Then

16 MsgBox"已经存在该课程名称！",vbOKOnly+vbExclamation,"警告"

17 txtCourseName.SetFocus

18 txtCourseName.SelStart=0

19 txtCourseName.SelLength=Len(txtCourseName.Text)

20 Exit Sub

21 '添加新课程

22 Else

23 adoCourse.Recordset.AddNew

24 adoCourse.Recordset.Fields(0)=txtCourseName.Text

25 adoCourse.Recordset.Fields(1)=txtBooks.Text

26 adoCourse.Recordset.Update

27 cmdAdd.Caption="增加"

28 cmdDelete.Enabled=True

29 cmdModify.Enabled=True

30 txtCourseName.Text=""

31 txtBooks.Text=""

32 txtCourseName.Enabled=False

33 txtBooks.Enabled=False

34 kechengshowtitle

35 ShowCourseData

36 End If

37 '进入添加状态

38 Else

39 txtCourseName.Enabled=True

40 txtBooks.Enabled=True

41 txtCourseName.Text=""

42 txtBooks.Text=""

43 txtCourseName.SetFocus

44 kechengshowtitle

45 ShowCourseData

46 cmdAdd.Caption="确定"

47 cmdDelete.Enabled=False

48 cmdModify.Enabled=False

49 End If

50 End Sub

【代码说明】代码第11行判断数据库中是否存在此课程名称，如果存在，则执行第16～20行的代码，如果不存在，则执行第23～35行的代码。

【运行效果】图23.11为学生信息管理系统的基本课程设置界面。课程名和教材的修改和添加操作结果通过Adodc控件更新到数据库中。

图　23.11　学生信息管理系统的基本课程设置界面

班级课程设置主要用于设置不同年级和不同专业所要教授的不同课程。该模块将班级信息和课程信息两个数据库的数据结合使用，为每个不同专业的班级设置每个学期的课程。图23.12为学生信息管理系统的班级课程设置界面。

图　23.12　学生信息管理系统的班级课程设置界面

向右箭头按钮用于将可选科目列表中的课程选择到所选科目列表中，作为相应班级在某学期的待学课程。向左箭头按钮用于删除所选的课程。界面上的课程列表是ListBox控件，其Style属性设置为1-CheckBox。

23.3.7　成绩管理模块

成绩管理模块主要包括考试类型设置、成绩添加和成绩浏览三个子模块。考试类型模块用于修改、添加和删除考试类型。这些考试类型保存在数据库中，当添加考试成绩的时候，可以选择该次考试为期末或期中等考试类型。考试类型的修改、添加和删除操作的程序结构与前面模块中相应操作是相同的。图23.13为学生信息管理系统的考试类型设置界面。考试类型的修改和添加操作结果通过Adodc控件更新到数据库中。

图　23.13　学生信息管理系统的考试类型设置界面

成绩添加模块要和前面的班级课程设置模块结合使用。只有设置了该班所要学习的课程，才能将该课程的成绩添加到数据库中。添加成绩操作实际上就是在成绩数据库中添加新的记录保存学生某课程的考试成绩，与前面添加档案或添加班级等程序结构相似。图23.14为学生信息管理系统的成绩添加界面。

图　23.14　学生信息管理系统的成绩添加界面

已经添加到数据库中的成绩可以通过成绩浏览界面进行浏览。图23.15为学生信息管理系统的成绩浏览界面。

图　23.15　学生信息管理系统的成绩浏览界面

成绩浏览操作十分简单，选择框中的年级、班级、学期和考试类型等查询条件。在使用SQL语句查询前，将这些查询条件放置到SQL连接语句中即可。

23.4　本章常见问题

1.如何使用SQL语句进行多表操作？

使用SQL语句可以对同一个数据库中的多个表进行同时操作。例如，同时查询同一个数据库中档案数据表Documents和班级数据表Classes中的部分数据，参考代码如下所示。

01 strSQL=trvDocument.Nodes.Item(trvDocument.SelectedItem.Index)

02 strSQL="select Documents.学号,Documents.姓名,Documents.性别,Classes.年级,Documents.班级,Classes

03 专业,Classes.年制,Documents.出生年月,Documents.家庭住址,Documents.邮政编码,Documents.

04 联系电话,Documents.入学时间,Documents.备注from Documents inner join Classes on

05 Documents.班级=Classes.班级where Documents.班级='"＆strSQL＆"' or年级='＆strSQL＆' order by

06 Classes.年级,Classes.班级,Documents.学号"

07 Set objRecordset=ExecuteSQL(strSQL)

该程序代码是学生信息管理系统中的档案浏览模块中的代码。strSQL为String型的SQL查询条件。通过该语句，可以同时查询到两个数据表中的部分数据信息，并将其组合在同一个数据记录集中。通过该数据记录集，就可以方便地将学生的所有信息显示出来。上述程序代码中的其他变量和控件参考本书提供的学生信息管理系统程序。

2.Adodc控件的Refresh方法与该控件的记录集Recordset的Update方法有什么区别？

Adodc控件的记录集Recordset的Update方法用于将更新数据库。例如，修改了数据库的内容或者添加了新记录，都要通过Update方法将其更新到数据库中。而Adodc控件的Refresh方法是将最新的数据库中的数据刷新到与其绑定的控件中。两者的区别是Update方法可以改变数据库中的内容，而Refresh方法不会改变数据库中的内容。

23.5　小结

本章主要介绍了学生信息管理系统的程序设计，该系统能够实现很多实用的学生信息管理功能。学生信息管理系统与学生考试系统，都是管理信息系统MSI的应用实例。由于篇幅限制，本章省略了很多程序代码。但是，有了第22章的基础，本章所用的程序结构都很容易实现。本章的重点是SQL查询技术的应用和系统功能的模块化设计方法。第24章将介绍可视电话系统。

第24章　可视电话

可视电话除了像普通电话一样能够进行通话以外，还增加了对方图像的显示。通过AVPhone3.0控件组，实时处理捕获和接收到的视频和音频数据，实现可视电话的功能。本章包括以下知识点。

❑使用AVPhone3.0控件组实现可视电话。

❑VidCap控件捕获视频数据。

❑VidCodec控件压缩或解压缩视频数据。

❑VidRnd控件回放视频。

❑AudCap控件捕获音频数据。

❑AudCodec控件压缩或解压缩音频数据。

❑AudRnd控件回放音频。

❑AVIFile控件保存音频和视频数据到AVI或WAVE文件中。

❑UDPSocket控件本地计算机和远程计算机之间数据传输。

通过本章的学习，读者可以接触到一个新的控件组AVPhone3.0，其中包含8个视频和音频数据处理控件。读者可以使用这些控件捕获、压缩、解压、回放、传输和存储视频和音频数据。

24.1　程序功能简介

可视电话是利用计算机网络连接本地计算机和远程计算机，实时发送和接收人的语音和图像多媒体数据，并进行回放和存储。Banasoft提供的AVPhone3.0控件组可以捕获视频和音频，并能够压缩、传输、解压、回放和存储视频和音频数据。AVPhone3.0控件组包含8个控件，分别为VidCap、VidCodec、VidRnd、AudCap、AudCodec、AudRnd、AVIFile和UDPSocket。本章主要使用AVPhone3.0控件组实现可视电话的主要功能，包括呼叫对方请求通话、接受对方通话请求、录像等。

24.2　设计思路和结构

可视电话的设计思路是：首先，捕获视频和音频；其次，分别进行压缩；再次，通过网络将压缩后的视频和音频数据发送到对方计算机；然后，解压接收到的视频和音频数据；最后，回放解压后的数据。图24.1所示为可视电话的结构图。

图　24.1　可视电话的结构图

将AVPhone3.OCX文件复制到C:\WINDOWS\system32路径下。在“部件”对话框的“控件”选项卡中选择"Banasoft AVPhone3 video/audio controls"选项就能将AVPhone3控件组添加到控件工具箱中。图24.2为控件工具箱中的AVPhone3控件组，从左至右分别为VidCap、VidRnd、AudCap、AudRnd、VidCodec、AudCodec、AVIFile和UDPSocket。

图　24.2　控件工具箱中的AVPhone3控件组

本章可视电话系统有一个Form窗体。根据可视电话的结构图，向该Form窗体上添加上述控件。表24.1为可视电话系统的对象及其属性值。

VidCap控件捕获摄像头输入的视频数据。VidCodec控件压缩该视频数据。使用UDPSocket控件将压缩后的视频数据从本地计算机传输到远程计算机。远程计算机通过UDPSocket控件接受视频数据，并由VidCodec控件进行解压缩。VidRnd控件回放将解压后的视频播放到显示屏上。音频的数据的处理过程与视频数据的处理方法类似。AVIFile控件用于保存音频和视频数据到AVI或WAVE文件中。

24.3　程序设计步骤

可视电话程序主要分为8个模块，分别为窗体加载与卸载模块、呼叫模块、挂机模块、录像模块、视频处理模块、音频处理模块、数据传输模块和超时等待模块。

24.3.1　窗体加载与卸载模块

【实例24.1】窗体加载与卸载模块主要用于程序加载时初始化视频和音频控件，程序卸载时关闭视频和音频设备。此外，在代码段的通用声明部分声明一些模块级变量。参考程序代码如下所示。

01 Option Explicit

02

03 '声明是否正在通话的变量

04 Private blnCalling As Boolean

05 '声明状态变量

06 Private lngState As Long

07 '声明计时变量

08 Private lngCount As Long

09 '声明录像变量

10 Private blnRecording As Boolean

11

12 '声明常量

13 Private Const TM_AUDIOFORMAT As Long=＆H10

14 Private Const TM_AUDIOFRAME As Long=＆H11

15

16 Private Const TM_CALLSETUP As Long=＆H1

17 Private Const TM_CALLANSWER As Long=＆H2

18 Private Const TM_CALLREJECT As Long=＆H3

19 Private Const TM_CALLHANGUP As Long=＆H4

20

21 Private Const TM_VIDEOFORMAT As Long=＆H21

22 Private Const TM_VIDEOFRAME As Long=＆H22

23 Private Const TM_VIDEOFRAMEKEY As Long=＆H23

24 Private Const TM_VIDEORATE As Long=＆H24

25

26 Private Sub Form_Load()

27 On Error GoTo ErrorTrap

28

29 '设置Timer控件的定时时间间隔

30 tmrTimer.Interval=1000

31

32 '绑定套节字到本地计算机

33 uskUDPSocket.Bind

34 '获得本地IP地址，并将该IP地址显示在窗体标题栏上

35 Caption=Caption＆"-"＆uskUDPSocket.GetIP(uskUDPSocket.LocalAddress)

36

37 '设置音量

38 ardAudRnd.Volume=2500

39

40 '设置当前默认音频设备

41 acpAudCap.Device=-1

42 '设置音频的输出格式为GSM

43 acdAudCodecSend.OutFormat="GSM"

44 '设置音频的输入格式与输出格式相同

45 acdAudCodecSend.InFormat=acpAudCap.Format

46

47 '设置当前默认视频捕获设备，即第一个可获得的设备

48 vcpVidCap.Device=-1

49 '设置视频输出格式，默认为iv50压缩格式

50 vcdVidCodecSend.OutFormat="iv50"

51 '设置压缩质量

52 vcdVidCodecSend.Quality=40

53 '设置视频输入格式与输出格式相同

54 vcdVidCodecSend.InFormat=vcpVidCap.Format

55

56 '设置当前音频的解压格式

57 ardAudRnd.Format=acpAudCap.Format

58 '设置当前视频的解压格式

59 vrdVidRnd.Format=vcpVidCap.Format

60 '设置视频解压速度

61 vrdVidRnd.Rate=-vcpVidCap.Rate

62 Exit Sub

63

64 ErrorTrap:

65 MsgBox Err.Description,vbCritical

66 End Sub

67

68 Private Sub Form_Unload(Cancel As Integer)

69 On Error GoTo ErrorTrap

70

71 '如果正在通话中，挂机

72 If blnCalling Then

73 Hangup

74 End If

75

76 '如果正在录像，停止录像

77 If blnRecording Then

78 '设置正在录像变量为False

79 blnRecording=False

80 '关闭录像文件

81 aflAVIFile.CloseFile

82 '重置录像按钮

83 cmdRecord.Caption="录像"

84 End If

85

86 '关闭所有音频设备

87 acpAudCap.Device=-2

88 acdAudCodecSend.OutFormat=vbNullString

89 acdAudCodecReceived.OutFormat=vbNullString

90 ardAudRnd.Format=vbNullString

91

92 '关闭所有视频设备

93 vcpVidCap.Device=-2

94 vcdVidCodecSend.OutFormat=vbNullString

95 vcdVidCodecReceived.OutFormat=vbNullString

96 vrdVidRnd.Format=vbNullString

97

98 uskUDPSocket.Bind 0

99 Exit Sub

100

101 ErrorTrap:

102 MsgBox Err.Description,vbCritical

103 Resume Next

104 End Sub

【代码说明】代码第04～10行声明的都是状态等变量，而代码13～24行声明的是函数中使用到的常量。代码第30～61行是初始化视频和音频控件，代码第72～99行是关闭视频和音频设备。

24.3.2　呼叫模块

【实例24.2】单击“呼叫”按钮进入呼叫模块程序，该模块主要是呼叫对方计算机。参考程序代码如下所示。

01 '呼叫

02 Private Sub cmdCall_Click()

03 On Error GoTo ErrorTrap

04 '声明对方计算机IP地址的变量

05 Dim strInput As String

06

07 If blnCalling Then

08 MsgBox"正在通话",vbExclamation

09 Exit Sub

10 End If

11

12 '从窗体标题中获得本地计算机IP地址IP

13 strInput=Caption

14 '获得输入的对方计算机IP地址

15 strInput=InputBox("输入对方IP地址","呼叫",Trim$(Mid$(strInput,InStr(strInput,"-")+1)))

16

17 If Len(strInput)＜=0 Then

18 Exit Sub

19 End If

20

21 '呼叫输入的对方计算机

22 uskUDPSocket.SetHost strInput

23 '向远程计算机发送数据

24 uskUDPSocket.Frame-1,-1,TM_CALLSETUP

25

26 '超时动作TM_CALLSETUP

27 lngState=TM_CALLSETUP

28 '计时等待时间设为10秒

29 lngCount=10

30

31 Exit Sub

32 ErrorTrap:

33 MsgBox Err.Description,vbCritical

34 End Sub

【代码说明】通过代码第15行输入对方计算机的IP地址，使用第24行的UDPSocket控件向对方计算机发送呼叫请求。UDPSocket控件的Frame方法用于向远程计算机发送数据。

24.3.3　挂机模块

【实例24.3】单击“挂机”按钮进入挂机模块程序，该模块主要是通话结束后中断与对方计算机之间的数据传输。参考程序代码如下所示。

01 '挂机

02 Private Sub cmdHand_Click()

03 On Error GoTo ErrorTrap

04 '挂机

05 If blnCalling Then

06 Hangup

07 End If

08 Exit Sub

09 ErrorTrap:

10 MsgBox Err.Description,vbCritical

11 End Sub

12

13 '挂机

14 Private Sub Hangup()

15 '如果正在录像，停止录像

16 If blnRecording Then

17 '设置正在录像变量为False

18 blnRecording=False

19 '关闭录像文件

20 aflAVIFile.CloseFile

21 '重置录像按钮

22 cmdRecord.Caption="录像"

23 End If

24

25 '向远程计算机发送"对方挂机"信息，通知对方已经挂机

26 uskUDPSocket.Frame-1,-1,TM_CALLHANGUP,,"对方挂机"

27

28 '初始化通话和状态变量

29 blnCalling=False

30 lngState=0

31

32 '设置当前音频的解压格式

33 ardAudRnd.Format=acpAudCap.Format

34 '设置当前视频的解压格式

35 vrdVidRnd.Format=vcpVidCap.Format

36 '设置视频解压速度

37 vrdVidRnd.Rate=-vcpVidCap.Rate

38 End Sub

【代码说明】代码第16～23行表示如果正在录像，先结束录像。然后代码第26行通过UDPSocket控件向对方发送挂机信息，代码第29～30行初始化相关变量。

24.3.4　录像模块

【实例24.4】单击“录像”按钮进入录像模块程序，该模块主要使用AVIFile控件将通话过程中的视频和音频录入到.AVI文件中。参考程序代码如下所示。

01 '录像

02 Private Sub cmdRecord_Click()

03 On Error GoTo ErrorTrap

04 Dim strRecordPath As String

05 Dim strFormat As String

06 Dim dblRate As Double

07

08 '如果正在录像，停止录像

09 If blnRecording Then

10 '设置正在录像变量为False

11 blnRecording=False

12 '关闭录像文件

13 aflAVIFile.CloseFile

14 '重置录像按钮

15 cmdRecord.Caption="录像"

16

17 '如果不在录像，将音像录到当前路径下的指定的文件中

18 Else

19 strRecordPath=App.Path

20 If Right$(App.Path,1)＜＞"\"Then

21 strRecordPath=strRecordPath＆"\"

22 End If

23

24 strRecordPath=strRecordPath＆"Capture.AVI"

25

26 '提示是否录像，如果选择OK键，就开始录像

27 If MsgBox("OK键开始录"＆IIf(blnCalling,"remote","local")＆"音像到"＆_

28 strRecordPath,vbQuestion+vbOKCancel)＜＞vbOK Then

29 Exit Sub

30 End If

31

32 '查找要录像的文件是否存在

33 If Len(Dir$(strRecordPath)) Then

34 '提示是否覆盖原来的文件

35 If MsgBox(strRecordPath＆"已经存在,是否覆盖?",vbExclamation+vbYesNo)＜＞vbNo 36 Then

37 Exit Sub

38 End If

39

40 '如果覆盖，就删除该文件

41 Kill strRecordPath

42 End If

43

44 '打开录像文件，将音像录入该指定的文件中

45 aflAVIFile.OpenFile Path,False

46

47 '如果正在录本地或对方音频，设置音频编解码器格式

48 If blnCalling Then

49 strFormat=acdAudCodecReceived.InFormat

50 Else

51 strFormat=acdAudCodecSend.OutFormat

52 End If

53 If Len(strFormat)＞0 Then

54 '设置添加到文件中的默认音频

55 aflAVIFile.StreamAdd-1

56 '设置流格式

57 aflAVIFile.StreamFormat(-1)=strFormat

58 End If

59

60 '如果正在录本地或对方视频，设置视频编解码器格式

61 If blnCalling Then

62 strFormat=vcdVidCodecReceived.InFormat

63 Else

64 strFormat=vcdVidCodecSend.OutFormat

65 End If

66 If Len(strFormat)＞0 Then

67 '设置添加到文件中的默认视频

68 aflAVIFile.StreamAdd-2

69

70 dblRate=vrdVidRnd.Rate

71 If dblRate＜0 Then

72 dblRate=-dblRate

73 End If

74 '设置流速度

75 aflAVIFile.StreamRate(-2)=dblRate

76 '设置流格式

77 aflAVIFile.StreamFormat(-2)=strFormat

78 End If

79

80 '设置正在录像变量为True

81 blnRecording=True

82 '重置录像按钮

83 cmdRecord.Caption="停止"

84 End If

85 Exit Sub

86

87 ErrorTrap:

88 MsgBox Err.Description,vbCritical

89 End Sub

【代码说明】上述代码第45行首先使用AVIFile控件的OpenFile方法打开用于保存录像的.AVI文件，然后将视频和音频以一定的格式添加到该文件中，格式的设置参考第48～84行。

24.3.5　视频处理模块

【实例24.5】视频处理模块的作用主要包括三个部分，分别是视频捕获、视频压缩和视频解压与回放。参考程序代码如下所示。

01 '捕获视频

02 Private Sub vcpVidCap_Frame(Data As Variant)

03 On Error GoTo ErrorTrap

04 If blnCalling Then

05 '将视频压缩数据写到编解码器

06 vcdVidCodecSend.Frame Data

07 Else

08 '将视频数据写到输出缓冲区

09 vrdVidRnd.Frame Data

10 '如果正在录像，将视频数据写到编解码器待压缩

11 If blnRecording Then

12 vcdVidCodecSend.Frame Data

13 End If

14 End If

15 ErrorTrap:

16 End Sub

【代码说明】当VidCap控件捕获视频帧时触发第03～15行的Frame事件。在该事件中使用VidCodec控件的Frame方法将捕获到的视频数据进行压缩。

【实例24.6】当发送端VidCodec控件压缩一帧视频数据时触发Frame事件。参考程序代码如下所示。

01 '压缩视频

02 Private Sub vcdVidCodecSend_Frame(Data As Variant,ByVal IsKeyFrame As Boolean)

03 On Error GoTo ErrorTrap

04 If blnCalling Then

05 '向远程发送压缩视频数据，发送到默认地址，默认端口

06 uskUDPSocket.Frame-1,-1,IIf(IsKeyFrame,TM_VIDEOFRAMEKEY,TM_VIDEOFRAME),,Data

07 Else

08 '将数据写入第一个视频流文件

09 aflAVIFile.StreamWrite-2,Data,IsKeyFrame

10 End If

11 ErrorTrap:

12 End Sub

【代码说明】在该事件中，第06行使用UDPSocket控件的Frame方法将压缩后的视频数据发送到对方计算机。

【实例24.7】当接收端VidCodec控件解压接收到的一帧视频数据时触发Frame事件。参考程序代码如下所示。

01 '解压视频

02 Private Sub vcdVidCodecReceived_Frame(Data As Variant,ByVal IsKeyFrame As Boolean)

03 On Error GoTo ErrorTrap

04 '解压接收到的视频数据

05 vrdVidRnd.Frame Data

06 ErrorTrap:

07 End Sub

【代码说明】在该事件中，第05行使用VidRnd控件的Frame方法将解压后的视频数据进行回放。

24.3.6　音频处理模块

【实例24.8】音频处理模块的作用也包括三个部分，分别是音频捕获、音频压缩和音频解压与回放。参考程序代码如下所示。

01 '捕获音频

02 Private Sub acpAudCap_Frame(Data As Variant)

03 On Error GoTo ErrorTrap

04 If blnCalling Then

05 '将音频压缩数据写到编解码器

06 acdAudCodecSend.Frame Data

07 Else

08 '将音频数据写到输出缓冲区

09 ardAudRnd.Frame Data

10 '如果正在录像，将音频数据写到编解码器待压缩

11 If blnRecording Then

12 acdAudCodecSend.Frame Data

13 End If

14 End If

15 ErrorTrap:

16 End Sub

17

18 '压缩音频

19 Private Sub acdAudCodecSend_Frame(Data As Variant)

20 On Error GoTo ErrorTrap

21 If blnCalling Then

22 '向远程发送压缩音频数据，发送到默认地址，默认端口

23 uskUDPSocket.Frame-1,-1,TM_AUDIOFRAME,,Data

24 Else

25 '将数据写入第一个音频流文件

26 aflAVIFile.StreamWrite-1,Data

27 End If

28 ErrorTrap:

29 End Sub

30

31 '解压音频

32 Private Sub acdAudCodecReceived_Frame(Data As Variant)

33 On Error GoTo ErrorTrap

34 '解压接收到的音频数据

35 ardAudRnd.Frame Data

36 ErrorTrap:

37 End Sub

【代码说明】上述代码可以看出，音频数据的处理方式与视频数据的处理方式是相似的。代码第02～16行是捕获音频。代码第19～29行用来压缩音频。压缩后必须还得解压，这通过代码第32～37行完成。

24.3.7　数据传输模块

【实例24.9】本地计算机与远程计算机之间的数据传输通过UDPSocket控件实现。当UDPSocket控件接到一个数据包时触发Frame事件。参考程序代码如下所示。

01 Private Sub uskUDPSocket_Frame(ByVal Address As Long,ByVal Port As Integer,ByVal Handle As Long,

02 ByVal Param As Long,Data As Variant)

03 On Error GoTo ErrorTrap

04

05 Select Case Handle

06 '呼叫设置

07 Case TM_CALLSETUP

08 '如果本地计算机正在通话，拒绝第三方的呼叫请求

09 If blnCalling Then

10 uskUDPSocket.Frame Address,Port,TM_CALLREJECT,,"对方正在通话"

11 '如果本地计算机不在通话，提示接受或拒绝第三方的呼叫请求的提示，并选择接受请求

12 ElseIf MsgBox(uskUDPSocket.GetIP(Address)＆"有呼叫请求,是否应答?",_

13 vbYesNo+vbQuestion)=vbYes Then

14 '接受请求

15 uskUDPSocket.Frame Address,Port,TM_CALLANSWER

16 '建立与对方计算机之间的连接

17 ConnectCalling Address,Port

18 '拒绝对方请求

19 Else

20 '拒绝对方请求，并向对方计算机发送拒绝接听的消息

21 uskUDPSocket.Frame Address,Port,TM_CALLREJECT,,"对方拒绝."

22 End If

23

24 '呼叫应答

25 Case TM_CALLANSWER

26 '如果不在通话中，与对方计算机建立连接

27 If Not blnCalling Then

28 ConnectCalling Address,Port

29 End If

30

31 '呼叫拒绝

32 Case TM_CALLREJECT

33 '如果不在通话中，清空状态变量，并显示接收到的数据

34 If Not blnCalling Then

35 lngState=0

36 MsgBox Data,vbExclamation

37 End If

38

39 '其他消息

40 Case Else

41 '如果不在通话中，忽略这些消息

42 If Not blnCalling Then

43 Exit Sub

44 End If

45

46 '设置超时延迟时间为30秒

47 lngCount=30

48

49 '如果正在通话中，处理这些消息

50 Select Case Handle

51 '挂机

52 Case TM_CALLHANGUP

53 '如果正在录像，停止录像

54 If blnRecording Then

55 '设置正在录像变量为False

56 blnRecording=False

57 '关闭录像文件

58 aflAVIFile.CloseFile

59 '重置录像按钮

60 cmdRecord.Caption="录像"

61 End If

62

63 '初始化通话和状态变量

64 blnCalling=False

65 lngState=0

66

67 '设置当前音频的解压格式

68 ardAudRnd.Format=acpAudCap.Format

69 '设置当前视频的解压格式

70 vrdVidRnd.Format=vcpVidCap.Format

71 '设置视频解压速度

72 vrdVidRnd.Rate=-vcpVidCap.Rate

73

74 '显示接收到的数据

75 MsgBox Data,vbInformation

76

77 '音频格式

78 Case TM_AUDIOFORMAT

79 '清空音频编解码器的输出格式

80 acdAudCodecReceived.OutFormat=vbNullString

81 '设置音频编解码器的输入格式

82 acdAudCodecReceived.InFormat=Data

83 '设定当前音频回放格式

84 ardAudRnd.Format=acdAudCodecReceived.OutFormat

85

86 '音频帧

87 Case TM_AUDIOFRAME

88 '如果正在录像，将压缩数据写到编解码器

89 If blnRecording Then

90 aflAVIFile.StreamWrite-1,Data,True

91 End If

92 '将音频数据写到编解码器待压缩

93 acdAudCodecReceived.Frame Data

94

95 '视频格式

96 Case TM_VIDEOFORMAT

97 '清空视频编解码器的输出格式

98 vcdVidCodecReceived.OutFormat=vbNullString

99 '设置视频编解码器的输入格式

100 vcdVidCodecReceived.InFormat=Data

101 '设定当前视频回放格式

102 vrdVidRnd.Format=vcdVidCodecReceived.OutFormat

103

104 '视频帧

105 Case TM_VIDEOFRAME

106 '如果正在录像，将视频压缩数据写到编解码器

107 If blnRecording Then

108 aflAVIFile.StreamWrite-2,Data,False

109 End If

110 '将视频数据写到编解码器待压缩

111 vcdVidCodecReceived.Frame Data,False

112

113 '视频帧Key

114 Case TM_VIDEOFRAMEKEY

115 '如果正在录像，将视频压缩数据写到编解码器

116 If blnRecording Then

117 aflAVIFile.StreamWrite-2,Data

118 End If

119 '将视频数据写到编解码器待压缩

120 vcdVidCodecReceived.Frame Data,True

121

122 '视频速度

123 Case TM_VIDEORATE

124 '设置视频回放速率

125 vrdVidRnd.Rate=Data

126 End Select

127 End Select

128 ErrorTrap:

129 End Sub

130

131 Private Sub ConnectCalling(ByVal Addr As Long,ByVal Port As Integer)

132 '如果正在录像，停止录像

133 If blnRecording Then

134 '设置正在录像变量为False

135 blnRecording=False

136 '关闭录像文件

137 aflAVIFile.CloseFile

138 '重置录像按钮

139 cmdRecord.Caption="录像"

140 End If

141

142 '正在通话

143 blnCalling=True

144 lngState=TM_CALLANSWER

145

146 '设置连接超时时间为30秒

147 lngCount=30

148 With uskUDPSocket

149 '设置对方计算机地址

150 .SetHost Addr

151 '设置对方计算机端口

152 .SetPort Port

153 '向对方设置音频格式

151 .Frame-1,-1,TM_AUDIOFORMAT,,acdAudCodecSend.OutFormat

152 '向对方设置视频格式

153 .Frame-1,-1,TM_VIDEOFORMAT,,vcdVidCodecSend.OutFormat

154 '向对方设置视频速度

155 .Frame-1,-1,TM_VIDEORATE,,vcpVidCap.Rate

156 End With

157 End Sub

【代码说明】代码第01～139行的Frame事件过程中，Handle参数表示接收到的消息类型，包括呼叫设置、呼叫应答、拒绝应答、挂机、接收到视频和音频格式或帧等。代码05～127行使用选择语句Select Case分别处理这些消息。

24.3.8　超时等待模块

【实例24.10】在呼叫对方计算机和应答呼叫时，需要一定的超时等待时间。参考程序代码如下所示。

01 '超时等待

02 Private Sub tmrTimer_Timer()

03 On Error GoTo ErrorTrap

04

05 '计时等待

06 If lngCount＞0 Then

07 lngCount=lngCount-1

08 Else

09 Select Case lngState

10 Case TM_CALLSETUP

11 '对方无应答

12 MsgBox"对方无应答",vbExclamation

13 Case TM_CALLANSWER

14 '通信超时,自动挂机

15 Hangup

16 MsgBox"通信超时,自动挂机",vbExclamation

17 End Select

18

19 '初始化状态变量

20 lngState=0

21 End If

22 ErrorTrap:

23 End Sub

【代码说明】代码第09～17行表示如果等待超时，提示用户对方无应答或通信超时。超时等待功能可以通过Timer控件延时指定的时间来实现。

24.4　本章常见问题

1.如何区分AVIFile控件StreamAdd方法是添加新的视频还是新的音频流数据到文件中？

AVIFile控件StreamAdd方法的语法格式如下所示。

object.StreamAdd fccType

其中，参数fccType是一个Variant类型的表达式。当fccType为-1时，添加的是新的音频数据；当fccType为-2时，添加的是新的视频数据。

2.为什么设置VidRnd控件的Rate属性值为VidCap控件的Rate属性值的相反数？

VidRnd控件的Rate属性用于返回或设置视频回放速率，其值范围为-30～30之间。当Rate属性值小于0时，设置无缓冲回放速率。当Rate属性值大于或等于0时，设置停止或缓冲回放速率。VidCap控件的Rate属性用于返回或设置视频捕获速率，其值范围为0～30之间。因此，设置VidRnd控件的Rate属性值为VidCap控件的Rate属性值的相反数，既可以实时回放视频，同时保证回放的速率与视频捕获的速率相同。

24.5　小结

本章详细介绍了可视电话程序的设计思路和结构，以及模块化的程序设计步骤。AVPhone3.0控件组是实现可视电话程序设计的关键，该控件组包含了所需的视频和音频的数据处理控件。AVPhone3.0控件组是一个陌生控件。读者需要了解AVPhone3.0控件组中各控件的作用，以及如何使用这些控件常用的属性、事件和方法。

光盘内容

光盘下载地址：http://pan.baidu.com/s/1nt6sHip