Visual Basic 2010入门经典

978-7-115-25145-9
作者: 【美】James Foxall
译者: 梅兴文
编辑: 傅道坤

图书目录:

详情

Visual Basic是一种非常流行的编程语言,本书通过引导读者创建一个图片查看程序,全面阐述了使用Visual Basic 2010创建应用程序所需的各种基本技能。

图书摘要

计算机技术译林精选系列

Visual Basic 2010入门经典

[美] James Foxall 著

梅兴文 译

人民邮电出版社

北京

Visual Basic是一种非常流行的编程语言,Visual Basic 2010是微软公司推出的 Visual Basic最新版本,其功能比以前任何版本都强大,可与C++等语言相媲美。本书通过引导读者创建一个图片查看程序,全面阐述了使用Visual Basic 2010创建应用程序所需的各种基本技能。

全书分五部分,共 24章。第一部分介绍了Visual Basic 2010学习版开发环境,引导读者熟练使用该 IDE;第二部分探讨如何创建应用程序界面,包含窗体和各种控件的用法;第三部分介绍了编程技术,包括编写和调用方法、处理数值、字符串和日期、决策和循环结构、代码调试、模块和类的创建以及图形绘制等;第四部分阐述了文件和注册表的处理、数据库的使用和自动化其他应用程序等;第五部分介绍了应用程序部署并概述了.NET框架。

本书适合没有任何编程经验的读者和Visual Basic新手阅读,也可作为大中专院校Visual Basic课程的参考资料。

 

James Foxall是Tigerpaw软件公司(www.tigerpawsoftware.com)的总裁。Tigerpaw公司位于 Nebraska 州 Bellevue,是一家微软认证合作伙伴,致力于为技术提供商提供解决方案。Tigerpaw的商业自动化解决方案曾获过奖,该产品将合同管理、市场营销、服务和维修、建议生成、库存控制和采购等自动化。在 2010 年初,有超过 25 000 名用户在使用最新版本的Tigerpaw。Foxall 在创建认证的 Office 兼容软件方面有丰富的经验,这使其成为 Microsoft Windows和Microsoft Office环境的应用程序接口和行为标准方面的权威。

James一直在编写Visual Basic商业产品代码,在这方面的经验超过14年。他编写过很多图书,其中包括《Practical Standards for Microsoft Visual Basic》和《MCSD in a Nutshell: The Visual Basic Exams》,还为杂志《Access-Office-VBA Advisor》和《Visual Basic Programmer’s Journal》撰写文章。James拥有信息管理系统(MIS)学士学位和工商管理硕士学位(MBA),是微软认证解决方案开发人员,还在全球各地就编程技术和业务流程改善方面发表演讲。不编程和撰写编程方面的图书时,他喜欢和家人在一起弹吉他、欣赏优秀乐队的作品以及玩电脑游戏。可通过www.jamesfoxall.com联系James。

Visual Basic是一种非常流行的语言,Visual Basic 2010是Microsoft推出的最新版本。它与以前的版本截然不同,功能更强大,可与C++等高级语言相媲美。伴随强大功能而来的是Visual Basic 2010更为复杂,这使得通过研究Visual Basic在线帮助、自学创建程序所需技能的时代一去不复返。

针对的读者及组织结构

本书面向那些几乎没有编程经验或选择Visual Basic作为第二编程语言的读者,旨在使读者尽快掌握Visual Basic 2010。作者基于使用Visual Basic编写大型商业应用程序和教授Visual Basic的经验编写本书,希望能够化繁为简,帮助读者掌握所需的知识。很多作者过于专注技术而不是其实际应用。本书致力于帮助读者掌握可立即用于开发项目的实践技巧,读者可将建议和成功经验张贴到www.jamesfoxall.com/forums。

本书分为五部分,每个部分关注使用Visual Basic开发应用的不同方面。这些部分遵循使用Visual Basic开始创建程序的任务流程,建议按顺序阅读。

第一部分“Visual Basic 2010环境”介绍Visual Basic环境,包括如何导航和使用各种Visual Basic工具。另外,读者还将学到一些重要的开发概念,如对象、集合和事件。

第二部分“创建用户界面”阐述如何创建功能完善、引人入胜的用户界面。在这部分,读者将学习窗体和控件(即用户界面元素,如文本框和列表框)。

第三部分“编程”探讨Visual Basic 2010编程细节———需要学习的知识很多。读者将学习如何创建模块和过程以及如何在代码中存储数据、执行循环和做出决策。学习核心编程技巧后,读者将学习面向对象编程和应用程序的调试。

第四部分“使用数据”介绍如何使用文本文件和数据库编程以及自动化 Word 和 Excel等外部应用程序。另外,还将介绍如何对用户的文件系统和Windows注册表进行操作。

第五部分“部署解决方案及其他”介绍如何将创建的应用程序分发到最终用户的计算机中。其中第 24章将从非技术角度概述Microsoft .NET Framework。

以前版本的很多读者提出了如何使本书更好的建议。许多人建议提供一些示例,这些示例建立在前几章的基础之上。本书已尽可能这样做。在本书中,读者将创建一个功能丰富的图片查看器程序,而不是学习独立的概念。首先将创建一个基本的应用程序,然后在程序中添加菜单和工具栏、创建一个“选项”对话框、修改程序以使用Windows注册表和文本文件,并创建安装程序用于将应用程序发布给其他用户。希望这种方式对读者有帮助,让读者在创建真实程序的过程中学习知识。

本书使用的约定

本书使用了一些约定,让读者能够充分利用和参考书中的信息。

“注意”提供有用的信息,读者可以马上阅读,也可阅读完当前话题后再阅读。

“提示”突出那些可以使Visual Basic编程更有效的信息。

“警告”提醒读者注意可能在特定情况下出现的问题或副作用。

为突出新术语,使用斜体显示新术语。

另外,本书对代码和其他文字使用不同的字体。代码使用等宽字体;占位符——用于表示需要在代码中输入实际单词或字符——使用斜体等宽字符;需要用户输入的文本用粗体表示。

菜单选项用>分开。例如,指示读者打开菜单“文件”并选择菜单项“新建项目”时,表示为“选择菜单‘文件’>‘新建项目’”。

书中列出的有些语句过长,无法在一行中容纳。在这种情况下,使用续行字符(下划线)指出下一行是当前语句的一部分。

前进和提高

现在到了学习编程的激动时刻,衷心希望读者阅读本书后,能够得心应手地使用众多Visual Basic工具创建、调试和部署中型Visual Basic程序。虽然还不能成为专家,但读者将对学到的东西之多感到惊讶。希望读者沿通往 Visual Basic专家之路前进时,本书能够帮助确定未来的方向。

我喜欢使用Visual Basic进行编程,有时候发现其回报令人难以置信。希望读者和我一样享受Visual Basic编程。

第1章 全身心投入:Visual Basic 2010编程之旅

第2章 Visual Basic 2010导航

第3章 理解对象和集合

第4章 理解事件

在本章中,读者将学习:

创建一个简单但有一定功能的Visual Basic应用程序;

让用户浏览硬盘;

显示硬盘中的图片;

熟悉一些编程术语;

学习Visual Studio 2010 IDE。

学习一门新的编程语言因其难度可能使人望而却步。如果您从来没有编写过程序,那么输入有些神秘的文本就可以产生精美的、功能强大的应用程序,这种行为对您来说可能就像一种魔法,您可能会想知道如何才能学会需要掌握的东西。答案当然是一步一步来。学习语言的第一步是建立自信。编程的一半是艺术一半是科学。虽然它看起来有点像魔术,但它更像是幻象;当您知道一切是如何发生的后,很多迷雾便会散去,让您能够集中关注那些产生所需结果所必需的机制。

大型商业解决方案也是通过一系列小步骤完成的。阅读本章后,读者将对整个开发过程有所认识,并迈出成为有成就的程序员的第一步。实际上,在后续几章中,将继续完善本章的示例。阅读本书后,读者将构建一个健壮的应用程序,包含可调整大小的窗口、直观的界面(包括菜单和工具栏)、Windows注册表的操作和专业的错误处理机制。

在本章中,您将通过逐步创建一个小而完整的Visual Basic程序来快速了解Visual Basic。大多数入门级编程图书都以创建一个简单的“Hello World”程序开始,但我还没见过一个有用的Hello World程序(它们通常什么都不做,只是将“Hello World”打印到屏幕上———真是有趣)。因此,在这里您将创建一个图片查看器(Picture Viewer)应用程序,可查看计算机上的Windows位图和图标。您将学习如何让用户查找文件以及如何在屏幕上显示选中的图片文件。本章介绍的技巧在很多实际应用程序中都将派上用场,但本章的目标是让读者认识到使用Visual Basic 2010 进行编程是多么富有乐趣。

开始创建Visual Basic 2010程序前,应先熟悉下列术语。

可发布的组件:指项目最后编译的版本。组件可发布给其他人和其他计算机,它们不要求在Visual Basic 2010开发环境(用于创建.NET程序的工具)下运行,但要求.NET运行时,这将在第23章讨论。可发布的组件通常称为程序。在第23章,将学习如何把即将创建的图片查看器程序发布给其他计算机。

项目:指可以编译以创建可发布组件的文件集合。项目的类型有多种,复杂的应用程序可能包含很多项目,如Windows应用项目和支持它的动态链接库(Dynamic Link Library,DLL)项目。

解决方案:组成应用程序或组件的项目与文件的集合。

注意:以前,Visual Basic是一种独立的语言;现在,情况不同了,Visual Basic是.NET 框架的一部分。.NET 框架包含所有.NET 技术,其中包括 Visual Studio.NET(开发工具套件)和公共语言运行时(Common Language Runtime, CLR),该运行时是组成所有.NET应用核心的文件集。在本书中,读者将学到有关这些术语的更多细节。现在,只要知道Visual Basic是.NET家族中的很多语言之一即可。其他的很多语言(如C#)也是.NET语言,也使用CLR,在Visual Studio内开发。

Visual Studio 2010是一个完整的开发环境,被称为集成开发环境(Integrated Development Environment,IDE)。IDE是用于创建应用程序的设计框架;创建Visual Basic项目所需的所有工具都在Visual Basic IDE中。再强调一次,Visual Studio 2010支持使用不同语言进行开发,其中Visual Basic是最受欢迎的一种。环境本身并不是Visual Basic,但在Visual Studio 2010中使用的语言是Visual Basic。要创建Visual Basic项目,首先需要启动Visual Studio 2010 IDE。

要启动Visual Studio 2010,在“开始”>“所有程序”菜单中选择“Microsoft Visual Basic 2010 Express”。如果运行的是完整的.NET零售版,快捷方式的名称可能不同。在这种情况下,在“开始”菜单中找到该快捷方式并单击它,以启动Visual Studio 2010 IDE。

首次启动Visual Studio 2010时,将在 IDE中看到“起始页”选项卡,如图 1.1所示。在这里可打开之前创建的项目或创建新项目。在这次快速之旅中,将新建一个Windows应用程序,因此打开“文件”菜单,再单击“新建项目”,这将打开图 1.2 所示的“新建项目”对话框。

注意:如果“起始页”看起来与图1.1不同,很有可能已经修改了默认设置。第2章将介绍如何改为默认设置。

图1.1 可从Visual Studio的“起始页”打开现有项目或创建新项目

图1.2 可通过“新建项目”对 话 框 创 建 多种.NET项目

“新建项目”对话框用于指定要创建哪种类型的Visual Basic项目(用Visual Basic和.NET框架支持的其他语言可创建多种类型的项目)。图1.2显示的选项有限,这是由于本书使用的是Visual Basic学习版;如果您使用的是Visual Studio完整版,将有众多其他的选项。

通过下列步骤新建一个Windows窗体应用程序。

1.确保选择的是“Windows窗体应用程序”图标;如果不是,单击该图标选中它。

2.“新建项目”对话框的底部是一个“名称”文本框,用于指定要创建的项目名称。在该文本框中输入“Picture Viewer”。

3.单击“确定”创建项目。

提示:创建项目前总是将“名称”文本框设置为有意义的名称,否则要移动或重命名项目时,将需要做更多的工作。

当Visual Basic新建Windows窗体应用程序项目时,它会为应用程序添加一个窗体(空的灰色窗口),让用户可以开始创建界面,如图1.3所示。

注意:在Visual Studio 2010中,窗体是指可显示给用户的窗口的设计视图。

图1.3 新的 Windows 窗体应用程序以一个空白窗体开始;乐趣刚刚开始!

读者的 Visual Studio 2010环境可能与书中显示的不同,这取决于使用的 Visual Studio 2010版本、是否设置过Visual Studio 2010以及其他因素(如显示器的分辨率)。然而,本章讨论的所有元素都存在于所有Visual Studio 2010版本中,如果图中所示的窗口在读者的 IDE中没有显示出来,请使用“视图”菜单显示它。

注意:要创建可在其他计算机上运行的程序,首先创建一个项目,然后将该项目编译成组件,如可执行程序(Executable,用户可运行的程序)或DLL(可供其他程序和组件使用的组件)。编译过程将在第23章详细讨论。现在要记住的是,听到有人说“创建”或“编写”程序时,就像现在正在创建图片查看器程序一样,他们说的是包括将项目编译成可发布的文件的所有步骤及编译本身。

首次运行Visual Studio 2010时,IDE中包含很多窗口,如右边的“属性”窗口,它用于查看和设置对象的属性。除这些窗口外,IDE中还包含很多选项卡,如IDE左边竖直的“工具箱”选项卡(如图1.3所示)。现在尝试一下:单击“工具箱”选项卡以显示“工具箱”窗口(单击选项卡将显示相应的窗口);将鼠标指向选项卡几秒钟也将显示相应的窗口。要隐藏窗口,只需将鼠标从窗口中移走(如果将鼠标指向选项卡来显示窗口的话)或单击其他窗口。要完全关闭窗口,单击窗口标题栏中的“关闭”按钮。

注意:如果通过单击选项卡而不是将鼠标指向选项卡来打开工具箱,工具箱将不会自动关闭。它会一直打开,直到用户单击另一个窗口。

可以调整任何窗口的大小和位置,还可根据需要隐藏和显示它们。第2章将介绍如何设置设计环境。

警告:除非特别指出,否则不要在Visual Studio 2010设计环境中双击。在大部分对象上双击将产生与单击完全不同的结果。如果错误地双击了窗体中的对象(稍后将讨论),将打开“代码”窗口。代码窗口的顶部是一组选项卡:一个用于窗体设计,一个用于代码。单击用于窗体设计的选项卡,可隐藏“代码”窗口并返回窗体。

设计环境右边的“属性”窗口可能是IDE中最重要的窗口,也是最常用的。如果计算机屏幕分辨率设置为800×600,可能每次只能看到几个属性。这样,在创建项目时将难以查看和设置属性。由于篇幅的约束,本书屏幕截图的分辨率设置为800×600,但应该设置尽可能高的分辨率。用Visual Basic 开发应用程序时,推荐使用 1024×768或更高的分辨率,因为这样可看到更多的工作空间。要修改显示设置,可在桌面上单击鼠标右键并选择“属性”。但要记住,最终用户可能使用比开发时更低的分辨率。

在Visual Basic中,几乎所有您与之打交道的都是对象。例如,窗体是对象,可放在窗体中以构建用户界面的元素(如列表框和按钮)也是对象。有很多种对象,它们是根据类型进行分类的。例如,窗体是Form对象,而窗体上的元素为Control对象(控件),第3章将详细讨论对象。有些对象没有物理外观而只存在于代码中,本书后面将介绍这种对象。

警告:读者将发现,本书经常会提到后续章节介绍的内容。在出版界,这称为前向引用。出于某种原因,这常常导致有些读者失去信心。笔者这样做旨在让读者认识到,首次提到某个主题时,并不要求读者对其有全面认识,后面将更详细地介绍它。笔者将尽可能少地进行前向引用,但不幸的是,讲授编程并非完全的线性过程,有时必须提到读者还未完全掌握的主题。在这种情况下,笔者将通过前向引用让读者知道后面将更详细地介绍该主题。

每个对象都有一组特性,被称为属性,而不管对象是否有物理外观。属性定义了对象的特征;每个人都有一定的属性,如身高和头发颜色。Visual Basic对象也有属性,如高度(Height)和背景颜色(BackColor)。创建新对象时,首先需要设置其属性,使对象按预期的显示和工作。要显示对象的属性,在对象的设计器(IDE的主要工作区域)中单击对象即可。

单击默认窗体的任意地方,看“属性”窗口中是否显示了它的属性。在“属性”窗口的下拉列表中将包含窗体的名称:Form1 System.Windows.Forms.Form,其中Form1是对象的名称,System.Windows.Forms.Form是对象的类型。

对于任何新对象,首先应设置的属性是Name(名称)。如果没有显示“属性”窗口,按F4键显示它。滚动到属性列表顶部,找到(Name)属性,如图1.4所示。如果Name属性没有列在最前面,表明“属性”窗口设置为按分类而不是按字母顺序显示属性。可单击属性网格上方的“字母顺序”按钮,使属性按字母顺序显示。

注意:建议将“属性”窗口设置为按字母顺序显示;这样将更容易找到我所说的属性。注意,Name属性总是在列表的开头,表示为(Name)。之所以用括号,是因为括号使属性处于列表的开头,因为按字母顺序排列时,符号在字母前面。

图1.4 添加新对象到项目中时,应首先修改Name属性

保存项目时,为项目及其文件选择名称和位置。用户创建对象时,Visual Basic将根据对象的类型为它指定唯一的通用名称。虽然这些名称可行,但它们的描述性不够,不实用。例如,Visual Basic将窗体命名为Form1,但项目有几十(甚至几百)个窗体的情况很常见,如果所有窗体都只能通过编号(Form2、Form3等)来区分,项目将很难管理和维护。

注意:用户实际使用的是form类(模板),它用于在运行时创建和显示窗体。在本章中,我简单地将它称为窗体,详情请参见第5章。

为更好地管理窗体,给每个窗体取一个描述性名称。Visual Basic允许用户在项目中新建窗体时对其进行命名。这里的默认窗体是Visual Basic创建的,因此用户没有机会给它命名。不仅要重命名窗体,还要重命名窗体文件。按如下步骤修改名称和文件名。

1.单击Name属性,将Form1改为ViewerForm。注意,这并没有改变窗体的文件名,文件名显示在“解决方案资源管理器”窗口(位于“属性”窗口上方)中。

2.右击“解决方案资源管理器”窗口(位于“属性”窗口上方)中的Form1.vb。

3.在弹出的菜单中选择“重命名”。

4.将Form1.vb改为ViewerForm.vb。

注意:这里使用后缀Form表示文件是一个form类。后缀是可选的,但它们在组织项目时很有帮助。

实际上,重命名文件时,窗体的Name属性将自动修改。在以后的示例中,将要求读者修改窗体文件名,这样Name属性将自动修改。这里让读者在“属性”窗口中修改Name属性,旨在说明该窗口的工作原理。

窗体的标题栏中显示的文本是Form1。这是因为创建窗体时,Visual Basic自动将窗体的标题栏设置为窗体的名称,而用户修改窗体名时它并不会改变。标题栏中的文本由窗体的Text属性决定,通过以下步骤修改标题文本。

1.单击窗体,使其属性显示在“属性”窗口中。

2.使用“属性”窗口中的滚动条找到Text属性。

3.将文本改为 Picture Viewer,再按回车键或单击其他属性,窗体标题栏中的文本将发生变化。

现在用户所做的修改还只保存在内存中;如果这时关闭计算机(不要这样做),将丢弃到目前为止所做的所有工作。要养成经常保存工作(将修改保存到硬盘中)的习惯。

单击工具栏中的“全部保存”按钮(一叠磁盘的图案),保存所做的工作。Visual Basic将打开“保存项目”对话框,如图 1.5 所示。文本框“名称”已填好,因为创建该项目时已对其命名。在“位置”文本框中指定要保存项目的位置,Visual Basic将在该路径下使用“名称”文本框中的值(这里是Picture Viewer)创建一个子文件夹。可使用默认路径,也可将其修改为所需的路径。可让Visual Basic创建一个解决方案文件夹,在这种情况下,Visual Basic将在该文件夹中创建解决方案文件,并创建一个子文件夹用于存储项目和实际文件。对大型项目而言,这是一个很方便的功能;但就现在而言没必要这样做,因此取消选中复选框“创建解决方案的目录”,再单击“保存”按钮保存项目。

图1.5 保存项目时,为项目及其文件选择名称和位置

使用过Windows的用户都熟悉图标———表示程序的小图片。图标最常出现在“开始”菜单中,位于其代表的程序名左边。在Visual Basic中,不但可以给程序指定图标,如果愿意,还可给程序中的每个窗体指定唯一的图标。

注意:下面的内容假定读者能够访问本书示例的源文件。这些文件可从http://www.samspublishing.com 下载。也可从笔者的网站 http://www.james foxall.com/books.aspx 下载这些文件并讨论本书。将示例源文件解压缩时,将为每章创建一个文件夹,每章的文件夹中有示例项目的子文件夹。在文件夹Hour 1\Picture Viewer中可找到该示例使用的图标。

读者并非一定要使用笔者为这个示例提供的图标;可以选择任意图标。如果没有可用的图标(或读者是有逆反心理的人),可跳过本节,这不会影响该示例的结果。

要为窗体指定图标,执行以下步骤。

1.在“属性”窗口中,单击Icon属性以选中它。

2.单击Icon属性后,该属性右边将出现一个带三个点的小按钮,单击该按钮。

3.使用弹出的“打开”对话框找到文件PictureViewer.ico或其他图标文件。找到图标后双击它,或单击选中它再单击“打开”按钮。

选好图标后,它将和单词 Icon 一起出现在 Icon 属性中,窗体的左上角也将出现该图标的缩小版本。当该窗体最小化时,Windows任务栏中显示的即为该图标。

接下来修改窗体的Width和Height属性。Width和Height的值都显示在Size属性下;Width在逗号的左边,Height在右边。可修改Size属性中的数字来修改Width或Height属性。这两个值都以像素为单位,也就是说,Size属性为“200, 350”的窗体为 200像素宽、350像素高。要分别显示和调整Width和Height,可单击Size旁边的小加号,如图1.6所示(单击加号后,它将变成减号)。

图1.6 有些属性可展开以显示更具体的属性

注意:像素是计算机显示的度量单位,是屏幕上可见的最小“点”。显示器的分辨率总是用像素表示,如800像素×600像素或1024像素×768像素。将属性增大或降低1像素是可做的最小可视化修改。

在属性名对应的文本框中输入数值,将Width属性改为400、Height属性改为325。要提交所做的修改,可按Tab键或回车键,也可单击其他属性或窗口。屏幕如图1.7所示。

图1.7 “属性”窗口中所做的修改在提交后将马上反映出来

注意:也可通过拖曳窗体的边框来改变它的大小,这将在第2章介绍;属性也可通过代码来修改,这将在第5章介绍。

现在,选择菜单“文件”>“全部保存”或单击工具栏中的“全部保存”按钮(一叠磁盘的图案)以保存项目。

设置窗体的初始属性后,现在通过在窗体中添加对象来创建用户界面。可置于窗体中的对象被称为控件。有些控件有可见的界面,用户可与之进行交互,而另外一些控件对用户总是不可见的。本示例将使用这两种控件。屏幕的左边是标题为“工具箱”的竖直选项卡,单击“工具箱”选项卡显示“工具箱”窗口以显示最常用的控件;如果有必要,单击“公共控件”,如图1.8所示。工具箱包含可用于项目中的所有控件,如标签(Label)和文本框。

图1.8 “工具箱”用于选择构建用户界面的控件

添加控件到窗体后,如果鼠标指针不在工具箱上,“工具箱”将立即关闭。为使工具箱保持可见,可单击“工具箱”标题栏中的图钉图标。

现在不要求您添加控件,但图片查看器界面将包含以下控件。

两个Button控件:在很多Windows程序中可单击的标准按钮。

一个PictureBox控件:用于向用户显示图像的控件。

一个OpenFileDialog控件:执行Windows“打开文件”对话框功能的隐藏控件。

通常,使界面能执行一定功能的最佳方法是,先设计用户界面,然后添加代码。下面几个小节将创建界面。

首先将一个Button控件添加到窗体中。为此,双击“工具箱”中的Button控件。Visual Basic将创建一个新按钮,并将其放在窗体的左上角,如图1.9所示。

图1.9 双击“工具箱”中的控件时,该控件将添加到窗体的左上角

使用“属性”窗口,按如下设置按钮的属性(译者注:在Visual Basic 2010中文版中,默认字体为 9点的宋体,需要扩大按钮才能容纳下“Select Picture”)。记住,按字母顺序查看属性时,Name属性列在最前面,因此不必再在列表中搜寻。

现在创建一个这样的按钮,即用户可通过单击它来关闭图片查看器程序。虽然可通过双击“工具箱”中的Button控件,再添加一个按钮,但这次将通过复制已创建的按钮来添加一个按钮到窗体中。这让您很容易创建这样的按钮,即其大小和其他属性与所复制的按钮相同。

为此,右击“Select Picture”按钮并从弹出的菜单中选择“复制”命令。接下来,在窗体的任意处右击并从窗体的快捷菜单中选择“粘贴”(也可使用键盘Ctrl+C组合键进行复制,用Ctrl+V组合键进行粘贴)命令。新按钮出现在窗体中央,且默认被选中。注意到其所有属性几乎都与原按钮相同,但名称已重新设置。按如下修改新按钮的属性。

最后需要添加到窗体中的可见控件是一个PictureBox控件。PictureBox有很多功能,但其主要的用途是显示图片,这正是本示例要使用的功能。双击“工具箱”中的 PictureBox,将一个PictureBox控件添加到窗体中,然后按如下设置它的属性。

修改属性后,窗体应如图1.10所示。单击工具栏的“全部保存”按钮来保存所做的工作。

图1.10 有用的用户界面不一定要很复杂

到目前为止,所用的控件都放在窗体中,且在应用程序运行时有物理外观。然而,并不是所有控件都有物理外观,这样的控件称为不可见控件(或运行时不可见的控件)。它们不是为直接的用户交互而设计的,而是为程序员设计的,其功能超出了Visual Basic的标准特性。

为让用户选择要显示的图片,需要让用户能够在其硬盘上查找文件。读者可能已经注意到,在每个Windows程序中打开文件时,显示的对话框几乎都一样。要求每个开发人员为标准文件操作编写代码是没有意义的,因此Microsoft通过控件提供了这样的功能,程序员可在项目中使用它。这个控件名为OpenFileDialog控件,可为开发人员节省大量时间,避免为实现该功能而反复编程。

注意:除OpenFileDialog控件外,还有其他控件也提供文件操作功能。例如, SaveFileDialog控件让用户能够指定文件名和路径以保存文件。

现在显示“工具箱”,使用“工具箱”底部的向下箭头来滚动列表,找到OpenFileDialog控件(它在“对话框”分类中),然后双击它,将它添加到窗体中。注意,该控件并不放在窗体中,而是出现在窗体下方的特定位置,如图1.11所示。这是因为OpenFileDialog控件并没有窗体界面可显示给用户。它在有必要时可显示界面(对话框),但它本身并不直接显示在窗体上。

图1.11没有界面的控件显示在窗体设计器下方

选中OpenFileDialog控件,按如下修改其属性。

警告:不要将FileName属性设置为“使其为空”,笔者的意思是将该属性的默认值删除,使其为空。

Filter属性用于限制(过滤)要在“打开文件”对话框中显示的文件类型。过滤器的格式为:描述|过滤器。在管道符号(“|”)前的文本是对文件类型的描述,管道符号后面的文本是用于过滤文件的格式。可再通过管道符号来分割每个“描述|过滤器”,以指定多个过滤类型。在Title属性中输入的文本将出现在“打开文件”对话框的标题栏中。

图片查看器程序的图形界面现在已经完成了。如果单击了“工具箱”中的图钉图标使其一直显示,现在可单击它以关闭“工具箱”。单击工具栏中的“全部保存”按钮保存所做的工作。

为使程序能够执行操作和响应用户交互,必须为程序编写代码。Visual Basic是一种事件驱动的语言,这意味着代码将响应事件而执行。事件可能来自用户,如用户单击按钮触发其Click事件;也可能来自Windows本身(对事件的完整解释请参见第4章)。目前,该应用程序看起来不错,但并不能做任何事情。用户单击Select Picture按钮直到患上腕管综合症,也不会有什么事情发生,因为没有告诉程序当用户单击按钮时要做什么。现在按 F5 键来运行项目,便可以看到这一点。可以单击按钮,但它们不会做任何事情。完成后,关闭窗口返回设计模式。

下面编写代码来实现两项任务。首先,编写代码让用户浏览其硬盘来查找和选择图片文件,然后将文件显示在图片框中(这听起来比实际难得多)。其次,为 Quit 按钮添加代码,使得用户单击该按钮时关闭程序。

要编写的第一段代码允许用户浏览其硬盘,选择一个图片文件,然后在PictureBox控件中查看选择的图片。这段代码在用户单击Select Picture按钮时执行,因此,它应添加到该按钮的Click事件中。

在设计视图中双击窗体中的控件时,该控件的默认事件将显示在代码窗口中。Button控件的默认事件是Click事件,这是因为单击是用户对按钮执行的最常见操作。现在,双击Select Picture按钮,在代码窗口中访问它的Click事件,如图1.12所示。

图1.12 将在这样的窗口中编写所有的代码

您访问事件时,Visual Basic将创建一个事件处理程序,事件处理程序本质上是一个模板过程,可在其中加入事件发生时要执行的代码。鼠标已放在代码过程中,因此,您所要做的便是添加代码。阅读完本书时,读者可以任意添加自己的代码,使应用程序按照您的意愿执行——大部分时候是这样的。现在,只需输入下面列出的代码。

养成为代码添加注释的习惯很重要。因此,读者要输入的第一条语句是一条注释。以撇号打头的语句表示这是注释;编译器不会处理这样的语句,因此可在撇号后输入任意文本。输入如下文本,然后按回车键:

' Show the open file dialog box.

要输入的下一语句触发窗体中OpenFileDialog控件的一个方法。可将方法理解为一种使控件执行某项功能的机制。ShowDialog()方法命令控件显示其“打开文件”对话框,让用户选择文件。ShowDialog()方法返回一个值,指明操作成功与否,然后可将这个值与预定义的结果(DialogResult.OK)进行比较。这时不必关心发生了什么;本书后面将介绍所有这些细节,本章旨在让读者在实践中学习。简单地说,ShowDialog()方法被调用,让用户浏览文件。如果用户选择了文件,将执行更多代码。当然,使用“打开文件”对话框比这个简单的示例中能做的更多,但这条简单的语句足以完成任务。输入下面的语句,并在行尾按回车键(不用担心大小写,Visaul Basic将为您更改大小写):

If ofdSelectpicture.ShowDialog = DialogResult.OK Then

注意:用户输入以 If打头的语句并按回车键后,Visual Basic将自动创建End If语句。如果用户再输入End If语句,将出现两条End If语句,导致代码无法运行。在这种情况下,应删除一条End If语句。第 13章将全面而详细地介绍If语句。

接下来是另一条注释。当前光标位于以 If打头的语句和End If语句之间,输入下面这一条语句,并在行尾按回车键:

' Load the picture into the picture box.

提示:无需按Tab键或使用空格来缩进代码,Visual Basic将自动缩进代码。

下面这条语句位于 If结构中(If语句和End If语句之间),它是真正将图片显示在图片框中的代码。

输入下面的语句:

picShowPicture.Image = Image.FromFile(ofdSelectPicture.Filename)

除显示选定的图片外,应用程序还在标题栏中显示图片的路径和文件名。创建窗体时,您使用“属性”窗口修改窗体的Text属性。要创建动态的应用程序,需要在运行时不断地调整属性,而这是使用代码实现的。插入下面两条语句(在每行行尾按回车键):

' Show the name of the file in the form's caption.

Me.Text = "Picture Viewer(" & ofdselectpicture.FileName & ")"

输入所有代码后,编辑器应如图1.13所示。

图1.13 确保代码和这里显示的完全一致

最后一小段代码是在用户单击Quit按钮时终止程序。为此,需要访问btnQuit按钮的Click事件处理程序。在代码窗口的顶部有两个选项卡。当前选项卡名为ViewerForm.vb*。这个选项卡包含文件名为 ViewerForm.vb 的窗体的代码窗口;另一个选项卡名为 ViewerForm.vb[设计]*,单击该选项卡将从代码视图切换到窗体设计器。如果单击该选项卡时发生错误,表明输入的代码有错误,需要将其编辑成与图 1.13 所示一致。窗体设计器出现后,双击 Quit 按钮来访问其Click事件。

在Quit按钮的Click事件处理程序中输入下列代码,在每行语句末尾按回车键:

' Close the window and exit the application

Me.Close()

注意:Me.Close()语句关闭当前窗体。当程序中最后一个加载的窗体被关闭后,应用程序将完全终止自身的运行。创建更健壮的应用程序时,可能要在终止应用程序前执行各种清理工作,但在这个示例中,只需直接关闭窗体。

现在应用程序完成了。单击工具栏中的“全部保存”按钮(一叠磁盘的图案),然后按F5键运行。也可单击工具栏中右侧的三角形按钮来运行程序,这个按钮类似于DVD播放器中的“播放”按钮,它称为“启动调试”,也可在“调试”菜单中找到。学习键盘快捷键能够加快开发过程,因此推荐在开发时尽量使用快捷键。

运行程序时,Visual Basic界面会改变,您设计的窗体将浮动在设计环境上,如图 1.14所示。

图1.14 在运行模式下,应用程序像在最终用户的计算机上一样运行

这时程序就像一个独立的程序运行于其他用户的机器上一样;您看到的正是用户运行程序(当然是没有Visual Studio 2010设计环境)时所能看到的。单击Select Picture按钮来显示Select Picture对话框,如图 1.15所示。使用该对话框来查找图片文件。找到文件后双击,或单击选中它,然后单击“打开”按钮。选中的图片将显示在图片框中,如图1.16所示。

注意:单击Select Picture按钮时,显示的默认路径取决于最后一次选择的路径,因此可能与图1.15所示的不同。

图1.15 OpenFileDialog控件处理查找文件的所有细节

图1.16 有什么比 1964 年的Fender Super Reverb音箱更漂亮呢?

注意:如果要从数码相机中选择并显示图片,很可能图片的格式是 JPEG,因此需要从“文件类型”下拉列表中选择这种格式。另外,如果图像很大,将只能看到图像的左上角(大小为图片框的大小)。后面将介绍如何调整图像大小来适应图片框的大小,甚至调整窗体大小来显示完整的大图片。

运行程序后,单击Quit按钮返回设计视图。

就这么简单!您刚创建了一个真正的Visual Basic程序。您使用工具箱来创建让用户能够与程序进行交互的界面,并在事件处理程序中编写代码,使程序可以执行一定的功能。这些是使用 Visual Basic开发应用程序的基本技能。甚至最复杂的程序也是使用这些基本方法创建的;创建界面,然后增加代码使程序可以执行。当然,编写正确的代码来实现所要的功能可能很复杂,但现在您已经踏上进程了。

如果仔细看看本书的课程安排,将发现我首先介绍Visual Basic(Visual Studio .NET)环境,接着介绍如何创建界面,然后是如何编写代码。这种安排是有意的。刚开始编写代码时读者可能会有点不安,但编程只是工作的一部分———不要忘记“Visual Basic”中的“Visual”。完成这24章课程后,读者将为程序开发打下坚实的基础。

很快,您将不再需要幕后教您的人了———您自己就是这样的人。

问:是否可显示除BMP和JPG外的其他类型的图片?

答:可以。PictureBox可以显示扩展名为BMP、JPG、ICO、EMF、WMF和GIF的图像。PictureBox甚至可用它支持的任意一种文件类型将图像保存到文件中。

问:是否能够用其他控件来显示图片?

答:PictureBox 是只显示图像时所用的控件。但是,还有很多其他控件允许您将图片作为控件的一部分来显示。例如,可通过将按钮的Image属性设置为一个有效的图片,在按钮控件中显示它。

1.哪种Visual Basic项目创建标准的Windows程序?

2.IDE中的哪个窗口用于修改窗体或控件的属性(位置和大小等)?

3.如何访问控件的默认事件(代码)?

4.应设置图片框的哪个属性来显示图像?

5.按钮控件的默认事件是什么?

1.Windows窗体应用程序。

2.“属性”窗口。

3.双击设计器中的控件。

4.Image属性。

5.Click事件。

1.修改图片查看器程序,使用户也能查找和选择GIF文件(提示:修改OpenFileDialog控件的Filter属性)。

2.创建一个新项目,并添加两个按钮,让它们垂直排列。然后调整它们的位置,使其水平排列。

在本章中,读者将学习:

导航Visual Basic;

使用Visual Studio 2010起始页打开和创建项目;

显示、隐藏、停靠和浮动设计窗口;

定制菜单和工具栏;

使用工具箱将控件添加到窗体中;

利用“属性”窗口查看和修改对象属性;

处理组成项目的多个文件;

如何获得帮助。

拓展Visual Basic知识的关键在于尽快熟悉Visual Basic的设计环境。正如工匠无需多考虑如何将钉子锤进木板中一样,完成保存项目、创建新窗体和设置对象属性等操作,应该做到得心应手。越熟悉Visual Basic工具,就越能将精力集中在用这些工具创建应用程序上。

在本章中,读者将学习如何通过移动、停靠、浮动、隐藏和显示设计窗口来定制设计环境,以及如何定制菜单和工具栏。熟悉环境后,将学习项目及组成项目的文件(进一步讨论第1章介绍的内容),学习使用最常用的设计窗口。最后,还将学习遇到问题时如何获取帮助。

默认情况下,Visual Basic 2010起始页如图 2.1所示,它是启动Visual Basic时最先看到的页面(如果还没有运行Visual Basic,现在就启动它)。Visual Basic 2010起始页是使用Visual Basic执行任务的入口,在这里可以打开以前编辑的项目、创建新项目以及获得帮助。

图2.1 起始页是所有.NET语言的默认入口

起始页由三个框组成。左上角的“最近使用的项目”框用于创建新项目以及打开现有的项目。要创建新项目,单击“新建项目”链接,这将打开图2.2所示的“新建项目”对话框。用户看到的模板列表可能不同,这取决于安装的是Visual Studio .NET系列中的哪种产品。当然,在本书中我们只关心Visual Basic项目类型。

图2.2 使用“新建项目”对话框来创建新的Visual Basic项目

注意:用Visual Basic可创建很多种项目,但本书主要关注Windows窗体应用程序的创建,这是Visual Basic 2010学习版中最常见和最主要的应用程序类型。读者也将学到其他几种类型,但提到创建新项目时,确保选择的是Windows窗体应用程序,除非特别说明了是其他类型。

创建新项目时,确保在“名称”文本框中输入一个名称,然后单击“确定”或双击模板图标。这确保项目创建时具有合适的路径和文件名,避免以后再修改这些属性。指定名称后,可通过双击代表项目模板类型的图标,或单击选中图标再单击“确定”来创建新项目。完成操作后,“新建项目”对话框将关闭,并创建了一个所选类型的新项目。

项目刚被创建时,项目文件是虚拟的——它们并没有保存到硬盘上。首次单击“保存”或“全部保存”时,将提示您指定保存项目的路径。默认使用项目的名称作为项目的文件夹名称,但所选的路径取决于最后一次创建的项目。如果读者是开发团队的成员之一,可能选择将项目放到共享硬盘上,这样别人也可以访问源文件。

注意:任何时候都可通过选择菜单“文件”>“新建项目”来创建新项目(不仅是在启动Visual Basic时)。创建或打开项目时,当前项目将关闭,但Visual Basic会询问是否要在关闭前保存对当前项目所做的修改。

输入项目名称并单击“确定”后,Visual Basic将创建项目。这时还没有任何文件被保存到硬盘上,直到用户单击工具栏中的“保存”或“全部保存”(或选择相应的菜单项)。

时间一长,用户打开现有项目的次数将比创建新项目的次数多。从Visual Studio起始页打开项目有如下两种方法。

如果是最近打开过的项目,项目名称会出现在起始页左上角的“最近使用的项目”框中(如图 2.1中的Picture Viewer)。显示的项目名称是创建时指定的,因此为项目取一个描述性名称很重要。单击项目名称将打开该项目。我猜95%的时候,用户会使用这种方法。

要首次打开项目(如打开示例项目),单击 Visual Basic 2010起始页上的“打开项目…”链接。这将显示一个标准的对话框,用于查找和选择项目文件。

注意:和创建新项目一样,可在任何时候通过选择菜单“文件”>“打开项目”来打开现有的项目,而不仅仅是刚启动Visual Basic后。记住,打开其他项目将导致当前项目关闭。如果用户对当前项目做了修改,将有机会保存所做的修改,然后再关闭该项目。

用户可定制Visual Basic的很多界面元素,如窗口和工具栏,以便更高效地工作。现在打开“文件”菜单并单击“新建项目”来创建一个新的Windows窗体应用程序,该项目用来演示如何定制设计环境。将项目命名为Environment Tutorial,然后单击“确定”创建项目。这个练习不会创建可复用的产品,但将帮助读者学习如何导航设计环境。读者的屏幕应如图2.3所示。

注意:读者的屏幕可能和图2.3不完全一致,但应该很接近。阅读本章后,读者将能够修改设计环境的外观,使其与该图一致;或将其修改为读者偏好的配置。

图2.3 默认的Visual Basic 2010集成开发环境

使用设计窗口

设计窗口(如图 2.3 所示的“属性”和“解决方案资源管理器”窗口)提供了构建复杂应用程序的功能。读者和同事的工作台并不会完全一样,同样,读者的设计环境和其他人的也不一定相同。

设计窗口可处于如下四种主要状态。

关闭:窗口不可见。

浮动:窗口在IDE上浮动。

停靠:窗口紧贴IDE的边缘,图2.3的“解决方案资源管理器”和“属性”窗口是停靠的。

自动隐藏:窗口是停靠的,但不使用时自动隐藏(如“工具箱”)。

1.显示和隐藏设计窗口

设计窗口被关闭时,它不出现在设计环境中。关闭和自动隐藏之间是有区别的,稍后将讨论。要显示被关闭或隐藏的窗口,从“视图”菜单中选择对应的菜单项。例如,如果“属性”窗口没有显示在设计环境中,可通过选择菜单“视图”>“其他窗口”>“属性窗口”(或按键盘快捷键F4)来显示它。需要使用设计窗口却找不到时,可使用“视图”菜单来显示。要关闭设计窗口,只需单击窗口的“关闭”按钮(标题栏右端的“×”按钮),这和关闭普通窗口一样。

2.浮动设计窗口

浮动的设计窗口是浮动在工作空间的可见窗口,如图 2.4 所示。浮动窗口就像典型的应用程序窗口,可通过拖曳将其放到任意地方,在使用了多个显示器的情况下,甚至可以放到其他显示器上。浮动窗口除可移动外,还可拖曳边框来修改其大小。要使窗口浮动,可单击停靠的窗口的标题栏,然后拖曳使其离开当前停靠的边缘。

图2.4 浮动窗口显示在设计环境上

3.停靠设计窗口

默认情况下,可见窗口是停靠的。停靠窗口紧靠着工作区域或其他窗口的两边、顶部或底部。例如,在图2.3中,“属性”窗口停靠在设计环境的右边(在图2.4中,它是浮动的)。要使浮动窗口变为停靠窗口,将浮动窗口的标题栏拖到设计环境的边缘,放在要停靠窗口的地方。拖动窗口时,屏幕上将出现一个菱形指南针(如图2.5所示)。在菱形指南针的某个图标上移动鼠标,Visual Basic将显示一个蓝色矩形,指出此时如果松开鼠标,窗口将停靠的位置。这是停靠窗口的简便方法。也可以直接将窗口拖到一边,这时也将出现同样的蓝色矩形。这个矩形将“跟踪”停靠的位置。如果此时松开鼠标,窗口将停靠。虽然这难以解释,但做起来非常容易。

图2.5 菱形指南针使停靠窗口很容易

注意:可以通过拖曳停靠窗口上与停靠边平行的边框来调整窗口的大小。如果两个窗口都停靠在同一边,拖曳两者之间的边框将放大一个窗口而缩小另一个窗口。

为试验这一点,首先需要将一个已经停靠的窗口变为浮动的。要使窗口浮动,可拖曳停靠窗口的标题栏使其离开停靠边缘。注意这并不适用于自动隐藏(稍后将解释)的窗口。通过以下步骤来停靠和浮动窗口。

(1)确保“属性”窗口当前是显示的(如果不是,按F4显示它)。右击该窗口的标题栏,在弹出的菜单中取消“自动隐藏”(如果该菜单项被选中),以确保“属性”窗口不是自动隐藏的。

(2)拖曳“属性”窗口的标题栏,使窗口离开停靠的边缘(将它往左边拖曳)。当窗口离开停靠边缘时,松开鼠标按键。这时“属性”窗口应变为浮动的。

(3)拖曳窗口的标题栏,将窗口拖向设计环境的右边。当菱形指南针出现时,将鼠标放在按钮图标上(如图2.5所示)。将看到一个蓝色的矩形,指出“属性”窗口将停靠的位置。松开鼠标按键,使窗口停靠。

提示:如果希望不管浮动窗口被拖到什么位置都不会停靠,可右击窗口的标题栏,在弹出菜单中选择“浮动”。要使窗口能够停靠,右击标题栏并选择“停靠”。

4.自动隐藏设计窗口

Visual Basic窗口能够在不使用时自动隐藏。虽然开始时用户对此可能觉得有点不安,但习惯后,这将是一种比较高效的工作方式;这样工作空间将空出很多,且只要将鼠标指向设计窗口它就将自动打开。设为自动隐藏的窗口总是停靠的;浮动窗口不能设为自动隐藏。当窗口自动隐藏时,它显示为停靠边缘上的一个选项卡,就像最小化的应用程序放在Windows任务栏中一样。

在设计环境的左边,有一个标题为“工具箱”的竖直选项卡。该选项卡代表一个自动隐藏的窗口。要显示自动隐藏的窗口,将鼠标移动到代表窗口的选项卡。将鼠标指向选项卡时, Visual Basic将显示该设计窗口,让用户能够使用其功能。将鼠标从窗口移开时,窗口将自动隐藏,这就是名称“自动隐藏”的由来。要使窗口自动隐藏,右击其标题栏,然后在弹出菜单中选择“自动隐藏”;也可单击标题栏中“关闭”按钮旁边的图钉图标,将窗口设置为自动隐藏状态。

使用前面介绍的方法,可以各种方式定制设计环境的外观。没有最优配置,不同的配置适用于不同的项目和不同的开发阶段。例如,我设计窗体的界面时,希望工具箱保持可见状态,但不妨碍我工作,因此常将它设置为浮动的,或关闭它的自动隐藏属性,将它停靠在设计环境的左边。然而,当大多数界面元素已经添加到窗体中后,我希望将注意力集中在代码上。这时我将工具箱停靠,并将它设置为自动隐藏:需要时能找到它,不需要时它将隐藏而不会妨碍我。不用害怕配置设计窗口,应该根据需要对其进行修改。

工具栏是几乎所有Windows程序中用于快速执行功能的最主要元素(读者可能想将它加到程序中,第9章将介绍如何做)。每个工具栏按钮都有对应的菜单项,工具栏中的按钮其实是与之对应的菜单项的快捷方式。使用Visual Basic 2010进行开发时,为最大程度地提高效率,应熟悉可用的工具栏。随着技能的提高,读者将能够定制现有的工具栏,甚至根据工作方式创建自己的工具栏。

Visual Basic 包含很多创建项目时可使用的内置工具栏。到目前为止,本章课程中的大部分示意图都包含一个工具栏:标准工具栏。读者可能希望总是显示该工具栏。

新的Visual Basic开发人员最常使用的工具栏是标准工具栏、文本编辑器和调试工具栏,这几个工具栏都将在本章的课程中介绍。用户也可以创建自定义的工具栏,在其中包含所有您认为必要的功能。

要显示或隐藏工具栏,可选择菜单“视图”>“工具栏”,这将显示一个可用工具栏列表。当前显示的工具栏前面有一个勾号,如图2.6所示。单击工具栏的名称可切换其可见状态。

图2.6 隐藏或显示工具栏使工作空间更高效

提示:也可右击任何已显示的工具栏来快速打开可用工具栏列表。

可以停靠和取消停靠Visual Basic设计窗口,工具栏也是这样。然而,与设计窗口不同, Visual Basic工具栏处于停靠状态时并没有可以单击或拖曳的标题栏。每个停靠的工具栏有一个拖曳手柄(drag handle):左端垂直排列的一系列点。要使工具栏浮动(取消停靠),单击并拖曳手柄,将其拖离工具栏停靠的边缘。当工具栏浮动时,将有一个标题栏,将标题栏拖曳到边缘可使工具栏停靠,这与停靠设计窗口的方法一样。

提示:一种停靠浮动工具栏或浮动窗口的快捷方式是双击其标题栏。

虽然停靠的工具栏的大小不能改变,但浮动工具栏的大小是可以调整的(浮动工具栏像其他正常窗口一样)。要调整浮动工具栏的大小,将鼠标移到要调整的一边,单击并拖曳边框。

IDE提供了一些方便的工具来创建应用程序的图形用户界面(GUI)。大多数GUI由一个或多个窗体(窗口)组成,窗体包含各种元素,如文本框、列表框和按钮。工具箱用于将控件放到窗体上。图 2.7是刚打开或创建Visual Basic项目时的默认工具箱。这些控件将在第 7章和第8章详细讨论。

图2.7 标准工具箱包含很多可用于创建健壮界面的控件

可以使用下列四种方法之一将控件添加到窗体中。

在工具箱中,单击代表要放到窗体中的控件的工具,然后在窗体中单击并拖曳来指定要放置的位置,这实际上画出的是控件边框。开始拖曳的位置为控件的左上角,松开鼠标的位置是控件的右下角。

双击工具箱中所需的控件类型。双击工具箱中的控件时,一个所选类型的新控件将放到窗体的左上角——如果窗体被选中的话。如果这样做时有一个控件被选中,新控件将出现在选中控件的右下方。控件的高度和宽度被设置为所选控件类型的默认大小。如果控件是运行时控件,如第1章的OpenFileDialog控件,它将出现在窗体下方。

从工具箱中拖曳控件,并将其放到窗体中。如果将鼠标指向窗体几秒钟,工具箱将消失,然后可以将控件放在任何位置。

右击现有的控件并选择“复制”,然后右击窗体并选择“粘贴”来创建控件的副本。

提示:如果读者喜欢通过单击和拖曳来绘制控件,建议将工具箱停靠在设计环境的右边或底部,或者将它设为浮动的。工具箱停靠在左边将干扰控件的绘制,因为它覆盖了部分窗体。

在工具箱的每个分类中,第一项都是“指针”,它其实并不是一个控件。若选择“指针”项,设计环境将处于选择模式而不是创建新控件的模式。当“指针”项被选中时,可单击窗体中的控件,这将在“属性”窗口中显示其所有属性。

开发项目的界面时,可能花很多时间通过图 2.8 所示的“属性”窗口查看和设置对象的属性。“属性”窗口由四部分组成:

一个对象下拉式列表;

一个属性列表;

一组用于改变属性网格外观的工具按钮;

一个所选属性的描述区域。

图2.8 使用“属性”窗口查看和修改窗体及控件的属性

“属性”窗口顶部的下拉式列表包含当前窗体和窗体上所有对象(窗体的控件)的名称。要查看控件的属性,从下拉式列表中选择它,或在窗体中单击它。记住,仅当工具箱中的“指针”项被选中时,单击控件才能选中它。

“属性”窗口的前两个按钮(“按分类顺序”和“字母顺序”)让用户能够选择查看属性的方式。如果选择“字母顺序”,“属性”窗口中的属性按字母顺序排列。单击“按分类顺序”按钮,则所有选中的对象属性按分类排列。例如,“外观”分类包含诸如BackColor和BorderStyle等属性。对属性进行操作时,选择最适合的视图,并在这两种视图之间切换。

“属性”窗口中的“属性”面板用于查看和设置所选对象的属性。设置属性有以下几种方法:

输入一个值;

从下拉式列表中选择一个值;

对于属性特定的选项,单击“生成”(Build)按钮。

注意:很多属性可用多种方法来修改。例如,颜色属性提供了一个颜色下拉式列表,但用户可以直接输入表示颜色的数值。

为深入了解如何修改属性,执行以下步骤。

1.双击工具箱中的TextBox,添加一个新文本框到窗体中。接下来将修改这个新文本框的一些属性。

2.单击“属性”窗口中的“(Name)”属性以选中它。如果属性是按字母顺序排列的,它将在列表的开头,而不是将其视为以字母N打头。为文本框指定名称:txtComments。

3.单击BorderStyle属性,并试着输入Big。无法输入这个词;BorderStyle属性只支持从列表中选择的值,但用户可输入列表中存在的值。选择 BorderStyle 属性时,在值的那列中将出现一个下拉式列表。单击箭头可显示BorderStyle属性支持的值。选择FixedSingle,并注意文本框的外观发生了什么样的变化。为使文本框看起来像三维的,打开下拉式列表,并选择Fixed3D。

注意:如果显示器采用了Windows 7、Vista或Windows XP主题,控件将不会有3D外观——它们将是平面的,带淡蓝色边框。笔者喜欢这种更新的界面,因此本书所有的截图都是在Windows XP主题下截取的。

4.选择BackColor属性,输入 guitar,然后按Tab键来提交输入。Visual Basic 将显示一条消息,指出该属性值无效。这是因为虽然用户可以输入文本,但只限于特定的值。对于BackColor,值必须是颜色名或特定范围内的数值。清除输入的文本,单击BackColor属性的下拉箭头,从下拉式列表中选择一种颜色。

5.选择Font属性。注意,这时将出现一个“生成”按钮(带三个小点的小按钮)。单击“生成”按钮,将出现一个与所选属性相关的对话框。在这里,出现的对话框用来设置文本的字体,如图2.9所示。单击“生成”按钮时,不同的属性将显示不同的对话框。修改完字体后关闭窗口。

图2.9 字体对话框让用户能够修改控件中文本的外观

6.找到 Size 属性,注意到它旁边有一个加号。这表示该属性有一个或多个子属性。单击加号将该属性展开,可以看到Size属性由Width和Height组成。

单击“属性”窗口中的属性,很容易知道属性要求的输入值类型。

在可接受的输入值方面,与颜色相关的属性有其独特之处,但它们的设置方法都相同。在Visual Basic中,颜色用一个三元组表示,每个数字都在 0~255。每个数字分别代表颜色的红、绿、蓝(RGB)分量。例如,“0,255,0”表示纯绿色,“0,0,0”表示黑色,而“255,255,255”表示白色。在有些情况下,颜色也可用特定名称来表示。

在“属性”窗口中,每个颜色属性都有一个彩色矩形,矩形的颜色就是为该属性设置的颜色。矩形的旁边显示了文本,文本要么是颜色的名称,要么是定义颜色的 RGB 值。在颜色属性中单击,将出现一个下拉箭头,但单击该箭头打开的下拉式列表不是典型的下拉式列表。图2.10显示了颜色属性的下拉式列表的“系统”选项卡。

图2.10 颜色下拉式列表让用户能够从三套颜色方案(“自定义”、“Web”和“系统”)中选择

颜色下拉式列表由三个选项卡组成:“自定义”、“Web”和“系统”。大多数颜色属性默认都使用系统颜色。第5章将详细介绍系统颜色。这里只提一下,不同计算机的系统颜色是不同的;它们由用户指定:右击桌面,然后从弹出菜单中选择“属性”进行设置。如果希望使用的颜色是用户设置的系统颜色之一,可使用系统颜色。颜色属性被设置为一种系统颜色时,系统颜色的名称将出现在属性面板中。

“自定义”选项卡(如图 2.11 所示)用于指定特定颜色,而不管用户的系统颜色设置如何;系统颜色的修改不会影响该属性。“自定义”选项卡的调色板中列出了最常用的颜色,但您可指定任何颜色。

注意:在各种调色板中,可显示的颜色数由显卡可产生的颜色数决定。如果显卡不支持足够的颜色,有些颜色将是仿色,也就是说它们将显示为一些颜色点而不是真正的颜色。开发应用程序时请记住:如果用户的显卡能力不足,在您的计算机上看起来好好的颜色在用户的显示器上将变得一团糟。

图2.11 颜色下拉式列表的“自定义”选项卡让用户能够定义任意颜色

“自定义”调色板最下面两行用于调制颜色。要给空的颜色槽指定颜色,右击这两行中的颜色槽,将弹出“Define Color”对话框,如图2.12所示。使用其中的控件创建所需的颜色,然后单击“Add Color”将颜色添加到调色板中选定的颜色槽。另外,该自定义颜色将自动分配给当前属性。

图2.12 “Define Color”对话框让用户能够创建自己的颜色

“Web”选项卡用于为 Web 应用程序从浏览器能够显示的颜色列表中选择颜色。然而,即使创建的不是Web应用程序,也可使用这些颜色。

属性的含义或作用并不总是很明显,特别是对于新的Visual Basic用户来说。“属性”窗口底部的说明区域显示了对所选属性的简单说明。要查看说明,单击属性或属性值区域。要获得属性的更完整说明,可单击属性选中它,然后按F1显示有关该属性的帮助。

任何时候在“属性”窗口的任意处(除属性值栏或标题栏)右击,然后在“属性”窗口的快捷菜单中选择“说明”,可以隐藏或显示“属性”窗口的说明区域。每次进行这样的操作时,都将说明区域的状态在可见和隐藏之间切换。要修改说明区域的大小,可单击并拖曳它和属性面板之间的边框。

要高效地创建界面和编写代码,必须理解Visual Basic 2010项目由什么组成以及如何在项目中添加和删除各种组件。在本节中,读者将学习“解决方案资源管理器”窗口以及如何使用它来管理项目文件。读者还将学习项目和项目文件的一些细节,包括如何修改项目的属性。

开发项目时,项目将变得越来越复杂,它们通常包含很多对象,如窗体和模块(代码组)。每个对象都用硬盘上的一个或多个文件来定义。此外,还可以构建由多个项目组成的复杂解决方案。图2.13所示的“解决方案资源管理器”窗口是管理或简单或复杂的解决方案包含的所有文件的工具。使用“解决方案资源管理器”,可以添加、重命名和删除项目文件,还可以选择对象以查看它们的属性。如果“解决方案资源管理器”窗口没有显示,选择菜单“视图”>“其他窗口”>“解决方案资源管理器”显示它。

图2.13 使用“解决方案资源管理器”来管理组成项目的所有文件

为更好地理解“解决方案资源管理器”窗口,执行以下步骤。

1.从“文件”菜单中选择“打开项目”,查找前一章创建的Picture Viewer程序。如果出现提示,不保存当前项目。

2.打开Picture Viewer项目。要选择的文件位于项目创建时Visual Basic创建的Picture Viewer文件夹中。文件的扩展名为.vbproj(表示Visual Basic解决方案)。如果提示是否要保存当前项目,选择“否”。

3.选择“解决方案资源管理器”中的 Picture Viewer。这时,靠近窗口顶部出现一个按钮,该按钮看起来像一张纸,且工具提示为“显示所有文件”,如图2.14所示。单击该按钮,“解决方案资源管理器”将显示项目中的所有文件。

图2.14 单击“显示所有文件”来显示辅助文件信息

注意:有些窗体和其他对象可能由多个文件组成。默认情况下,Visual Basic隐藏用户不直接操纵的项目文件。如果单击ViewerForm.vb左边的加号,将看到名为ViewerForm.resx和ViewerForm.Designer.vb的子项。第5章将介绍这些额外的文件;现在只需再次单击“显示所有文件”按钮将这些相关的文件隐藏即可。

双击“解决方案资源管理器”中列出的任何对象,可使用默认视图查看该对象。每个对象都有一个默认视图,但可能有多个视图。例如,窗体有窗体设计视图和代码视图。默认情况下,在“解决方案资源管理器”中双击窗体将显示窗体的设计视图,在该视图中可以操纵窗体的界面。

读者已经知道一种访问窗体背后的代码的方法:双击对象访问其默认事件处理程序。需要经常访问窗体的代码,而不增加新的事件处理程序。这样做的一种方法是使用“解决方案资源管理器”。在“解决方案资源管理器”中选择了一个窗体时,“解决方案资源管理器”窗口的顶部将出现另外两个按钮,分别用于显示代码编辑器和窗体设计器。

经常需要使用“解决方案资源管理器”,因此可将它停靠在一边,并设置为自动隐藏或始终可见。“解决方案资源管理器”窗口是Visual Basic中最容易掌握的工具,用户必须能够得心应手地使用它。

项目是使用Visual Basic创建的。通常,“项目”和“程序”可互换使用,如果读者理解其中的重要区别,这并不是大问题。项目是构成程序或组件的源文件集合,而程序是将源文件编译成Windows可执行文件(.exe)等而创建的二进制文件。项目总是包含一个主项目文件,还可以包含任意数量的其他文件,如窗体文件或类模块文件。主项目文件存储关于项目的信息(如组成项目的所有文件)以及定义项目各个方面的属性(如将项目编译成程序时使用的参数)。

那么,什么是解决方案呢?随着读者技能的提高和应用程序的复杂度增大,将必须构建多个项目,它们协同工作来实现目标。例如,可能需要构建一个自定义的用户控件,如设计其他项目时使用的自定义数据网格,也可能将复杂应用程序的业务逻辑分割成独立的组件,运行在不同的服务器上。用于实现这些目标的所有项目被统称为解决方案。因此,在最简单的情况下,解决方案只不过是一组项目。

提示:仅当项目彼此相关时才将它们组合成解决方案。如果有多个项目,但每个项目都是独立的,应将每个项目作为独立的解决方案。

当您保存项目时,Visual Basic将为您创建解决方案文件。解决方案文件的扩展名为.sln,可以像打开项目文件那样打开解决方案文件。如果解决方案只包含一个项目,则打开解决方案文件和打开项目文件的效果相同。然而,如果需要同时处理多个项目,则应打开解决方案文件。

前面提到,项目总是包含一个主项目文件,还可能包含一个或多个辅助文件,如构成窗体的文件或代码模块。在项目中创建和保存对象时,将在硬盘上创建和保存一个或多个相应的文件。为Visual Basic 2010源对象创建的每个文件都有.vb扩展名,表示它定义了一个Visual Basic对象。务必用容易理解的名称来保存对象,否则当项目规模增大时将导致混乱。

注意:在以前的Visual Basic版本(6.0或更早的版本)中,可以根据扩展名确定项目文件定义的对象类型。例如,窗体文件的扩展名为.frm。遗憾的是,现在情况并非如此,因此用户必须小心地给文件指定唯一的名称。

组成项目的所有文件都是文本文件。有些对象需要存储二进制信息,如用于表示窗体BackGroundImage属性的图片。二进制数据存储在XML文件中(也是文本文件)。如果窗体包含图标,则有一个定义该窗体(其大小、包含的控件以及代码)的文本文件,还有一个关联的资源文件,其文件名与窗体文件相同,但扩展名为.resx。这个辅助文件的格式为XML,它包含创建窗体所需的所有二进制数据。

注意:要查看窗体的源文件,可使用记事本来打开。但不要保存对文件所做的任何修改,否则它将不可用。

下面是在项目中可能用到的一些组件。

模块:模块用于存储不与特定窗口相关联的代码过程。

类模块:类模块是一种特殊模块,让用户能够创建面向对象的应用程序。在本书中,读者将学习如何使用面向对象语言进行编程,但大部分时候是学习如何使用 Visual Basic提供的对象。在第16章中,读者将学习如何使用类模块来创建自己的对象。

窗体:窗体是构成应用程序界面的可视化窗口。窗体用一种特殊的类模块定义。

用户控件:用户控件(以前称为ActiveX控件,再以前是OLE控件)是用于其他项目的窗体上的控件。例如,用户为合同管理器创建一个包含日历界面的用户控件。创建用户控件要求有熟练的编程技能,因此本书不介绍它们。

和控件等其他对象一样,Visual Basic项目也有属性。项目有很多属性,其中的一些与高级功能相关,本书不介绍它们。然而,读者需要知道如何访问项目属性以及如何修改一些常用的属性。

要访问项目的属性,在“解决方案资源管理器”中右击项目名称Picture Viewer,并在弹出菜单中选择“属性”;也可双击“解决方案资源管理器”中的项目名称。现在执行这两种操作之一。

项目的属性显示在一组竖直排列的选项卡中,如图2.15所示。

图2.15 项目属性用于配置整个项目

在本书后面,必要时将在介绍其他内容时,介绍“项目属性”对话框中的选项卡和选项。请查看Picture Viewer的属性,但现在不要做任何修改。要关闭“项目属性”对话框,可单击IDE中选项卡区域右上角的小“×”按钮,也可单击其他选项卡。

启动Visual Basic 2010并创建新的Windows窗体应用程序项目时,Visual Basic只为项目创建一个窗体。然而,项目可包含更多窗体。可根据需要创建新窗体或将现有窗体添加到项目中(是不是感觉到了强大的功能?)。还可创建和添加代码模块、类及其他类型的对象。

在项目中添加新的或现有的对象有三种方法:

从“项目”菜单中选择对应的菜单项;

单击标准工具栏中的“添加新项”按钮右边的下拉箭头,然后在打开的下拉式列表中选择对象类型,如图2.16所示;

在“解决方案资源管理器”窗口中右击项目名称,然后从弹出菜单中选择“添加”,这时将出现一个子菜单,从中可以选择对象类型。

图2.16 单击该下拉箭头是将对象添加到项目中的三种方法之一

从这些菜单中选择要添加的对象类型后,将出现一个对话框,其中列出了可添加到项目中的对象;所选的项类型默认被选中,如图2.17所示。只要对对象进行命名,然后单击“添加”按钮,就将创建一个所选类型的新对象。要创建其他类型的对象,单击该类型以选中它,然后对其进行命名,并单击“添加”按钮。

图2.17 不管选择的是哪个菜单项,都可以通过该对话框添加任何类型的对象

将新窗体和模块添加到项目中很容易,且添加的数量不限。当项目变得越来越复杂时,用户将越来越依赖于“解决方案资源管理器”来管理项目中的所有对象。

有时也需要将对象从项目中删除,虽然这并不像添加项目文件那样经常发生。从项目中删除对象比添加对象还要简单。要删除对象,在“解决方案资源管理器”中右击该对象,然后选择“删除”。这不仅会将对象从项目中删除,也会将源文件从硬盘上删除。

编程很复杂。所有东西都互相关联,因此很难将每个编程概念分开然后用线性方式加以说明。相反,在学习一个主题的过程中,通常在学到另一个主题前就需要接触它。正如第 1章指出的,笔者尽量避免这种前向参考,但有些概念需要读者首先熟悉一下。这些主题在对应的章节中都将介绍,但阅读本书之前读者必须至少听说过。

变量是代码中用于保存值的元素。例如,可以创建一个保存用户名或用户年龄的变量。每个变量(存储实体)都必须在使用前创建。创建变量的过程称为声明变量。另外,每个变量被声明为保存某种特定类型的值,例如用于保存人名的文本(称为字符串)或用于保存年龄的数值。下面是一个变量声明的例子:

Dim strFirstName As String

这条语句创建一个名为strFirstName的变量,其类型为String,也就是说它可以存储赋给它的任何文本。变量的内容可以根据需要进行修改。

首先要记住的是,变量是存储位置,使用前必须声明,它存储特定类型的数据。

编写Visual Basic代码时,将代码放在过程中。过程是一组执行特定功能的代码语句。可在过程的代码中调用另一个过程。例如,可以创建一个过程来统计订单中的商品总数,用另一个过程来计算订单的销售税款。过程分两种:一种是不返回值的过程,另一种是返回值的过程。另外,有些过程允许将数据传递给它们。例如,上面提到的税款计算过程可能允许调用它的语句将总金额传递给它,然后它使用这个金额来计算税款。当过程从调用代码那里接受数据时,这些数据称为参数。过程并不一定要接受参数。

不返回值的过程使用关键字Sub声明,如下例所示:

Public Sub MyProcedure()

 ' The procedure's code goes here.

End Sub

声明有返回值的过程时使用关键字Function。另外,在过程名的后面还指定了数据类型,它表示过程返回的数据类型:

Public Function MyProcedure() As String

 ' The procedure's code goes here.

End Function

请注意As String。关键字As用于指定一种数据类型。在这个例子中,该函数返回一个字符串,这是文本。

如果过程接受参数,参数将位于括号中,同样也需要使用As来指定接受的数据类型:

Public Function CalculateTax(dblItemTotal As Double) As String

 ' The procedure's code goes here.

End Function

MessageBox.Show()

读者肯定熟悉Windows消息框,它是用于显示消息给用户的小对话框,如图2.18所示。Visual Basic 2010 提供了一种显示这种消息的简单方式:MessageBox.Show()语句。下面是MessageBox.Show()语句的最简单形式:

MessageBox.Show("This is a standard message box")

图2.18 Visual Basic可以轻松显示简单消息框

在本书中,将经常用到消息框,第17章将详细介绍它。

虽然Visual Basic被设计得尽可能直观,但有时用户在执行某项任务时也需要帮助。坦率地说,Visual Basic并不如以前的版本直观——与其强大功能和灵活性伴随而来的是复杂性。不管用户的知识有多丰富,由于Visual Basic如此复杂,包含如此丰富的功能,用户有时肯定需要使用帮助,尤其是编写Visual Basic代码时;用户并不总是知道所需的命令及其语法。幸运的是,Visual Basic提供了全面的帮助功能;不幸的是,它并没有希望的那样全面。

要在设计环境中访问帮助,可按 F1键。通常,按 F1时,Visual Basic将直接显示与用户正在执行的工作相关的帮助主题。这被称为上下文帮助,它很管用。例如,编写Visual Basic代码时,要显示关于任何Visual Basic语法或关键字(函数、对象、方法、属性等)的帮助信息,只要在代码编辑器中输入相应的文本,将光标放在文本中的任何位置(包括第一个字母前和最后一个字母后),然后按 F1。也可使用“帮助”菜单获得帮助。

注意:如果项目处于运行模式,则按F1不会显示Visual Basic帮助,而显示该应用程序的帮助——如果为该应用程序创建了帮助。

在本章中,读者学习了如何使用Visual Basic 起始页——Visual Basic 2010的入口;还学习了如何创建新项目和打开现有的项目。Visual Basic环境包括工作空间、工具箱及其他工具。读者学习了如何在该环境中导航,包括如何使用设计窗口(隐藏、显示、停靠和浮动)。

Visual Basic有很多不同的设计窗口。在本章中,读者首先详细学习了其中的几个设计窗口。读者学习了如何使用“属性”窗口获取和设置属性,如何使用“解决方案资源管理器”管理项目,以及如何使用工具箱为窗体添加控件。读者将经常用到这些技能,因此应立即熟悉它们。最后,学习了如何访问Visual Basic帮助功能,这对学习使用Visual Basic很重要。

Visual Basic 2010是应用广泛、功能强大的开发工具——比它以前的任何版本都更强大。不要期望一夜之间就成为专家,这根本是不可能的。然而,通过学习本章介绍的工具和技巧,读者已经跨出了第一步。记住,每次使用 Visual Basic时,读者都将用到本章学到的大部分知识。掌握这些基本知识后,读者将很快能够创建有用的程序!

问:如何获得除“属性”窗口的说明区域外的有关属性的信息?

答:单击属性以选中它,然后按F1键——上下文帮助也适用于“属性”窗口中的属性。

问:我想同时显示很多设计窗口,但找不到这样的布局。有什么建议吗?

答:提高分辨率。我不会在低于1024×768的分辨率下进行开发。事实上,我的所有开发机器都有两个显示器,分辨率都是1680×1050或更高。在屏幕上的投资有很高的回报。

1.除非特别指明,否则在创建本书的示例时创建的是哪种类型的项目?

2.要使得将鼠标指向其选项卡时,停靠的设计窗口显示,而移开鼠标时消失,应修改窗口的什么设置?

3.要添加控件到窗体中,应使用哪个设计窗口?

4.要修改对象的属性,应使用哪个设计窗口?

5.要修改项目的属性,必须在哪个设计窗口中选择项目?

1.Windows窗体应用程序。

2.自动隐藏设置。

3.工具箱。

4.“属性”窗口。

5.解决方案资源管理器。

1.使用“自定义颜色”对话框创建一种颜色,然后将窗体的BackColor属性设置为这种颜色。

2.将工具箱移动到 IDE 的右边使它停靠在那里,并使它自动隐藏。完成后,再把它移回去。

在本章中,读者将学习:

理解对象;

获得和设置属性;

触发方法;

了解方法的动态性;

编写基于对象的代码;

理解集合;

使用对象浏览器。

第 1章通过创建 Picture Viewer项目介绍了Visual Basic编程;第 2章介绍了集成开发环境(IDE)和成功使用Visual Basic的重要技巧。本章将开始介绍一个重要的编程概念:对象。

阅读本书前,编程中的“对象”对读者来说可能是个全新的术语。随着使用Visual Basic的时间增加,读者将越来越多地接触到对象。与其前身不同,Visual Basic 2010是真正的面向对象语言。本章并不详细讨论面向对象编程,面向对象编程是一个复杂的主题,远远超出了本书的范围。相反,读者将概括性地了解对象。

用户在Visual Basic中使用的都是对象,因此理解对象对于成功使用Visual Basic至关重要。例如,窗体是对象,窗体中的控件也是对象;Visual Basic项目中几乎所有元素都是对象,并属于一个对象集合。所有对象都有自己的特征(称为属性),大部分都有方法,很多对象还有事件。不管是创建简单的应用程序还是构建大型企业解决方案,都必须理解什么是对象以及它是如何工作的。在本章,读者将学习到是什么让对象成为对象,还将学习集合。

注意:如果读者看过编程方面的书,可能听说过术语面向对象以及多态、封装和继承。Visual Basic的这些面向对象的特性真是激动人心,但它们超出了本章的范围(第24章将介绍)。读者将在本书中学到一点关于面向对象编程的知识,但如果对提高编程技能感兴趣,应该在阅读完本书后再买一本专门介绍该主题的书。

面向对象编程成为热门技术已经很久了,但对 Visual Basic 程序员来说,仅当 Visual Basic .NET推出后,这才变成现实(以前的Visual Basic版本都不是真正的面向对象语言)。有关对象的内容无处不在———网络、杂志和书籍。对象到底是什么呢?严格地说,对象是将数据和功能封装为一体的编程结构,公开访问它的唯一方法是通过该编程结构的接口(属性、方法和事件)。事实上,这个答案可能有点含混不清,因为对象的种类很多,且几乎每天都在增加。然而,所有对象都具有某些特定的特征,如属性和方法。

在Visual Basic中,最常用的对象是窗体对象和控件对象。前两章介绍了如何使用窗体和控件,以及如何设置窗体和控件的属性。例如,在第 1章的Picture Viewer项目中,为窗体添加了一个图片框和两个按钮。PictureBox和Button控件都是控件对象,但它们是不同类型的控件对象。另一个与技术不相关的例子是宠物。狗和猫是截然不同的实体(对象),但它们都属于 Pet 对象。同样,文本框和按钮是不同类型的对象,但它们都被称为控件对象。这点小区别很重要。

所有对象都有特性,用于指定和返回对象的状态。这些特性称为属性,在前两章中,使用“属性”窗口设置过一些属性。每个对象都呈现出一组属性,但不是所有对象都有一组相同的属性。为说明这一点,我将继续以虚构的Pet对象为例。假设有一个Dog对象,它具备所有狗都有的通用属性。这些属性包括:狗的名字、毛色和腿数。所有狗都有这些属性;然而,对于不同的狗,这些属性的值是不同的。图3.1说明了Dog对象及其属性。

图3.1 属性是描述对象的特征

读者已知道如何使用“属性”窗口来读取和修改属性。然而,“属性”窗口只在设计时可用,且只能用于对窗体和控件的属性进行操纵。大多数情况下,属性的获取和修改是通过Visual Basic代码而不是使用“属性”窗口实现的。在代码中引用属性时,首先指定对象的名称,名称后紧接着一个点号(.),然后是属性名,其语法如下:

ObjectName.Property

例如,如果有一个名为btnClickMe的Button对象,可用如下方法来引用其Text属性:

btnClickMe.Text

这行代码返回Button对象btnClickMe的Text属性值。要设置属性的值,可使用等号(=)。例如,要修改Button对象btnClickMe的Left属性,可使用下面一行代码:

btnClickMe.Left = 90

属性位于等号左边时,将设置该属性的值。属性位于等号右边时,将获得该属性的值。在早期的BASIC中,设置值时使用关键字Let,这让新手容易理解代码,但这是不必要的。尽管如此,使用Let可使语句更清晰,因此对于上面的代码,笔者将使用关键字Let:

Let btnClickMe.Left = 90

在等号左边引用属性时,表明要设置属性的值,这很容易理解。给变量赋值时,不能再使用关键字Let。如果输入使用关键字Let的代码语句,将不会出现错误,但代码编辑器会自动将Let从语句中删除。

下面这行代码将Button对象btnClickMe的Left属性值赋给一个临时变量。这条语句获取属性Left的值,因为在等号右边引用了Left属性:

intLeftVariable = btnClickMe.Left

变量将在第11章详细讨论。读者可暂时将变量视为存储位置。处理器执行这条语句时,它获取Button对象btnClickMe的Left属性值,然后把它放在变量(存储位置)intLeftVariable中。假设btnClickMe的Left属性值为90(如上一条语句设置),计算机将如下处理这条语句:

intLeftVariable = 90

和现实生活一样,有些属性只能读取而不能修改。假设Dog对象有一个Gender属性用来指定性别,显然不能改变狗的性别(至少笔者是这么认为的)。因为Gender属性可读取而不能修改,因此被称为只读属性。读者将经常遇到在设计视图中可设置而在程序运行时却是只读的属性。

一个只读属性的例子是Combo Box控件的Height属性。虽然在“属性”窗口中可以查看Height属性的值,但不能修改它———不管您怎么努力。如果您试图使用Visual Basic代码来修改它,Visual Basic将把该值改回默认值。

注意:要确定对象的哪些属性是只读的,最佳的方法是访问有关该对象的在线帮助。

知道什么是属性以及如何查看、修改属性后,接下来将通过修改第 1 章创建的 Picture Viewer项目以尝试使用属性。第1章介绍了如何使用“属性”窗口设置窗体的Height和Width属性,这里将使用Visual Basic代码来修改这些属性。

下面在Picture Viewer中添加两个按钮,其中一个按钮在它被单击时放大窗体,另一个则缩小窗体。这是一个简单的例子,但很好地说明了如何在Visual Basic代码中修改对象的属性。

首先打开第 1章创建的Picture Viewer项目。如果读者从笔者的网站下载了示例源代码,其中提供了一个Picture Viewer项目,读者可以从它开始。双击“解决方案资源管理器”中的ViewerForm.vb以显示窗体设计器。

项目运行时,窗体的高度和宽度分别是读者在“属性”窗口中指定的Height和Width属性值。按下面的步骤在窗体中添加两个按钮,让用户能够通过单击按钮来缩放窗体。

1.双击工具箱中的Button工具,添加一个新按钮到窗体中。按如下设置该按钮的属性。

2.现在添加缩小窗体的按钮。再次双击工具箱中的 Button 工具,添加一个新按钮到窗体中。按如下设置该按钮的属性。

窗体现在应如图3.2所示。

图3.2 每个按钮都是对象,按钮所在的窗体也是对象

要完成该项目,需要添加少量 Visual Basic 代码,以便在用户单击按钮时修改窗体的Height和Width属性。

3.双击图案为“^”的放大按钮,以访问其代码,然后输入下面的语句。输入后不要按回车键或添加空格。

Me.Width

输入点号时,将出现图 3.3所示的下拉列表。Visual Basic很聪明,知道Me代表当前窗体(稍后将更详细地介绍),帮助您为这个对象编写代码,它显示了一个包含该窗体的所有属性和方法的下拉列表。这种功能称为智能感知(IntelliSense)。当智能感知下拉框出现时,可使用向上和向下箭头来导航列表,按Tab键来选择高亮显示的列表项。这样可以防止成员名拼写错误,从而减少编译错误。因为 Visual Basic是完全面向对象的,您将在很大程度上依赖于智能感知下拉列表;如果没有它们,我宁愿去挖水渠也不编程。

图3.3 智能感知下拉列表(也称为自动完成下拉列表)使编程更容易

4.使用退格键来删除输入过的代码,然后输入下面的代码(在每行行尾按回车键):

Me.Width = Me.Width + 20

Me.Height = Me.Height + 20

前面提到过,Me 指代码所属的对象(这里为窗体),并非指人。Me 是一个保留字;不能用它来命名对象或变量,因为它在Visual Basic中有特殊含义。在窗体模块中编写代码时,总是使用Me而不要使用窗体的名称。Me比当前窗体的名称要简短得多;而且它使得代码更容易移植(可将代码复制并粘贴到另一个窗体模块中,而不必修改窗体名称)。另外,如果将来需要修改窗体名称,不必修改对旧名称的引用。

注意: Me只适用于基于对象的模块(如窗体模块)中,不能将其用于标准模块中,标准模块将在第10章介绍。

输入的这段代码只是修改窗体的 Width 和 Height 属性,分别在当前的 Width 和 Height属性值的基础上增加20像素。

5.选择设计窗口顶部的“ViewerForm.vb[设计]”选项卡,重新显示窗体设计器。然后双击图案为“v”的按钮,以访问它的Click事件并添加如下代码:

Me.Width = Me.Width - 20

Me.Height = Me.Height - 20

这段代码与btnEnlarge_Click事件中的代码类似,只是将Width和Height属性分别减少20像素。读者的屏幕应如图3.4所示。

图3.4 输入的代码应与图中的一致

提示:创建项目时,经常存盘是个好习惯。如果该选项卡的名称末尾有星号(如图 3.4 所示),则表明在该选项卡中修改了文件,但没有将其存盘。现在单击工具栏中的“全部保存”按钮保存项目。

再次单击“ViewerForm.vb[设计]”选项卡,重新显示窗体设计器。Picture Viewer项目现在可以运行了!按F5键,使项目处于运行模式。单击Select Picture按钮,然后从硬盘中选择一幅图片。

接下来,单击“^”按钮若干次,注意观察窗体是如何变大的。窗体将越来越大(如图3.5所示)。

图3.5 窗体变大了,但外观与设计时相同

单击“v”按钮使窗体变小。单击若干次以满足您的好奇心(直到烦了为止),然后单击工具栏中的“停止调试”按钮终止程序并返回到设计窗口。

读者是否注意到,当窗体大小改变时窗体中的按钮和图片的大小并没有变。第6章将介绍如何调整窗体中内容的大小。

除了属性,大多数对象都有方法。方法是对象可以执行的行为,而属性只是描述了对象的特征。要理解这种区别,再次以 Dog 对象为例。Dog 对象有一系列可以执行的行为。在Visual Basic中,这些行为称为方法,包括吠叫、摇尾和咬地毯(不征求许可)。图 3.6说明了Dog对象及其方法。

图3.6 调用方法导致对象执行操作

可将方法视为函数———这正是方法的本质。方法被调用时将执行代码。可传递参数给方法,而方法也可以返回值。然而,方法并不一定要接受参数(由调用代码传递给它的数据),也不一定要返回值;很多方法只是通过代码执行一种操作。方法的调用(触发)与属性的引用类似:首先引用对象名称,接着是点号,然后是方法名,如下所示:

ObjectName.Method

例如,要用Visual Basic代码使虚构的Dog对象Bruno吠叫,可使用下面一行代码:

Bruno.Bark()

注意:方法通常用于让对象来执行操作,如保存或删除数据库中的记录。另一方面,属性用于获取和设置对象的特征。区分代码中的语句是属性引用还是方法调用的方法之一是:方法调用后面有一对括号,如AlbumForm.Show Dialog()。

调用方法很简单;真正的技巧在于知道对象支持什么方法以及该何时调用特定的方法。

属性和方法之间是互相联系的,有时候特定的方法可能由于一个或多个属性值的设置而变得不可用。例如,如果将Dog对象Bruno的NumberOfLegs属性设置为0,那么显然Walk()和Fetch()方法将不可用。如果将NumberOfLegs属性设置为4,就可触发Walk()和Fetch()方法了。

真正理解什么是对象以及对象是如何工作的唯一方法就是使用它们。前面说过,但怎么重复都不过分:在Visual Basic 2010中任何东西都是对象。这有优点也有缺点,缺点之一是,在有些情况下,与以前相比,现在需要编写更多代码才能完成特定的任务———有时候是更多的字符,有时候是更多的语句。另一方面,Visual Basic 2010的功能远远超过Visual Basic 6.0,这使得与以前的版本相比,Visual Basic 2010的学习曲线更陡峭。如果您以前使用的是Visual Basic 6.0,将有很多东西需要学习和调整,但这是值得的。

到目前为止,您创建的每个项目都使用了对象,但现在要创建的是一个示例项目,专门用来说明该如何使用对象。如果读者不熟悉使用对象进行编程,可能觉得有点困惑。我将详细地介绍每一个部分,一步一步地教会您。

接下来将修改Picture Viewer项目,使其包含这样一个按钮:单击该按钮时,将在图片周围绘制彩色边框。在这个示例中,读者将接触一些绘画函数,但不用担心,这里并不要求您理解绘画代码的细节,而只要求您理解对象的工作原理。

继续使用本章使用的Picture Viewer项目,现在在窗体中添加一个新按钮,并按如下设置其属性。

现在为该按钮的 Click 事件添加代码。此处将解释每条语句,在所有步骤结束后将列出完整的代码清单。

1.双击Draw Border按钮以访问其Click事件。

2.按如下输入第一行代码(别忘了在每行行尾按回车键):

Dim objGraphics As Graphics

这创建了一个变量,该变量将存储一个对象实例。对象并不会凭空出现;您必须创建它们。当窗体加载到内存中时,同时也加载了它的所有控件(也就是创建控件对象),但并不是所有对象都这样自动地被创建。创建对象实例的过程称为实例化。加载窗体就实例化了该窗体对象,该对象又实例化了它的控件对象。再加载一个窗体实例,将实例化一个新的窗体对象及其所有的控件。这样,内存中便有两个窗体,以及两组窗体使用的控件。

要在代码中实例化对象,首先要创建一个变量来保存对这个实例化对象的引用。然后可以像操纵对象一样操纵该变量。第2步编写的Dim语句创建了一个新变量objGraphics,它存储到Graphics对象的引用。在第11章将更详细地介绍变量。

接下来,输入第二行代码,如下所示:

objGraphics = Me.CreateGraphics

CreateGraphics()是窗体的一个方法(记住,关键词Me是对当前窗体的引用)。在后台, CreateGraphics()方法相当复杂。现在可以这样理解CreateGraphics()方法:它实例化一个新对象,该对象代表当前窗体的客户区域。客户区域是窗体标题栏和边框内的灰色区域。绘制到objGraphics 对象上的所有图像都将出现在窗体上。这条语句将 objGraphics 变量指向由CreateGraphics()方法返回的对象。注意,属性或方法返回的值不一定是传统意义上的值(如数值或文本),它们也可以是对象。

输入如下所示的第三行代码:

objGraphics.Clear(SystemColors.Control)

这条语句使用用户在 Windows 控制面板中选择的颜色,也就是 Windows 绘制窗体所用的颜色来清除窗体的背景。

这是怎么发生的呢?在声明了 objGraphics 对象后,使用窗体的 CreateGraphics()方法来实例化一个新的 Graphics 对象,并将其赋给 objGraphics 变量。刚输入的这条语句调用objGraphics对象的Clear()方法。Clear()方法是所有Graphics对象用于清理图形表面的方法,它接受单个参数:要用于清理表面的颜色。

传递的参数值看起来可能有点奇怪。记住,“点”是一种将对象与其属性和方法(属性、方法和事件通常称为对象的成员)分开的方式。知道这一点后,就可以判断出 SystemColors是一个对象,因为它出现在所有点号之前。对象引用的层级可以很深,这将在代码中使用很多点号。要记住的要点如下。

出现在点号左边的文本总是代表一个对象(或命名空间)。

出现在点号右边的文本是属性引用或方法调用。如果文本后面跟着一对括号,表明是方法调用;否则,很可能是属性。

与属性一样,方法也可以返回对象。为判断两个点号之间的文本代表属性还是方法,一个准保不会错的方法是看智能感知下拉列表中的成员图标或参考对象的文档。

这条语句最后的文本是Control。因为Control后面没有点号,可知它不是对象;因此,一定是属性或方法。由于这串对象引用要返回一个颜色值,用于清理图形对象,因此可知这里的 Control 一定是用来返回一个值(因为需要返回值来设置 Clear()方法)的属性或方法。通过快速查看文档,可以知道Control确实是属性。Control的值总是等于用户计算机为窗体和按钮表面指定的颜色。默认是浅灰色(通常被称为战舰灰),但用户也可在计算机上修改这个值。通过使用该属性来指定颜色,而不是使用灰色的颜色值,可以保证代码将窗体清除为系统颜色,而不管计算机使用的颜色主题是什么。

输入下面的语句(注意,不要在每行后按回车键,直到输入了这里所示的全部代码。这些代码列在两行中仅仅是因为页面太小的缘故)。

objGraphics.DrawRectangle(Pens.Blue, picShowPicture.Left - 1,

picShowPicture.Top - 1, picShowPicture.Width + 1, picShowPicture.Height + 1)

这条语句在窗体的图片四周绘制一个蓝色矩形。在这条语句内是一个方法调用和五个属性引用。您能识别出来吗?紧接在 objGraphics(和点号)后面的是 DrawRectangle。因为没有等号,因此可以推断这是一个方法调用。和 Clear()方法一样,DrawRectangle 后的括号将传递给方法的参数括起来。

DrawRectangle()方法依次接受下面5个参数:

一支画笔;

左上角的X值;

右下角的Y值;

矩形的宽度;

矩形的高度。

DrawRectangle()方法使用传递给它的 X、Y、Width 和 Height 的值来绘制一个矩形。线条的属性(颜色和宽度等)由Pen参数指定的画笔决定(这里不详细介绍画笔,如果读者对此感兴趣,请参阅联机帮助)。再看一下点号,注意传递的是 Pens 对象的 Blue 属性。Blue是对象的一个属性,返回一个宽为1像素、颜色为蓝色的预定义Pen对象。

接下来的两个参数传递的是属性值。具体地说,分别是图片的顶部和左边的值减 1。如果传递顶部和左边的值,矩形将精确地从窗体中的PictureBox左上角开始绘制,这样就看不见这个矩形,因为默认情况下,控件将覆盖窗体上绘制的图形。

最后的两个属性引用是PictureBox的Height和Width属性。这里同样调整了1像素,以确保矩形绘制在PictureBox的边框外。

最后,需要输入下面的代码语句来执行清理工作:

objGraphics.Dispose()

对象通常使用了其他对象和资源。对象的底层机制是比较复杂的,几乎不可能在入门级编程书籍中讨论。然而,要强调一点:不再需要对象时,必须显式地销毁它。如果不销毁对象,它可能常驻在内存中,且它可能保存对内存中其他对象或资源的引用。也就是说,应用程序可能产生内存泄漏,这会慢慢地(或相当快地)消耗系统内存和资源。这是Windows 编程要极力避免的一个问题,然而,资源使用的性质以及程序员必须在使用对象后清理对象的要求,使得这个问题很容易发生。如果应用程序发生了内存泄漏,而用户并不能修复,将带来破坏性后果。

那些在最后必须显式清理掉的对象通常都提供了一个Dispose()方法。当不再需要这样的对象时,调用其Dispose()方法,确保释放可能占用了的资源。

为方便起见,这里列出了所有的代码:

注意:在这里,调用DrawRectangle( )的语句有三行代码,其中前两行代码的末尾有一个下滑线字符(_),也被称为连行字符,它告诉Visual Basic编译器,后面的语句是当前语句的继续。在代码中,可以也应该使用该字符来分割长语句。

继续阅读下面的内容前,单击工具栏上的“保存全部”按钮保存所做的工作。

下面的内容比较容易:按 F5 键或单击工具栏上的“启动调试”按钮运行项目。运行时的窗体看起来应与设计时相近。单击按钮Draw Border,将在PictureBox的四周绘制一个蓝色矩形,如图3.7所示。

图3.7 使用对象可以绘制简单线条和复杂的绘图

注意:如果运行项目时出现错误,返回代码窗口,确保输入的代码与书中提供的一致。

如果在绘制矩形后按Alt+Tab切换到另一个应用程序窗口,再返回时,矩形将消失。实际上,用另一个窗体覆盖图像后都将发生这种情况。第 18章将介绍如何持久化图像,以便窗体被遮盖时其中的图像不会消失。

现在,单击Visual Basic工具栏中的“停止调试”终止项目的运行。我希望读者从这个例子中学到的不仅是如何绘制矩形,而是理解如何在编程中使用对象。和学习其他任何东西一样,重复能够帮助理解。也就是说,在本书中读者将大量使用对象。

顾名思义,集合就是对象的集合。集合使得对大量相似对象的操作变得简单,因为它让我们能够编写代码对集合中的元素进行迭代处理。迭代处理就是使用循环对多个对象执行特定的操作,而不必为每个对象编写操作代码。除包含一组带索引的对象外,集合还有自己的属性,也可能有方法。图3.8说明了集合的结构。

图3.8 集合包括一系列相似的对象,并有自己的属性和方法

继续以Dog/Pet对象为例,考虑Animals集合应是什么样的。Animals集合可能包括一个或多个Pet对象,也可能是空的(不包含对象)。所有集合都有一个Count属性,它返回集合包含的对象总数。集合也可能有方法,如 Delete()方法用于将对象从集合中删除,Add()方法用于将一个新对象添加到集合中。

为更好地理解集合,下面创建一个小型Visual Basic项目,它遍历窗体的Controls集合,并显示窗体中每个控件的Name属性。按照下面的步骤来创建这个示例项目。

1.启动Visual Basic(如果还没有启动它),创建一个新的Windows窗体应用程序项目,将其命名为Collections Example。

2.使用“解决方案资源管理器”将窗体的文件名改为CollectionsExampleForm.vb(右击“解决方案资源管理器”中的 Form1.vb,再选择“重命名”),并将窗体的 Text 属性设置为Collections Example(需要单击该窗体才能显示其属性)。

3.双击工具箱中的Button工具添加一个新按钮到窗体中。按如下设置该按钮的属性。

4.接下来,将一个文本框和标签控件添加到窗体中。添加控件到窗体中时,确保每个控件的名称是唯一的。可以为控件取任何名称,但在控件名称中不能使用空格。将控件拖到窗体中不同的位置,确保它们不重叠。

5.将控件添加到窗体中后,双击Show Control Name按钮,为它的Click事件添加代码。输入下列代码:

注意:每个窗体都有一个Controls集合,该集合可能不包含任何控件。即使窗体中没有任何控件,窗体也有Controls集合。

在上述代码中,读者应该对第一条语句不陌生。和前一个示例中一样,该语句创建一个用于存储值的变量;但不同的是,不是创建一个可用于存储对象的变量,而是创建一个只能存储数字的变量。

接下来的语句(以For打头的语句)完成多项任务。首先,它将intIndex变量初始化为 0,然后开始循环(循环将在第 14 章讨论):每次将 intIndex 变量加 1,直到 intIndex等于窗体控件的数量减1。intIndex必须比Count属性小1的原因是,集合中第一个元素的编号为 0。因此,第一个元素的索引为 0,第二个元素的位置为 1,依此类推。如果试图引用集合中位置等于Count属性值的元素,将导致错误,因为引用的索引比集合的最高位置大1。

MessageBox.Show()方法(第2章提到过,将在第17章讨论)是.NET框架中的一个类,用于显示包含文本的简单对话框。这里提供的文本(即 Show()方法将显示的),是多个字符串的拼接(concatenation)。拼接是将字符串添加到一起的操作,将在第12章介绍。

按 F5 键或单击工具栏上的“启动调试”按钮来运行项目。忽略窗体中的其他控件,单击Show Control Name按钮。应用程序将为窗体中的每个控件显示一个与图 3.9相似的消息框(由于使用了循环)。程序显示完控件的名称后,选择菜单“调试”>“停止调试”终止程序,然后保存项目。

图3.9 Controls 集合让您能够获得窗体中的每个控件

Visual Basic 2010中的一切都是对象,创建应用程序时将用到大量的集合。集合很有用,越快熟悉它们,就越能提高效率。

Visual Basic 2010有一个很有用的工具,让您能够轻松地查看项目中所有对象的成员(属性、方法和事件),这就是对象浏览器(如图3.10所示)。使用文档不完善的对象时,这个工具很有用,因为它让您能够查看对象支持的所有成员。要打开对象浏览器,可按F2。使用菜单“视图”也可打开“对象浏览器”,但在笔者编写本书时使用的Beta 2版中,菜单“视图”中没有“对象浏览器”。

“对象浏览器”左上角的下拉列表“浏览”用于指定浏览范围。选择“我的解决方案”,可查看只在当前活动解决方案中引用的对象;选择“所有组件”,可查看所有可能的对象。单击“浏览”下拉列表最右边的“对象浏览器设置”按钮旁的下拉箭头,可以定制对象集合。建议在有了使用Visual Basic 2010对象和对象浏览器的经验后再定制对象设置。

图3.10 对象浏览器让您能够查看对象的所有属性和方法

在对象树中,顶级节点(树中的每项都称为节点)是库。库通常是计算机中的 DLL 或EXE文件,包含一个或多个对象。要查看库中的对象,只需展开库节点。选择库中的对象后,对象树右边的列表将显示有关该对象的成员的信息(如图3.10所示)。要获得更详细的信息,单击右边的列表中的成员,对象浏览器将在列表下方的区域中显示有关该成员的信息。

在本章中,读者学习了关于对象的很多知识。对象有属性,它们描述了对象的特征。有些属性可在设计时使用“属性”窗口进行设置,大部分属性也可用 Visual Basic代码在运行时设置。在等号左边引用属性将修改属性,而在等号右边引用属性将获取属性的值。

除了属性,对象还有可执行的函数,这被称为方法。与属性一样,方法在对象引用后加一个点号来引用。对象可能包含很多方法和属性,有些属性本身也可以是对象。读者还学习了如何“通过点号”来弄明白长对象引用的含义。

对象通常作为组来使用,这称为集合。集合通常包含属性和方法,集合让您能够很容易地在一组类似的对象中进行迭代。最后,读者还学习了对象浏览器,它可用于查看项目中对象的所有成员。

读者在本章获得的知识对于理解Visual Basic编程必不可少,因为对象和集合是构建应用程序的基本元素。掌握对象和集合后———阅读本书后读者将实现这一目标,将充分理解使用Visual Basic .NET创建健壮的应用程序的复杂性。

问:是否有简单的方法可以获得关于对象成员的帮助?

答:答案是肯定的。Visual Basic的上下文帮助不仅可用于可视对象,也可用于代码。要获得关于成员的帮助,编写一条包含该成员的代码语句(不一定要完整的语句),将光标放在该成员的文本内,然后按F1。例如,要获得关于Integer数据类型的帮助,输入Integer,将光标放在Integer内,然后按F1。

问:除属性和方法外,对象成员是否还有其他类型?

答:有。实际上事件也是对象的成员,虽然人们不总是这么认为。不是所有对象都支持事件,而大多数对象支持属性和方法。

1.判断对错:Visual Basic 2010是真正的面向对象语言。

2.定义对象状态的特征称为______。

3.要修改属性的值,必须在等号的哪边引用属性?

4.哪个术语表示根据模板创建一个新对象?

5.对象(对使用代码的对象可用)的外部函数称为______。

6.判断对错:对象的属性可以是另一个对象。

7.一组相似的对象称为______。

8.使用什么工具查看对象成员?

1.对。

2.属性。

3.左边。

4.实例化。

5.方法。

6.对。

7.集合。

8.对象浏览器。

1.创建一个新项目,并将两个文本框和一个按钮添加到窗体中。编写代码以实现:单击按钮时,将第一个文本框中的文本放在第二个文本框中。提示:使用文本框控件的Text属性。

2.修改本章的集合示例,显示所有控件的高度而不是名称。

在本章中,读者将学习:

理解事件驱动的编程;

触发事件;

避免递归事件;

访问对象的事件;

使用事件参数;

创建事件处理程序;

更新事件名称。

使用Visual Basic 2010集成的设计工具,为应用程序创建引人注目的界面很容易。可以创建优美的窗体,窗体上包含可单击的按钮、可输入信息的文本框、可显示图片的图片框以及其他很多用户可与之交互的有创意的元素。然而,这只是创建Visual Basic程序的第一步。除设计界面外,还要使程序执行操作,使之能够与用户进行交互以及同Windows互相响应。这是通过事件来实现的。在第3章中,读者学习了对象及其成员———主要是属性和方法。在这一章,将学习对象的事件以及基于事件的编程,还将学习如何使用事件使应用程序可响应。

使用传统的编程语言(通常称为过程语言),程序本身完全指明了执行什么代码以及在什么时候执行。运行程序时,程序的第一行代码首先执行,接着代码以事先完全确定的路径继续执行。代码的执行有时会有分支和循环,但整个执行路径完全由程序决定。这意味着这样的程序在与用户交互方面是受限制的。例如,程序可能要求按预定的顺序将文本输入到屏幕上的控件中。这与 Windows 应用程序不同,在 Windows 程序中,用户可与界面的不同部分进行交互———通常可以是用户选择的任何顺序。

Visual Basic 2010集成了一个事件驱动的编程模型。事件驱动的应用程序没有过程程序的约束。过程语言采用自顶而下的方法,而事件驱动的程序的事件中有逻辑层次的代码。事件发生的顺序并没有预先确定;用户可以通过与程序交互来触发特定的事件,如单击按钮等,从而完全地控制要执行哪些代码。

在第 3 章,读者了解到方法是对象的函数。从某种意义上说,事件是一种特殊的方法,对象使用它来告知一些对客户(使用对象的代码)有用的状态变更。实际上,Visual Basic 2010文档经常将事件称为方法(这无疑会使编程新手感到困惑)。事件是可用特殊方式来调用的方法———通常通过用户与窗体中的控件交互或通过Windows本身———而不是在代码语句中直接调用。

事件的类型有很多种,触发这些事件也有很多方式。读者已经看到,用户可以通过单击按钮来触发其事件。用户交互并非触发事件的唯一方式,可以通过下列四种方式来触发事件。

用户与程序交互可以触发事件。例如,单击按钮将触发其Click事件。

对象在需要时可触发自己的事件。例如,定时器每隔一段时间触发其Timer事件。

操作系统(不管用户运行的是哪个版本的Windows)可以触发事件。

使用Visual Basic代码调用事件以触发它们,这与调用方法一样。

1.由用户交互触发的事件

最常见的触发事件的方式是用户与程序交互。所有窗体以及窗体中几乎所有的控件都有一组与其对象类型对应的事件。例如,Buttton控件有很多事件,包括前几章用过的Click事件。用户单击按钮时,按钮的Click事件将触发,然后Click事件中的代码将执行。

Textbox 控件让用户能够通过键盘输入信息,它也有一组事件。Textbox 控件有一些与Button控件相同类型的事件,如Click事件,但Textbox控件还有一些Button控件没有的事件,如 MultilineChanges 事件。当文本框的 Multiline 属性改变时,将触发 MultilineChanges事件。因为用户不能在Button控件中输入文本,所以按钮没有Multiline属性,因此也就没有MultilineChanges事件。每个支持事件的对象都支持一组独特的事件。

每种事件都有自己的行为,理解使用的事件很重要。例如,对新的开发人员来说, TextChanged 事件的行为可能并不直观,因为它在文本框的内容改变时才触发。想一想,在项目中的一个空文本框中输入下面的句子时将发生什么:

www.ChemicalEcho.com

读者可能很容易认为,仅当提交输入后(如离开文本框或按回车键),TextChanged事件才被触发,但事实并非如此。TextChanged事件将被触发20次———每输入一个字符就触发一次———因为每输入一个新字符,文本框的内容都将发生变化。再强调一次,了解使用事件的细节和精确行为很重要。如果没有完全理解事件就使用它们,程序可能产生异常(通常是不希望的)结果。

2.由对象触发的事件

有时对象也触发自己的事件。最常见的例子是Timer控件的Tick事件。与常用的对话框控件一样,Timer 控件是不可见控件;程序运行时它并不出现在窗体中,设计时它出现在为不可见控件保留的区域中。Timer控件的唯一目的就是每隔由Interval属性指定的时间间隔触发其Tick事件。

通过设置Timer控件的Interval属性,可以控制Tick事件的执行间隔(以毫秒为单位)。触发Tick事件后,Timer控件自动复位,并在经过间隔时间后再次触发Tick事件。这将不断发生,直到间隔被修改、Timer控件关闭或Timer控件所属的窗体被卸载。定时器的常见用途是在窗体中创建一个时钟。可以在标签(Label)中显示时间,通过将显示当前时间的代码放在 Tick事件中,可以每隔一定间隔更新显示的时间。第8章将创建一个使用Timer控件的项目。

3.由操作系统触发的事件

触发事件的第三种方法是由 Windows 本身触发。您常常可能没有认识到这些事件的存在。例如,当窗体被其他窗口完全或部分挡住时,程序需要知道遮挡的窗口何时调整了大小或被移走,以便能够重新绘制被挡住的部分。在这方面,Windows和Visual Basic需要协同工作。当遮挡的窗口被移走或调整了大小时,Windows通知Visual Basic重新绘制窗体,然后Visual Basic重新绘制窗体。这还导致Visual Basic触发窗体的Paint事件。可在Paint事件中编写代码来创建窗体的自定义显示,如使用Graphics对象在窗体上绘制形状。这样,每当窗体重新绘制时,自定义的绘制代码都将执行。

必须避免创建的代码永无休止地触发事件自身。不停地触发自己的事件称为递归事件。以前面讨论的Textbox控件的TextChanged事件为例,来讨论导致递归事件的情况。每当文本框中的文本改变时,TextChanged事件就被触发。在TextChanged事件中添加修改文本框中文本的代码,将导致再次触发 TextChange 事件,从而导致无限循环。递归事件直到 Windows 返回StackOverflow异常(如图4.1所示)才终止,该异常表明Windows没有资源继续执行递归。

图4.1 出现StackOverflow异常时,应确定是否因递归事件而引起

在多个事件之间循环时,也将导致递归行为。例如,事件A触发事件B,事件B反过来又触发事件A,这样将在这两个事件之间无限循环。递归行为可发生在很多事件中,而不只是一个或两个。

注意:递归过程确实有其用途,如编写复杂的数学函数时。举个例子,递归事件通常用于计算阶乘。然而,有意创建递归事件时,必须保证递归不是无限的。

访问对象的事件很简单,如果读者照本书的例子做了,则已经访问过很多对象的默认事件。要访问对象的事件,只要在窗体设计视图中双击对象。

下面创建一个项目来体验如何使用事件。启动Visual Basic 2010,创建一个新的Windows应用程序项目,将其命名为View Events,然后执行以下步骤。

1.在“解决方案资源管理器”中,右击 Form1.vb 并选择“重命名”,再将文件名改为ViewEventsForm.vb。然后,将该窗体的属性Text改为View Events Example。

2.使用工具箱将一个图片框添加到窗体中。

3.将图片框的名称改为picText,然后双击图片框以访问其事件。

读者的屏幕应如图 4.2 所示。注意到在代码窗口的顶端有两个下拉列表。一个包含文本picText,另一个包含Click。左边的下拉列表中包含当前窗体中所有的对象(包括窗体本身及其所有的控件);右边的下拉列表包含左边的列表中选定对象的所有事件。

图4.2 在代码编辑器中,使用事件下拉列表创建事件过程

当前看到的是picText对象的Click事件。光标位于Click事件过程内部,让用户能够直接输入代码。光标前面的代码语句是事件声明———定义事件结构的语句。注意,这个事件声明包含对象的名称、下划线(_)和事件名称。事件名称后是一对括号,括号内的项称为参数,这将在下一节讨论。这便是事件过程的标准声明结构。

单击事件列表(右边的列表),看看图片框支持的所有事件。从列表中选择MouseDown,代码窗口将变成如图4.3所示。

图4.3 首次选择对象的事件时,Visual Basic将创建空的事件过程

在事件列表中选择一个事件时,Visual Basic将为该事件创建一个事件过程。完整的事件声明如下:

Private Sub picText_MouseDown(ByVal sender As Object, _

 ByVal e As System.Windows.Forms.MouseEventArgs) _

 Handles picText.MouseDown

新事件的声明与第一个相同,也包含对象名和下划线;但其他部分不同,即事件名称不同(这里为MouseDown)。

注意:Private和Sub是Visual Basic保留字,用于指定过程的作用域和类型。作用域和类型将在第10章讨论。

前面提到过,括号内的项称为参数。事件参数是一个变量,由Visual Basic创建并分配值。这些参数变量用于获取(有时也用于设置)事件内部的相关信息。这些数据可能是文本、数字或对象———几乎可以是任何东西。事件过程内的多个参数总是用逗号分开。可以看到, MouseDown事件有两个参数。事件过程被触发时,Visual Basic自动创建参数变量并为它们赋值,供在事件过程的这次调用中使用;下次事件过程发生时,参数的值将重新设置。在代码中可以根据这些参数的值来做出判断或执行操作。

窗体的MouseDown事件有如下参数:

ByVal sender As Object

ByVal e As System.Windows.Forms.MouseEventArgs

现在不要考虑关键字ByVal,这将在第11章讨论。

ByVal 的后面是参数名,而 As 后面的字符串指出了参数包含的数据类型。第一个参数sender存储一个通用对象。Object参数可以是Visual Basic支持的任何对象类型。有些包含文本,有些包含数值,还有一些(很多)包含对象。例如,sender 参数将保存对引起事件的控件的引用。

而MouseDown事件的参数e则表明真正的行为在何处发生。参数e也存储一个对象,但在这里,这个对象是 System.Windows.Forms.MouseEventArgs 类型。这个对象有与MouseDown事件相关的属性。要查看这些属性,输入下列代码,但在点号后不要按任何键:

e.

输入点号后,将显示一个下拉列表,显示对象e的成员(属性和方法),如图4.4所示。通过对象e,可以知道关于MouseDown事件的很多信息。表4.1列出了较有趣的内容。

图4.4 智能感知下拉列表使开发人员不必记住成百上千对象的成员

表4.1 System.Windows.Forms.MouseEventArgs的常用成员

注意:每当事件发生时,参数由Visual Basic初始化,确保它们总是反映事件的当前状况。

每个事件都有特定的参数。例如,TextChanged事件返回的参数与MouseDown事件的不同。随着对事件的不断使用———读者将使用大量的事件———将很快熟悉每种事件的参数。第10章将介绍如何为自己的函数和过程创建参数。

下面通过修改第 3章的Picture Viewer项目来演示如何使用MouseMove事件。将通过修改使得当用户在图片上移动鼠标时,在窗体上显示光标的X和Y坐标。将使用参数e来获得光标的坐标。

打开在第 3章创建的Picture Viewer项目。如果提示您是否保存修改,单击“否”。如果没有创建该项目,可从我的网站下载。

需要添加两个标签控件到窗体中:一个用于显示X值,一个用于显示Y值。标签控件用于显示静态文本;用户不能在标签中输入文本。在工具箱中双击 Label 工具,将一个标签控件添加到窗体中,并按如下设置其属性。

使用工具箱再添加一个标签控件到窗体中。按如下设置它的属性。

现在窗体应如图 4.5 所示。经常保存是个好习惯,现在单击工具栏中的“全部保存”按钮来保存项目。

图4.5 标签控件将静态的文本显示给用户

这个例子的界面完成了,下面进入有趣的部分。接下来创建让程序能够执行功能的事件过程。第一个要使用的事件是 MouseMove 事件。双击窗体上的图片框以访问其事件过程。双击控件时,总是为这类控件的默认事件创建事件过程,对于图片框来说,这是Click事件。然而,当前我们对Click事件并不感兴趣。打开事件列表(右上角的下拉列表),从列表中选择MouseMove,Visual Basic将为该图片框创建一个MouseMove过程。

注意到Visual Basic保留了为用户创建的默认事件过程。最好不要保留不用的代码,因此现在删除Click事件过程。

要完全删除事件过程,必须完全删除下面的代码:

删除不用的过程后,代码应如图4.6所示。

图4.6 用户选择新事件时,Visual Basic将新建一个空事件过程——如果以前没有创建该事件过程

将光标放在过程 picShowPicture_MouseMove的语句Private Sub和End Sub之间。

在MouseMove事件过程中输入下列代码:

lblX.Text = "X: " & e.X

lblY.Text = "Y: " & e.Y

这段代码相当简单,读者可能能够理解。如果还不清楚,下面将进行解释。看第一行:lblX.Text在=的左边,因此Text是标签的属性,将给它设置值。字符串“X: ”是要放在标签控件的 Text 属性中的字面值。之所以要包含这个字符串,是因为设置 Text 属性时,新文本将完全覆盖原来的文本。因此,即使在“属性”窗口输入了“X: ”作为属性值,仍然需要在设置属性时包含它。为使这个标签发挥作用,还要包含X的真正值,它保存在对象e的X属性中。因此,将字符串“X: ”与e.X保存的值拼接起来。第二条语句的功能相同,只是针对Y值。

对象的一个优点是不必记住它们的所有细节。例如,不必记住每种按钮的返回值(谁愿意记住e.X、e.Y或e.Button)。只要记住参数e包含有关事件的信息。输入e和点号后,将出现智能感知下拉列表,显示出e的成员。不要为本书中将遇到的对象引用而感到烦恼。读者无法全部记住它们,也没有必要;您只要学习重要的对象,遇到问题时可以使用帮助。此外,知道父对象(如这一例子中的对象 e)后,就很容易通过智能感知下拉列表确定属于它的对象和成员。

单击工具栏中的“全部保存”按钮保存您的工作。接下来,按 F5 键来运行项目,并将鼠标在图片框中移动。可以看到光标的坐标(相对于图片框)显示在两个标签控件中,如图4.7所示。

图4.7 MouseMove 事件使得跟踪控件上的鼠标很容易

现在将鼠标移出图片框。注意到标签中显示了鼠标最后一次在图片框上的坐标。仅当鼠标在事件关联的控件(这里是图片框)上移动时,MouseMove事件才被触发。我们不能将那些数字就留在那里,不是吗?

PictureBox正好有另一个事件,可用它来修复这个问题:MouseLeave事件。MouseLeave事件仅在鼠标离开控件时触发。执行下列步骤,在鼠标离开图片框时清除坐标。

1.如果项目还在运行,关闭Picture Viewer窗体来终止它。

2.打开控件picShowPicture的事件列表,并选择MouseLeave。

3.在MouseLeave事件中输入以下代码:

lblX.Text = ""

lblY.Text = ""

按 F5 键再次运行项目,并让鼠标在图片框上移动,然后移出图片框,注意到这时坐标消失了。再将鼠标移回图片框,坐标又出现了———太好了!现在停止运行项目。

还有一项工作要做。注意到运行项目后标签上显示了“X:”和“Y:”吗?当用户在图片框上移动鼠标时才显示它们不是更好吗?可以在“属性”窗口中将这两个标签的Text属性设置为空。然而,如果这样做,在设计器的窗体中将看不到它们,导致可能将其他控件放在它们上面。一种更好的解决方案是在窗体加载时初始化它们的值。执行下列步骤可以实现这一点。

1.从左上角的下拉列表中选择“(ViewerForm 事件)”,这是到窗体的对象引用(如图4.8所示)。

图4.8 窗体本身总是在对象列表的开头

2.打开右上角的事件列表并从中选择Load。Load事件在窗体加载时自动执行,非常适合用于初始化标签控件。

3.输入下面两条代码语句:

lblX.Text = ""

lblY.Text = ""

这就完成了。按 F5 键来运行项目。窗体被加载时,坐标标签应为空的(这使得它们不可见)。当鼠标在图片框上移动时,将显示坐标,而当鼠标离开图片框时,坐标又消失了。选择正确的事件,再加上一点代码,就能完成很多功能。

如果读者使用过Visual Basic 2010之前的版本,肯定遇到过没有归属的事件。您知道,事件过程的名称由控件名、下划线和事件名组成,如txtAddress_TextChanged。用户修改控件的名称时,Visual Basic不会根据控件的新名称相应地修改其事件声明,但确保事件与控件相关联。这是因为每个事件声明的末尾都有关键字Handles,然后是对象名;关键字Handles将事件处理程序同控件的事件关联起来。修改控件名时,Visual Basic将修改Handles引用,但不修改事件名本身。虽然代码仍能够运行,但应该手工修改相应过程的名称,使其同新的控件名匹配;这对于调试复杂的代码很有帮助。

提示:虽然用户修改控件名时,Visual Basic不会更新事件过程的名称,但将更新在其他过程中对该控件的引用。这是Visual Basic中一种较新的功能,可节省大量的时间。

在这一章,读者学习了事件驱动的编程,包括什么是事件、如何触发事件以及如何避免递归事件。另外,还学习了如何访问对象的事件和如何使用参数。读者编写的很多代码都将响应某种事件而执行,通常需要为一个控件的多个事件编写代码。理解事件如何工作,包括知道可用的事件及其参数,将能够创建复杂的可响应各种用户和系统输入的Visual Basic程序。

问:能够为对象创建自定义的事件吗?

答:可以为自定义的对象(将在第 16 章介绍)创建自定义的事件,也可以为已有的对象创建自定义事件。然而,创建自定义事件不在本书的讨论范围内。

问:没有界面的对象是否能够支持事件?

答:是。然而,要使用这种对象的事件,对象变量必须用特定的方式创建,否则事件不可用。这有点复杂,超出了本书的范围。如果代码中有支持事件的对象,可参考帮助文档:使用关键字WithEvents获取有关如何使用这些事件的信息。

1.列举三种可导致事件发生的情况。

2.判断对错:所有对象都支持同一组事件。

3.按钮的默认事件是什么?

4.在事件中编写代码导致该事件被触发时,将导致事件不断被触发,这种情况称为什么?

5.访问控件的默认事件处理程序的最简便方法是什么?

6.所有控件事件都传递一个对触发事件的控件的引用。保存这个引用的参数的名称是什么?

7.修改控件的名称后应如何做?

1.用户输入、系统输入和其他代码。

2.错。

3.Click。

4.递归。

5.在窗体设计器中双击控件。

6.sender。

7.相应地修改其事件过程的名称。

1.使用目前为止学到的知识创建一个新项目,该项目有一个窗体,窗体在设计时为灰色,而在运行时为蓝色。

2.创建一个包含一个窗体和一个文本框的项目。添加代码到TextChanged事件中,使得用户输入文本时导致递归。提示:在用户输入的文本后拼接一个字符,使用的语句如下:

txtMyTextBox.Text = txtMyTextBox.Text & "a"

&告诉Visual Basic,在已有的文本框内容末尾加上字母 a。注意,这将导致堆栈溢出错误,这不是件好事!

第5章 创建窗体:基础知识

第6章 创建窗体:高级技能

第7章 使用传统控件

第8章 使用高级控件

第9章 给窗体添加菜单和工具栏

在本章中,读者将学习:

修改窗体的名称;

改变窗体的外观;

在窗体的标题栏中显示文本;

将图像添加到窗体背景中;

为窗体添加图标;

改变鼠标光标;

显示和隐藏窗体;

以正常、最大化或最小化状态显示窗体;

指定窗体的初始显示位置;

禁止窗体出现在任务栏中。

对于绝大部分Windows应用程序界面来说,窗体都是必不可少的。窗体实际上就是窗口,这两个术语经常互换使用。更准确地说,窗口指的是用户可见并可与之交互的对象;而窗体是开发设计时看到的窗口。窗体让用户能够在程序(如第 1章创建的Picture Viewer程序)中查看和输入信息。这些信息可能是文本、图片、图形———几乎可以是能够在屏幕上查看的任何东西。正确地理解如何设计窗体,让您能够为程序创建坚实的界面。

可将窗体理解为用于创建程序界面的画布。在这块画布上,可以写上文本、绘制形状或放上可与用户交互的控件。Visual Basic窗体的一个优点是它们像动态画布;不仅可以通过操纵窗体上的控件来调整窗体的外观,还可以操纵窗体的属性。

在前几章中,读者已经操纵过窗体的下列外观属性:

Text;

Height;

Left;

Top;

Width。

读者将看到,定制窗体不仅仅是操纵这些基本属性。

Windows窗体有太多东西需要介绍,因此我将这些内容分为两章。本章将介绍窗体的基础知识———在项目中添加窗体、操纵窗体的属性、使用 Visual Basic代码显示或隐藏窗体。虽然在前几章,读者已经执行过其中的一些操作,但这里将学习这些操作的具体细节。下一章将介绍有关窗体的高级技巧。

创建新对象时,首先要为它取一个描述性名字,这是本章要介绍的第一项内容。打开第4章创建的Picture Viewer项目,如果没有创建这个项目,可从作者网站下载。

Picture Viewer现在有一些有用的功能,但不是很灵活。在本章,将为该程序创建一个“选项”对话框。通过以下步骤,添加一个用作对话框的窗体。

1.选择菜单“项目”>“添加Windows窗体”打开“添加新项”对话框。

2.在“名称”文本框中,输入OptionsForm.vb。这将作为窗体的名称和硬盘上定义该窗体的文件的名称。

3.单击“添加”按钮(或双击“Windows 窗体”图标)关闭“添加新项”对话框,将窗体添加到项目中(如图5.1所示)。

图5.1 每个新窗体都是一块空画布

任何时候都可以通过“属性”窗口修改窗体的名称,这样修改的是窗体的Name属性(但并没有修改硬盘上的文件名)。尽可能在创建窗体时就赋予窗体及其文件一致的名称。

“属性”窗口实际上可以显示窗体的两组属性。现在它显示的可能是窗体的文件属性(对硬盘上的物理文件进行说明的属性,如图5.2所示)。如果是这样,在设计器中单击窗体,以查看它的开发属性。单击窗体本身将显示窗体的开发属性,而在“解决方案资源管理器”中单击窗体名将显示窗体的物理文件属性。这就是为什么我通常让您在设置窗体属性前单击窗体的原因。

图5.2 文件属性有时很有用,但不能用来对窗体进行太多的操作

现在浏览一下“属性”窗口中的窗体属性。本章将介绍如何使用窗体的一些常用属性来定制窗体的外观。

应将窗体标题栏中的文本设置为有意义的内容(注意,正如读者在本章后面将看到的,并非所有窗体都有标题栏)。标题栏中显示的文本实际上是赋予窗体的Text属性的值。通常,该文本为下列内容之一。

程序的名称。当窗体是程序的主要或唯一窗体时,这是最合适的。例如,第1章创建的主窗体便使用了程序的名称。

窗体的用途。这大概是标题栏显示文本的一种最常见情况。例如,如果窗体用于选择打印机,可将 Text 属性设置为“选择打印机”。采用这种方式时,应使用主动语句(例如,不要使用“打印机选择”)。

窗体的名称。如果将窗体的名称放在窗体的标题栏中,要使用有意义的名称,而不要使用窗体实际名称。例如,如果采用命名规范将窗体命名为 LoginForm,应使用“登录”或“用户登录”。

将窗体的Text属性改为Picture Viewer Options。可能需要单击窗体,才能在“属性”窗口中看到其属性。窗体应如图5.3所示。

图5.3 设置标题栏文本时应尽量使用常识

注意:和窗体的其他大部分属性一样,可在任何时候使用Visual Basic代码来修改Text属性。

虽然大部分窗体的背景色都是灰色(这是 Windows 标准的 3D 颜色方案的一部分),但窗体的背景色可修改为任何颜色。要修改窗体的背景色,可以修改BackColor属性。BackColor属性是一个独特的属性,可用颜色名或RGB值来指定。

默认情况下,BackColor属性设置为名为Control的颜色。该颜色是系统颜色,可能不是灰色。当 Windows 被安装时,它配置了一种默认的颜色方案。在 XP 以前的所有 Windows版本中,窗体和其他对象的颜色都是战舰灰。在XP、Vista和Windows 7中,这种颜色是浅褐色(但它在大部分显示器上看起来还是灰色)。Windows 用户可以修改系统颜色。例如,色盲患者倾向于将系统颜色改为对比度比默认颜色更强的颜色,以便能够更容易区分对象。将系统颜色指定给窗体或控件时,对象的外观将调整以符合用户的系统颜色方案。这不仅发生在窗体首次显示时;对系统颜色方案的修改将立即传递到所有使用相关颜色的对象上。

注意:尽量使用系统颜色,使应用程序与用户期望的相符,并避免使用色盲患者不易区分的颜色。

现在,修改窗体的背景颜色:在“属性”窗口中删除属性BackColor的值Control,再输入“0,0,255”,然后按回车键或Tab键来提交输入。提交输入后,RGB值将变成Blue。如果Visual Basic找到与输入的RGB值相匹配的颜色名,将自动把RGB转换成颜色名。

现在窗体是蓝色的,因为您输入了一个没有红色、没有绿色和最大蓝色的值(颜色值的取值范围为0~255)。实际上,可能很少直接输入RGB值,而是从调色板中选择颜色。要查看可用来为BackColor属性选择颜色的调色板,可单击“属性”窗口中BackColor属性的下拉箭头,如图5.4所示。

图5.4 所有颜色属性都有可以选择颜色的调色板

注意:第18章将讨论系统颜色。

当下拉列表出现时,Web选项卡中的蓝色被选中。这是因为输入的RGB值是0,0,255, Visual Basic自动查找与输入值相匹配的颜色名,因此找到了蓝色。第 2章解释了调色板,因此这里不再详细介绍。现在,选择“系统”选项卡以查看可用的系统颜色,然后从列表中选择Control,将窗体的BackColor属性改回默认的Windows颜色。

除修改窗体的背景色外,还可将图片放在背景中。要将图片添加到窗体中,可设置窗体的BackGroundImage属性。将图像添加到窗体中时,图像被绘制在窗体的背景上。窗体上的所有控件都出现在图片上。

执行下列步骤将图像添加到窗体中。

1.单击窗体选中它。

2.将窗体的Size属性改为“400,300”。

3.单击“属性”窗口中的BackGroundImage属性。

4.单击出现在属性旁的“生成”按钮(带三个点的小按钮)。

5.这时将打开对话框“选择资源”,如图5.5所示。单击单选按钮“本地资源”。

图5.5 硬盘上的图像是本地资源

6.单击“导入”,然后找到文件 Options.bmp,该文件可通过从我的网站下载示例文件得到。

7.返回“选择资源”对话框。单击“确定”加载图片。这时,选择的图片将显示在窗体的背景中,如图5.6所示。

图5.6 与图片框一样,窗体也可显示图片

如果选择的图像比窗体小,Visual Basic将显示额外的图片拷贝,制造出平铺效果(Tiled Effect)。您选择的图像被有意设为与窗体大小相同,因此不必担心这个问题。

在“属性”窗口中,BackGroundImage 属性的左边是一个包含加号的小框。这表示该属性还有相关属性(子属性)。单击加号以展开子属性列表,如图5.7所示。对于BackGroundImage属性,Visual Basic将显示与分配给该属性的图像相关的很多属性,如图像的尺寸和格式。注意,这些属性都是只读的(Tag属性除外);但并非所有子属性都是只读的。

图5.7 子属性显示图像的细节

提示:为窗体添加背景图片可使程序更优美,但可能使窗体产生不必要的混乱,导致用户感到迷惑。不要仅仅是因为能够这样做就去添加背景图片,仅当图片真正有助于优化界面时才添加图片。

将图片从窗体中删除与添加图片一样简单。要将添加到窗体中的图片删除,右击BackgroundImage属性名,然后在弹出的菜单中选择“重置”。右击的必须是属性名或值列的“生成”按钮,而不能是值本身。如果右击了属性值,将弹出不同的菜单,该菜单没有“重置”选项。试着删除图片,但在继续阅读前要重新加载它。

分配给窗体的图标显示在窗体标题栏的左段;窗体最小化时它显示在任务栏中;按Alt+Tab 组合键切换到另一个任务时,它显示在用图标表示的任务列表中。通常图标代表了应用程序;因此,应给用户可最小化的每个窗体分配图标。如果不分配图标给窗体,Visual Basic将提供一个默认图标,供窗体最小化时使用。默认图标是通用的,实际上并不能代表什么,因此应避免使用默认图标。

以前,推荐给每个窗体一个独特的图标,以表示窗体的用途。这一点在包含了上百个窗体的大型应用程序中很难做到。相反,通常最好将所有窗体的Icon属性设置为最能代表应用程序的图标。由于在第1章是通过“属性”窗口给主窗体指定图标的,这里将再次使用该图标,但将使用Visual Basic代码来指定。要将图标指定给窗体,按下列步骤进行。

1.在窗体设计器中双击窗体,以访问其默认事件:Load事件。

2.在Load事件中输入下面的语句:

Me.Icon = ViewerForm.Icon

本书前面介绍过,Me 表示代码所属的窗体,这里为 Options 窗体。上述代码将 Option窗体的图标设置为窗体Picture Viewer(主窗体)的图标。现在,如果在设计视图中修改主窗体的图标,也可确保Options窗体将显示正确的图标;如果通过“属性”窗口来设置Options窗体的图标,将失去这种灵活性。

单击工作区域顶端的“OptionsForm.vb[设计]”选项卡,切换到窗体设计器。在 Picture Viewer Options窗体的标题栏中,有三个按钮,如图 5.8所示。

图5.8 可以控制显示这三个按钮中的哪个

窗体标题栏中的三个按钮分别是:

最小化;

最大化;

关闭。

注意,窗体的图标也是一个按钮,但仅当应用程序运行时才是,在设计模式下不是。如果用户单击图标,将显示一个下拉菜单,菜单中包含一些基本选项,如图5.9所示。

图5.9 窗体图标的行为类似于按钮

最小化和最大化按钮可分别让用户能够快速隐藏窗体或让窗体覆盖整个屏幕。读者在应用程序时可能使用过这两个按钮。不必为它们编写代码———这将由Windows自动实现。您只需确定窗体是否需要最大化或最小化按钮。例如,在这个 Options 窗体中,内容不能调整大小,因此不需要最大化按钮。另外,您希望用户使用这个窗体后关闭它,因此也不需要最小化按钮。现在,通过设置窗体的下列属性来删除这两个按钮。

如果不希望用户能够通过“关闭”按钮(窗体右上角带“×”的按钮)来关闭窗体,可以将属性ControlBox设置为False。然而,需要知道的是,当属性ControlBox被设置为False时,最大化和最小化按钮将被自动隐藏。如果需要最大化和最小化按钮,必须将ControlBox属性设置为True。

读者在使用其他Windows程序时,可能注意到窗体的边框可以不同。有些窗体的边框可以单击并拖曳以调整窗体的大小;有些窗体的边框是固定的,不可修改;还有些窗体根本没有边框。窗体边框的外观和行为由其FormBorderStyle属性控制。

FormBorderStyle属性可设置为以下几个值之一:

None;

FixedSingle;

Fixed3D;

FixedDialog;

Sizable;

FixedToolWindow;

SizableToolWindow。

现在按F5键来运行项目,在Picture Viewer的主窗体上移动鼠标。这个窗体有可调整的边框,也就是说可以通过拖曳边框来调整窗体的大小。将鼠标移到窗体的边缘,鼠标将从大箭头变为带双向箭头的线段,箭头指出了边框可拉伸的方向。将鼠标移到一角时,鼠标将变成斜的,表示可以同时伸展相交于这个角的两条边。单击并拖曳边框来改变窗体的大小。

现在,选择菜单“调试”>“终止调试”(或单击窗体上的“关闭”按钮)来终止项目的运行,并将OptionsForm的FormBorderStyle属性改为None。可以看到标题栏也消失了,如图5.10所示。当然,当标题栏消失时,就没有可见的标题栏文本,没有控件框,也没有最小化和最大化按钮。另外,也不能移动或调整窗体的大小。将窗体的BorderStyle指定为None,通常都是不合适的,但如果您需要这样做(如启动屏幕),Visual Basic 2010提供了这种功能。

图5.10 无边框的窗体也没有标题栏

接下来,将OptionsForm窗体的FormBorderStyle属性改为FixedToolWindow。这种设置将导致窗体的标题栏比正常显示的要小,文本的字体也更小,如图5.11所示。另外,在标题栏中,除文本外只有“关闭”按钮。Visual Basic各种设计窗口,如“属性”窗口和工具箱,都是典型的工具窗口。

图5.11 工具窗口是一种特殊窗口,其工具栏占用尽可能少的空间

FormBorderStyle属性表明,只要修改一个属性就可以极大地影响对象的外观和行为。将窗体的FormBorderStyle属性改回到FixedSingle。

通常,如果窗体可以调整大小,便可以最大化,即覆盖用户的整个屏幕;也可以最小化到任务栏中。如果要限制窗体最小化和最大化时的大小,可分别设置属性 MinimumSize 和MaximumSize。通常,应避免这样做,但有时这样做很有用。要知道的一点是,如果窗体有最小化按钮,设置MinimumSize属性并不能防止用户将窗体最小化。

第三部分“编程”将详细介绍Visual Basic 2010编程,本章将尽量避免涉及太多的编程细节,让读者能够将注意力集中在当前介绍的概念上。然而,如果不能显示和隐藏窗体,懂得如何创建窗体将毫无意义。Visual Basic 2010在程序启动时只自动显示一个窗体。要显示其他窗体,必须编写代码。

显示窗体的方法有多种,本节将介绍最常用的方法。

下面完善Picture Viewer程序,让用户能够显示刚才创建的Options窗体。按下面的步骤在程序中添加这种功能。

1.在“解决方案资源管理器”中双击ViewerForm.vb,以便在设计器中显示主窗体。

2.右击按钮Draw Border,并从快捷菜单中选择“复制”。

3.右击窗体(标签X和Y的下方)并选择“粘贴”。

4.按如下设置新按钮的属性。

5.双击按钮来访问它的Click事件。

6.输入如下代码:

OptionsForm.Show()

按F5键运行项目,然后单击Options按钮,Options窗体将出现在主窗体的上面。单击Picture Viewer窗体(后面的窗体)的标题栏,这将把Picture Viewer窗体拉到前面,遮住窗体Options。有时候,这种行为正是您希望的,但大多数时候不希望如此。对于大多数窗体,希望用户关闭它之前,不能切换到其他窗体。下一节将介绍如何修改这种行为,但在此之前,请结束运行项目。

可以对用户显示两种窗体:模态窗体和非模态窗体。非模态窗口不会导致其他窗口不能使用(使用Show()方法来显示Options窗体时,它显示为非模态窗体,因此当Options窗体显示时,可以通过单击切换到 Picture Viewer窗体)。非模态窗口的另一个例子是Word(和Visual Basic 2010)中的“查找和替换”窗口。当“查找和替换”窗口可见时,用户仍可以访问其他窗口。

另一方面,当窗体作为模态窗口显示时,同一应用程序中的其他所有窗口都不可用,直到模态窗体关闭;其他窗体将不会接受键盘或鼠标输入,用户只能处理模态窗体。模态窗体关闭后,用户可与程序中其他的可见窗口交互。如果在模态窗体中打开了另一个窗体,该窗体将获得焦点直到关闭,依此类推。模态窗体最常用于创建对话框,用户在其中需处理特定数据和控件后才能继续。例如,Microsoft Word的“打印”对话框便是一个模态对话框。“打印”对话框显示后,Word主窗口便不能访问,直到“打印”对话框关闭。程序中的大多数辅助窗口都是模态窗口。

注意:可从模态窗体显示另一个模态窗体,但不能从模态窗体显示非模态窗体。

窗体的模态性取决于窗体如何显示而不是如何创建(模态窗体和非模态窗体的创建方式相同)。要将窗体显示为非模态窗口,使用窗体的 Show()方法;要将窗体显示为模态窗口,调用窗体的ShowDialog()方法。将按钮的Click事件的代码改为:

OptionsForm.ShowDialog()

输入代码后,按F5键运行项目。单击Options按钮来显示Options窗体。将窗体稍微拖离主窗体,然后试着单击Picture Viewer主窗体或它的控件,将发现单击无效。现在通过单击模态窗体标题栏上的“关闭”按钮来关闭它。这时,Picture Viewer主窗体又可用了,可以再次单击“Options”按钮(或其他按钮)了。完成测试后,终止项目的运行。

提示:可在代码中检测窗体的Modal属性来确定窗体是否显示为模态窗体。

使用窗体的属性Size、Location和StartPosition,可以在任何位置以任何大小显示窗体;也可使窗体最小化或最大化显示。不管窗体最大化、最小化还是正常显示,这些窗体的状态都由窗体的WindowState属性决定。

单击“OptionsForm.vb[设计]”选项卡以显示窗体设计器。在“属性”窗口中查看窗体的WindowState属性。新窗体的WindowState属性默认被设置为Normal。运行项目时,窗体的大小和它在窗体设计器中的大小一致,显示的位置由窗体的 Location 属性指定。现在将WindowState属性改为Minimized。在窗体设计器视图中什么都不会发生,但按F5键运行项目并单击 Options 按钮时,您可能认为窗体没有显示,但事实并非如此。该窗体以最小化方式显示到任务栏中了。

终止项目的运行,将 WindowState 属性改为 Maximized。这时窗体设计器视图中也是什么也没有发生。按F5键运行项目并单击Options按钮;这次,Options窗体将覆盖整个屏幕。注意到背景图像被平铺以填满整个窗体(如图 5.12 所示),这一点在添加图像到窗体中时解释过。

图5.12 如果窗体的 Back groundImageLayout属性设置为Tiled,将在窗体上平铺背景图像

注意:窗体最大化时,它将覆盖整个屏幕,而不管 Windows 当前使用的屏幕分辨率是多少。

停止运行项目,将WindowState属性改回Normal。在设计时很少将窗体的WindowState属性设置为Minimized(但可能指定为Maximized),但可能遇到需要在运行时修改(或确定) WindowState 属性的情况。和大多数属性一样,可使用代码来实现这一目标。例如,下面这条语句将Options窗体最小化:

OptionsForm.WindowState = FormWindowState.Minimized

输入代码时不必记住值的名称;输入等号后,智能感知下拉列表将打开。

窗体首次显示在屏幕上的位置不是随机的,而是由窗体的 StartPosition 属性控制的。StartPosition属性可设置为表5.1列出的值之一。

表5.1 StartPosition属性的值

注意:最好将所有窗体的StartPosition属性都设置为CenterParent,除非有理由不这样做。对于项目中第一个显示的窗体,可以考虑使用 Windows DefaultLocation(但我通常喜欢用CenterScreen)。

要了解该属性如何影响窗体,执行下列步骤。

1.按F5键运行项目。

2.移动Picture Viewer窗体并单击Options按钮。注意观察Options窗体出现在什么地方。

3.关闭Options窗体。

4.将Picture Viewer窗体移到右上角,然后再次单击Options按钮。

您是否注意到了,不管单击Options按钮时 Picture Viewer窗体的位置在哪里,Options窗体总是出现在同一位置?我并不喜欢这种方式。现在,终止项目的运行,将 Options 窗体的 StartPosition 属性修改为 CenterParent。接下来,重复上面的步骤,可以看到不管 Picture Viewer窗体在哪里,Options窗体总是出现在Picture Viewer窗体的正中央。

停止运行项目,并保存所做的工作。

能够为最小化的窗口显示一个图标是个不错的功能,但有时候必须防止窗体出现在任务栏中。如果应用程序有很多工具窗口浮动在主窗口上,如Visual Basic 2010的“解决方案资源管理器”和工具箱,则不应将除主窗口外的所有窗体都显示在任务栏中。要防止窗体出现在任务栏中,可将窗体的ShowInTaskbar属性设置为False。如果用户将ShowInTaskbar属性被设置为False的窗体最小化,将不能通过任务栏访问它,但仍可以按Alt + Tab组合键切换到该窗体;Visual Basic不允许应用程序对用户完全不可用。

窗体完成其任务后,我们希望它消失。然而,窗体“消失”的含义有两种。第一种是可在不关闭窗体或释放窗体资源的情况下使窗体不显示(这称为隐藏)。为此,可将窗体的Visible属性设置为False。这将隐藏窗体的可见部分,但窗体仍驻留在内存中,可通过代码进行操纵。另外,窗体被隐藏时,其所有变量和控件的值都保持不变,因此如果窗体再次显示,看起来将与其Visible属性被设置为False时一致。

第二种方法是完全关闭窗体并释放它占用的资源。不再需要窗体时应关闭它,让Windows能够收回该窗体占用的所有资源。为此,可调用窗体的Close()方法,如下所示:

Me.Close()

在第3章,读者知道Me用于引用当前Form对象。因为Me表示当前Form对象,因此可使用Me来操纵当前窗体的属性或调用其方法,如Me.Visible = False。

Close()方法告诉Visual Basic,不只是隐藏窗体,还要完全销毁它。

执行以下步骤,创建一个用于关闭Options窗体的按钮。

1.选择“frmOption.vb[设计]”选项卡以显示Options窗体的窗体设计器(如果它还没有显示)。

2.添加一个新按钮到窗体中,并按如下设置其属性:

3.接下来,在设计器中双击OK按钮来访问其Click事件,并输入下列语句:

Me.Close()

4.最后,按F5键运行项目。单击Options按钮以显示Options窗体,然后单击OK按钮关闭它。这时,窗体不只是隐藏,而是完全从内存中卸载,不再存在了。

注意:如果只想隐藏窗体而不从内存中卸载,可调用窗体的Hide()方法或将窗体的Visible属性设置为False。这将使窗体的状态保留到再次显示时。

在本章,读者学习了创建窗体的基本知识。学习了如何在项目中添加窗体、设置基本的外观属性以及使用代码来显示和隐藏窗体。在下一章,读者将学习有关使用窗体的高级技能。掌握本章和下一章的内容后,就可以开始深入学习 Visual Basic控件了———这是创建界面的乐趣真正开始的地方!

问:有多少窗体属性应在设计时而不是运行时设置?

答:应在设计时将可以设置的属性都设置。首先,这样比较容易,因为您看到的将是用户看到的。另外,调试也会更容易,因为代码少了。

问:是否应让用户最小化和最大化所有窗体?

答:可能不需要。首先,如果最大化时窗体的控件没有被设置为作相应的调整,最大化是没有意义的。“关于”对话框、“打印”对话框和“拼写检查”窗口不应设置为大小可调整。

1.判断对错:窗体标题栏中显示的文本由TitleBarText属性的值决定。

2.名称为Control的是哪种颜色?

3.举出三个显示窗体图标的地方。

4.标题栏比正常标题栏更小的窗口称为什么?

5.要使窗体的最小化和最大化按钮可见,哪个元素必须是可见的?

6.通常,最好将窗体的StartPosition属性设置为什么?

7.要在代码中最大化、最小化或恢复窗体,要设置什么属性?

8.要显示隐藏的窗体,应设置窗体的什么属性?

1.错。窗体标题栏中显示的文本由窗体的Text属性决定。

2.系统颜色。

3.在标题栏中、任务栏中以及用户按Alt+Tab组合键时。

4.工具窗口。

5.窗体的ControlBox属性必须设置为True。

6.主窗体设置为CenterScreen,其他所有窗体设置为CenterParent。

7.窗体的WindowState属性。

8.将窗体的Visible属性设置为True。

1.创建一个Windows应用程序项目,项目中只有一个窗体,窗体上有两个按钮。其中的一个按钮被单击时,将窗体左移2像素。另一个按钮将窗体右移2像素。提示:使用窗体的Left属性。

2.创建一个Windows应用程序项目,项目中包含三个窗体。其中启动窗体包含两个按钮,其他两个窗体为工具窗口,并让一个按钮显示第一个工具窗口,另一个按钮显示第二个工具窗口。

在本章中,读者将学习:

将控件添加到窗体中;

调整控件的位置、大小和间距以及对齐和锚定控件;

创建智能的Tab顺序;

调整控件的z-顺序;

创建总是在最前面的窗体;

创建透明窗体;

创建多文档界面。

窗体就像一块画布,虽然可以设置窗体的属性来定制它,但还需要添加控件来执行功能。在前一章,读者学习了如何将窗体添加到项目中、设置窗体的基本属性以及显示和隐藏窗体。在本章,读者将学习如何将控件添加到窗体中,包括排列和对齐控件,以创建美观和有用的界面;还将学习如何创建高级的多文档界面(Multiple Document Interface,MDI),就像Photoshop等应用程序所使用的那样。学习本章的内容后,读者将能够学习Visual Basic中各种控件的细节。

控件是放在窗体中供用户交互的对象。如果读者完成了前几章的示例,则已经为窗体添加过控件。然而,下面将把很多控件添加到窗体中,理解这个过程的各个方面很重要。

所有可添加到窗体中的控件都位于工具箱中。默认情况下,工具箱停靠在设计环境的左边。如果只是偶尔将控件添加到窗体中,这个位置很方便;然而,如果要执行大量的窗体设计工作,最好将它停靠在设计环境的右边,这样它就不会覆盖当前的工作窗体。

注意:要让工具箱不再停靠并移到其他位置,必须确保它没有被设置为自动隐藏。

工具箱中有可以展开和折叠的分类标题。在大多数设计中,使用的都是“公共控件”中的控件。然而,随着技能的提高,可能需要使用其他分类中更复杂和专用的控件。

在窗体中添加控件有4种方法,读者学习了其中一种,即复制控件并将其粘贴到窗体中,而本章将使用其他3种方法。打开前一章创建的Picture Viewer项目(或打开我的网站提供的起始项目),然后在“解决方案资源管理器”中双击OptionsForm.vb,在设计器中显示Options窗体。

1.在工具箱中双击来添加控件

添加控件的最简单方法是在工具箱中双击控件。现在试一下:显示工具箱,并双击TextBox工具。Visual Basic将在窗体的左上角创建一个新的文本框(必须将鼠标移出工具箱使工具箱关闭,才能看到新添加的控件)。用户双击工具箱中的控件(运行时不可见的控件除外)时,Visual Basic 在当前获得焦点的控件上创建新控件,其大小是添加的控件类型的默认大小。如果窗体上没有其他控件,新控件将放在左上角。将控件添加到窗体中后,可以任意地移动或调整其大小。

2.通过从工具箱中拖曳来添加控件

如果要控制新控件的放置位置,可以将控件拖到窗体中。现在试一下:显示工具箱,单击Button控件,将它拖到窗体中。当鼠标大概位于希望的位置时,松开鼠标按钮。

3.通过绘制来添加控件

将控件放到窗体中的最后一种也是最精确的方法是,在窗体上绘制控件。执行下列步骤。

1.显示工具箱,并单击ListBox工具选中它。

2.将鼠标移到希望列表框左上角出现的位置,然后单击并按住鼠标。

3.拖曳鼠标到希望列表框右下角出现的位置,然后松开鼠标按钮。

这样,创建的列表框将与绘制的矩形重叠。这是将控件添加到窗体中的最精确方法。

提示:如果读者喜欢通过单击并拖曳鼠标来绘制控件,强烈建议使工具箱浮动或将其停靠在设计环境的右边或下边。如果将工具箱停靠在左边,将干扰控件的绘制,因为它覆盖了窗体的很大一部分。

注意,每个工具分类中的第一项都是“指针”。指针实际上并非控件。当“指针”项被选中时,设计环境处于选择模式而不是可创建新控件的模式。当“指针”被选中时,可在设计器中单击控件以选中它并查看其属性。这是开发环境的默认行为。

将控件添加到窗体中很容易;难的是如何排列控件以创建直观和吸引人的界面。可创建的界面是无穷无尽的,因此这里不介绍如何设计特定的界面(但强烈建议创建外观和行为与类似的商业应用程序接近的窗体)。然而,将介绍如何移动和排列控件以及调整其大小的技巧,使它们按希望的那样显示。掌握这些技巧后,就能更高效地创建界面,将节省的时间用于编写代码。

1.使用网格(大小和对齐)

读者可能已经注意到,到现在为止,读者使用的控件似乎都自动对齐到看不见的网格———没错,确实如此。在启用了网格的项目中,在窗体上绘制或移动控件时,控件将自动与最近的网格对齐。这使得调整控件的大小和位置更精确。实际上,我经常发现网格的作用有限,因为希望的大小和位置通常与网格的位置不符。然而,可以控制网格的粒度和可见性,建议对两者都进行控制。

在Visual Basic 2010中,网格设置是全局的:不能分别为项目或窗体设置网格。要显示网格设置,选择菜单“工具”>“选项”,打开“选项”对话框,然后单击左侧目录树中的“Window窗体设计器”以显示设计器设置,如图6.1所示。

图6.1 网格设置在 Visual Basic 2010中是全局的

这里我们感兴趣的设置如下。

GridSize:这决定了网格在水平方向和垂直方向上的粒度,单位为像素。更小的网格意味着可以更细致地控制控件的大小和位置。

LayoutMode:这决定了设计器将用户移动的控件对齐到网格还是其他控件。

ShowGrid:这决定了在设计器的窗体上是否显示网格点。

SnapToGrid:这决定了是否使用网格。如果设置为False,网格大小设置将被忽略,控件将不会自动与网格对齐。

当前,读者没有使用网格来绘制控件,但在移动控件时使用了对齐线,因为LayoutMode设置为 SnapLines。本节稍后将更详细介绍这一点。现在要介绍网格的工作原理,请将LayoutMode设置为SnapToGrid。

下面为网格指定更细的粒度(网格点之间的间距更小)。这有助于设计,因为边缘不容易对齐到不希望的位置。

要调整网格的粒度,可修改GridSize的设置。将网格的Width和Height设置得越小,网格就越精确,用户就能够更细致地控制大小和位置;反过来,使用的值越大,网格就越粗糙,用户的控制能力就越小。使用较大的网格时,控件边缘更容易与网格对齐,用户更难细致地控制控件的大小或位置。执行下列步骤。

1.将GridSize属性改为6,6。

2.将ShowGrid属性改为True。

3.单击“确定”按钮保存修改,并返回窗体设计器。注意到现在出现了网格点,如图6.2所示。如果没有出现网格点,需要先关闭设计器中的选项卡,然后双击“解决方案资源管理器”中的OptionsForm.vb进行刷新。

图6.2 网格不一定要可见才是活动的

试着拖曳窗体中的控件,或者拖曳它们的边缘以调整大小。注意到使用更细的网格将能够更好地控制位置。将GridSize改为一组更高的值,如25,25,再看看此时的情况。完成后,将GridSize值改回为4,4。

不幸的是,小网格有一个副作用,那就是网格可能分散注意力。读者可以决定最喜欢的方式,但通常我都关闭窗体上的网格。事实上,我更喜欢下面将讨论的新特性“Snap To Line”。

注意: ShowGrid属性只决定是否显示网格,而不决定它是否处于活动状态;网格是否活动由窗体的SnapToGrid属性决定。

2.使用对齐线(snap line)

“对齐线”是一种有用的新布局模式。现在执行下列步骤,让Visual Basic 2010使用“对齐线”。

1.选择菜单“工具”>“选项”,打开“选项”对话框。

2.单击“Window窗体设计器”以显示布局设置。

3.将LayoutMode改为SnapLines。

4.将ShowGrid属性设置为False以隐藏网格。

5.单击“确定”保存设置。

对齐线使控件边缘和其他控件的边缘对齐,帮助用户更快地创建更好的界面。要掌握它,最好的方法是使用。执行下列步骤。

1.拖曳控件,使它们大概处于图6.3所示的位置。

2.单击ListBox选中它。

3.单击控件左边缘上的白色正方形,将它向左拖曳。当其边缘靠近上面按钮的竖直边缘时,将出现对齐线,且边缘将与该线对齐,如图6.4所示。

图6.3 从这种布局开始

图6.4 对齐线使对齐控件边缘更容易

可以继续拖曳控件边缘,当其垂直与其他控件对齐时,Visual Basic将创建更多的对齐线。控件也支持水平对齐线,拖曳控件时所有对齐线都管用。当前,这种特性对读者来说可能微不足道,但它的确是Visual Basic增加的一项重要功能,将为您节省很多时间。

3.选择一组控件

随着技能的提高,读者创建的窗体将越来越复杂。有些窗体可能包含几十个甚至几百个控件。Visual Basic提供了使得可以很容易地对齐一组控件的功能。

默认情况下,单击窗体上的控件将选中它,同时取消选中原来选中的控件。要对多个控件进行操作,需要选中一组控件。这有两种方法。第一种方法使用选框。要选择一组控件,首先在窗体的任何位置单击并拖曳鼠标,这时窗体上将出现一个矩形。松开鼠标后,与矩形交叉的所有控件都将被选中。注意,不必使用选框将整个控件都包围起来,只需与控件部分交叉就能选中它。下面试一下:在窗体的左下角单击,然后将鼠标拖向窗体的右上角。交叉或包围除OK按钮外的所有控件,如图6.5所示。当矩形包围或交叉所有控件后,松开鼠标按钮,这些控件将被选中,如图6.6所示。

图6.5 单击并拖曳来创建选框

图6.6 所有选中的控件都有虚线边框和大小调整手柄

控件被选中时,将有虚线边框和几个大小调整手柄(位于虚线边框的四个角和四边中央的正方形)。注意这些大小调整手柄。在被选中的控件组中,大小调整手柄为白色的控件是活动控件。使用Visual Basic工具(如对齐和格式工具)对一组控件进行操作时,将使用活动控件的值。例如,如果将图6.6所示的选中控件左对齐,每个控件的Left属性值都将设置为活动控件(带白色手柄的控件)的Left属性值。实际上,使用选框选择一组控件时,用户并不能控制Visual Basic将选择哪个控件作为活动控件。在这里,希望所有控件都与按钮对齐,因此必须采用另一种选择控件的方法。现在,在窗体的任何位置单击(不要单击控件),取消选中所有控件。

注意:并非所有的大小调整手柄都是可移动的。例如,Visual Basic不允许修改文本框的高度,除非将文本框的Multiline属性设置为True,因此,当文本框控件被选中时,只有左右两边会出现白色的大小调整手柄。

选择多个控件的第二种方法是,在单击控件时按住Shift键或Ctrl键(两者的效果相同)。这类似于在资源管理器中选择多个文件。执行下列步骤。

1.单击列表框选中它。只有一个控件被选中时,它将被视为活动控件。

2.按住Shift键并单击左上角的文本框;现在列表框和文本框都被选中。列表框是活动控件,因为最先被选中。同样,选中多个控件后,活动控件有白色的大小调整手柄,以便用户识别。

3.按住Shift键,单击按钮控件(不是OK按钮),将它添加到选中的控件组中。现在,所有控件都选中,列表框仍是活动控件。

注意:按住Shift键并单击选中的控件,将取消选中该控件。

必要时可以结合使用这两种选择方法。例如,首先使用选框选中所有控件。如果选中了不想选中的控件,只要按住Shift键并单击该控件,就可以取消选中它。

提示:如果必须单击相同的控件两次,如取消选中然后重新选中,这时速度要慢。如果单击的速度太快,Visual Basic会将您的动作解释为双击,进而为控件创建一个新的事件处理程序。

4.对齐控件

Visual Basic包含很多格式工具,可用于设计美观的界面,它们大多数都在图 6.7所示的布局工具栏中。现在,右击 Visual Basic顶部的工具栏,并在弹出菜单中选择“布局”以显示布局工具栏。布局工具栏包含水平和垂直对齐控件边缘或中心的选项。

图6.7 布局工具栏可使对齐控件简单快捷

将鼠标在布局工具栏上自左向右缓慢地移动,可查看工具提示。使用布局工具栏,可以:

使选中的控件左对齐、居中对齐或右对齐;

使选中的控件顶部对齐、中间对齐或底部对齐;

使选中的控件等宽或等高;

使选中的控件在水平方向或垂直方向的间距相等;

将选中的控件的前移或后移;

为控件设置Tab顺序,以方便使用键盘导航。

第一项将选中的控件与网格对齐———不是很有趣。然而,其余的按钮都很有用。记住, Visual Basic基于活动控件来对齐控件,这很重要。现在,单击“顶端对齐”按钮,可以看到选中的控件都与活动控件对齐了,如图6.8所示。

图6.8 基于活动控件来对齐选中的控件

5.使控件的大小相同

除对齐控件外,还可使所有选中控件的大小都相同———高度、宽度或两者。为此,可使用工具栏上的“使大小相同”按钮。现在,单击“使大小相同”按钮使所有控件大小相同。这样,所有选中控件的大小都与列表框的大小相同(相当大)。现在试一下:在“属性”窗口的Size属性中输入“75,25”,然后按Tab键提交输入。注意到这种修改影响了所有选中的控件。像这样用“属性”窗口来影响所有选中的控件,能够快速地修改很多需要使用相同属性值的控件。稍后将更详细地介绍这一点。

6.使一组控件的间距相等

正如很多销售人员说的,“…不仅仅是这些”。使用布局工具栏,还可使控件的间距相等。现在试一下:单击工具栏中的“使水平间距相等”,所有控件将均匀分布。接下来,单击工具栏中的“减少水平间距”按钮几次,注意到每次单击时控件的间距都将缩小。使用布局工具栏上的按钮,还可以增大控件的水平间距或垂直间距,或完全删除控件间的间距。现在单击工具栏上的“全部保存”按钮来保存项目。

7.为一组控件设置属性值

下面是很多有经验的Visual Basic开发人员可能忽略的一个技巧:选中多个控件后,可在“属性”窗口中修改属性值。这使得所有选中控件的对应属性都将发生改变。

确保3个控件仍被选中并显示“属性”窗口(如果它还没有显示)。选中一组控件后,“属性”窗口看起来有点不同,如图6.9所示。

没有显示Name属性。这是因为不允许两个控件的名称相同,因此Visual Basic不允许用户输入该属性值。

只显示了所有控件都有的属性。因为选中不同类型的控件后,只有一小部分相同的属性可以访问。如果选中了相同类型的控件,将看到更多的属性。

对于选中控件具有不同值的属性(如这里的Location),该该属性为空。

图6.9 可同时查看多个控件的属性值

如果在属性中输入值,将修改所有选中控件的对应属性。为理解这一点,将 BackColor属性改为黄色,可以看到,所有控件的BackColor属性都变成了黄色。

在本章中,实际上并会不使用这3个控件,因此现在按Delete键将选中的控件删除。

8.锚定控件和自动调整控件的大小

在Visual Basic 新窗体引擎的功能中,我最喜欢的是能够将控件锚定到窗体的一边或多边,以及当用户调整窗体的大小时控件能够自动地调整其大小。以前,必须使用第三方组件(通常很麻烦)或通过在窗体的Resize事件中编写代码来实现这一点,但在Visual Basic 2010的窗体引擎中,这是一项内置的功能。

所有新控件默认的行为是停靠在其容器的左上角。如果希望控件总是显示在窗体的右上角或左下角,该如何办呢?下面介绍如何锚定控件使其在窗体被调整大小时相应地自动进行调整。

执行下列步骤。

1.双击“解决方案资源管理器”窗口中的ViewerForm.vb,这是接下来要修改的窗体。

2.按F5键运行项目。

3.拖曳窗体的右下角放大窗体。注意到控件并没有随着窗体的边缘移动,如图6.10所示。

图6.10 默认情况下,控件锚定在窗体的左上角

4.现在,选择菜单“调试”>“停止调试”终止项目的运行。

5.单击Select Picture按钮选中它,同时取消对窗体的选中。

6.按住Shift键并单击下列按钮:Quit、Draw Border、Options、^和 v。

7.单击“属性”窗口中的 Anchor 属性,然后单击出现的下拉箭头,打开 Anchor 属性特有的下拉框,如图6.11所示。

图6.11 可通过这个独特的下拉框设置控件的Anchor属性

下拉框中央的灰色矩形表示正在设置其属性的控件。上下左右四个瘦长的矩形表示控件可停靠的边;如果矩形被填充,表示控件的边缘已停靠在窗体的这个边缘。执行下列步骤,观察Anchor属性是如何工作的。

1.单击控件左边的矩形,使它变为未填充的,然后单击控件右边的矩形使它变为填充的,如图6.12所示。

图6.12 这种设置将使控件锚定到窗体的顶部和右边

2.单击其他任何属性来关闭该下拉框。Anchor属性的值现在应为“Top,Right”。

3.按F5键运行程序,然后拖曳窗体的一边将其放大。

是不是相当有趣?Visual Basic已经将按钮的右边锚定到窗体的右边,如图6.13所示。实际上,锚定指的是确保控件边缘与窗体边缘的相对距离为常量,这是构建界面的一个极其强大的工具。

图6.13 锚定是一种用于创建自适应窗体的有用功能

注意到,当窗体调整大小时,图片框和坐标标签仍保持原来的位置。这不成问题,也可用Anchor属性来解决。执行下列步骤修改X和Y标签的锚定属性(如果没有停止运行项目,先停止它)。

1.单击X标签选中它。

2.按住Shift键并单击Y标签选中它。

3.与设置按钮一样地设置Anchor属性———取消选中左边,并选中右边(如图6.12所示)。

4.单击其他属性来关闭Anchor属性的下拉框。

图片框与其他控件有点不同,除要锚定它的顶部和左边外,还要锚定它的右边和底部使它能够随窗体放大或缩小。这其实很容易实现。执行下列步骤。

1.单击图片框选中它。

2.打开 Anchor 属性,选中 4 个锚定点(所有 4 个矩形都应以灰色填充,如图 6.14所示)。

图6.14 这种设置使控件随窗体的四边而调整

现在按 F5 键运行项目,拖曳窗体的右下角使它放大。注意到图片框将调整其大小以适应窗体的大小,如图6.15所示。这在查看大图像时很有用。

图6.15 适当地使用Anchor属性可创建灵活的窗体

知道如何使用 Anchor 属性后,不需编写代码就可构建用户可以调整大小的窗体。需要注意的是,当窗体缩到很小时控件可能消失,这取决于Anchor属性的设置。

即使是熟练的Visual Basic编程人员也经常忽略Tab顺序。作为用户,您可能很熟悉Ta b顺序,但可能没有意识到它。在窗体上按Ta b键时,焦点将从当前控件移到Ta b顺序中的下一个控件。这让用户能够快捷地使用键盘导航窗体。控件的 Tab 顺序由 TabIndex属性决定。TabIndex 值为 0 的控件是窗体显示时获得焦点的控件;按 Tab 键,TabIndex值为 1的控件将获得焦点,依此类推。添加控件到窗体中时,Visual Basic为新控件分配下一个可用的TabIndex值(在Tab顺序的最后)。每个控件都有唯一的TabIndex值,TabIndex值总是使用升序。

如果窗体的Tab顺序设置不正确,按Tab键将导致焦点以意外的顺序从一个控件跳到另一个控件,这将给用户带来不便。以前,修改控件的Tab顺序的唯一方法是,通过“属性”窗口来修改TabIndex属性。例如,要使控件成为Tab顺序中的第一个控件,应将其TabIndex属性设置为 0;Visual Basic会相应地调整其他所有控件的TabIndex值。这通常是一个痛苦的过程。虽然手工设置TabIndex属性很方便,如在已有的Tab序列中插入一个控件时,但还有一种更好的方法来设置窗体的Tab顺序。

现在按F5键来运行项目,注意到Select Picture按钮获得了焦点(以蓝色边框加亮)。如果按回车键,将相当于单击该按钮,因为它获得了焦点。现在按Tab键,则Quit按钮将获得焦点,这是因为您在添加 Select Picture按钮后添加了Quit按钮。再按 Tab键,您是否以为Draw Border按钮将获得焦点呢?用户也会这样以为。然而,获得焦点的是^按钮,因为它是下一个添加到窗体中的控件。下面修正这种错误,请单击工具栏中的“停止调试”按钮或关闭该窗口以停止运行项目。

下面通过Visual Basic提供的可视化方法来设置窗体的Tab顺序。

1.布局工具栏上的最后一个按钮是“Tab键顺序”按钮。单击它,Visual Basic将在控件上显示一组数字,如图 6.16 所示。控件上的数字表示它的 TabIndex 属性值,很明显 Tab顺序是错误的。

图6.16 每个控件上的数字表 示 该 控 件 的TabIndex值

2.单击 Select Picture按钮,数字的背景从蓝色变为白色,表示这个控件被选中。如果这个控件的TabIndex值不为0,单击它时这个数字将变为0。

3.单击Quit按钮,将它指定为Tab顺序的下一个按钮。

4.现在,Draw Border按钮的Tab顺序是 5。单击它,它上面的数字将变为 2。

5.按下列顺序单击其余控件:X标签、Y标签、Options按钮、^按钮和v按钮。

6.单击最后一个按钮时,所有数字都变回蓝色背景;现在Tab顺序已经设置好。单击布局工具栏上的“Tab键顺序”按钮,使设计器退出Tab顺序模式。

7.按F5键运行项目,可以看到按Tab键时焦点的移动是合理的。

提示:可在程序代码中调用控件或窗体的 SelectNextControl()方法,使焦点按Tab顺序移动。

要将控件从Tab顺序中删除,将其TabStop属性设置为False。控件的TabStop属性设置为 False 时,用户仍可用鼠标选中该控件,但使用 Tab 键不能进入该控件。应合理地设置该控件的TabIndex属性,以便控件获得焦点(如被单击)时,按Tab键将使焦点移到下一个合理的控件中。

Tab顺序和对齐是有效地在窗体上放置控件的重要因素。然而,它们只是在二维空间(x轴和y轴)内解决了控件的排列。有时需要让控件重叠,虽然这样的情况很少。两个控件重叠时,最后添加到窗体上的控件显示在上面。可使用布局工具栏右边的“置于顶层”和“置于底层”按钮来控制控件的堆叠顺序。

提示:使用代码调用控件的BringToFront()和BringToBack()方法,可将控件前移或后移。

读者可能知道,单击窗口时它将显示在最前面,其他所有窗口都显示在其后(模态窗口除外)。有时,可能希望窗口始终显示在最前面,而不管它是不是当前窗口(即是否获得焦点)。一个这样的例子是Visual Basic和Word等应用程序的“查找”窗口。不管“查找”窗口是否获得焦点,它总是显示在最前面。要创建这样的窗口,可将其 TopMost 属性设置为True。虽然不像航天领域那样,但这也表明,简单的属性修改或方法调用就能完成看似很难的任务。

窗体另一个很酷的属性是 Opacity。该属性控制窗体及其所有控件的不透明度。默认的Opacity属性值是100%,意味着窗体及其控件是完全不透明的(实心的),0%创建完全透明的窗体(这没有实际意义)。50%创建一个透明度在实心和不可见之间的窗体,如图 6.17 所示。Microsoft Outlook 2003及更新的版本在弹出警告,指出用户有电子邮件时很好地利用了透明性。这些警告的透明度从0增加到100,并在100%停留一段时间,然后再逐渐降低到0,最后消失。在程序中可使用第14章讨论的简单循环实现这一点。

图6.17 魔幻窗体

可滚动窗体是指无法容纳全部内容时可显示滚动条的窗体。这是一个很好的功能,也很容易在应用程序中实现。

窗体的滚动行为由下面三个属性决定。

按 F5 键运行项目,将窗体的右下角朝左上方拖曳以缩小窗体。注意到当窗体缩小时,虽然控件尽可能适当地调整了大小,但还是有些控件完全看不见。除非窗体是可滚动的,否则访问这些控件的唯一途径就是放大窗体。

执行下列步骤。

1.如果项目还在运行,停止它。

2.将ViewerForm.vb窗体的AutoScroll属性设置为True。

3.按F5键运行项目。

4.将窗体的右下角朝左上方拖曳,使窗体缩小。注意到这样做时,窗体右边将出现滚动条,如图6.18所示。可用滚动条来滚动窗体中的内容,以访问原本不可见的控件。

图6.18 没有滚动条就不能访问看不到的控件

停止运行项目并保存所做的工作。

到目前为止,创建的所有项目都是单文档界面(Single Document Interface,SDI)的。在SDI程序中,每个窗体都与其他窗体平等;窗体间不存在层次关系。Visual Basic也允许创建多文档界面(MDI)程序。MDI程序包含一个父窗口(也称为容器)以及一个或多个子窗口。MDI程序的一个经典例子是Adobe PhotoShop。启动 PhotoShop时,出现一个父窗口。在这个父窗口中,可以打开任意数量的文档,每个文档都在一个子窗口中。在MDI程序中,所有子窗口都共享父窗口的工具栏和菜单栏。对子窗口的一种限制是,它们被限定在父窗口的边界内。

图6.19所示的PhotoShop打开了多个子文档窗口。

图6.19 MDI程序由一个父窗口以及一个或多个子窗口组成

注意:除子窗口外,MDI 应用程序还可以有任意数量的常规窗口(如对话框)。

下面创建一个简单的MDI项目。执行以下步骤来创建该项目。

1.选择菜单“文件”>“新建项目”打开“新建项目”对话框(这是一个模态窗口)。如果出现提示,请保存对Picture Viewer项目所做的修改。

2.在“名称”中输入“MDI Example”,然后单击“确定”创建项目。

3.在“解决方案资源管理器”中右击 Form1.vb,在弹出菜单中选择“重命名”,然后将窗体的名称改为 MDIParentForm.vb。接下来将窗体的 Text 属性该为 MDI Parent,并将其IsMdiContainer属性设置为True(如果不将IsMdiContainer属性设置为True,这个例子将不可行)。

注意到Visual Basic已经将客户区域改为暗灰色,并呈现下陷效果,这是MDI父窗口的标准外观。所有可见的子窗口都出现在该区域中。

4.选择菜单“项目” >“添加Windows窗体”创建一个新窗体,将该窗体命名为Child1Form,并将其Text属性改为Child 1。

5.用同样的方法添加第三个窗体到项目中。将该窗体命名为 Child2Form,并将其 Text属性改为Child 2。

6.单击工具栏上的“全部保存”按钮,并将该项目命名为MDI Example。

7.在“解决方案资源管理器”中双击MDIParentForm.vb,以便在设计器中显示父窗口。

8.双击该窗体以访问其默认事件:Load事件。输入下列代码:

Child1Form.MdiParent = Me

Child1Form.Show()

现在,读者应该知道最后一条语句的作用:以非模态方式显示窗体。这里要介绍的是第一条语句,它将窗体的MdiParent属性设置为当前窗体(Me指的总是当前对象),当前窗体是一个MDI父窗体,因为它的IsMdiContainer属性被设置为True。显示新窗体时,它将作为MDI的子窗体。

现在按 F5 键运行项目,注意到子窗体显示在父窗体的客户区域中。如果调整父窗体的大小,使一个或多个子窗体不能完全显示时,将出现滚动条,如图6.20所示。如果删除设置MdiParent属性的语句,窗体将浮动在父窗体上(因为它不再是子窗体),不再被限定在父窗体的边界内。

图6.20 子窗体只能显示在父窗体的边框内

选择菜单“调试”>“停止调试”停止运行项目,并执行下列步骤。

1.在“解决方案资源管理器”中双击Child1Form窗体,使其显示在设计器中。

2.在窗体中添加一个按钮,并按如下设置该按钮的属性:

3.双击按钮访问它的Click事件,然后添加下列代码:

Child2Form.MdiParent = Me.MdiParent

Child2Form.Show()

这段代码显示第二个子窗体。注意这段代码与前一段代码之间的区别。不能将第二个子窗体的MdiParent设置为Me,因为Me引用当前窗体(Child1Form,它并不是MDI容器)。然而,Me.MdiParent引用当前子窗体的父窗体,这正是最初将窗体设置为子窗体时设置的属性。因此,只需将第一个子窗体的父窗体传递给第二个子窗体,这样它们就成为同一个窗体的子窗体。

注意:任何窗体都可作为子窗体(当然,MDI父窗体除外)。要使窗体成为子窗体,将其MdiParent属性设置为一个已定义为MDI容器的窗体。

4.按F5键运行项目。可以看到子窗体中的按钮,单击这个按钮(如果看不到这个按钮,可能是误将它添加到第二个子窗体中了)。单击按钮时,将显示第二个子窗体,如图 6.21 所示。注意到该窗体也被限定在父窗体的边界内。

图6.21 子窗体之间是平等的

提示:MDI 父窗体有 ActiveMdiChild 属性,通过它可获得对当前活动子窗口的引用。

注意:要使父窗口在项目启动时更大,可在设计时或窗体的Load事件中设置窗体的属性Size.Height和Size.Width。

关于窗体有一点要记住:可以创建任意数量的窗体实例。然而,管理同一窗体的多个实例很复杂,这超出了本书的范围。

在“Windows应用程序”项目中,默认情况下,启动窗体是加入到项目中的第一个窗体。这是用户创建新的“Windows应用程序”项目时,Visual Basic自动创建的窗体。

每个“Windows窗体应用程序”项目都必须有启动窗体作为程序的入口。要更换启动窗体,可在“解决方案资源管理器”中右击项目名,然后选择“属性”。属性“启动窗体”位于第一个属性面板中,如图6.22所示。

如果将“启动窗体”设置为某个子窗体,项目运行时其行为可能与预期的不同:将出现指定的启动窗体,但它不会是子窗体,因为将该窗体的 MdiParent 属性设置为有效父窗体的代码没有执行。

如果读者还对MDI窗体感到迷惑,不要担心。作为Visual Basic新手,您编写的大多数应用程序都将是SDI程序。更熟悉Visual Basic项目的创建后,再尝试创建MDI项目。记住,不要只是因为可以创建MDI程序就使用MDI;仅当项目要求时才使用MDI。

图6.22 应用程序的入口由“属性”中的“启动窗体”来确定

理解窗体很重要,因为窗体是动态的画布,可以在上面构建用户界面。如果不知道如何使用窗体,整个应用程序将受损。使用窗体不仅是设置属性,特别是考虑到终端用户时。随着经验的增长,您对窗体设计得心应手,只需凭感觉就能完成。

在本章中,读者学习了如何做一些有趣的事情(如创建透明窗体)以及一些高端技巧(如创建MDI应用程序);还学习了如何创建可滚动的窗体(不可忽略的界面元素),还花了很多时间学习窗体上的控件,这很重要,因为窗体的主要功能就是用来放置控件。在接下来的两章中,读者将学习Visual Basic的很多功能强大的控件,这些控件将成为开发武器库中的重要武器。

问:是否对于创建每个的窗体都需要考虑锚定和滚动?

答:绝对不是。在大部分应用程序中,多数窗体都是对话框。对话框是用于从用户那里收集数据的模态窗口。对话框的大小通常都是固定的,也就是说其边框样式被设置为不可调整的。对于大小固定的窗体,不必考虑锚定或滚动。

问:如何确定项目是否应使用MDI界面?

答:如果程序将打开同一种窗体的很多实例,就应该使用MDI界面。举个例子,如果要创建图像编辑程序,允许用户同时打开很多图像,使用MDI将是合理的。此外,如果有多个窗体共享相同的工具栏和菜单,也应考虑使用MDI。

1.判断对错:在一系列选中的控件中,第一个选中的控件总是活动控件。

2.从工具箱中将控件添加到窗体中的方法有几种?

3.如果双击工具箱中的工具,它将放在窗体的什么位置?

4.哪个属性将控件边缘锚定到窗体边缘?

5.需要修改什么来隐藏窗体上的网格?

6.哪个工具栏包含均匀排列控件和对齐控件的功能?

7.要使窗体成为MDI父窗体应设置哪个属性?

1.对。

2.有三种主要的方法:双击工具箱中的工具;拖曳工具箱中的工具;单击工具箱的工具然后在窗体中绘制它。

3.放在当前选中的控件上;如果没有选中控件,则放在窗体的左上角。

4.Anchor属性。

5.“选项”对话框中的ShowGrid属性。

6.布局工具栏。

7.将窗体的IsMdiContainer属性设置为True。

1.创建一个新的Windows应用程序项目,并在窗体中央添加一个按钮。将按钮的Anchor属性设置为不同的值,在不同的属性设置下运行程序。

2.修改本章的 MDI Example 项目,使得首先显示子窗体 Child2Form,它再显示Child1Form。

在本章中,读者将学习:

使用Label控件显示静态文本;

让用户通过文本框输入文本;

创建密码框;

使用按钮;

使用面板、分组框(group box)、复选框(check box)和单选按钮(option button);

使用列表框和组合框(combo box)显示列表。

前两章详细介绍了如何使用窗体。窗体是用户界面的基石,但它们本身没有多少用处。要创建有用的界面,需要使用控件。控件是窗体上用户可交互的各种工具。有很多不同类型的控件,从用于显示静态文本的简单标签控件,到在诸如资源管理器中用于显示数据树的树视图等复杂控件。本章将介绍最常用(简单)的控件,我将其称为传统控件;在第8章中,读者将学习可用于创建专业级界面的高级控件。

标签控件用于显示静态文本给用户。这里的“静态”是指用户不能直接修改文本(但开发人员可使用代码修改)。标签控件是最常用的控件之一,也是最简单的控件之一。

标签通常用于为其他控件(如文本框)提供说明性文本;也用于向用户提供状态型信息以及在窗体上提供使用说明。

本章的工作大部分都将在第 6章创建的Picture Viewer项目的基础上进行。虽然将添加控件到界面中,但在进入第三部分前,这些控件并不能执行任何功能。

首先执行以下步骤。

1.打开第6章创建的Picture Viewer项目。

2.在“解决方案资源管理器”中双击OptionsForm.vb,在设计器中显示Options窗体。

3.双击工具箱中的Label,添加一个新标签控件到窗体中。Label控件的主要属性是Text属性,它决定了显示给用户的文本。Label控件被添加到窗体中时,其Text属性被设置为控件的名称,这不太有用。按如下设置这个Label控件的属性:

注意,标签会随着文本的输入自动调整大小。要创建多行标签,单击Text属性来显示下拉箭头,然后单击该箭头打开一个文本编辑器,如图 7.1 所示。此时可输入文本并按回车键来创建多行。在大多数情况下,最好将标签的文本放在一行内,但有时也需要使用多行。

图7.1 使用这个文本编辑器创建多行标签

标签控件通常是显示用户不能修改文本的最佳控件。然而,当需要用户输入或编辑文本时,就应使用文本框。如果读者在窗体中输入过信息,肯定用过文本框。现在,双击工具箱中的TextBox项,将一个新文本框添加到窗体中。按如下设置文本框的属性:

现在窗体应如图7.2所示。

图7.2 结合使用标签和文本框

虽然99%的情况下,您会让文本框的Text属性为空,但让文本框包含文本可使其某些方面更容易理解。现在,将文本框的Text属性设置为This is sample text。记得按回车键或Tab键提交对属性的修改。

文本框和标签控件都有TextAlign属性(很多其他控件也有)。TextAlign属性指定控件内文本的对齐方式——与字处理器的对齐设置很像。对齐方式有Left(左对齐)、Center(居中对齐)和Right(右对齐)。

执行以下步骤,看TextAlign属性是如何发挥作用的。

1.将文本框的TextAlign属性改为Right,文本框中的文本将变为右对齐。

2.接下来将文本框的TextAlign属性改为Center,看看居中对齐是什么样的。正如读者看到的,该属性非常简单。

3.最后,将文本框的TextAlign属性改回到原来的Left。

第6章介绍了选定控件的大小调整手柄。可用于调整大小的手柄是白色的;而被锁定的手柄呈灰色。注意到文本框仅在左右两边有白色的大小调整手柄,这意味着只能调整控件的左右两边(只能调整宽度而不能调整高度)。这是因为该控件被定义为单行文本框,即只能显示一行文本。如果只显示一行文本,文本框再高又有什么用呢?

要使文本框可以显示多行文本,将其 Multiline 属性设置为 True。现在,将文本框的Multiline属性设置为True,将发现所有的大小调整手柄都是白色的。虽然可通过“属性”窗口设置文本框的Multiline属性,但还有一种更快捷的方式:选择文本框,再单击文本框上包含箭头的方块(如图7.2所示),这将打开一个快捷菜单,其中显示了属性Multiline。单击该属性旁边的复选框以选中它,然后单击该菜单外部以关闭该菜单。大多数控件都有这样的快捷菜单,但其中的内容随选定的控件而异。每当看到带箭头的小框后,都单击它来打开快捷菜单,以熟悉通过各种控件的快捷菜单可设置哪些属性。

将文本框的Text属性设置为“This is sample text.A multiline text box will wrap its contents as necessary.”,按回车键或 Tab键提交对属性的修改。注意到文本框只显示了输入文本的一部分,因为控件不够大,不能显示所有文本,如图7.3所示。将Size属性改为139,52,就可以看到文本框的全部内容了,如图7.4所示。

有时候不希望用户与控件交互。例如,您可能在应用程序中实现一种安全模型,如果用户没有所需的权限,就不让用户更改数据。几乎每个控件都有Enable属性,该属性决定了用户能否与控件交互。现在,将文本框的Enable属性设置为False,并按F5键运行项目,然后单击Options按钮打开Options窗体。虽然在设计视图中没有发生显著的变化,但在运行时控件发生了很大的变化:文本显示为灰色而不是黑色的,且文本框不接受焦点,也不允许用户修改文本,如图7.5所示。

图7.3 文本框包含的文本可以比它能显示的要多

图7.4 可调整多行文本框的大小

图7.5 不能与 Enable 属性为 False 的控件交互

现在,选择菜单“调试”>“停止调试”停止运行项目,然后将控件的Enable属性改回True。

虽然可以调整文本框的大小,但有时控件的内容仍比显示区域大。如果添加到窗体中的文本框可能出现这种情况,可将ScrollBars属性从None值改为Vertical、Horizontal或Both,从而给文本框添加滚动条。

注意:要让文本框显示滚动条,其Multiline属性必须为True。另外,如果将ScrollBars属性设置为Both,水平滚动条仅在WordWrap属性设置为False时才能显示。如果将WordWrap属性设置为True,文本总是自动换行以适应控件的大小,因此不存在文本框右侧的文本不能显示的问题,也就不需要水平滚动条。

现在,将文本框的ScrollBars属性修改为Vertical,注意到文本框中出现了滚动条,如图7.6所示。

图7.6 如果文本框可能包含大量文本,为它添加一个滚动条

注意:如果将文本框的 AcceptReturn 属性设置为 True,用户将可以在文本框中按回车键来换行。如果将 AcceptTabs属性设置为 True,用户可以在控件内按Tab键来创建新列(而不是使焦点移到下一个控件)。

使用MaxLength,可以限制用户在文本框中能够输入的字符数。所有的新文本框都有默认的MaxLength属性值 32 767,但可以根据需要修改该值。要理解这一点,执行下列步骤。

1.按如下修改文本框的属性:

2.按F5键运行项目。

3.单击Options按钮来显示Options窗体。

4.将如下文本输入到文本框中:So you run and you run。

注意到无法输入超过 10个字符,可输入的只有So you run。该文本框只允许 10个字符,不管是通过键盘还是粘贴操作输入。MaxLength 属性最常用于文本框的内容需要写入数据库的场合,因为数据库中的字段长度通常都有限制(数据库的使用将在第21章介绍)。

5.现在停止运行项目,将文本框的MaxLength属性改为0,这表示不限定最大字符数。

现在保存所做的工作。

读者可能使用过密码框:将每个输入的字符显示为星号的文本框。任何文本框都可设置为密码框,只需给其 PasswordChar 属性分配一个字符。现在,设置文本框的 PasswordChar属性,输入星号(“*”)作为属性值。再次运行项目并打开Options窗体,接下来在文本框中输入文本。这时,每输入一个字符,显示出来的都是星号,如图 7.7 所示。虽然用户不能看到文本框中包含的文本,但在代码中引用Text属性时,返回的总是实际文本。

图7.7 密码框将所有输入的文本显示为其密码字符

注意:仅当文本框的Multiline属性设置为False时,文本框才能显示密码字符。

选择菜单“调试”>“停止调试”,停止运行项目。删除PasswordChar属性的星号,然后单击工具栏上的“全部保存”保存项目。

通常很少使用标签的事件,但经常要用到文本框的事件。文本框支持很多事件;表 7.1列出了最常用的事件。

表7.1 文本框控件的常用事件

Windows显示的每个对话框都至少有一个按钮。按钮让用户只需单击鼠标就可调用函数。

该窗体上已经有OK按钮。通常,OK按钮用来接受用户的值并关闭窗体。在本书后面,将使OK按钮完成这种功能。当窗体有OK按钮时,通常也应创建一个Cancel按钮,它卸载窗体且不保存用户的值。

双击工具箱中的Button项,将一个新按钮添加到窗体中。按如下设置该按钮的属性:

创建什么也不做的按钮是没有意义的,因此,现在双击该按钮访问其 Click 事件,然后加入下面的语句:

Me.Close()

第5章介绍过,这条语句关闭当前窗口。现在Cancel按钮的功能与OK按钮的功能是相同的,稍后将对此进行修改。

提示:可在程序中调用按钮的PerformClick方法来触发按钮的Click事件,这和用户单击按钮的效果相同。

创建对话框时,通常将一个按钮指定为默认按钮(称为接受按钮)。如果窗体有接受按钮,用户按回车键将触发该按钮的 Click 事件,而不管哪个控件获得焦点。当用户需要在对话框中输入一些文本,并只需按回车键来提交数据并关闭对话框时,这很有用。

执行下列步骤将OK按钮指定为接受按钮。

1.在“解决方案资源管理器”中双击OptionsForm.vb,在设计器中显示该窗体。

2.单击该窗体,以便在“属性”窗口中显示其属性。

3.在“属性”窗口中,单击窗体的 AcceptButton 属性,将出现一个下拉箭头。单击该箭头并从列表中选择btnOK按钮。注意到窗体上的这个按钮现在有蓝色边框,表示它为窗体的默认按钮,如图7.8所示。

图7.8 接受按钮带蓝色边框

4.按F5键来运行项目,然后单击Options按钮来显示Options窗体。

5.在文本框中单击使其获得焦点,然后按回车键,窗体被关闭。再说一次,在指定了接受按钮的窗体中按回车键,将触发接受按钮的 Click 事件,就像用户单击了该按钮一样,而不管焦点在哪个控件上。但实际上有一种例外情况:如果获得焦点的控件是一个多行文本框,按回车键将在文本框中创建新行,而不会触发接受按钮的Click事件。

一般地说,为窗体创建接受按钮时,应该同时创建取消按钮。取消按钮在用户按Esc键(与回车键相反)时其Click事件被触发,而不管焦点在哪个控件上。通常在取消按钮中加入代码来关闭窗体而不提交用户所做的任何修改。现在执行下列步骤,将Cancel设置为取消按钮。

1.停止运行项目。

2.将窗体的CancelButton属性改为btnCancel。

需要决定将什么按钮指定为窗体的接受按钮和取消按钮时,可参考下面几点。

如果窗体有“确定”或“关闭”按钮,该按钮很可能应指定为接受按钮;

如果窗体有“确定”按钮和“取消”按钮,将“确定”按钮作为接受按钮,将“取消”按钮作为取消按钮(这很明显,但经常被忽略);

如果窗体只有“关闭”或“确定”按钮,将窗体的AcceptButton属性和CancelButton属性都设置为该按钮;

如果窗体有“取消”按钮,将窗体的CancelButton属性设置为该按钮。

复选框用于在窗体上显示真/假和是/否值,使用Windows应用程序时,读者可能见过很多复选框。单击复选框将使它在选中和不选中(真/假、是/否等)两种状态之间切换。

现在将一个复选框添加到Options窗体中,并按如下设置其属性:

复选框的CheckState属性决定复选框是否被选中。试着改变该属性的值,然后看窗体上的效果。注意到可将复选框的CheckState属性设置为Interminate,这将在控件中显示一个大正方形。通常不需要这样做,但应知道这种功能。继续阅读后面的内容前,务必将CheckState属性设置为Unchecked。

窗体现在应如图7.9所示。

图7.9 使用复选框来表示真/假和是/否状态

在本节中,读者将学习如何使用面板和分组框为一组控件创建容器,还将学习如何使用单选按钮控件和容器控件向用户提供多种选择。

控件可添加到窗体上是因为窗体是容器对象——可以包含控件的对象。然而,窗体不是唯一的一种容器。有些控件也用作容器,容器还可包含一个或多个容器。面板和分组框都是容器控件,两者的作用类似,但各自有其更合适的应用场合。

Group Box是一个容器控件,有可用于创建边框——称为框架(frame)——和标题的属性。现在,在工具箱中双击Group Box项(位于“容器”控件分类下),将一个新分组框添加到窗体中。创建新分组框时,它默认有边框,标题被设置为控件的名称。

试着在分组框的中间单击然后拖曳,就像其他控件一样,您将发现无法这样做。可将分组框看做一个小型窗体——您不能单击窗体并到处移动它。在分组框上单击并拖曳将选定分组框中的控件——这与窗体一样。要移动分组框,单击并拖曳左上角有四个箭头的小图像,如图7.10所示。

图7.10 单击并拖曳这个方框来移动分组框

如下设置分组框的属性:

设置后的分组框应如图7.11所示。

图7.11 分组框就像窗体中的窗体

分组框是一个相当简单的控件。除用来定义边框和显示标题外,分组框的用处是为其他控件提供容器。下一节将演示使用分组框作为容器的好处。

注意:在很大程度上说,Panel控件是Group Box控件的精简版,因此这里不深入讨论Panel。如果只需要简单的容器控件而不需要Group Box控件提供的额外特性(如边框和标题),就使用 Panel 控件。一种例外是,面板和窗体一样也提供了滚动功能,而分组框不支持。

复选框是显示真/假和是/否值的优秀控件。然而,复选框之间互相独立;如果窗体中有五个复选框,它们当中的每个都可以是选中或不选中的——以任何方式组合。而单选按钮在它们所处的容器内是互斥的。也就是说,每个容器内只能同时有一个单选按钮被选中。选择一个单选按钮将自动取消对容器中其他按钮的选择。单选按钮用于提供给用户一组选择项,且只允许用户选择其中的一项。为更好地理解单选按钮是如何互斥的,下面在 Options 窗体中创建一组单选按钮。

将控件添加到分组框中有以下几种方法:

直接在分组框中绘制控件;

将控件放在分组框中;

将控件添加到窗体中,然后剪切该控件,再选择分组框,最后将控件粘贴到分组框中。

下面将使用第二种方法:将新控件直接放到分组框中。执行下列步骤。

1.单击工具箱中的RadioButton项,并将它拖放分组框中。

2.在分组框上松开鼠标。

3.单击并拖曳单选按钮来移动它。松开鼠标时,确保单选按钮在它所属的容器内,否则它将被移到其他容器或窗体中。

按如下设置单选按钮的属性:

注意,Location 属性总是以容器对象为参照。如果控件在窗体上,它的位置将相对于窗体左上角;如果控件在分组框中,其位置将相对于分组框左上角。现在复制这个单选按钮,并将控件副本粘贴到分组框中。

1.右击单选按钮,在弹出菜单中选择“复制”。

2.单击分组框选中它。

3.右击分组框,然后在弹出菜单中选择“粘贴”,创建一个新的单选按钮。如下设置该单选按钮的属性:

现在有两个单选按钮(如图7.12所示),按F5键运行项目。

图7.12 单选按钮让用户只能选择一项

单击Options按钮显示Options窗体,以查看单选按钮。第二个单选按钮被选中,单击第一个单选按钮(Default Gray),第二个单选按钮将自动变为不选中(其Checked属性被设为False)。两个单选按钮足以演示互斥性,但要知道,可以在分组框中添加任意数量的单选按钮,它们的行为与此相同。

要记住的一点是,互斥只发生在位于同一容器内的单选按钮之间。要创建互相独立的单选按钮,需要再创建一个容器。很容易创建一个新分组框(或面板),然后将一组新单选按钮放到这个容器中。两组单选按钮将是互相独立的,但每组单选按钮之间仍是互斥的。

现在停止运行项目,将单选按钮optBackgroundDefault的Checked属性设置为True,并保存所做的工作。

列表框用于向用户显示一个列表。使用很少的Visual Basic代码,就可以在列表中添加或删除列表项。另外,还可以设置列表框,使用户可以选择其中的一项或多项。当列表框包含的项比可显示的多时,将自动出现滚动条。

注意:列表框的一个变种是组合框,组合框看上去像右边带下拉箭头按钮的文本框。单击组合框的按钮,将显示一个下拉列表框。使用组合框中的列表与使用列表框一样,因此本节将讨论列表操作的细节,然后在下一节讨论组合框的特性。

这里不在Picture Viewer项目中添加列表框或组合框,因此,执行下列步骤创建一个新项目。

1.创建一个新的Windows窗体应用程序项目,将其命名为Lists。

2.将默认的窗体名改为ListsForm.vb,并将窗体的Text属性设置为Lists Example。

3.双击工具箱中的ListBox项将一个新列表框控件添加到窗体中,然后按如下设置列表框的属性:

列表框包含的每项都是列表框的 Items 集合的成员。对列表项的操作包括添加或删除项目,这是通过Items集合实现的。通常用代码来操纵Items集合(本章稍后将介绍),但在设计时也通过“属性”窗口操纵Items集合。

Items集合是列表框的一个属性。在“属性”窗口中找到Items属性,并单击选中它,将出现熟悉的带三点的按钮,表示可以对该属性进行高级设置。单击该按钮打开“字符串集合编辑器”。要在集合中添加项,只需在文本框中输入——每项占一行。

输入下列项:

Persian Wind;

Portal;

Dark and Stormy Night;

Cadence of Madness;

Lift Off;

Reentry。

完成后,屏幕应如图7.13所示。单击“确定”提交输入并关闭窗口。列表框中现在包含了输入的项。

图7.13 设计时使用该对话框操纵Items集合

在第3章,读者学习了对象、属性、方法和集合。在运行阶段操纵列表时,所有这些知识都将派上用场。列表框(和组合框)的 Items 属性是一个返回集合(集合在很多方面都类似于对象,也有属性和方法)的对象属性。要操纵列表项,可操纵Items集合。

列表可以包含重复的值,读者将在这个例子中看到这一点。因此,Visual Basic需要一种机制来区分列表中的每个项,而不能根据项文本来区分。这是通过给 Items 集合中的每个元素分配一个唯一的索引来实现的。列表中第一个元素的索引为 0,第二个元素的索引为 1,依此类推。索引是元素相对于Items集合中第一个元素——而不是列表中显示的第一个元素——的顺序位置。

1.添加元素到列表中

使用Items集合的Add()方法,将新元素添加到Items集合中。下面将创建一个按钮,用来将一张唱片添加到列表中。在窗体中添加一个新按钮,并按如下设置它的属性:

双击按钮访问它的Click事件,然后加入下面的代码:

lstChemicalEchoSongs.Items.Add("Orbit")

注意,Add()方法接受一个字符串参数:要添加到列表中的文本。

注意:与在设计时添加的元素不同,通过代码添加的元素在程序结束运行后不会保留下来。

现在,按 F5 键运行项目,然后单击这个按钮,新唱片将添加到列表底部。再次单击按钮,将添加一个唱片名相同的元素到列表中。列表框并不关心元素是否已存在于列表中;每次调用Items集合的Add()方法都将添加一个新元素到列表中。

Items集合的Add()方法可作为函数来调用,在这种情况下,它返回索引(新添加的元素在集合中的顺序位置),如在下面的代码所示:

Dim intIndex As Integer

intIndex = lstChemicalEchoSongs.Items.Add("Orbit")

知道元素的索引很有用,稍后读者将明白这一点。

停止运行项目,并保存所做的工作。

提示:要将元素添加到Items集合的指定位置,可使用Insert()方法。Insert()方法接受一个索引和文本。别忘了,列表中第一个元素的索引为0,因此要在列表顶部添加一个元素,可用这样的语句:lstChemicalEchoSongs.Items. Insert(0,”Orbit”)。

2.将元素从列表中删除

将元素从列表中删除与添加元素一样简单,只需一个方法调用:调用 Items 集合的Remove()方法。Remove()方法接受一个字符串,即要删除的元素的文本。下面创建一个按钮用于从列表中删除一个元素。

显示窗体设计器,在窗体上创建一个新按钮。按如下设置该按钮的属性:

双击按钮访问它的Click事件,然后加入下面的代码:

lstChemicalEchoSongs.Items.Remove("Orbit")

Remove()方法命令Visual Basic搜索 Items集合,从第一个元素(index = 0)开始查找,并删除找到的第一个与指定文本匹配的元素。记住,可以有多个文本相同的元素。Remove()方法只删除找到的第一个,找到文本并删除后,Visual Basic就停止查找。

按F5键运行项目。单击Add a Song按钮几次,将Orbit添加到列表中,如图 7.14所示。接下来,单击Remove a Song,注意到Visual Basic将找到并删除指定唱片的一个实例。

图7.14 列表框可包含重复的项,但每个项在集合中都被唯一标识

注意:要删除指定索引处的元素,可使用 RemoveAt()方法。例如,要删除列表中的第一个元素,可用下面的语句:lstChemicalEchoSongs.Items. RemoveAt(0)。如果列表中没有指定的项,该代码将导致异常(错误)。

停止运行项目,并保存所做的工作。

3.清除列表

要完全清除列表的内容,使用Clear()方法。下面添加这样一个按钮到窗体中:单击该按钮将清除列表。添加一个新按钮到窗体中,并按如下设置该按钮的属性:

双击按钮访问它的Click事件,然后加入下面的代码:

lstChemicalEchoSongs.Items.Clear()

按F5键运行项目,然后单击Clear List按钮。Clear()并不关心元素是在设计时还是在运行时添加到列表中的;Clear()总是将列表中所有的元素删除。现在停止运行项目,并保存所做的工作。

提示: Add()、Insert()、Remove()、RemoveAt()和Clear()都是 Items集合的方法,而不是列表框本身的方法。如果忘了这些方法都是Items集合的成员,可能会为在代码中输入了列表框的名称和点号却找不到这些成员而感到迷惑。

4.检索列表中选定元素的信息

有两个属性提供有关选定元素的信息:SelectedItem和SelectedIndex。这两个是列表框本身的属性,而不是列表框的Items集合的属性。SelectedItem返回当前选定元素的文本。如果没有元素被选中,该属性返回一个空字符串。有时需要知道选定元素的索引,这可通过列表框的 SelectedIndex 属性获得。列表的第一个元素的索引为 0。如果没有元素被选中, SelectedIndex返回−1,这是无效的元素索引。

下面添加这样一个按钮到窗体中:单击该按钮时,将在一个消息框中显示选定元素的文本和索引。首先,停止运行项目并将窗体的Size.Height属性改为320,以便能够再容纳一个按钮。创建界面时,经常需要做这样的细微调整,因为不可能预先考虑到一切。

将一个新按钮添加到窗体中,并按如下设置它的属性:

双击按钮访问它的Click事件,然后输入下面的代码(在第一行之后按回车键):

MessageBox.Show("You selected " & lstChemicalEchoSongs.SelectedItem & _

", which has an index of " & lstChemicalEchoSongs.SelectedIndex)

MessageBox.Show()是一个用于向用户显示消息的Visual Basic函数,将在第 17章详细介绍。

按F5键运行项目,然后单击Show Selected按钮。注意,由于没有在列表中选择元素,消息框显示选定元素的索引为−1(表示没有元素被选中)。单击列表中的一个元素选中它,然后单击Show Selected按钮。这次,可以在消息框中看到选定元素的文本和索引,如图 7.15所示。停止运行项目,并保存所做的工作。

注意:可以将列表框设置为允许同时选中多个元素。为此,将列表框的SelectionMode属性设置为MultiSimple(用户单击元素可切换其选中状态)或MultiExtended(要选择多个元素,必须按下Ctrl或Shift键)。要判断多选择列表框中有哪些元素被选中,使用列表框的SelectedItems集合。

图7.15 使用属性Selecte -dItem和Selecte-dIndex 可以轻松地确定列表中选定的元素

列表框和组合框都有Sorted属性。控件刚创建时,该属性设置为False。如果将该属性改为True,Visual Basic将按字母顺序对列表的内容进行排序。列表的内容排序后,Items集合中每个元素的索引都将改变;因此,不能再使用Sorted属性被设为Ture之前获得的索引值。

Sorted是属性而不是方法。您不必调用Sorted来对列表的内容进行排序;只需将Sorted属性设置为True,Visual Basic就将进行排序。这意味着使用Add()方法或 Insert()方法添加的所有元素都自动被插入到合适的排序位置,而不是插入到列表的最后或某个指定的位置。

停止运行项目,并保存所做的工作。

列表框很不错,但有两个缺点。第一,列表框占据很多空间。第二,用户不能输入自己的值,而只能选择列表中的元素。如果要节省空间或要允许用户输入列表中不存在的值,就应使用组合框。

与列表框控件一样(参见上一节有关操纵列表的内容),组合框也有一个Items集合。这里将介绍组合框的基本原理。

双击工具箱中的ComboBox项,将一个新组合框添加到窗体中。如下设置组合框的属性:

首先需要指出的是,组合框有 Text 属性,而列表框没有。该属性与文本框 Text 属性的作用相同。当用户从下拉列表中选择一个元素时,选定元素的值将作为组合框的Text属性值。组合框的默认行为是允许用户在控件的文本框部分输入任何文本——即使文本不存在于列表中。稍后将介绍如何修改这种行为。

在“属性”窗口中选择组合框的 Items 属性,并单击出现的按钮。在“字符串集合编辑器”中添加下列元素,然后单击“确定”提交输入:

Black;

Blue;

Gold;

Green;

Red;

Yellow。

按F5键运行项目。单击组合框右边的箭头,将显示一个下拉列表,如图7.16所示。

图7.16 组合框可节省空间

现在试着输入Magenta,Visual Basic允许这样做。实际上,可以输入任何文本。这可能是您希望的,但通常只允许用户输入列表中有的值。为此,可修改组合框的 DropDownStyle属性。关闭窗体以停止运行项目,将组合框的 DropDownStyle 属性改为 DropDownList。按F5键运行项目,然后试着在组合框中输入,您将发现不能这样做,而只能从列表中选择。实际上,单击组合框中的文本框部分也将打开列表,与单击下拉箭头一样。停止运行项目,将属性DropDownStyle改回到DropDown。然后,将属性AutoCompleteSource改为ListItems,将属性AutoCompleteMode改为Suggest。再次运行项目,并在组合框中输入B,将打开组合框并列出以B打头的列表项。将属性AutoCompleteMode改为Append,再次运行项目,并在组合框中输入内容,组合框将出现与输入内容最接近的列表项,这对用户来说很方便。

可以看到,组合框和列表框提供了类似的功能,实际上,用于处理它们的列表的代码是相同的。然而,这两个控件的用途稍微不同。使用哪个控件更好呢?这得看情况。使用专业应用程序时,注意它们的界面,这样您将对特定情况下应使用哪个控件有感性认识。

在本章,读者学习了如何将向用户显示文本。标签控件用于显示静态文本(用户不能修改的文本),文本框控件用于显示可编辑的文本。您现在能够创建包含多行文本的文本框,且知道当控件无法显示所有文本时如何添加滚动条。

所有窗体都至少有一个按钮,现在读者已经知道如何添加按钮到窗体中,以及如何做一些有趣的事情,如给按钮添加图片。从很大程度上说,使用按钮只不过是将按钮添加到窗体中,设置它的Name属性和Text属性,并为它的Click事件添加一些代码而已——所有这些您现在都知道如何做了。

复选框和单选按钮分别用于显示真/假和互斥的选项。在本章,读者学习了如何使用这两个控件以及如何使用分组框将相关的控件分组。

最后,还学习了如何使用列表框和组合框向用户显示元素列表。您现在知道了如何在设计和运行时将元素添加到列表中,并知道如何对元素进行排序。列表框和组合框是功能强大的控件,建议对它们的功能进行更深入的研究。

没有控件,用户将不能与窗体交互。在本章,读者学习了如何使用标准控件来构建有用的界面。本章讨论的控件在 Visual Basic最初出现时就已存在,它们的行为几年来并没有什么改变。这里只是简单地介绍了这些控件,它们还有更多需要了解的细节。掌握这些控件很容易,因为将大量使用它们。另外,在本书后面,将添加代码来存储用户在这些控件中输入的值,并在显示窗体时显示这些值。

问:能够将单选按钮直接放在窗体中吗?

答:可以。窗体是容器,因此放在窗体中的所有单选按钮都是互斥的。如果要增加另一组互斥的按钮,必须将它们放在一个容器控件中。通常,最好将单选按钮放在分组框中,而不要直接放在窗体上,因为分组框为单选按钮提供了边框和标题,且在设计窗体时更容易移动一组单选按钮(只要移动分组框即可)。

问:我见过看起来像列表框的控件:列表中每个元素都有一个复选框,是否可能创建这样的控件?

答:可以。在以前的Visual Basic版本中,这种功能是列表框内置的。在Visual Basic 2010中,它用另一个完全不同的控件来实现:复选列表框。

1.要显示用户不能编辑的文本应使用哪个控件?

2.标签控件和文本框控件共有的属性是什么?哪个属性的值决定了用户看到的控件内容?

3.必须将文本框控件的哪个属性设置为True,才能调整它的高度?

4.Button控件的默认事件是什么?

5.尽管另一个控件获得了焦点,但当用户按回车键时也将触发按钮的Click事件,这样的按钮称为什么?

6.什么控件用于向用户显示是/否值?

7.如何创建两组互斥的单选按钮?

8.要操纵列表中的元素,使用哪个集合?

9.什么方法将元素添加到列表中指定的位置?

1.标签控件。

2.Text属性。

3.将Multiline属性设置为True。

4.Click事件。

5.接受按钮。

6.复选框。

7.将按钮放在两个不同的容器控件中。

8.Items集合。

9.Insert()方法。

1.使用前几章学到的知识,为Options窗体设置Tab顺序,使文本框User Name成为第一个获得焦点的控件,并确保用户单击按钮前选择了一个列表项。

2.创建一个包含两个列表框的窗体。在设计时使用“属性”窗口将一些元素添加到一个列表框中。创建一个按钮,单击这个按钮时,将第一个列表中的选定元素删除,并将该元素添加到第二个列表中。

在本章中,读者将学习:

创建定时器;

创建带选项卡的对话框;

在图像列表控件中存储图片;

使用列表视图(List View)控件创建增强的列表;

使用树视图(Tree View)控件创建层次列表。

第7章介绍的标准控件可用于创建很多有用的界面。然而,要创建真正健壮和互动的应用程序,必须使用更高级的控件。作为Windows用户,您已经见过很多这样的控件,如Tab控件,它使用选项卡组织数据,还有 Tree View控件,它显示像资源管理器中那样的层次列表。在本章中,读者将学习这些高级控件以及如何使用它们来创建专业级界面,就像商业产品中常见的那样。

注意:在本章的很多示例中,将介绍如何在设计阶段将元素添加到集合中。记住,在设计时可以实现的,用Visual Basic代码同样可以实现。

第7章使用的所有控件都有一个共同特征,那就是用户可以直接与之交互。并非所有控件都具备这种能力——或者说限制——这取决于您怎么看。有些控件是只为开发人员设计的,如第 1章的Picture Viewer项目使用的OpenFileDialog控件。另一个在运行时不可见的控件是Timer控件。Timer控件只是每隔指定的时间触发事件。

执行下列步骤创建一个定时器示例项目。

1.创建一个新的Windows应用程序项目,将其命名为Timer Example。

2.在“解决方案资源管理器”中,右击 Form1.vb 并选择“重命名”,再将窗体名改为TimerExampleForm.vb。

3.将窗体的Text属性设置为Timer Example(别忘了单击窗体本身以显示其属性)。

4.双击工具箱的Timer项(位于工具箱的“组件”分类下),添加一个新Timer控件到窗体中。

Timer控件在运行时是不可见的,因此它被添加到屏幕底部的灰色区域中,而不是窗体,如图8.1所示。

图8.1 运行时不可见的控件显示在设计器的底部,而不是窗体中

按如下设置Timer控件的属性:

您可能注意到了,和以前用过的其他控件相比,Timer控件的属性很少。Timer控件最重要的属性是Interval属性,该属性决定了Timer控件每隔多长时间触发其Tick事件(在该事件中添加代码,当指定的时间间隔结束时就执行特定的操作)。Interval 属性以毫秒为单位,因此设置为 1 000时就等于 1秒,这正是这个例子使用的设置。和很多控件一样,理解Timer控件的工作原理的最好方法是使用它。下面将使用一个Timer控件和一个Label控件来创建一个简单的时钟。这个时钟是这样工作的:每隔 1秒(因为 Interval属性设置为 1 000毫秒), Timer控件就触发其Tick事件。在Tick事件中,将标签的Text属性更新为当前的系统时间。

将一个新标签添加到窗体中,并按如下设置它的属性:

续表

标签的AutoSize属性决定了当它的Text属性改变时,标签是否自动调整大小。因为我们将文本居中对齐,因此不将它设置为自动调整。

接下来,双击Timer控件以访问其Tick事件。当定时器被启动时,它从0开始计数(以毫秒为单位),到达Interval属性指定的毫秒数后,Tick事件就被触发;然后定时器又从0开始计数。这样的循环不断进行,直到定时器被停止(其Enable属性设置为False)。因为在设计时将定时器的 Enable 属性设置为 True,因此当它所在的窗体加载时,它就开始计数。在Tick事件中输入下面的代码:

lblClock.Text = TimeOfDay

TimeOfDay( )是一个方便的Visual Basic函数,它根据系统时钟返回当前时间。因此,这条语句的功能是,将标签的Text属性设置为当前时间。该语句每隔1秒执行一次,记住这一点很重要。现在按F5键运行项目,可以看到标签控件就像一个时钟,每秒更新一次时间,如图8.2所示。

图8.2 定时器使得每隔指定时间执行一次代码很简单

停止运行项目并保存所做的工作。

定时器很有用,但注意不要滥用。定时器要发挥作用,Windows必须知道它,并不断地将当前的内部时钟与定时器的间隔时间进行比对。这样才能在适当的时候通知定时器执行其Tick事件。换句话说,定时器消耗系统资源。对于使用少数定时器的应用程序来说,这并不是问题,但不要在应用程序中使用几十个定时器,除非别无选择(通常总会有其他的选择)。

Windows 95是第一个引进选项卡界面的Windows版本。从此以后,选项卡作为一个主要的界面元素被广泛采用。选项卡主要有两个优点:将控件合理地分组和减少所需的屏幕空间。虽然选项卡看起来很复杂,但它们实际上很容易创建和使用。

下面在Picture Viewer项目的Options对话框中添加一组选项卡。在这个例子中,选项卡显得有点多余,因为并没有太多控件,这样做旨在学习如何使用选项卡,因此执行下列步骤。

1.打开第7章创建的Picture Viewer项目。

2.在“解决方案资源管理器”中,双击OptionsForm.vb将其显示在设计器中。

3.接下来,双击工具箱中的TabControl项(位于工具箱中的“容器”分类下),将一个新Tab 控件添加到窗体中。Tab 控件默认有两个选项卡,这正是这个例子所需要的。按如下设置Tab控件的属性:

4.Tab控件上的选项卡由控件的TabPages集合决定。在“属性”窗口中单击Tab控件的TabPages属性,然后单击出现的小按钮。Visual Basic将打开“TabPage集合编辑器”。可以看到,Tab控件默认有两个选项卡,如图8.3所示。

图8.3 使用“TabPage集合编辑器”来定义选项卡

5.集合中的每个选项卡称为一个页面。Visual Basic将每个新页面命名为TabPageX,其中X是唯一的数字。虽然从技术上说,可以不必修改页面的名称,但为页面取一个有意义的名称会比较方便,如pgeGeneralPage、pgePreferencesPage等。默认选中的是TabPage1,它的属性显示在右边。将该选项卡的Name属性改为pgeGeneral,并将其Text属性(显示在选项卡标签上的文本)设置为General(将属性按字母顺序显示会使操作更方便)。

6.单击左边列表中的TabPage2选中它;然后将其Name属性改为pgeAppearance,并将其Text属性设置为Appearance。

7.单击“确定”保存修改。

现在Tab控件有两个定义好了的选项卡(页面),如图8.4所示。

图8.4 每个选项卡应有有意义的文本

提示:添加或删除选项卡的一种快捷方法是,使用“属性”窗口底部说明面板中的快捷方式。

Tab控件的每个选项卡都是一个容器,类似于Panel和Group Box。这就是为什么不能在Tab控件上单击来移动它的原因。要移动容器控件,必须单击并拖曳General页上有四个箭头的小图像(见图8.4)。执行下列步骤,将第7章创建的单选按钮控件移到新的选项卡中。

1.单击分组框选中它(注意不要单击单选按钮);然后右击并选择“剪切”。

2.单击Tab控件选中它。

3.单击Appearance页切换到Tab控件的第二页,然后在Appearance页中央单击。

4.在Appearance页中右击,然后选择“粘贴”。

5.单击General页,返回Tab控件的第一页。

6.拖曳“移动图像”(带箭头的小正方形)来移动Tab控件,将选项卡控件向下移。

7.单击User Name标签控件选中它;然后按住Shift键并单击User Name文本框和复选框。

8.按Ctrl+X,剪切窗体上选中的控件。

9.单击Tab控件选中它。

10.在General页面中右击,并选择“粘贴”。

11.将Tab控件的Location属性设置为12,12,Size属性设置为287,145。

12.单击并拖曳Appearance页上的控件,使它们大致出现在选项卡中央,如图8.5所示。

图8.5 使用Tab可以很容易地对相关的控件分组

为修饰Tab控件,单击Appearance标签切换到Appearance页,然后将分组框移到页面中央(通过拖放)。移到满意的位置后,单击General标签切换到第一页。

通过理解两个简单的编程元素,将能够完成99%的Tab控件处理。第一个元素是在运行时知道哪个页面被选中。控件的SelectedIndex属性(不是TabIndex属性)设置和返回当前选定选项卡的索引:0表示第一个选项卡,1表示第二个,依此类推。第二个元素是怎样知道用户何时切换了选项卡。Tab控件有一个SelectedIndexChanged事件,当选定的选项卡改变时将触发这个事件。在这个事件中,可以通过SelectedIndex的值来确定用户选定的选项卡。

关于Tab控件,最困难一点的可能是每个选项卡都有自己的一组事件。如果双击Tab控件,可查看Tab控件的全局事件(其中包括SelectedIndexChanged事件)。如果双击选项卡中的一页,可查看该页面的事件;每个页面都有自己的一组事件。

现在运行项目来观察选项卡是如何工作的。完成后别忘了保存项目。

本章讨论的很多控件都能够将图片与不同类型的元素关联起来。例如,资源管理器中用于浏览文件夹的 Tree View控件,在每个文件夹节点旁边显示图片。这些图片不都相同;控件使用不同的图片来表示每个节点的信息。Microsoft本可以让每个控件在内部存储图像,但这样效率很低,因为这样不允许控件共享图片——每个需要图像的控件都必须自己存储图像。这也可能导致维护问题。例如,假设有 10个Tree View控件,每个控件都为文件夹节点显示文件夹图像。假设升级应用程序时,要将文件夹图像更新为更漂亮的图像。如果图像存储在每个Tree View控件中,则 10个控件都必须更新(很可能错过某个)。相反,Microsoft创建了一个控件专门用于存储图片,并为其他控件提供图片,这就是 Image List。将图片放到 Image List控件中后,很容易在其他类型的控件之间共享该图片。

本节不使用Picture Viewer项目,因此执行下列步骤创建一个新项目。

1.创建一个新的Windows应用程序项目,将其命名为Lists and Trees。

2.在“解决方案资源管理器”中,右击Form1.vb并将其重命名为ListsAndTreesForm.vb,并将其Text属性设置为Lists and Trees。

3.双击工具箱的 ImageList项(位于工具箱的“组件”分类下),将一个 Image List控件添加到窗体中。与Timer控件一样,Image List是运行时不可见的控件,因此显示在窗体的下方,而不是窗体上。将 Image List的名称改为 imgMyImages。

4.Image List控件的唯一作用是存储图像供其他控件使用,图像存储在该控件的 Images集合中。在“属性”窗口中单击 Image List控件的 Images属性,然后单击出现的小按钮,Visual Basic将打开“图像集合编辑器”。注意到这个编辑器与本章前面用过的其他编辑器类似。

5.单击“添加”显示“打开”对话框,使用该对话框来找到并选中一个 16 × 16像素的图标。如果没有 16 × 16像素的图标,可以用Microsoft“画图”创建一个BMP图像,也可从http://www.samspublishing.com/或http://www.jamesfoxall.com/books.aspx下载我提供的示例图片。添加图像后,单击“确定”关闭“图像集合编辑器”。

现在看一下Image控件的ImageSize属性,它应为16,16。如果不是,表明所选的位图不是 16 × 16像素;该属性应被设置为第一个加载到 Image List中的图片的尺寸,但笔者也遇到过没有自动设置的情况。如果您使用的是尺寸不同的图像,可能需要手工将属性 ImageSize设置为正确的值。

不能期望显示图片的背景总是白色(或其他任何颜色),因此 Image List 控件有一个TransparentColor 属性。默认情况下,TransparentColor 属性设置为 Transparent。因为这里使用的是一个图标,而图标文件包含透明度信息,因此这里不必设置TransparentColor属性。如果使用的是BMP文件或不包含透明度信息的其他格式,则需要使用该属性来指定一种颜色,在位图被其他控件使用时,该颜色将变成透明的。

这样就将图像添加到 Image List控件中了。Image List控件的强大功能并不在于控件本身提供的属性或方法,而在于它能够链接到其他控件,让其他控件可以访问它存储的图片。在下一节中,读者将这样做。

List View控件与列表框很像。List View可用于创建简单列表、多列网格和图标托盘等。Windows资源管理器右边的面板就是一个List View。资源管理器的List View的主要显示选项有图标、列表、详细信息和平铺,这些对应于List View通过设置其View属性可提供的显示选项。读者可能不知道这一点,但可以在资源管理器中右击,然后在弹出菜单中使用“查看”子菜单来改变List View的外观。下面创建一个包含几个元素的List View,查看它的不同视图——包括使用前一节创建的 Imange List为元素显示图片。

提示:这里只能简单地介绍这个功能强大的控件。学习完本章的基础知识后,强烈建议读者在List View控件上花些时间,看看帮助文档和可以找到的任何补充材料。我一直在用 List View,它是一个强大的工具——显示列表是很常见的任务。

现在,双击工具箱中的List View项,将一个List View控件添加到窗体中。按如下设置List View的属性:

正如读者看到的,可通过“属性”窗口(当然也可以通过代码)将 Image List关联到控件。并非所有控件都支持 Image List,但对那些支持 Image List的控件,只需设置一个属性就能链接到 Image List控件。实际上,List View允许链接到两个 Image List:一个用于大图标(32 × 32像素),一个用于小图标。在这个例子中,将只使用小图片。如果要使用大图像,可将List View控件的LargeImageList设置为包含大图像的 Image List。

将View属性改为Details时,控件将显示列标题,但由于还没有定义列,因此标题不会出现。该标题的内容由Columns集合中定义的列决定。

执行下列步骤,在List View中创建列。

1.在“属性”窗口中选择Columns属性,然后单击出现的小按钮。Visual Basic将打开“ColumnHeader集合编辑器”窗口。

2.单击“添加”按钮创建一个新列,并将其Text属性改为Name,Width属性改为120。

3.再单击“添加”创建第二个列,将其Text属性改为State。在这个例子中,没有修改列名,因为不需要通过列名来引用它们。

4.单击“确定”保存列设置并关闭窗口。

现在List View有两个命名列,如图 8.6所示。

图8.6 使用 List View来显示多列列表

执行下列步骤,在List View中添加两个元素。

1.在“属性”窗口中选择Items属性,然后单击出现的小按钮,这将打开“ListViewItem集合编辑器”对话框。

2.单击“添加”按钮创建一个新项,并将其Text属性改为 James Foxall。

3.打开 ImageIndex属性的下拉列表。注意到列表中包含链接的 Image List控件中的图片(如图8.7所示)。选择该图像。

图8.7 链接的 Image List控件中的图片可用于该控件

元素的Text属性决定了元素在List View中显示的文本。如果View属性被设置为Details且定义了多列,Text属性值出现在第一列,之后的列值由SubItems集合决定。

4.单击SubItems属性(它位于ListViewItem的“数据”分类下),然后单击出现的小按钮,这将打开“ListViewSubItem集合编辑器”。

5.单击“添加”按钮创建一个新的子元素,并将其Text改为Nebraska。

6.单击“确定”返回“ListViewItem集合编辑器”。

7.单击“添加”创建另一个子元素。将 Text 属性改为读者的姓名,并使用前面介绍的方法添加子元素。对于子元素的Text属性,输入读者所在的州名。采用前面介绍的方法给元素指定一个图像。

8.完成后,单击“确定”关闭“ListViewItem集合编辑器”。现在List View有两个列表元素,如图8.8所示。

图8.8 List View比标准列表框提供了多得多的功能

9.使用List View控件的View属性来看各种设置对控件外观的影响。Large Icons设置不显示图标,因为List View的LargeImageList属性没有链接 Image List控件。进入下一步之前,务必将将View属性设置为Details。

10.按F5键运行项目,试着通过州名选择姓名,这不可行。List View的默认行为是,只将对第一个列的单击视为对元素的选择。

11.停止运行项目,将List View的FullRowSelct属性改为True;然后再次运行项目。

12.再次单击州名,这次选中了姓名(实际上是整行都被选中)。我个人偏向于将List View的FullRowSelct设置为True,但这只是个人偏好。现在停止运行项目并保存所做的工作。

前面介绍了使用List View控件的基础知识。即使在设计视图中完成了这个例子的所有步骤,也可能需要使用代码操纵列表元素,因为有时不一定事先知道列表要显示什么。接下来,将介绍如何在代码中操纵List View。

1.使用代码添加列表元素

使用Visual Basic代码添加元素很简单——如果要添加的元素很简单的话。要将元素添加到List View中,使用 Items集合的Add()方法,如下所示:

lstMyListView.Items.Add("Mike Saklar")

如果元素要包含图片,可将图片的索引作为第二个参数,如下所示:

lstMyListView.Items.Add("Monte Sothmann",0)

如果元素有子元素,将更复杂。Add()只能指定文本和图像索引。要访问列表元素的其他属性,需要在代码中获得对元素的引用。记住,新元素默认只有一个子元素;必须创建其他的子元素。Items集合的Add()方法返回对新添元素的引用。知道这一点后,就可以创建一个新变量来存储对元素的引用,并使用该变量来创建子元素和进行任何操作(关于使用变量的详情请参见第11章)。下面的代码创建一个新元素,并将一个子元素加入到该元素的SubItems集合中:

Dim objListItem As ListViewItem

objListItem = lstMyListView.Items.Add("Mike Hartman", 0)

objListItem.SubItems.Add("Nebraska")

2.在代码中确定选中的元素

List View控件有一个 SelectedItems集合,该集合包含了对控件中每个被选中元素的引用。如果 List View的MultiSelect属性设置为 True(这是默认设置),用户可以在单击元素时按住 Ctrl键或 Shift键以选择多个元素。这就是 List View支持的是 SelectedItems集合而不是SelectedItem属性的原因。要获得有关被选中元素的信息,可通过它的索引值来引用它。例如,要显示第一个被选中元素的文本(只有一个元素被选中时,就是该元素),可用如下代码:

If lstMyListView.SelectedItems.Count > 0 Then

 MessageBox.Show(lstMyListView.SelectedItems(0).Text)

End If

检查SelectedItems集合的Count属性的原因是:如果没有元素被选中,引用SelectedItems集合的第0个元素将导致运行错误。

3.使用代码删除列表元素

要删除列表元素,使用Items集合的Remove()方法。Remove()方法接受对列表元素的引用。例如,删除当前选中的元素,可用如下语句:

lstMyListView.Items.Remove(lstMyListView.SelectedItems(0))

同样,使用这条语句之前,必须核实有元素被选中。

4.删除所有列表元素

使用代码填充List View时,首先需要清除List View。这样,填充List View的代码再次被调用时,将不会出现重复的元素。要清除List View的内容,使用 Items集合的Clear()方法,如下所示:

lstMyListView.Items.Clear()

List View控件是一个多才多艺的工具。实际上,我几乎不用标准的List Box控件;我更喜欢用 List View,因为它提供了更多的功能(如为元素显示图像)。这里只是简单地介绍了List View,但现在对这个功能强大的工具的了解足以让读者在开发中使用它。

Tree View控件用于显示层次性数据。Tree View最常见的用途可能是在资源管理器中,在资源管理器中,用户使用Tree View来浏览计算机上的驱动盘和文件夹。Tree View非常适合用于显示层次性数据,如包含员工信息的组织结构图。在本节中,将介绍 Tree View控件的基本知识,让读者能够在应用程序中使用这个功能强大的界面元素。

Tree View的元素包含在Nodes集合中,就像List View的元素存储在 Items集合中一样。将元素添加到树中,就是将它们加入到Nodes集合中。现在读者可能已经发现,理解了对象和集合的基本知识后,可将这些知识运用到Visual Basic的任何地方。例如,使用List View控件的 Items集合与使用Tree View的Nodes集合所需的技能类似。实际上,这些概念与列表框和组合框的使用类似。

现在,双击工具箱中的TreeView项,将一个Tree View控件添加到窗体中。如下设置Tree View控件的属性:

在设计阶段,节点的用法与List View的 Items集合的用法类似。因此,这里介绍如何使用代码操纵节点。要添加节点,调用Nodes集合的Add()方法(这个例子中将这样做)。在窗体中添加一个新按钮,并按如下设置其属性:

双击按钮访问其Click事件,并输入下面的代码:

tvwLanguages.Nodes.Add("James")

tvwLanguages.Nodes.Add("Visual Basic")

按F5键运行项目,然后单击按钮。树中将出现两个节点,每调用一次Add()方法生成一个,如图8.9所示。

图8.9 节点是出现在树中的元素

注意到这两个节点出现在层次结构的同一层次,每个节点都不是另一个节点的父节点或子节点。如果所有节点都在层次结构的同一层次,应考虑使用List View控件,因为创建的只是一个列表。

停止运行项目,返回按钮的Click事件。任何节点都可以是其他节点的父节点或某个节点的子节点(节点的父节点可通过其Parent属性来引用)。为实现这一点,每个节点都有自己的 Nodes 集合。这可能有点难以理解,但如果认识到子节点属于父节点,这就是合符逻辑的。

下面创建一个新按钮,它添加和之前相同的两个节点,但让第二个节点成为第一个节点的子节点。返回到窗体的设计视图,然后创建一个新按钮,并按如下设置其属性:

双击新按钮访问其Click事件,并加入下列代码:

Dim objNode As TreeNode

objNode = tvwLanguages.Nodes.Add("James")

objNode.Nodes.Add("Visual Basic")

这段代码与List View示例中创建的类似。Nodes集合的Add()方法返回新创建节点的引用。因此,这段代码创建一个TreeNode变量,创建一个新节点并将引用赋给该变量,然后将另一个节点添加到第一个节点的Nodes集合中。要查看这段代码的效果,按F5键运行项目,然后单击 Create Child按钮。可以看到列表中只有一个元素,元素左边有一个加号,表示该元素包含子节点。单击加号,该节点将展开,从而显示其子节点,如图8.10所示。

图8.10 可使用 Tree View控件创建任何深度的层次结构

这只是一个简单的例子——只有一个父节点,父节点只包含一个子节点。然而,这里使用的原理与创建包含成百上千个节点的复杂树是相同。

要删除节点,调用Nodes集合的Remove()方法。Remove()方法接受一个有效的节点作为参数,因此必须知道要删除哪个节点。Nodes集合与List View的 Items集合很相似,因此做法也一样。例如,Tree View控件的SelectedNode属性返回当前选中的节点,同样,要删除当前选中的节点,可用下面这条语句:

tvwLanguages.Nodes.Remove(tvwLanguages.SelectedNode)

如果这条语句在没有节点被选中的情况下调用,将导致错误。在第11章,读者将学习数据类型和等价性,这里简单介绍一下:如果对象变量没有引用对象,它等价于 Visual Basic关键字Nothing。知道这一点后,可以使用如下逻辑来验证是否有元素被选中(注意,与List View控件不同,在Tree View控件中不能同时选中多个节点):

If Not (tvwLanguages.SelectedNode Is Nothing) Then

 tvwLanguages.Nodes.Remove(tvwLanguages.SelectedNode)

End If

注意:删除父节点的同时将删除其所有子节点。

要清除Tree View中的所有节点,调用Nodes集合的Clear()方法,如下所示:

tvwLanguages.Nodes.Clear()

和List View一样,这里也只是简单地介绍了Tree View。学习这里介绍的Tree View基本知识并熟悉它们后,进行深入地研究以发掘它不那么显而易见的强大功能和灵活性。

Visual Basic包含很多这样的控件:提供的功能超出了第7章讨论的传统控件的标准功能。本章介绍了最常用的高级控件,读者学习了如何使用Timer控件在预定的时间间隔触发事件;还学习了如何使用Tab控件创建包含选项卡的对话框。

另外,在本章中,读者还学习了如何将图片添加到 Image List控件中,让其他控件能够使用它们。Image List是一个很有用的工具,使得在多个控件之间共享图像更容易。最后,还介绍了List View和Tree View控件,这两个控件用于构建显示结构化数据的高端界面。在这些控件上花费的时间越多,就越能创建出卓越的界面。

问:如果需要大量定时器但又在意对系统资源的占用,该如何办?

答:尽可能让单个定时器承担多项职能。如果两个事件发生的时间间隔相同,这很容易实现——为什么要创建第二个定时器呢?两个事件发生的时间间隔不相同时,可以使用一些决策技巧和静态变量(将在第11章介绍)来共享定时器事件。

问:使用 Image List控件还能做什么?

答:可在Tree View控件中的节点被选中时为其分配一个唯一的图片。也可在Tab控件的选项卡标签中显示图像。Image List有很多用途,对高级控件有更多了解后,将有更多机会用到 Image List中的图像。

1.Timer控件的Interval属性以什么为单位?

2.什么集合用于为Tab控件添加新选项卡?

3.什么属性返回当前选中的选项卡的索引?

4.判断对错:应使用不同的 Image List控件来存储大小不同的图像。

5.要查看List View控件中的列,必须将View属性设置为什么?

6.可加入到List View元素中的额外数据列应存储在哪个集合中?

7.要判断List View有多少元素,应使用什么对象的什么属性?

8.Tree View控件的每个元素称为什么?

9.如何使一个节点成为另一个节点的子节点?

1.毫秒。

2.TabPages集合。

3.SelectedIndex属性。

4.对。

5.Details。

6.SubItems集合。

7.使用Items集合的Count属性。

8.节点。

9.将它添加到父节点的Nodes集合中。

1.再添加一个 Image List到List View项目中。将一个图标(32 × 32像素)放到这个 Image List中,将其属性 ImageSize设置为32,32,再将List View控件的LargeImageList属性设置为该Image List。将View属性改为Large Icons或Tile,看看在这两种视图下将如何使用大图标。

2.创建一个新项目,在默认窗体中添加一个 List View、一个按钮和一个文本框。单击按钮时,使用文本框中输入的文本在List View中创建一个新元素。

在本章中,读者将学习:

添加、移动和删除菜单项;

创建复选菜单项(checked menu item);

对菜单进行编程;

实现上下文菜单;

指定快捷键;

创建工具栏项;

定义开关按钮(toggle button)和分隔条;

创建状态栏。

使用图形用户界面(GUI)与程序交互和进行导航,是Windows的最优秀的特性之一。尽管如此,很多Windows用户仍主要依靠键盘,仅在必要时才使用鼠标;尤其是数据输入人员,他们的手不会离开键盘。很多软件公司接到来自气愤用户的投诉电话,抱怨常用的功能只有通过鼠标才能使用。菜单对于依赖于键盘的用户来说是最容易的程序导航方法,使用Visual Basic为应用程序创建菜单是前所未有的简单。在本章中,读者将学习如何在窗体中创建菜单、操纵菜单以及对菜单编程;还将学习如何使用ToolBar(工具栏)控件来创建美观实用的工具栏;最后,将学习如何使用状态栏使窗体更完美。

笔者说过,在Visual Basic中为应用程序创建菜单比以往更容易,这并不是开玩笑。创建菜单是一个舒服、简短的过程。创建好的菜单的重要性怎么强调都不过分,而且这很容易做到,因此没有理由不为应用程序创建菜单。

提示:首次运行一个应用程序时,用户通常是先查看菜单而不是打开用户手册(大部分用户从来不看用户手册!)。通过提供完善的菜单,可以让程序更容易学习和使用。

将菜单添加到窗体中是通过一个控件来实现的,它就是Main Strip控件。Menu Strip控件有点怪,它是除本章稍后将介绍的Context Menu Strip控件外,我知道的唯一一个这样的菜单:放在为不可见控件(如Timer控件)预留的窗体下方区域中,却在窗体上有可见界面的控件。

执行下列步骤。

1.将使用第8章创建的Picture Viewer项目,因此打开这个项目。

2.在“解决方案资源管理器”中双击ViewerForm.vb,在设计视图中显示Picture Viewer主窗体。

3.需要在窗体顶部预留出空间,因此将窗体的Size.Height属性改为375。

4.将PictureBox的Location属性改为8,52,Size属性改为282,279。

5.按住Shift并单击或使用选框来选中窗体上除图片框外的所有控件,注意要选中标签X和Y。选中这些控件后,单击并拖曳Select Picture按钮,使其顶部与图片框对齐(拖曳时,其他所有选中的控件也将随Select Picture按钮移动)。现在窗体应如图 9.1所示。注意,选择窗体中的控件后,可使用箭头键来移动它们,但这样做将不会出现对齐线。

图9.1 需要在窗体顶部为菜单和工具栏留出空间

6.双击工具箱中的 MenuStrip(位于“菜单和工具栏”分类中),在窗体中添加一个新Menu Strip控件,并将其名称改为mnuMainMenu。如图 9.2所示,控件被添加到窗体设计器底部的面板中,在窗体顶部有“请在此处键入”字样。

图9.2 菜单被添加到窗体中时没有菜单项

7.单击“请在此处键入”字样并输入&File,然后按回车键。当您开始输入时,Visual Basic将显示两个新的“请在此处键入”框,如图9.3所示。

图9.3 创建菜单项时, Visual Basic自动准备更多菜单项

注意“属性”窗口(如果没有显示,按F4键显示它),您刚刚输入的文本创建了一个新菜单项。每个菜单项都是一个对象,因此有属性。默认情况下,Visual Basic 将菜单命名为FileToolStripMenuItem(可能需要单击新创建的菜单项 File,以便能够看到其属性)。这是一个很长的名字,但足以完成使命。

您可能感到奇怪,为什么要在File前面输入&字符。现在看一下菜单,将看到Visual Basic并没有显示&;而是在File中给字符F加上了下划线。当&字符用在菜单项的文本中时,实际上是要Visual Basic给它后面的字符加上下划线。对于顶级菜单项,如刚才创建的File菜单项,加下划线的字符表示快捷键,按Alt键和快捷键可以打开菜单,就像用户用鼠标单击一样。应避免为窗体的多个顶级菜单项指定相同的快捷键。为避免这种冲突,可使用任意字符作为快捷键,而不一定要使用第一个字符(例如,F&ile代表File)。当菜单项显示在下拉菜单中(而不是顶级菜单)时,加下划线的字符成为热键。当菜单可见(打开)后,用户可按热键来触发对应的菜单项,就像用鼠标单击它一样。同样,不要给同一菜单的多个菜单项指定相同的热键。

注意:在按钮和其他一些控件的Text属性中,也可使用&字符来指定快捷键。例如,如果将按钮的Text属性设置为C&lick Me,则用户按Alt + L将相当于单击了该按钮。

8.单击出现在File菜单项右边的“请在此处键入”字样,输入&Tools,然后按回车键。Visual Basic将再显示另外两个“请在此处键入”项——与输入File菜单项时相同。要添加新菜单项,只需单击“请在此处键入”框然后输入菜单项文本。

注意:如果单击已有菜单项下面的“请在此处键入”框,创建的菜单项将与当前菜单项处于同一菜单中。如果单击已有菜单项右边的“请在此键入”框,将创建一个子菜单项,而框左侧的菜单项将作为其入口。单击菜单栏顶部的“请在此处键入”框将创建顶级菜单。

只要窗体有足够的空间,就可以创建任意多的顶级菜单项。对于 Picture Viewer来说, File和Tools菜单已经足够。现在只需为这些顶级菜单创建可供用户选择的菜单项。执行下列步骤来创建这些菜单项。

1.单击File菜单项,在它下面将显示“请在此处键入”框。单击这个“请在此处键入”框,输入&Open Picture…,然后按回车键。

2.单击刚创建的菜单项以选中它,然后将其重命名为mnuOpenPicture。

3.单击Open Picture菜单项下的“请在此处键入”框,输入&Quit,然后按回车键。将新菜单项重命名为mnuQuit。现在单击工具栏上的全部保存保存所做的工作。

4.单击Tools菜单项选中它。这时在Tools菜单项的右边和下面都将显示一个“请在此处键入”框。单击Tools菜单项下面的“请在此处键入”框,输入&Draw Border,然后按回车键。将新菜单项重命名为mnuDrawBorder。

5.接下来的操作比较复杂。将鼠标放在Draw Border菜单项下的“请在此处键入”框,将出现一个小的下拉箭头。单击这个箭头,并选择Separator(分隔条,如图9.4所示)。下拉箭头用于指定菜单项的类型。可创建组合框、文本框或这个例子中用于隔离各类不相关菜单项的分隔条。

图9.4 除常规菜单项外,还可创建文本框、组合框和分隔条

6.选择Separator后,Draw Border下方将出现一条线,并出现一个新的“请在此处键入”框。单击该框选中它,输入&Options…,然后按回车键,创建一个新菜单项。将该菜单项重命名为mnuOptions。

7.最后,单击图片框和其他控件,结束对菜单的编辑。

删除和移动菜单项比添加新菜单项更简单。要删除菜单项,右击它,然后从出现的上下文菜单中选择“删除”。要移动菜单项,将它从当前的位置拖曳到要放置的新位置。

没有子菜单的菜单项可以在它的文本旁显示一个复选标记。复选标记用于创建有状态的菜单项——选中或未选中的菜单。下面创建一个复选菜单项。还记得第7章中为Options窗体创建的复选框吗?它用于指定在Picture Viewer关闭前是否提示用户。下面为此创建一个菜单项。执行以下步骤。

1.单击File菜单打开它。

2.单击Quit菜单项下的“请在此处键入”框,输入Confirm on Exit,然后按回车键。将新菜单项重命名为mnuConfirmonExit。

3.右击Confirm on Exit菜单项,并从上下文菜单中选择Checked,如图 9.5所示。如果菜单与图 9.5所示的不一致,单击另一个菜单,然后再在Confirm on Exit菜单项上右击。也可以单击菜单项,然后在“属性”窗口中修改其Checked属性。

图9.5 菜单项可用于指示状态

4.单击并拖曳Confirm on Exit菜单项,将它放在Quit菜单项上。这将把Confirm on Exit菜单项移动到Quit菜单项的上方。菜单现在应如图9.6所示。

图9.6 菜单是以交互方式创建的

按F5键运行项目。窗体上将显示菜单,就像设计的那样,如图9.7所示。单击File菜单打开它,然后单击Quit;这时什么也不会发生。实际上,单击该菜单项,其Checked状态也不会改变。下一节将介绍如何为菜单项添加代码,使它们能够发挥作用(并改变它们的checked状态)。

图9.7 菜单在运行时与设计时显示的一样

现在停止运行项目并保存项目。

每个菜单项都是一个唯一的对象——实际上可以单击每个菜单项选中它,然后在“属性”窗口中修改它的属性,从而菜单项进行编辑。虽然菜单项本身不是控件,但像控件一样,也可以为菜单项添加代码。下面为创建的菜单项添加代码。

执行下列步骤为菜单创建代码。

1.单击File菜单打开它。

2.双击Open Picture菜单项。与双击控件一样,Visual Basic将在代码编辑器中显示该菜单项的默认事件。对于菜单项,默认事件是Click事件。

3.输入下列代码:

这正是第 1章创建Select Picture按钮时输入的代码,因此这里不再讨论。

4.在“解决方案资源管理器”中双击ViewerForm.vb切换到窗体Picture Viewer的窗体设计器。

5.双击Confirm on Exit按钮访问它的Click事件。输入下面的代码语句:

mnuConfirmOnExit.Checked = Not(mnuConfirmOnExit.Checked)

用户单击 Confirm on Exit菜单项时,该语句将菜单项的 checked状态设置为与当前的checked 状态相反。函数 Not()用于执行布尔值(True 或 False)的取反操作。不用担心,第12章将详细介绍。现在只需知道,如果Checked属性的当前状态设置为True,则Not()将返回False;如果Checked属性的当前状态设置为False,则Not()将返回True。因此,每单击一次该菜单项,Checked值都将在True和False之间切换。

6.在“解决方案资源管理器”中双击ViewerForm.vb重新切换到Picture Viewer窗体的窗体设计器(也可单击选项卡“ViewerForm.vb[设计]”)。

7.双击Quit菜单项访问它的Click事件,并输入下面的代码:

Me.Close()

在第1章解释过,这条语句的作用是关闭窗体,关闭这个窗体就将关闭整个应用程序,因为它是唯一加载的窗体。

8.返回窗体设计器,单击Tools显示Tools菜单,然后双击Draw Border菜单项访问它的Click事件。输入下面的代码:

这段代码也来自第1章,因此有关这段代码的作用可参见第1章。

9.返回窗体设计器,双击Options菜单项,然后在它的Click事件中输入下面的代码:

OptionsForm.ShowDialog()

至此,添加了使菜单发挥作用所需的所有代码。执行以下步骤来测试项目。

1.按F5键运行项目。按Alt+F打开File菜单(记住,F是快捷键)。

2.单击Confirm on Exit选项,菜单关闭。再次单击File打开它,注意到该菜单项现在不再被选中。再次单击它将使它再次被选中。

3.单击除Quit外的所有菜单项,确保它们都正常工作。完成后,单击File菜单中的Quit关闭运行的项目。

如果选择了Confirm on Exit,读者可能注意到没有确认是否要退出。这是因为还没有添加判断Confirm on Exit复选状态的退出代码。第 11章将实现该菜单项和Options的功能。

提示:设计菜单时,参考一些流行的 Windows 应用程序,并思考一下您的菜单和这些程序的菜单有什么异同。即使您的应用程序很独特,从而有一些与其他应用程序不同的菜单,但也会有一些相同的地方(如剪切、复制、粘贴、打开等)。应尽可能仿造流行应用程序中类似菜单项的结构和设计,这样将缩短应用程序的学习曲线,使用户更容易上手,同时节省您的时间。

上下文菜单(也称为快捷菜单)是右击窗体上的对象时弹出的菜单。上下文菜单的名称来源于它们显示的选项是上下文相关的——与右击的对象直接相关。大多数 Visual Basic控件都有默认的上下文菜单,但也可根据需要自定义上下文菜单。上下文菜单的创建与常规菜单类似。然而,上下文菜单的创建使用的是另一个控件:Context Menu Strip。

执行下列步骤,在项目中创建一个自定义的上下文菜单。

1.在窗体设计器中显示ViewerForm.vb窗体。

2.双击工具栏中的ContextMenuStrip控件,添加一个新ContextMenuStrip到窗体中。与MenuStrip 控件一样,ContextMenuStrip 控件也显示在窗体设计器底部的面板中。将 Context Menu Strip控件的名称改为mnuPictureContext。

3.选中ContextMenuStrip控件时,靠近窗体的顶部将出现一个供编辑的上下文菜单。单击“请在此处键入”框,输入Draw Border(如图 9.8所示),然后按回车键,这样就创建了一个菜单项。至此创建了一个只包含一个菜单项的上下文菜单。

图9.8 可像常规菜单一样对上下文菜单进行编辑

4.双击新菜单项访问它的Click事件,并输入下面的代码:

这正是Draw Border菜单项和Draw Border按钮使用的代码。这段代码在三个地方出现是不是显得有点冗余?第10章将介绍如何共享代码,避免在多个地方重复输入代码。

5.在“解决方案资源管理器”中双击ViewerForm.vb,返回Picture Viewer窗体的设计器。

6.控件与上下文菜单的链接是通过设置一个属性实现的。现在,单击窗体中的图片框选中它,然后将图片框的ContextMenuStrip属性改为mnuPictureContext;这样上下文菜单就链接到图片框了。

7.按F5键运行项目,然后右击图片框。可以看到如图9.9所示的上下文菜单。选择菜单项Draw Border,图片框周围将绘制出一个边框。

图9.9 上下文菜单是方便的快捷方式

8.停止运行项目并保存项目。

如果读者使用过Microsoft应用程序,很可能知道一些键盘快捷键。例如,在任何应用程序中按Ctrl + P,都与打开“文件”菜单并选择“打印”等效。

现在,执行下列步骤,为菜单项添加快捷键。

1.单击窗体顶部的File菜单打开它,然后选择Open Picture。

2.在“属性”窗口中,单击ShortcutKeys属性,然后单击出现的下拉箭头。下拉箭头让您能够为选中的菜单项定义快捷键,如图9.10所示。

3.选择 Ctrl 键并在下拉列表“键”中选择 O(代表“打开”);完成后单击其他属性关闭下拉列表。

4.按F5键运行项目。然后按Ctrl+O,应用程序的响应与打开File菜单并单击Open Picture项相同。

图9.10 使用菜单项的ShortcutKeys属性指定快捷键

提示:尽可能指定合理的快捷键组合,虽然并非总是能做到。例如,F6 的含义并不直观。然而,指定修饰键(如Ctrl)和字符组合时,就有一定的灵活性。例如,对于菜单项Quit,Ctrl+Q比Ctrl+T更直观。另外,如果菜单项与商业应用程序的菜单项相同或类似,应使用与商业程序一样的快捷键。

停止运行项目并保存项目。

一般来说,当程序有菜单(大多数程序都应该有)时,也应有工具栏。工具栏(不知什么原因,在Visual Basic中称为Toolstrip)是用户执行程序功能的最简便方法之一。与菜单项不同,工具栏项总是可见的,因此即时可用。另外,菜单栏项还有工具提示(ToolTip),用户只要将鼠标指向工具按钮,就可以知道其用途。

工具栏项实际上是菜单项的快捷方式;工具栏的每一个工具项都应该有对应的菜单项。记住,有些用户喜欢使用键盘,因此他们喜欢使用键盘通过菜单来问程序的功能。

放在工具栏中的工具取决于应用程序支持的功能。然而,创建工具栏和工具项的方法是一样的,而不管使用的是什么按钮。工具栏是使用ToolStrip控件创建的。

执行下列步骤,在Picture Viewer项目的主窗体中添加一个工具栏。

1.在窗体设计器中显示ViewerForm.vb窗体(如果它还没有显示)。

2.双击工具箱中的ToolStrip项,在窗体中添加一个新ToolStrip控件。这样便在窗体顶部添加了一个新工具栏。将工具栏的名称改为tbrMainToolbar。

3.注意到工具栏出现在菜单的上方。使用过Windows程序的人都知道,工具栏应在菜单下方。右击工具栏并在上下文菜单中选择“置于顶层”,这将使工具栏移到菜单下方。现在窗体应如图9.11所示。

图9.11 新工具栏没有按钮

与前面介绍过的其他很多控件一样,ToolStrip 控件支持一个特殊集合:Items 集合。Items集合包含出现在工具栏上的按钮。在“属性”窗口中单击Items属性,然后单击出现的小按钮,将显示“项集合编辑器”。成员列表显示的是工具栏本身但没有按钮,因为新工具栏没有按钮。

注意:下面要添加三个图像到工具栏中:一个用于Open,一个用于Draw Border,一个用于Options。可从我的网站http://www.jamesfoxall.com/books.aspx下载这三幅图像。

打开左上角的下拉列表,如图9.12所示。这个列表包含可添加到工具栏中的工具类型。

图9.12 工具栏可包含多种不同类型的工具

在这个例子中,将创建按钮和分隔条。读者可在另一个项目中尝试使用不同类型的工具。执行下列步骤。

1.在下拉列表中选择Button,然后单击“添加”创建一个新按钮。按如下设置它的属性(可能需要将属性排列顺序改为“字母顺序”):

2.单击按钮的Image属性,然后单击出现的小按钮。单击“导入”,然后找到并选择图像Open。

3.单击“确定”,将图像保存到按钮中。

4.再次打开下拉列表并选择Button,创建一个新按钮,并按如下设置它的属性:

5.将Draw Border按钮的 Iamge属性设置为一个有效的图像文件。

6.再次打开下拉列表并选择Button,创建一个新按钮,并按如下设置它的属性:

7.将Options按钮的Image属性设置为一个有效的图像文件。

至此,为工具栏创建了按钮;但还有最后一项工作需要完成。专业设计师总是使用分隔条将多组工具按钮分开,分隔条是显示在两个按钮之间的竖直线条。您创建的三个按钮都是不相关的,因此下面创建分隔条将它们彼此分开。执行下列步骤。

1.在下拉列表中选择Separator,创建一个分隔条。分隔条被添加到这三个按钮的后面。单击分隔条并将其拖放到Draw Border按钮上,这将移动分隔条使其位于按钮Open和Draw Border之间。

2.再从下拉列表中选择Separator,创建另一个分隔条。单击分隔条并将其拖放到Options按钮上,这将移动分隔条,使其位于按钮Draw Border和Options之间。

3.单击窗体以取消选中工具栏控件,现在屏幕应如图9.13所示。

图9.13 工具栏已定义好,可以添加代码使其发挥作用了

注意:可以使用“项集合编辑器”将工具添加到工具栏中,而不像这里这样动态地将它们添加到工具栏中。

工具栏编程与菜单编程很相似。可以看出,Microsoft尽量将东西标准化。例如,在以前的.NET版本中,使用的是包含Buttons集合的Toolbar控件。在2005版中,Toolbar控件被包含 Items集合的ToolStrip控件取代。List View控件包含 Items集合,Tree View控件也是。发现这种模式了吗?学习如何使用一个控件的 Items 集合后,就很容易使用其他控件的 Items集合。

执行下列步骤使工具栏发挥作用。

1.单击窗体下方的控件tbrMainToolbar以选中它。

2.在工具栏中双击Open按钮访问它的Click事件。确保双击的是按钮而不是工具栏。双击工具栏将访问完全不同的事件。输入下列代码:

3.单击选项卡“ViewerForm.vb[设计]”返回窗体设计器。

4.双击Draw Border按钮,并将下列代码添加到它的Click事件中:

5.单击选项卡“ViewerForm.vb[设计]”返回窗体设计器。

6.双击Options按钮,并将下列代码添加到它的Click事件中:

OptionsForm.ShowDialog()

保存项目,然后按 F5 键运行项目。现在,单击工具栏上的按钮应执行与单击菜单项一样的操作。第10章将介绍如何在两个控件之间共享代码。

虽然在这个项目中不会用到,但要知道在工具栏中可以创建下拉列表。Visual Basic 2010在很多地方使用了下拉列表,如图9.14所示。要创建这样的下拉列表,在工具栏中添加一个DropDownButton,而不是常规按钮。这将创建一个子菜单,就像在本章前面定义常规菜单一样。

图9.14 可创建这样的下拉列表

本章要介绍的最后一个控件是 Status Bar(状态栏)控件。状态栏不如其他控件(如ToolStrip和MenuStrip等)那样迷人,甚至不如它们那么有用(但也没它们难用)。状态栏为应用程序提供了额外的功能,它在标准位置显示信息,用户也已经习惯状态栏。最简单的状态栏显示标题和一个大小调整手柄——控件右边的几个点,用户可用鼠标拖曳它们来改变窗体的大小。

现在,双击工具箱中的StatusStrip项(位于“菜单和工具栏”分类下),在窗体中添加一个新状态栏。滚动设计器左边的滚动条,在窗体底部可以看到这个状态栏。将该 StatusStrip的名称改为sbrMyStatusStrip。由于将其他控件锚定了,这个状态栏会覆盖窗体底部的一些控件。执行以下步骤来修复这种问题。

1.单击窗体上的图片框,将其Size属性改为265,256。

2.将放大按钮和缩小按钮的Location.Y属性改为285。现在窗体应如图9.15所示。

图9.15 状态栏总是显示在窗体底部

单击StatusStrip以选中它,并看一下它的左边,是不是很熟悉?它与给MenuStrip添加菜单项以及给ToolStrip添加按钮时的界面是一样的。现在,单击下拉箭头并选择StatusLabel。这将创建一个新状态栏标签,按如下修改它的属性:

打开下拉列表创建这个状态标签时,读者可能注意到了,在状态栏中也可以添加其他类型项。这里只需标签就可以了。在第10章,读者将编写代码,在标签中显示打开的图片的文件名。

按 F5 键运行项目,将鼠标指向状态栏右下角的一组点,光标将变成双箭头,可以在这里单击并拖曳鼠标来调整窗体的大小。然而,状态栏没有聪明到能够识别窗体的边框不能调整(例如窗体被设置为固定窗口或固定工具窗口时),因此您必须将状态栏的SizingGrip属性改为False以隐藏大小调整手柄。

菜单、工具栏和状态栏为应用程序提供了很多便利,大大增强了程序的可用性。在本章,读者学习了如何使用 MenuStrip 为应用程序创建复杂的菜单;学习了如何添加、移动和删除菜单项以及如何定义快捷键以便更好地使用键盘导航;还知道了工具栏提供了访问常用菜单项的捷径。读者学习了如何使用ToolStrip控件来创建包含位图且分组合理、功能完善的工具栏。最后,读者还知道了如何使用状态栏来给应用程序锦上添花。使用这些工具是应用程序界面设计过程的一个重要组成部分,现在读者具备了将它们添加到程序中的技能。

问:如果多个窗体具有几乎相同的菜单,是否真的需要花费时间为所有窗体都创建菜单?

答:没有想象的那么多。创建一个包含通用菜单项的 MenuStrip 控件,然后将这个控件复制并粘贴到其他窗体中。可以在这个菜单结构的基础上创建菜单,这节省了大量的时间。但是要知道,复制并粘贴控件时,并不会复制相应的代码。

问:有些程序允许用户自定义菜单和工具栏。Visual Basic的菜单以及工具栏能实现这一点吗?

答:不能。为实现这种功能,必须编写很多代码或购买第三方组件。我个人认为,购买支持这种功能的组件是更好的选择。

1.判断对错:使用Context Menu Strip控件创建窗体的菜单栏。

2.要创建快捷键或热键,在字符前面使用什么?

3.要在菜单项旁添加复选标记,应设置菜单项的什么属性?

4.如何为菜单项添加代码?

5.工具栏项属于哪个集合?

6.判断对错:工具栏上的每个按钮都有自己的Click事件。

7.哪个控件在窗体底部向用户显示信息?

1.错。使用MenuStrip控件创建。

2.&。

3.Checked属性。

4.双击菜单项。

5.Items集合。

6.对。

7.StatusStrip控件。

1.创建一个新项目,并构建一个包含下拉列表的ToolStrip。

2.指出如何使用ToolStrip控件在按钮上显示状态信息(提示:Items集合有一个特殊项支持这种功能)。

第10章 创建和调用过程

第11章 使用常量、数据类型、变量和数组

第12章 执行算术运算、字符串操作和日期/时间调整

第13章 使用Visual Basic代码做出决策

第14章 使用循环提高效率

第15章 调试代码

第16章 使用类设计对象

第17章 与用户交互

第18章 使用图形

在本章中,读者将学习:

创建Visual Basic代码模块;

创建过程;

调用过程;

传递参数;

退出过程;

避免递归过程。

读者已经花了9章学习导航Visual Basic和创建应用程序界面所需的基本技能。创建好的界面很重要,但这只是创建Windows 程序的很多步骤之一。创建应用程序的基本界面后,需要使程序能够执行功能。程序可能自发地执行操作,也可能是根据用户与GUI 的交互来执行——不管是哪种方式,都需要编写Visual Basic代码,使应用程序执行任务。在本章中,读者将学习如何编写代码(模块)、如何创建可执行的独立代码例程(称为过程)以及如何调用创建的过程。

模块是存储代码的地方。编写Vsiual Basic之前,必须从模块开始。读者已经使用过一种模块:窗体模块(详情请参阅第5章)。双击窗体上的对象时,将访问位于窗体模块中的事件。除窗体模块外,还可以创建标准模块(类模块)。

注意:类模块是被用作实例化对象的模板,第16章将讨论创建这种对象的细节。本章讨论的大多数技巧也适用于类模块,但这里重点讨论标准模块,因为它们更易于使用。

虽然可以将程序的所有代码都放在一个标准模块甚至是类模块中,但最好创建不同的模块,将相关的代码分组。另外,对于只在一个窗体中调用的代码,最好将其放在该窗体的模块中。在本章中,读者将把代码放在窗体模块中以及创建的新模块中。

注意:当前的开发趋势是以面向对象编程(OOP)为中心,而面向对象的编程是以类模块为中心的。第16章将简单地介绍面向对象编程,但这是一个非常复杂的主题,因此不会详细介绍。强烈建议读者熟悉完本书的内容后,阅读专门讨论面向对象编程的书籍,如《Sams Teach Yourself Object-Oriented Programming with Visual Basic.NET in 21 Days》(第 2版,Sams Publishing 2002年出版)。

使用标准模块的一个通用规则是,通过创建模块将相关代码分组。这并不是应创建几十个模块,而是将相关的函数组合成大小合适的模块。例如,创建一个包含所有打印例程的模块,再创建一个包含数据访问例程的模块。另外,我喜欢创建一个通用模块,将不适合于放在专用模块中的各种例程放在这里。

注意:与使用标准模块相比,创建类并实例化对象是更好的选择,但没有任何规则是一成不变的。有些OOP纯粹主义者强烈建议决不要使用标准模块。别忘了,标准模块只是一种工具,任何工具都有其用途。

下面将在第 9章创建的Picture Viewer项目的基础上进行开发,因此现在打开它。

接下来,选择菜单“项目”>“添加新项”,在出现的“添加新项”对话框中单击“模块”,如图10.1所示。

图10.1 使用该对话框在项目中添加新项

注意到该对话框与用于添加新窗体的对话框相同。将模块名改为DrawingModule.vb,并单击“添加”创建新模块。Visual Basic创建新模块后,将切换到代码窗口,以便用户输入代码,如图10.2所示。

单击工具栏中的“全部保存”保存项目。

图10.2 模块没有图形界面,因此总是在代码编辑器中处理模块

创建用于存储代码的模块后,可以开始编写Visual Basic过程了。过程是可供其他代码调用的一组代码。过程与事件类似,但它不是在用户与窗体或控件交互时执行,而是在被代码语句调用时而执行。

在Visual Basic中有两种过程:

返回值的过程(被称为函数);

不返回值的过程(被称为子程序)。

需要创建返回值的过程的原因很多。例如,函数可能需要根据任务是否成功完成而返回True或False。也可以编写接受特定参数(传递给过程的数据,与过程返回的数据相对应)并根据参数来返回一个值的过程。例如,可以编写这样一个过程:给它传递一个句子,它返回句子中的空格数。这种可能性只受想象力的限制。但要记住,过程不一定要返回值。

要创建过程,无论它是子程序(不返回值的过程)还是函数(返回值的过程),首先必须在模块中声明它。在新创建的模块中,输入下面的代码并按回车键:

Public Sub OpenPicture()

按下回车键后,Visual Basic将自动插入一个空行,并创建文本End Sub,如图 10.3所示。这便创建了一个新过程。

过程的声明(用于定义过程的语句)由若干部分组成。第一个单词(这里是Public)是一个关键字(在Visual Basic有特殊含义的单词)。Public定义了过程的作用域(作用域将在第11章讨论),它指定该过程可以在当前模块外调用。可以用关键字Private代替Public,使得只能在当前模块中访问该过程。由于要从Picture Viewer窗体调用该过程,因此将其声明为Public。

图10.3 语句Public Sub和End Sub组成了子程序的基本结构

注意:作用域限定符是可选的,如果没有指定,将创建一个Public过程。总是应该显式地指定过程的作用域。

Sub(子程序的简称)是另一个Visual Basic关键字,用于声明不返回值的过程。稍后将介绍如何创建返回值的过程(被称为函数)。

第三个单词OpenPicture 是该过程的名称,它可以是任意字符串。但要注意,不能将关键字用作过程名,也不能在过程名中使用空格。这个例子中,该过程执行的是Open Picture菜单项和Open Picture工具栏按钮的功能,这是其名称的来由。应总是为过程取一个反映其用途的名称。只要过程不在同一个作用域内,它们就可以有相同的名称(第11章将讨论作用域)。

提示:有些编程人员喜欢在名称中使用空格,这样更具可读性,但在很多情况下,如为过程命名时,不能使用空格。一个常用的技巧是使用下划线来代替空格,如Open_Picture,但我建议混合使用大小写,就像这个例子中那样。

紧跟在过程名称之后的是一对括号。OpenPicture()不接受任何参数(传入的数据),因此它后面的括号为空;如果要将数据传递给过程,将在这对括号内指定,这将在稍后介绍。

注意:即使过程不接受参数,也必须提供括号。

在OpenPicture()过程中输入下列代码:

注意,这段代码几乎与前几章为Open Picture按钮、菜单项和工具栏按钮编写的代码相同。唯一不同的是,由于该模块独立于窗体,因此在引用窗体上的控件或获得/设置窗体的属性时,必须显式地指定窗体名(而不能使用Me)。另外,这里编写了将选择的文件名显示在状态栏而不是标题栏中的代码。

您已经在三个地方键入了这段代码(或其变种)。之前提到过,重复输入代码并不好,将解决这个问题。发现重复代码时,应将重复的代码放在一个过程中。这样,需要时只要调用该过程而不必复制代码。这样做有很多优点,包括:

减少错误。每次输入代码都有可能发生输入错误。只输入一次代码,可降低产生错误的可能性;

一致性和可维护性。复制代码时,经常会忘记某个使用了代码的地方。可能改正了一个地方的错误,却忘了其他地方;或在一个地方添加了新的特性,却忘了其他地方。通过使用过程,只需维护一处代码。

下面创建一个这样的过程:在图片框周围绘制边框。将光标置于End Sub后(如图10.4所示)并按回车键,然后输入下面的代码:

图10.4 在箭头指向的地方开始创建新过程

这个过程中包含一些新内容。首先,过程声明的括号内有文本。前面提到过,这是定义参数的地方。参数是传递给过程的数据,与过程返回的值不同。这里创建了一个类型为图片框的参数。有关这方面的详情将在第11章介绍,现在读者只要知道下面的概念:调用这个过程的代码将传递一个图片框对象引用给它,这样该过程就能够使用被引用的图片框,就像直接对图片框本身进行操作一样。

创建的第一个过程本来也应该这样,但我希望读者知道这两种方法。在第一个过程(OpenPicture)中,使用硬编码来指定ViewerForm,这意味着这些代码只适用于ViewerForm窗体。第二个过程将图片框引用作为参数,因此可用于项目中任何窗体上的图片框。这是一个需要牢记的重要概念。在过程中,应尽可能使用参数,避免通过硬编码来引用属于其他窗体的对象。

在整个过程中,都使用objPicturebox参数,而不是对窗体中图片框对象的硬编码引用。这很容易理解,但过程中的第二条语句需要解释一下。请注意其中的objPictureBox.Parent,所有控件都有父控件,这就是控件所属的容器。属性Parent是一个对象属性,它返回对父控件的引用。就窗体上的图片框而言,Parent指的是窗体本身。因此objPicturebox.Parent.CreateGraphics的含义与在窗体中调用picShowPicture.CreateGraphics或Me.CreateGraphics相同。

现在,模块应如图10.5所示。

图10.5 代码必须位于过程中,而过程必须位于模块声明和End Module之间

刚才创建的两个过程都不返回值。下面声明一个函数——返回值的过程。下面是函数声明的语法:

Scope Function functionname(parameters) As datatype

声明返回值的过程和声明不返回值的过程之间有两个重要区别。首先,使用关键字Function 而不是 Sub;其次,在括号后面添加了文本。声明函数时,总是需要在括号后面指定两个单词,其中第一个单词总是为As,而第二个单词是具体的数据类型声明。数据类型将在第11章详细讨论,因此现在不理解也没关系。然而,要理解它在这里的作用。

正如前面指出的,函数返回一个值。在 As 后面指定的是函数返回的数据类型。现在不用在项目中创建函数,但考虑下面的例子:

Public Function ComputeLength(ByVal strText As String) As Integer

 Return strText.Length

End Function

这里有三个地方要注意。

括号后面的As Integer:它表示函数将返回一个整型值;如果函数返回一个字符串,应声明为As String。为函数声明适当的数据类型很重要,这将在第 11章讨论。

关键字Return实现两项功能。首先,它使函数立即终止——不再执行后面的任何代码;其次,它将指定的返回值传递回来。在这段代码中,函数返回的是给定字符串的字符数。

使用End Function而不是End Sub来表示函数结束,这使得End语句同相应的声明语句一致。

除上述三项内容外,子程序与函数类似。记住这些重要区别,就能根据情况决定创建子程序还是函数。

调用过程很简单——比创建过程简单得多!到现在为止,您已经创建了两个过程,每个过程都包含至少在三个地方使用了的代码。下面删除所有这些重复的代码,取而代之的是调用刚创建的通用过程。为此,执行下列步骤。

1.在“解决方案资源管理器”中双击ViewerForm.vb,在窗体设计器中显示该窗体。

2.要替换的第一处代码是为工具栏的Open Picture按钮输入的代码。双击Open Picture按钮访问它的Click事件。删除事件声明和End Sub之间的所有代码,如图 10.6所示。

图10.6 删除这段代码并用过程调用取代它

3.删除原来的代码后,输入下面的代码:

OpenPicture()

就这样简单!要调用子程序(不返回值的过程),只需使用过程名并在后面加上一对括号。如果过程接受参数,还需要在括号内指定参数。

注意:输入有效的过程名和左括号后,Visual Basic将显示提示工具,指出过程所需的参数。记住所有过程的所有参数很难,更不用说它们的顺序了,因此这项功能将节省用户大量的时间。

4.还有两个地方使用了Open Picture代码。在“解决方案资源管理器”中双击ViewerForm.vb,返回该窗体的设计视图,单击File菜单打开它,然后双击Open Picture菜单项。

5.删除Click事件中的代码,用下面的代码取代它:

OpenPicture()

返回到窗体设计器。

到目前为止,只创建了子程序——不返回值的过程。现在读者已经知道,调用子程序使用其名称和括号;对于函数——返回值的过程,调用方法稍有不同。请看下面这个函数:

这个函数接受两个参数,将它们相加,然后返回结果。

调用函数时,将函数视为它返回的值。例如,要设置窗体的Height属性,可使用下面的代码:

MyForm.Height = 200

这条语句将窗体的高度设置为200。假设要使用AddTwoNumbers()过程来指定窗体的高度。将过程视为它返回的值,可以用该函数代替上面的字面值,如下所示:

MyForm.Height = AddTwoNumbers(1, 5)

在这个例子中,窗体的高度将被设置为6,因为将1和5作为参数传递给了函数,函数将它们相加。下一节将更详细地介绍如何使用参数。

注意:调用函数时,必须像对待它返回的值一样对待函数调用。这通常意味着将函数调用放在等号的右边或将它嵌入到表达式中。

现在,已经创建了一个过程,并在两个地方调用了它——您的应用程序已经初见端倪。既然已经有了工具栏和菜单,就不再需要第1章创建的按钮了。

执行以下步骤来删除按钮。

1.双击窗体右边的Select Picture按钮。将整个事件过程删除,包括以Private开头的过程声明和End Sub语句,如图 10.7所示。

2.单击工作空间顶部的“ViewerForm.vb[设计]”选项卡返回窗体设计器。

3.按钮 Select Picture应该被选中,因为您刚才双击了它;如果没有,单击以选择它。按Delete键将其删除。

图10.7 删除过程时,必须删除过程声明和End语句

4.对按钮Quit、Draw Border和Options重复第 1~3步。务必将每个按钮的事件过程都删除!现在,屏幕应该如图10.8所示。

图10.8 有菜单和工具栏后,按钮便不再需要

5.下面进一步整理窗体。将标签X的Location属性设置为336,256,将标签Y的Location属性设置为336,269。最后,将图片框的Size属性设置为322,257。现在窗体应如图10.9所示。

图10.9 比原来好多了

传递参数

在过程中使用参数让调用过程的代码能够将数据传递给过程。读者已经知道参数是如何定义的——在过程声明的括号内。参数定义由参数名、As和数据类型组成,如下所示:

Public Sub MyProcedure(strMyStringParameter As String)

注意:阅读第11章关于变量的介绍后,读者将更明白这种结构。现在,读者只需对如何定义和使用参数有大概的了解即可。

可以为过程定义多个参数,参数之间用逗号隔开,如下所示:

Public Sub MyProcedure(strMyStringParameter As String, _

 intMyIntegerParameter as Integer)

调用过程通过实参(argument)将数据传递给形参(parameter)。这只是个语义问题,在过程声明定义时,称为形参(parament);当它是调用过程的一部分时,称为实参。实参在括号内传递——与定义形参时相同。如果过程有多个实参,用逗号隔开。例如,可以使用如下语句将值传递给刚才定义的过程:

MyProcedure("This is a string", 11)

参数在过程内与常规变量一样。记住,变量是值可变的存储实体。在上面的语句中,将字面值传递给过程;但也可传递变量的值,如下所示:

MyProcedure(strAString, intAnInteger)

要记住一点:在Visual Basic中传递参数时,参数是按值传递而不是按引用传递。按值传递时,过程收到的是数据的拷贝;对参数的修改不会影响原变量的值。而按引用传递时,参数实际上是原变量的指针,在过程内对参数的修改将传递到原变量。要按引用传递参数,可在参数定义前面加上关键字ByRef,如下所示:

Public Sub MyProcedure(ByRef strMyStringParameter As String, _

 intMyIntegerParameter as Integer)

没有使用ByRef定义的参数是按值传递的;这是Visual Basic中参数传递的默认方式。因此,在上面的语句中,第一个参数按引用传递,第二个参数按值传递。实际上,如果您输入上述语句,Visual Basic将自动在 intMyIntegerParameter前面加上ByVal。

注意:在以前的Visual Basic版本中,默认行为是参数按引用而不是按值传递。要按值传递参数,必须在参数定义前面加上ByVal。

您已经创建了一个接受一个参数的过程。下面再看一下这个过程:

注意到参数是使用ByRef声明的,这是因为要传递的不是常规数据,如数字或字符串。这个过程接受一个对象作为参数——具体地说是一个图片框。在该过程中,引用了图片框的属性,因此必须按引用传递该参数(为引用对象的属性和方法,需要指向该对象的指针)。执行下列步骤来调用这个过程。

1.在窗体设计器中显示ViewerForm窗体。

2.双击工具栏中的Draw Border按钮,并删除该过程的内容,但不要删除其外壳,即以Private打头的第一条语句和最后的End Sub语句。

3.在Click事件中输入下面的语句:

DrawBorder(picShowPicture)

4.返回窗体设计器,单击窗体上的Tools菜单,然后双击Draw Border项。

5.在这个过程中,使用下面的语句替换所有的代码:

DrawBorder(picShowPicture)

现在已经将该过程同菜单和工具栏关联起来了。按 F5 键运行项目,并测试各个菜单项和工具栏按钮(如图 10.10所示)。菜单项Confirm on Exit仍无效,下一章将解决这个问题。

图10.10 专业应用程序除良好的界面设计外,还要求良好的过程设计

停止运行项目并保存。

通常,过程内的代码是从头到尾执行的——差不多是这样。遇到End Sub或End Function语句后,将返回到调用过程的语句处执行。可使用语句Exit Sub或Exit Function离开过程。显然,在使用关键字Sub声明的过程中,使用Exit Sub来退出;而在使用关键字Function声明的过程中,使用Exit Function来退出。Visual Basic遇到这样的退出语句后,过程将立刻终止,并返回到调用该过程的语句。应尽可能在过程中少用Exit语句,如果过程的出口太多,将难以理解和调试。

调用过程可能导致无限循环,请看下面两个过程:

调用其中任何一个过程都将产生过程调用的无限循环,导致如图10.11所示的错误。

图10.11 无限递归导致堆栈溢出异常(错误)

这种无限循环也称为递归循环。简单地说,Visual Basic在一个称为堆栈的区域为每个过程调用分配一定的内存。堆栈的可用空间是有限的,因此无限递归最终将耗尽可用堆栈空间,进而导致异常。这是一种严重的错误,必须采取措施避免这样的递归。

存在合理使用递归的情况,最著名的用途是在算法中,如用于积分或遍历硬盘上所有的文件夹。然而,精心设计的递归并不会导致无限循环;这样的递归总有一个结束点(在耗尽堆栈空间前)。如果读者对这样的算法感兴趣,可阅读专门介绍这方面的书籍。

在本章中,读者知道过程是一组独立的代码集合,用于完成一个或一组相关的任务。过程是编写 Visual Basic代码的地方。有些过程可能只有一行代码,而有些过程可能有几页。读者学习了如何定义和调用过程;创建和调用过程对于成功地使用 Visual Basic进行编程非常重要。务必避免创建递归过程!读者将经常使用过程,因此很快将对这些技能得心应手。

模块用于对相关过程进行分组。在本章中,,重点介绍的是标准模块,它不过是一个过程容器。别忘了将相关过程放在同一个模块中,并给每个模块取一个描述性名称。在第 16章,将运用读者的模块技能,开始使用类模块,这要求更好地分离不同的模块。

提示:每个过程应只完成一项特定的功能;避免创建完成很多不同任务的过程。例如,假如要创建一组代码,在窗体上绘制椭圆;另外,还要清除窗体。如果将两组代码都置于同一个过程中,椭圆被画出来后马上就会被清除。如果将各组代码置于不同的过程中,可以通过调用一个过程来画椭圆,然后在任何时候调用另一个过程来清除它。将这些例程置于一个模块中,而不要与特定窗体相关联,这样就能在其他需要的窗体中使用它们了。

问:定义过程时,需要考虑过程的作用域吗?

答:将所有过程都定义为Public似乎是很吸引人,但这是一种不好的编程习惯,原因有很多。首先,在大型项目中,可能有很多名称相同但功能略微不同的过程。通常这些过程只用于有限的范围内。如果将所有过程都定义为Public,在同一作用域内以相同的名称创建过程时,将发生冲突。所以,如果过程不需要为Public,就不要把它定义为Public。

问:模块的合理数量是多少?

答:这个很难说。在我创建的最大的应用程序中(这是一个非常大的应用程序),有大约18个模块,但随着时间的推移,我用类模块代替它们,从而减少了模块数量。如果模块数超过这个数字,项目可能变得难以管理。

1.用于存放过程的实体称为什么?

2.判断对错:要访问类模块中的过程,必须首先创建一个对象。

3.要声明返回值的过程,使用Sub还是Function?

4.判断对错:使用Call语句调用返回值的过程。

5.调用语句传递给过程的数据称为什么?

6.传递多个变量给过程时,用什么将它们分开?

7.一个或一组过程以循环的方式不断地互相调用称为什么?

1.模块。

2.对。

3.Function。

4.错。在老版本的Visual Basic中,必须使用Call来调用子程序,但现在不需要了。调用函数时,从来就不需要Call。

5.参数。

6.逗号(,)。

7.无限递归。

1.创建一个过程作为窗体的一部分,它接受一个字符串并输出另一个字符串。在文本框的TextChanged()事件中加入代码来调用该过程,并将文本框的内容作为参数传递给它。该过程返回的是传入的字符串的大写版本(提示:使用Visual Basic函数UCase( ))。

2.创建一个调用自身的过程。在按钮的Click()事件中调用这个过程,并观察导致的错误。

在本章中,读者将学习:

理解数据类型;

确定数据类型;

将数据转换为不同的数据类型;

定义和使用常量;

声明和引用变量;

理解显式变量声明和严格类型检查;

使用数组;

确定作用域;

声明静态变量;

使用命名规范。

编写Visual Basic过程时,经常需要存储和获取各种信息。实际上,在我编写的所有应用程序中,没有一个不需要在代码中存储和获取数据。例如,可能要记录过程被调用了多少次,或者需要保存属性值供以后使用。这样的数据可存储为常量、变量或数组。常量是在设计时定义的名称值,定义后便不能修改,但可以引用。而变量就像存储箱,可以根据需要来获取或修改变量中的数据。数组就像是一组变量,可以在一个数组变量中存储很多值。

定义这些存储实体时,必须确定它们将包含的数据类型。例如,新定义的变量要存储字符串值(文本)还是数字?如果是数字,它是整数或是其他的?确定要存储的数据类型后,必须选定数据对于项目中其他过程的可见性(这种可见性称为作用域)。在本章中,读者将学习Visual Basic 2008的数据类型(如果读者以前使用的是Visual Basic 6.0,将发现有些数据类型的名称虽然相同,但含义是不同的)、如何创建和使用这些存储机制以及如何通过缩小作用域将代码中的问题减至最少。

注意:在本章中,将在第 10章创建的Picture Viewer项目的基础上进行开发,开始创建代码,让Options窗体上的控件实现其功能。

每种编程语言都有编译器。编译器是Visual Studio .NET框架的一部分,它将您编写的代码转换为计算机可理解的语言。编译器必须理解代码使用的数据类型。例如,如果要让编译器将下面的值相加,它将无法理解:

"Fender Strat" + 63

当编译器无法理解时,它要么拒绝编译代码(这种情况更好,因为这样可在用户运行应用程序前将问题解决),要么在遇到不能理解的代码后停止执行并显示异常(错误)。这两种错误将在第 15章详细讨论。显然,不能将单词“Fender Strat”与数字 63相加,因为它们是不同类型的数据。在Visual Basic中,它们被称为数据类型不同。在Visual Basic中,常量、变量和数组都必须定义为存储特定类型的信息。

确定数据类型——定义常量、变量或数组的数据类型——可能有点让人费解。对于Visual Basic 来说,一个数字不仅仅是数字。包含小数点的数字与不包含小数点的数字是不同的。Visual Basic可以对不同数据类型的数字执行算术运算,但不能将一种类型的数据存储在与其类型不兼容的变量中。由于这种限制,定义常量、变量或数组时,应慎重考虑它们将存储的数据类型。表 11.1列出了Visual Basic的数据类型以及每种数据类型的取值范围。

表11.1 Visual Basic数据类型

续表

注意:第一眼看到数据类型列表时,您可能会觉得有点吓人,但在这些数据类型中进行选择时,可以遵循一些通用规则。对各种不同的数据类型更熟悉后,就能更好地对数据类型进行选择了。

使用数据类型时,可遵循下列有用的指南。

如果要存储文本,使用String数据类型。String数据类型可用于存储任何有效的键盘字符,包括数字和非字母字符。

只存储True或False时,使用Boolean数据类型。

要存储不包含小数点且在−32768和32767之间的数字,使用Short数据类型。

要存储不包含小数点且值超出了 Short 范围的数字,使用 Integer或Long(long integer的缩写)数据类型。

如果需要存储包含小数点的数字,使用Single数据类型。对于包含小数点的数字,Single数据类型几乎足够,除非编写的是极其复杂的数学程序或需要存储非常大的数;在这些情况下,可使用Double数据类型。

要存储金额,使用Decimal数据类型。

如果需要存储日期或时间,使用Date数据类型。使用Date数据类型时,Visual Basic能够识别常见的日期和时间格式。例如,如果存储 7/22/2012,Visual Basic不会将该值当作简单的文本字符串;它知道该值代表的是2012年7月22日。

不同的数据类型占用不同的内存空间。为节省系统资源,最好使用占用内存最少,但足以存储所有可能值的数据类型。例如,如果只存储从1到10十个数字,使用Short而不要用Long。

要特别注意Object数据类型。如果将变量或数组定义为Object数据类型,将可以存储任意类型的值;Visual Basic将在您设置变量值时确定使用哪种数据类型。

使用Object数据类型有一些缺点。首先,Object数据类型比其他数据类型占用更多内存。另外,Visual Basic处理Object数据类型的计算要花更多的时间。不要使用 Object 数据类型,除非真的有特殊理由——确实有一些适当的理由,如事先不知道要存储哪种数据类型。熟悉显式数据类型,并合理地使用它们。

在大多数情况下,Visual Basic不允许将一种类型的数据赋给另一种类型的变量。改变值的数据类型称为“类型转换”(casting)。转换到取值范围更大或精度更高的数据类型被称为向上转换;而转换到取值范围更小或精度更低的数据类型被称为向下转换。Visual Basic通常向下转换,而不向上转换。例如,不需要显式地转换,就可将 Single 变量的值赋给 Double变量,因为这样做不会丢失数据:Double数据类型的精度比Single更高。然而,在不使用数据类型转换函数进行显式类型转换的情况下,不能将Single变量的值设置为Double变量的值。Visual Basic要求您将Double显式地转换为Single,因为这样做可能丢失数据。

表11.2列出了可用来将数据转换为其他类型的类型转换函数(可将其中的C视为代表类型转换)。这些函数的用法非常简单:将要转换的数据作为参数传入,函数将返回类型为其返回类型的值。例如,要将Double变量的值赋给Single变量,可使用下面这样的语句:

sngVariable = CSng(dblVariable)

表11.2 类型转换函数

注意:Boolean变量只能存储True或False。然而,理解Visual Basic在幕后如何使用Boolean值至关重要。在Visual Basic中,True在内部存储为−1,而False存储为 0。实际上,任何非零值都可代表True,但Visual Basic在内部总是将True视为−1。将数值转换为Boolean时,Visual Basic将 0转换为False,将其他值转换为True。这一点在使用布尔逻辑来评估数值时很重要,布尔逻辑将在第12章详细讨论。

在过程中硬编码数值(如 intVotingAge = 18)可能导致很多错误。硬编码数字一般称为“魔术数字”,因为它们通常看上去布满疑云:这些数字的含义是不清楚的,因为数字本身并没有暗示数字表示的是什么。常量用于避免魔术数字的问题。

设计时将常量定义为特定的值,这个值在程序的整个生命周期内都不变。常量有下列优点。

消除或减少数据输入问题。需要pi时,使用常量c_pi比每次输入3.14159265358979简单得多。编译器能够捕获拼写错误或未声明的常量,但根本不关心输入的值。

代码更容易更新。如果硬编码抵押贷款利率(mortgage interest rate)6.785,然后这种利率变为7.00,就必须在代码中修改每个6.785。除数据输入问题外,可能还会将不表示利率的6.785修改了——这个值可能表示储蓄公债收益率(savings bond yield,这样的储蓄公债收益率可太高了)。使用常量时,只要在定义常量的地方修改一下,所有引用该常量的代码都将使用新值。

代码更容易阅读。魔术数字通常很不直观。而命名得好的常量,将使代码更明晰。例如,下面哪条语句更具可读性呢?

定义常量的语法如下:

Const name As datatype = value

例如,要定义存储pi值的常量,可使用如下语句:

Const c_pi As Single = 3.14159265358979

注意到我给常量名使用了前缀c_,这样做使得阅读代码时更容易地区分哪个是变量,哪个是常量。详情请参见这一章稍后的“命名规范”一节。

定义常量后,可以在代码中用常量名来代替常量的值。例如,要输出两倍pi值的结果,可用如下语句(字符“*”表示乘号,这将在下一章介绍):

Debug.WriteLine(c_pi * 2)

使用常量简单得多,如果键入下列代码,将更容易出错:

Debug.WriteLine(3.14159265358979 * 2)

常量只能在它们的作用域内被引用。稍后的“确定作用域”一节将讨论作用域。

下面将使用在本章所学的知识,使第7章添加的选项发挥作用。首先使用常量为选项控件创建默认值。第7章创建的Options窗体允许用户对下列三个选项进行操作。

用户名:显示在Picture Viewer主窗体的标题栏中;

确认是否退出的提示:用于决定用户是否真的要关闭Picture Viewer程序;

图片框的默认背景色:可设置为灰色(默认)或白色。

在下列步骤中,将为 Prompt on Exit选项的默认值创建一个常量。打开第 10章创建的Picture Viewer项目,然后执行下列步骤。

1.在“解决方案资源管理器”中单击ViewerForm.vb以选中它。

2.单击“解决方案资源管理器”顶部的“查看代码”按钮,查看ViewerForm.vb的代码。

3.要创建的常量是模块级常量,即它们在被声明模块内的任何地方都可用。这意味着它们不会放在特定过程中。声明模块变量的位置是在模块声明(Public Class classname)后。将光标放在声明的下一行,按回车键创建新行,然后输入下面的常量声明(如图11.1所示):

Const c_defPromptOnExit = False

图11.1 在这里声明模块级常量

在下一节,读者将学习如何使用该常量来设置变量的值。

在代码中引用变量名时,与常量类似,Visual Basic将在执行代码时用变量的值代替变量名。然而,这并不像常量那样是在编译时发生的。相反,它发生在运行时——变量被引用时。这是因为变量与常量不同,它们的值在任何时候都可能发生改变。

定义变量称为声明,这通常是使用关键字Dim来完成的。非局部变量的声明稍微有点不同,这将在本章后面的“确定作用域”一节讨论。读者在前几章使用过Dim语句,因此对基本的Dim语句应该很熟悉:

Dim variablename As datatype

提示:可以在同一行声明多个同种类型的变量,如:

Dim I, J, K As Integer

然而这通常被视为糟糕的做法,因为使用清晰的变量名时,这通常导致代码难以阅读。

不一定要为变量指定初始值,但可以在Dim语句中这样做,这是Visual Basic的一种很有用的特性。例如,要创建一个新String变量并将它初始化为一个值,可用如下两条语句:

Dim strBandName As String

strName = "Chemical Echo"

然而,如果在设计时就知道变量的初始值,可在Dim语句中包含该值,如下所示:

Dim strBandName As String = "Chemical Echo"

但要注意,提供初始值并不能使变量成为常量;它仍是变量,其值可在任何时候改变。这种创建初始值的方法减少了一条代码语句,使代码更容易阅读,因为不需要查看变量什么时候被初始化。

要注意的一点是,所有数据类型都有默认的初始值。对于String数据类型,为Nothing,这还像有点奇怪,其实际含义是没有值,即不包含任何文本的字符串。在代码中,空字符串用""表示。对于数字数据类型,默认值为0,因此下述语句的输出为2:

Dim sngMyValue As Single

Debug.WriteLine (sngMyValue + 2)

注意:不能将保留字用作常量名或变量名。例如,不能使用 Sub 或 Public作为变量名。有很多关键字,可以在帮助中搜索“Reserved Keyword(保留字)”来获悉。读者将熟悉一些常用保留字,因为要经常使用它们。对于其他保留字,当您使用了它们时,编译器将告诉您。如果定义变量时使用了命名规范:在变量名中使用前缀来表示它们的类型,使用保留字的风险将极大地降低。

将字面值(硬编码值,如6或“guitar”)赋给变量的语法取决于变量的数据类型。

对于字符串,必须将值放在引号中,如下所示:

strCollegeName = "Bellevue University"

对于Date值(将在第12章更详细地讨论),需要使用#将值括起,如下所示:

dteBirthDate = #7/22/1969#

对于数值,直接使用数字即可:

intAnswerToEverything = 42

变量可用于任何需要表达式的地方。例如,算术函数对表达式进行操作。可以将两个字面数相加,并将结果存储在一个变量中,如下所示:

intMyVariable = 2 + 5

另外,也可以将这两个字面数之一或两个都用数值变量或常量来表示,如下所示:

intMyVariable = intFirstValue + 5

intMyVariable = 2 + intSecondValue

intMyVariable = intFirstValue + intSecondValue

变量是在代码执行过程中存储值的一种好方式,您将经常使用变量——从执行判断和创建循环到将变量作为存储值的临时地方。如果在设计时就知道值且值不会改变,应使用常量。当事先不知道值或值可能改变时,使用变量,且变量的数据类型应与变量的功能相符。

提示:在Visual Basic中,变量是作为对象创建的。可以轻松地创建变量并使用其成员(也就是属性和方法)。使用变量的成员时,只要输入变量名和点号(这只在声明了变量后管用)。例如,要知道字符串变量的字符数,可用字符串变量的Length属性,如下所示:

strMyVariable.Length

数据类型对象有很多功能强大的特性。

默认情况下,Vsiual Basic要求用户使用变量前进行声明,这被称为显式变量声明。另外,还可以要求Visual Basic执行严格的数据类型检查(data typing)。严格类型检查(strict typing)意味着在不会丢失数据或精度的情况下,Visual Basic将自动执行扩大转换,但对于其他转换,用户必须显式地将数据从一种类型转换为另一种类型。

以前,Visual Basic的默认行为是,允许用户随时创建变量(隐式变量声明),它甚至不执行严格类型检查。虽然用户可以禁用这两项新功能,但不应这样做。实事上,我建议读者启用严格类型检查,在新项目中它默认被禁用。

看看下面的代码,它存在一个问题,您发现了吗?在项目属性Option Explicit被启用的情况下,下面的代码将导致编译错误,因为在MessageBox.Show ()语句中错误地拼写了变量名。如果显式变量声明被禁用,Visual Basic在编译时将不会检查这种不一致性,进而运行下述代码:

Dim intMyVariable As Integer

intMyVariable = 10

MessageBox.Show(intMyVariabl)

那么,运行时是否会发生错误呢?如果不会,将显示什么呢?10?不,什么都不显示!

注意到在语句MessageBox.Show()中,少了变量名 intMyVariable中的 e。Visual Basic编译代码时,它检查每个代码项,看是不是关键字、函数调用、变量、常量或它能够理解的其他实体。如果代码项是Visual Basic不能理解的内容,默认将生成编译器错误。

然而,在显式变量声明被禁用的情况下(即选项Option Explicit被设置为Off),Visual Basic的行为将发生变化。它创建一个Object类型的新变量。正如前面指出的,所有变量都被初始化为某个默认值。新Object变量为空,因此这个示例不显示任何东西,也不会发生错误。谈到难以发现的错误,这是我能想到的最难发现的错误之一。如果夜已深,您很困,可能需要几个小时才能发现变量名拼写不正确(这是我的亲身经历,相信我)。没有任何理由将显式声明变量关闭。在新项目中,Option Explicit被默认设置为On,因此没有理由再去设置该选项。

严格类型检查(strict typing)指的是Visual Basic执行数据类型检查的过程:只能将数据类型正确的值赋给变量。如果要将值赋给取值范围更小或精度更低的变量,必须使用类型转换函数。在这种功能被关闭的情况下(这是新项目的默认设置),Visual Basic允许将任何类型的值赋给任何变量,而不管变量的数据类型如何。在这种情况下,必须准确地猜测应如何转换数据,这可能导致精度降低,如将Double数据赋给Single变量时导致大数字被裁剪掉。需要关闭严格类型检查的情况很少,但在高级编程中可能需要这样做。启用严格类型检查可迫使程序员编写更好的代码,因此我提倡在项目中将其启用。要在项目Picture Viewer中启用Option Strict,可执行如下步骤。

1.右击“解决方案资源管理器”中的项目名并从上下文菜单中选择“属性”。

2.在“项目属性”页中,单击左边的选项卡“编译”。

3.从下拉列表Option Strict中选择On,如图 11.2所示。

4.将Option Infer改为Off。

现在按F5运行项目,将被告知有生成错误,如图11.3所示。

执行下述步骤来修复这种错误。

1.单击“否”,停止运行项目并查看错误。

2.Visual Basic返回代码窗口,但在其下方打开了一个新窗口:错误列表。另外,您声明的常量带蓝色波浪下划线,如图11.4所示(您需要返回ViewerForm.vb选项卡查看代码)。

图11.2 将Option Strict设置为 On 可强制用户编写更好的代码

图11.3 启用Option Strict导致代码出现生成错误

图11.4 “错误列表”窗口帮助您找出代码中的问题

3.注意错误消息,它是一条精确的错误消息。Visual Basic指出,它需要知道用户创建的每个常量、变量和数组的数据类型,因为Option Strict被启用。您的常量声明没有指定数据类型,因此Visual Basic不知道如何处理该常量。双击错误消息以显示导致问题的代码行,然后将常量声明修改为如下所示:

Const c_defPromptOnExit As Boolean = False

修改代码后,“错误列表”将被清空(现在将其关闭)。您为常量指定了数据类型,因为Visual Basic知道应将该常量视为Boolean(True/False)值。

提示:如果保留Option Infer设置为On,代码会继续执行,而不会出现错误。Option Infer让Visual Basic根据赋给变量的值来猜测变量的数据类型。建议将该选项设置为Off,否则将抵消Option Explicit的效果。

数组是一种特殊的变量——它是一个多维变量。如果将常规变量视为一个邮箱,通过引用变量可以获取或改变邮箱的内容;则数组就像是一排邮箱(每个邮箱称为一个元素),通过引用数组变量可以在任何时候获取和设置各个邮箱的内容,这是通过指向对应邮箱的索引来实现的。

数组的声明与常规变量类似,只有一个需要注意的区别。请看下列语句:

Dim strMyArray(10) As String

这条语句类似于声明常规变量的 Dim 语句,不同的是用括号括起的 10。括号中的数字表示数组将包含多少个“邮箱”,它必须是字面值或常量,而不能是变量。需要牢记的要点是,指定的数字不是数组将包含的元素数,而是少1,读者稍后将明白这一点。

注意:可以创建大小在运行时可调整的数组。然而,这超出了本书的范围。

要将一个值用作数组索引,可在引用数组变量时指定该索引值。大多数计算机操作都将0而不是1作为序列中的第一个数,数组索引也是这样的。例如,对于包含10个元素的数组——用(9)声明,各个元素应分别用索引0、1、2、3、4、5、6、7、8、9来引用。

注意:索引的上限等于声明数组时指定的数字。因为0是一个有效的索引,因此元素数比声明数组时使用的数字大1。这可能比较容易混淆。为简化开发,可忽略元素0,而只用元素1到声明的上限。

要给数组变量的第一个元素赋值,可以使用0作为索引,如下所示:

strMyArray(0) = "This value goes in the first element"

要引用第二个元素,可使用如下所示的语句:

strMyVariable = strMyArray(1)

注意:数组变量的数据类型适用于数组中的所有元素。任何元素都可以使用Object类型来存储任意类型的数据,但不推荐这样做,原因在之前讨论过。

数组变量只要求一个声明,但可存储大量的数据;因此数组适合于存储一系列相关的信息。前面数组示例是一维数组,数组可以比这些示例复杂得多,可以包含多维数据。例如,可定义一个数组变量来存储不同人的个人信息。多维数组使用多个参数来声明,如下所示:

Dim intMeasurements(3,2) as Integer

这条Dim语句创建了一个二维数组。第一维(包含4个元素:0、1、2和3)作为对第二维(包含3个元素:0、1和2)的索引。假设要在这个数组中存储三个人的身高和体重,可以像引用一维数组那样引用这个数组,但需要增加一个参数索引。这两个索引一起指定了一个元素,就像战舰游戏中战舰相对于指定地点的坐标。图 11.5 说明了元素之间的关系。

图11.5 二维数组看起来就像是墙面上一系列的邮箱

元素根据第一个索引进行分组;可以将第一组索引视为一个一维数组。例如,要在第一个子数组中存储一个人的身高和体重(记住,索引从0开始),可用如下所示的代码:

intMeasurements(0,0) = FirstPersonsHeight

intMeasurements(0,1) = FirstPersonsWeight

为数组元素创建常量很有用,这使得数组引用更容易理解。请看下面的语句:

可用下面的语句来存储第二个和第三个人的身高和体重。

在这个数组中,第一维用来区分不同的人;第二维用于存储第一维中每个元素的身高和体重。因为总是将身高存储在数组第二维的第一个位置,体重存储在第二维的第二个位置,因此这些数据很容易使用。例如,只要知道用于存储数据的第一维索引,就可以获取一个人的身高和体重。用下面的代码可将所有三个人的体重总和打印出来:

Debug.WriteLine(intMeasurements (0, c_Weight) + _

 intMeasurements(1, c_Weight) + _

 intMeasurements(2, c_Weight))

当使用数组时,要记住下面几点:

数组任何一维的第一个元素的索引都是0;

声明数组时只需能够满足数据存储需求即可,不要过多;

数组的数据类型应适合要存储在数组元素中的值。

数组是在Visual Basic代码中存储和使用数据集合的好方法。数组使大量数据的使用更简单、更高效。要提高数组的使用效率,可以研究第 14 章将讨论的 For…Next 循环。使用For…Next循环,可以快速遍历数组中所有的元素。

常量、变量和数组是在Visual Basic代码中存储和获取数据的有用方法。几乎没有一个程序不使用它们。然而,要正确地使用它们,必须理解作用域。

读者首次遇到作用域是在第10章,使用了关键字Public和Private。您知道代码放在过程中,而过程存储在模块中。作用域指常量、变量、数组或过程在代码中可以被“看到”的范围。对于常量或变量,作用域可以是以下几种之一:

块级;

过程级(局部);

模块级;

全局(也被称为命名空间级)。

注意:作用域对数组变量的作用与对常规变量的作用一样。为简单起见,我在本节讨论作用域时将使用变量,但是要知道这里所讨论的对数组(还有常量)也适用。

块作用域也称为结构作用域,是Visual Basic.NET新增的。Visual Basic检查变量是否是在结构中声明的,如果是,变量的作用域便是块。

结构是由两条语句而不是一条语句组成的编码结构。例如,在本书前面,读者使用了If…Then决策结构,这种结构如下所示:

If expression Then

 statements to execute when expression is True

End If

第14章将介绍的Do...Loop结构用于创建循环,它类似于下面这样:

Do

 statements to execute in the loop

Loop

如果变量是在结构中声明的,则其作用域被限定在该结构内。该变量在遇到相应的Dim语句后才被创建,在结构结束处被销毁。对于只在结构内才需要的变量,应考虑在结构内声明它,使其作用域为结构。请看下面的例子:

通过将Dim语句放在If结构中,可确保只在需要时才创建它。变量占用资源,因此如果只在特定的条件下才需要变量,应将Dim语句放在诸如 If…End If等决策结构中,这可确保必要时才占用资源。

注意:各种不同的结构(包括循环和决策结构)将在后面几章中讨论。

在过程内部声明常量或变量时,常量或变量的作用域便是过程级或局部的。创建的大多数变量的作用域都是过程级的。实际上,您在前几章创建的几乎所有变量的作用域都是过程级的。可在同一过程内引用局部常量或变量,但局部常量或变量对其他过程是不可见的。如果试图引用其他过程中定义的常量或变量,Visual Basic 将返回编译错误(变量或常量不存在)。通常,最好将所有局部变量在过程的顶部声明,但Visual Basic并不关心Dim语句在过程中的什么位置。但要注意,如果将Dim语句放在结构内,对应变量的作用域将是块,而不是局部。

当常量或变量的作用域为模块级时,它对包含该变量声明的模块中所有的过程都是可见的。然而,对于其他所有模块中的过程,这个常量或变量是不存在的。要创建作用域为模块级的常量或变量,必须将声明放在模块中而不是过程中。声明区域位于每个模块的开头,本章前面就是在这里声明的模块级常量。当很多过程必须共享同一变量,且将值作为参数传递不可行时,应使用模块级作用域。

注意:虽然使用 Dim 声明的模块级变量是模块私有的,但最好使用关键字Private来声明私有的模块级变量。这意味着可以创建公有的模块级变量,这将在下一节讨论。

对于不是用来生成窗体的模块,很容易添加代码到声明区域中;只需将Public/Private语句添加到模块声明行之后和任何过程定义之前,如图11.6所示。

图11.6 声明区域在所有过程的声明之前

提示:一种快速进入模块声明区域的方法是,在模块窗口右上角的过程下拉列表中选择“(声明)”。

可在任何过程(无论该过程位于哪个模块中)中看到和引用的常量或变量的作用域为全局(命名空间级)。全局变量的一种常见用途是存储到数据库连接的引用,这样所有需要访问数据库的代码都能够通过该变量来访问数据库。全局常量和变量的创建类似于声明模块级常量和变量。全局变量和常量必须在模块的声明区域中声明,就像模块级常量和变量一样;差别在于使用的关键字为Public(创建全局变量和常量必须遵循的约束将在下一节讨论)。

要声明全局常量,以关键字Public打头,如下所示:

Public Const MyConstant As Integer = 1

要声明全局变量,使用关键字Public替代关键字Dim或Private,如下所示:

Public strMyVariable as String

提示:如果Visual Basic显示编译错误消息“名称Variablename没有声明”,首先查看引用变量或常量时拼写是否正确,然后检查要引用的变量或常量在当前作用域中是否可见。

要创建全局变量或常量,必须在标准模块(而不是基于类的模块)中声明它们。如果在类模块(如 Form 类模块)中声明一个公有变量或常量,其行为将类似于类的属性。这是一种很有用的技巧,将在第16章讨论。然而,这样的变量或常量的作用域不是全局的,而是模块级的。

在同一个作用域中,两个变量的名称不能相同;但在不同的作用域中,变量的名称可以相同。例如,如果在标准模块(不是类模块)中创建两个名称相同的公有变量,将创建两个名称相同的全局变量,每当您试图访问该变量时都将导致编译错误。在这种情况下,所有的变量引用都存在歧义,Visual Basic应使用哪个变量呢?然而,可以创建名称与全局变量(或模块变量)相同的局部变量。Visual Basic将总是使用作用域最小的变量,因此在声明局部变量的过程中引用该变量时,Visual Basic将使用局部变量;而在另一个过程中引用变量时,局部变量不可见,因此代码将引用全局变量。

提示:一般来说,作用域越小越好。应尽可能将变量声明为块变量或局部变量,如果必须扩大作用域,尽可能将变量声明为模块级的。仅当别无选择(通常有其他选择)时才使用全局变量。作用域越大,出现问题的可能性越高,且越难以调试。通常,必须使用全局变量时,应考虑将数据封装在一个类中,这将在第16章讨论。

在过程中创建变量时(其作用域为局部或块),该变量只在过程或块的生命周期内存在。离开该作用域后,变量将被销毁,存储在该变量中的值也将不复存在。下次调用该过程时, Visual Basic将创建一个全新的变量。请看下面的过程:

如果调用该过程,将创建一个名为intMyInteger的新变量,将其设置为当前值加10,然后过程结束。数字变量的默认初始值为 0,因此到达 End Sub时,变量 intMyInteger的值为10。过程结束后,该变量不再在其用作域中,因此被销毁。如果再次调用该过程,将创建一个名为intMyInteger的新变量(其默认值为0)并将其值增加10。同样,该过程将结束,而改变量将被销毁。

要创建在过程调用之间被保留的变量,可使用关键字 Static。要创建静态变量,使用关键字Static代替关键字Dim。下面的代码与前一个示例类似,但变量被声明为静态的,它在两次调用该过程之间存在,因此其值保持不变:

首次调用该过程时,将创建变量intMyInteger且其值默认为0,然后该变量被增加10。

过程结束时,该变量不会被销毁,而保留在内存中且其值保持不变(因为它是使用关键字Static声明的)。下次调用该过程时,Visual Basic不需要创建新变量,而是使用以前的变量,并将其值增加 10。这样,在第二次调用该过程结束时,变量的值为 20。每次调用该过程,变量intMyInteger的值都将增加10。

静态变量没有常规变量那么常用,但也有其用途。静态变量让您能够尽可能缩小作用域(这是件好事)。在只有一个过程使用的情况下,为何创建一个模块级变量呢?创建 Visual Basic项目时,别忘了静态变量。如果要创建在两次过程调用之间被保留的变量,且要求其作用域为过程或块,可创建一个静态变量。

注意:一个没有介绍过的作用域标识符是Friend。使用Friend声明的变量或过程的作用域为在整个项目中是全局的,但对于通过自动化来访问代码的应用程序来说不可见。由于本书不介绍如何创建自动化服务器,因此不会在Picture Viewer项目中使用Friend,但Vsiual Basic经常使用它。

要使代码更易于理解(这总是一个重要的目标)并减少编程错误,需要一种简单方法来判断在Visual Basic代码中引用的变量或控件的类型。

表11.3列出了常用数据类型的前缀。虽然不一定要使用前缀,但使用前缀有很多优点。

表11.3 常用数据类型的前缀

前缀不仅可用于表示数据类型,也可用于表示作用域,如表11.4所示。在超大型应用程序中,作用域指示符几乎是必要的。Visual Basic并不关心您是否使用前缀,然而,一致地使用前缀,不仅有利于您,也有利于阅读您的代码的人。

表11.4 示作用域的前缀

前缀不只是可用于变量。所有标准对象都可以使用三个字符的前缀。因为控件和对象太多而不能列出所有的前缀,但读者将发现本书中的控件都使用了前缀。如果读者对命名规范和编码标准感兴趣,可参考我编写的《Practical Standards for Microsoft Visual Basic.NET》第二版(Microsoft Press 2002年出版)。

注意:仅当没有特定前缀时才使用前缀obj。在引用其他程序的自动化库时最常用到这个前缀。例如,自动化Microsoft Word时,将创建一个Word应用程序对象。由于没有专用于Word对象的前缀,因此可使用obj,如下所示:

Dim objWord As Word.Application

提示:可将鼠标指向代码中的变量,工具提示将显示该变量的声明。

在本章前面,读者已经添加了一个模块级常量到Picture Viewer项目中。在这一节中,将创建变量来存储Options窗体上控件的值。在第16章中,将完成该项目中有关选项的代码。

在前几章中,为Picture Viewer项目定义了三个选项。下面看看这三个选项。

User Name(用户名):用户可输入其姓名,这将显示在窗体的标题栏中。考虑一下用于存储该选项的数据类型。应使用String(即文本)。

Prompt to confirm on exit(退出提示):该选项有两个可能的值,true和 false。因此该选项需要使用布尔值。

默认的图片背景色。这是一种特殊情况。前面介绍了常用的数据类型,但除此之外还有很多不同的数据类型,其中之一便是Color。将使用Color类型的变量来存储用户的背景色偏好。

执行以下步骤来创建变量。

1.显示ViewerForm.vb窗体(而不是OptionsForm.vb窗体)的代码。

2.定位到窗体模块的声明区域——就是创建常量c_defPromptOnExit的地方。

3.在常量定义行后输入下面三个变量声明:

Private m_strUserName As String

Private m_blnPromptOnExit As Boolean

Private m_objPictureBackColor As Color

现在代码应如图11.7所示。

图11.7 在声明区域创建公有变量

创建变量后,需要初始化它们。执行下列步骤来初始化这些变量。

1.设置窗体最好的地方是窗体的 Load 事件。进入窗体的事件需要些技巧。如果在对象下拉列表(代码窗口左上角的列表)中选择窗体名,将只能看到很少一部分的窗体事件。必须从该列表中选择“(ViewerForm事件)”,如图11.8所示。然后,从事件列表中选择Load来显示它。

2.Load事件中已经有两行代码:设置标签X和Y值的两条语句。将下面两条语句加到现有代码之后:

m_blnPromptOnExit = c_defPromptOnExit

m_objPictureBackColor = System.Drawing.SystemColors.Control

图11.8 需要选择该选项来查看所有窗体的事件

第一条语句只是将用于存储用户退出提示的模块变量的值设置您之前创建的常量。这里这样做旨在让读者明白如何结合使用常量和变量。如果要修改 Prompt on exit选项的默认行为,只要在声明区域中修改常量的值。

第二条语句将默认背景色设置为控件的默认系统颜色。这在第2章解释过,因此在这里不再详细解释。注意,并没有为模块级变量m_strUserName创建初始化语句,这是因为字符串变量默认被初始化为空,这正是我们需要的。

3.至此,已经在代码中为选项创建了变量并将它们初始化了。然而,选项的值实际上还没有在项目中使用。用户关闭窗体时,Prompt on exit选项处于Checked状态,但图片框的背景色需要在窗体显示前设置好。在刚创建的两行代码后输入下面的语句:

picShowPicture.BackColor = m_objPictureBackColor

至此,本章余下的任务是实现Prompt on Exit的功能。这只需要做少量的工作,因为已经常见创建项来跟踪Prompt on Exit选项是否被选中。首先需要确保菜单项与变量同步,您不希望该变量被设置为 False 时菜单项被选中,这样将导致程序的响应与用户期望的相反。继续执行下面的步骤实现Prompt on Exit变量的功能。

4.将下面这条语句添加到 Form_Load 事件中,就在刚才输入的用于设置图片框背景色的语句之后。这条语句确保窗体加载时菜单项的 checked 状态与变量的状态相符。因为您将布尔变量m_blnPromptOnExit初始化为false,所以菜单项的状态将是unchecked。

mnuConfirmOnExit.Checked = m_blnPromptOnExit

5.您已经为菜单项创建了一个过程,使得在用户单击它的时候修改它的checked状态。可以滚动代码窗口,定位到过程mnuConfirmOnExit_Click中;也可以切换到设计视图然后双击该菜单项。记住,在 Visual Basic中解决一个问题总是有多种方法!找到这个过程后,将下面这条语句添加到现有代码之后:

m_blnPromptOnExit = mnuConfirmOnExit.Checked

是否注意到这条语句与第 4步中输入的语句正好相反?它的作用是告诉Visual Basic,当菜单项的checked状态在Click事件中被更新后,将变量的值设置为菜单项的checked状态。

6.现在变量将与菜单项同步,还需要加入实现Prompt on Exit功能的代码。再次在对象列表中选择“(ViewerForm事件)”,在事件列表中选择FormClosing,并输入下列代码:

前面已经提过 MessageBox.Show()函数(第 17 章将详细解释)。现在读者只需知道,用户关闭Picture Viewer时,如果变量m_blnPromptOnExit的值为 true,MessageBox.Show()函数将询问用户是否真的要退出。如果用户选择“否”,则e.Cancel属性被设置为true,这样将取消关闭窗体(关于FormClosing事件的e对象,可参考在线帮助)。

现在按F5运行项目。启动程序时,变量m_blnPromptOnExit为False,菜单项为unchecked状态。如果单击窗体右上角的“关闭”按钮,Picture Viewer程序将关闭。再次运行项目,这次在关闭窗体前单击Confirm on Exit菜单项使其状态变为 checked。关闭窗体时,将要求确认,如图11.9所示

图11.9 应让用户控制他们的体验

在本章中,读者学习了如何通过创建常量避免使用魔术数字。通过用常量替代字面值,可以提高代码的可读性,减少代码编写错误的可能性,而且使得将来改变数值更容易。

另外,读者学习了如何为数据元素创建变量,它们的初始值在设计时是未确定的,或者这些元素的数值在运行时会改变。读者了解了数组是如何增加变量维数的,以及如何在代码中声明与引用数组。

Visual Basic执行严格的数据类型检查。在本章中,读者学习了各种数据类型以及如何使用它们,还学习了如何选择数据类型以及将数据从一种类型转换成另一种类型的函数。最后,学习了作用域——一个重要的编程概念——以及如何在项目中管理作用域。

编写的代码能让其他不参加编写代码的人清楚地理解,这是很有意义的目标。命名前缀对于实现这个目标大有帮助。在本章中,读者学习了常用数据类型的命名前缀以及使用前缀来表示作用域。

最后,读者利用这些概念,创建了一个常量和一些变量来处理Picture Viewer程序的选项,还添加了代码来实现它们的功能!还没有为 Options 选择窗体添加代码来实现其功能,但将在第16章实现。

问:对于众多数据类型,有没有什么性能方面的诀窍?

答:诀窍之一是当使用整数(没有小数的数值)时最好使用与处理器匹配的数据类型。例如,现在大多数家庭与办公室的计算机都是 32位处理器。Visual Basic Integer数据类型是由 32位组成的。信不信由您,Visual Basic 处理 Integer变量比处理Short变量快,虽然Short变量更小一些。这与CPU、内存和总线的架构有关。解释这一点很复杂,但结论是通常应使用Integer而不是Short,即使所用的数值不需要Integer这么大的取值范围。

问:数组只限于二维吗?

答:虽然只介绍了二维数组,如 intMeasurements(3,1),但数组可以有很多维,如intMeasurements(3,3,3,4)。从技术上说,最大维数为 32,但您可能不会使用 3 维以上的数组。

1.金额应使用什么数据类型?

2.什么数据类型可用于存储任何类型的数据,其实质是一种通用的数据类型?

3.在Visual Basic内部,将哪些数字值视为等同于True和False?

4.通过在某个地方定义一个字面值,可创建什么以避免使用魔术数字?

5.在代码中可以创建什么数据元素,其值可根据需要任意改变?

6.在使用Dim a_strMyArray(5)定义的数组中,第一个和最后一个索引分别是多少?

7.哪个术语表示常量或变量的可见性?

8.一般来说,最好是限制变量的作用域还是尽量使用最大的作用域?

9.哪种局部变量在两次调用过程之间被保留?

1.Decimal数据类型。

2.Object数据类型。

3.Visual Basic将0视为False,将非零值视为True。

4.使用常量避免使用魔术数字。

5.可在变量的作用域内任意改变其值。

6.第一个索引是0,最后一个索引是4。

7.作用域描述了常量、变量和过程的可见性。

8.最好使用最小的作用域。

9.静态变量。

1.创建一个包含文本框、按钮、标签控件的项目,当用户单击按钮时,将文本框中的内容赋给一个变量,然后再将变量的内容赋给到标签的Text属性。(提示:使用String变量。)

2.重写以下代码,使用一个数组变量来代替两个标准变量。(提示:不要使用多维数组。)

在本章中,读者将学习:

执行算术运算;

理解运算符优先级顺序;

比较;

理解布尔逻辑;

操纵字符串;

使用日期和时间。

算术对于每个人的日常生活来说都是必不可少的,它对开发Windows程序也至关重要。您可能不会开发不执行加法、减法、乘法和除法运算的程序。在本章中,读者将学习如何在代码中执行算术运算;还将学习运算符优先级顺序,它决定了 Visual Basic如何计算复杂的表达式(等式)。理解运算符优先级后,将学习如何比较相等——这种操作将经常用到。

布尔逻辑是Visual Basic在决策结构中用于对表达式求值的逻辑。如果您之前从未编过程序,布尔逻辑对您来说可能是一个全新的概念。在本章中,将解释布尔逻辑的相关概念,可用于创建按预期执行的高效代码。最后,还将学习如何操纵字符串以及使用日期和时间。

要成为一名程序员,必须有扎实的数学知识;开发Visual Basic程序要执行大量基本的数学运算。要得到任何给定计算的结果,必须:

知道用于执行数学函数的数学运算符;

理解和正确使用运算符优先级顺序。

使用正确的数学运算符很简单。大多数运算符都易记,且在没有把握时总是可以查找得到。这里不详细介绍每个数学函数(如果到现在都没问题的话,那么我相信读者已经掌握了基本的数学知识),但将介绍所有函数。

注意:在第15章中,读者将学习Debug.WriteLine()方法。在本章中,读者不需要输入任何代码,但我将在代码段中使用该方法。有关这些示例,读者只需知道方法Debug.WriteLine()将一个字符串发送到“输出”窗口。例如, debug.WriteLine(“Dan Haught”)将在“输出”窗口中打印Dan Haught。

简单的加法是使用标准加号“+”执行的。下面这一行代码打印4、5和6的和:

Debug.WriteLine(4 + 5 + 6)

使用算术运算符时不一定要用硬编码值。算术运算符也可以对数值变量或常量进行操作。例如:

Const c_FirstValue As Integer = 4

Const c_SecondValue As Integer = 5

Debug.WriteLine(c_FirstValue + c_SecondValue)

这段代码打印常量c_FirstValue和c_SecondValue的和,即9。

与加法运算符一样,您可能也很熟悉减法运算符,因为它与计算器或等式中的符号是一样的:“−”字符。下面这一行代码打印 2(6− 4)。

Debug.WriteLine(6 - 4)

和手写数学符号一样,“−”也表示负号。例如,要打印值“−6”,可用如下的语句:

Debug.WriteLine(-6)

如果您用过加法机,应熟悉乘法运算符:字符“*”。使用 Shift+8 或按小键盘最上面一行的“*”键可输入该字符。虽然在纸上手写乘法等式时通常使用“×”,如 3 × 2 = 6,但如果在代码中这样写将产生错误,而必须使用“*”字符。下面的语句将打印20(5乘以4):

Debug.WriteLine(5 * 4)

除法运算使用“/”运算符。如果将除法视为分数,这个运算符就很容易记了。例如,八分之一被写成1/8,表示1除以8。下面的语句打印8(32除以4):

Debug.WriteLine(32 / 4)

警告:不要将除法运算符(/)和反斜杠(\)混为一谈。如果使用反斜杠, Visual Basic也将执行除法运算,但只返回整数部分(余数被丢弃)。

乘方将一个数自乘指定的次数。例如,102表示10的 2次方,即 100。在Visual Basic代码中,该表达式如下:

Debug.WriteLine(10 ^ 2)

位于运算符^左边的是底,右边的是指数。要输入^,可按Shift + 6。

取模算术运算是对两个数执行除法运算,但只取余数。取模运算使用关键字Mod,而不是“/”符号。下面是取模运算语句的例子以及它们将打印出来的值:

前两条语句比较容易理解:10除以5商2余0,10除以 3商3余1。Visual Basic这样处理第三条语句:12除以4.3商2余 3.4;对于最后一条语句,Visual Basic是这样处理的:13.6除以5商2余3.6。

当一个等式(称为表达式)中包含多个算术运算符时,Visual Basic必须对表达式进行解析。这些运算符被执行的顺序称为运算符优先级。为完全理解运算符优先级,必须复习一下代数知识(在代码中执行的大多数数学运算都是代数运算)。

看下面的表达式:

Debug.writeLine(6 + 4 * 5)

在这个表达式中有两个算术运算符。要求这个表达式的值,Visual Basic必须执行两次运算:乘法和加法。应该先执行哪个运算呢?这是否有影响呢?当然有!如果 Visual Basic先执行加法然后再执行乘法,将得到下面的结果:

第 1步:6 + 4 = 10。

第 2步:10 * 5 = 50。

最后的结果是,Visual Basic打印50。现在看先执行乘法后执行加法的结果:

第 1步:4 * 5 = 20。

第 2步:20 + 6 = 26。

在这种情况下,Visual Basic将打印出 26。这与第一次先执行加法的结果大为不同。为防止这种问题,Visual Basic总是按照一定的顺序执行数学运算——按照运算符优先级的顺序。表 12.1 列出了算术运算符和布尔运算符的优先级顺序(布尔运算符将在本章稍后讨论)。如果读者熟悉代数,将注意到 Visual Basic所用的运算符优先级顺序与代数公式中相同。

表12.1 Visual Basi的运算符优先级顺序

所有比较运算符,如>、<和=(将在下一节讨论)的优先级相同。当运算符具有相同的优先级时,Visual Basic将从左到右进行计算。注意,乘法和除法运算符的优先级相等,因此如果一个表达式中包含这两个运算符,将从左到右执行。当表达式中包含多类(算术、比较或逻辑)运算符时,算术运算符优先,然后是比较运算符,最后是逻辑运算符。

与在纸上手写等式一样,可使用括号来提升运算符优先级。放在括号内的运算符总是最先计算。看前一个例子:

Debug.WriteLine(6 * 5 + 4)

根据运算符优先级顺序,Visual Basic将如下计算该表达式:

Debug.WriteLine((6 * 5) + 4)

首先执行乘法,然后是加法。如果要先执行加法再执行乘法,可用如下的语句:

Debug.WriteLine(6 * (5 + 4))

注意:编写复杂的表达式时,必须注意运算符的优先级顺序,并在必要时使用括号来覆盖默认优先级。我总是尽量使用括号,以保证代码的正确性,并使代码更容易阅读。

值尤其是变量的比较,甚至比算术运算都要常用(但要理解表达式求值,必须理解Visual Basic的算术运算)。

比较运算符最常用于决策结构中,这将在下一章解释。实际上,要理解这些运算符,最佳方式是在简单 If…Then决策结构中使用它们。在 If...Then结构中,Visual Basic将判断 if语句中的表达式,如果表达式为True,则执行 If和End If之间的代码语句。例如,下面是一个用英语而不是用Visual Basic 代码表示的 If…Then语句:

If dogs bark, then smile

如果这是Visual Basic代码格式,Visual Basic将对 If条件进行计算,在这个例子中就是dogs bark(狗叫了)。如果条件为真,则Then后面的代码将被执行。因为狗叫了,所以您要微笑。注意这两件事情(狗叫和您笑)是不相关的。但这并不重要;重要的是if条件为真,将执行特定的动作(语句)。

决策时经常需要将一个变量与另一个变量或特定值进行比较。下面列出了一些基本的比较以及Visual Basic计算结果:

比较运算比较简单。如果编写一个比较语句时遇到了困难,在写代码前先用英语把它表示出来。

布尔逻辑是一种特殊的算术/比较运算。布尔逻辑用于判断表达式为True还是False。这对您来说可能是一个新概念,但不必担心,这并不难理解。布尔逻辑通过逻辑运算符实现。看下面的句子:

If black is a color and wood comes from trees, then print “ice cream”.

可能第一眼您很难看出它的意义。然而,Visual Basic使用布尔逻辑就能够读懂这个句子。首先,注意到这个句子中实际上有三个表达式。我在下面的句子中添加了括号以突出两个最明显的表达式:

If (black is a color) and (wood comes from trees), then print “ice cream”.

布尔逻辑以True或False来表示每个表达式的值。因此,用True或False代替每个表达式,将得到下面的句子:

If (True) and (True), then print “ice cream”.

其次,为清晰起见,将最后得到的表达式放在括号中,将得到下面的句子:

If (True And True), then print “ice cream”.

这时就要用到逻辑运算符。如果And运算符的两边都为真,那么And运算符返回真(有关逻辑运算符的完整列表,请参阅表12.2)。在这个句子中,And运算符两边的表达式都为真,因此这个表达式的值为真。使用True来代替表达式,则得到:

If True, then print “ice cream”.

表12.2 逻辑(布尔)运算符

其结果是打印出 ice cream。如果表达式的值为False,则什么也不打印。正如将在第13章看到的,决策结构总是判断表达式的结果为True还是False,然后根据这个结果来执行语句。

注意:使用布尔逻辑时,别忘了第 11章介绍的,Visual Basic使用−1表示True,使用0表示False。在代码中,总是应该使用True或False,但要知道Visual Basic如何在内部处理这些值。

And运算符用于执行逻辑连接。如果And运算符两边的表达式值都为真,则And运算返回真。任一个表达式的值为假,And运算的结果即为假,如下面的例子所示:

Not运算符执行逻辑取反。也就是说,它返回与表达式相反的值。看下面的例子:

前两条语句很简单;与True相反的值是False,反过来,与False相反的值是True。至于第三条语句,根据Visual Basic的运算符优先级,算术运算符首先执行(即使没有使用括号),因此计算过程的第一步如下所示:

Debug.WriteLine(Not (True))

与True相反的是False,因此Visual Basic将打印False。

第四条语句求值后为:

Debug.WriteLine(Not (False))

这是因为 4不小于 2,它被Visual Basic最先计算。由于与False相反的是True,因此这条语句将打印True。

Or运算符用于执行逻辑分离。如果Or运算符有一边的表达式为真,那么Or运算的结果就为真。下面是使用Or运算符的例子及其结果:

Xor运算符的作用很小。我很少使用它,但在需要它的功能时,它将非常好用。如果Xor运算符的一边且只有一边的表达式为真,Xor运算的结果就为真。看下面的例子:

第 11章提到过,字符串是文本。Visual Basic提供了许多操纵字符串的函数。虽然字符串操作从技术上说不是算术运算,但对字符串的操作与对数字的操作有一些类似的地方,例如将两个字符串相加;字符串操作与等式的创建很类似。您很可能要在程序中大量地使用字符串。Visual Basic提供了许多函数用于字符串操作,例如查询字符串的一部分或在一个字符串中查找另一个字符串。在下面几节中,将学习字符串操作的基础知识。

在Visual Basic中,可以将两个字符串相加形成一个新字符串。虽然这不能算是一种算术运算,但它很像对字符串进行算术运算,因此本章是介绍这方面内容的合理地方。将两个字符串相加的过程称为拼接。拼接很常用,例如,可能要拼接变量与硬编码字符串,以显示有意义的消息给用户,例如“您是否确定要删除用户XXX”,其中“XXX”就是变量的内容。

要拼接两个字符串,用“&”运算符,如下面这行代码所示:

Debug.WriteLine("This is" & "a test.")

这条语句将打印出:

This isa test.

注意,“is”和“a”之间没有空格。要添加空格,可在第一个字符串的末尾或第二个字符串的开头添加,还可以把空格单独作为一个字符串,像下面这样:

Debug.WriteLine("This is" & " " & "a test.")

直接放在引号中的文本称为字符串字面量。拼接变量与拼接字符串字面量的方法相同。下列代码创建了两个变量,将第一个变量的值设置为 James,第二个变量的值设置为第一个变量与空格、字符串字面量Foxall拼接的结果。

最后的结果是:变量 strFullName包含字符串 James Foxall。务必熟悉字符串的拼接,您将经常这样做。

提示:除&外,Visual Basic还允许使用+来拼接字符串,但不要这样做,这使得代码难以理解,且在没有启用Option Strict时可能得到错误的结果。

Visual Basic包含了许多函数,使得使用字符串更容易得多。这些函数让您能够很容易地获取字符串的一部分、计算字符串中的字符数,甚至判断一个字符串是否包含另一个字符串。下面是对基本的字符串函数的总结。

1.使用Len()判断字符数

Len()接受一个字符串(变量或字面量)并返回字符串中包含的字符数。下面的语句打印26,即字符串字面量“Pink Floyd reigns supreme”的字符总数。记住,用引号括起的字符串告诉Visual Basic,这是一个字符串字面量,引号本身不是字符串的一部分。Len()常用于为其他函数提供支持,这将在稍后介绍。

Debug.WriteLine(Len("Pink Floyd reigns supreme.")) ' Prints 26

提示:要获悉字符串变量包含的字符数,另一种办法是使用变量的 Length属性,如下所示:

Debug.WriteLine(strMyStringVariable.Length())

2.使用Microsoft.VisualBasic.Left()从字符串左边开始检索文本

函数 Microsoft.VisualBasic.Left()返回传入的字符串的左边部分。使用限定符Microsoft.VisualBasic的原因是,很多对象(包括控件和窗体)都有Left属性。如果单独使用Left将给编译器带来歧义,因此需要全限定。

函数Microsoft.VisualBasic.Left()接受两个参数:

要检索其左边部分的字符串;

要检索的字符数。

函数Microsoft.VisualBasic.Left()总是从左边开始检索文本。例如,下面的语句打印Queen,即字符串的前5个字符:

Debug.WriteLine(Microsoft.VisualBasic.Left("Queen to Queen's Level 3.", 5))

函数 Microsoft.VisualBasic.Left()常与 InStr()(稍后将讨论)结合使用,来检索包含文件名和路径的变量中的路径部分,如 C:\Myfile.txt。如果知道字符\的位置,就能够使用函数Microsoft.VisualBasic.Left()来获得路径。

注意:如果指定的字符数超过了字符串包含的字符数,将返回整个字符串;如果不知道字符串中包含的字符数,可使用函数Len()。

3.使用Microsoft.VisualBasic.Right()从字符串右边开始检索文本

函数Microsoft.VisualBasic.Right()与函数Microsoft.VisualBasic.Left()类似,但不是从字符串左边开始检索文本,而是从字符串的右边开始检索文本。然而,返回的字符的排列顺序总是与原来的顺序相同。

Microsoft.VisualBasic.Right()不是从右边向左边开始检索字符,而是从最右边的字符开始向左数指定的字符数,并返回字符串右边的字符。下面的语句返回“hing.”,即字符串最后面的5个字符:

Debug.WriteLine(Microsoft.VisualBasic.Right("Duct tape fixes everything.",

5))

4.使用Mid()从字符串中间检索文本

需要从字符串中间(既不是左边也不是右边)检索部分文本时,可使用函数Mid()。该函数让用户能够指定从哪里开始检索以及检索多少个字符。函数 Mid()接受下列三个参数:

要从中检索文本的字符串;

从第几个字符开始检索;

要检索的字符数。

下面的语句打印文本 look li。因为Mid( )函数从第 5个字符(look中的 l)开始检索,并检索7个字符:

Debug.WriteLine(Microsoft.VisualBasic.Mid("You look like you could " _

 & "use a monkey.", 5, 7))

注意,可以省略最后一个参数,认识到这一点的人不多。这样做时,Mid()函数将返回从起始位置开始到字符串末尾之间的所有字符。下面的语句打印“ter crows at midnight.”,因为其中的Mid( )函数返回从第 9个字符开始到字符串末尾的所有字符。

Debug.WriteLine(Mid("The rooster crows at midnight.", 9))

5.使用InStr()判断一个字符串是否包含另一个字符串

有时需要判断一个字符串是否包含另一个字符串。例如,假设让用户在文本框中输入姓名,然后要将名和姓分开以将它们分别存储到数据库的不同列中。最简单的方法是在字符串中查找分隔名和姓的空格。可以使用循环来检查字符串中的每个字符,直到找到空格为止,但Visual Basic包含了一个函数可以执行这种功能,这比自己实现更快、更简单,这就是 InStr()函数。InStr()的语法如下所示:

Instr([start, ] stringtosearch, stringbeingsought) ' Returns an Integer

注意:函数 InStr( )对我来说始终是一个谜。首先,这是我见到的唯一一个第一个参数可以省略,而其他参数必不可少的函数。其次,文档指出第二个参数为string1,而第三个参数为string2。这使得难以记住每个参数的用途,导致您不得不参考帮助。

InStr()函数在一个字符串中搜索另一个字符串。如果找到这样的字符串,将返回与搜索的字符串匹配的第一个字符的位置。如果没有找到,将返回 0。下面的代码搜索一个包含文本“James Foxall”的变量,找到空格的位置,然后使用Left( )和Mid( )将名和姓存储到不同的变量中:

提示:这段代码假设字符串包含空格且空格不在字符串的开头和结尾。在实际的应用程序中,这样的代码需要更健壮,例如,检查InStr()是否返回0,即没有找到匹配的字符串。

运行这段代码时,InStr()返回6,即第一个空格的位置。注意,使用Microsoft.VisualBasic.Left()时,将intLocation减去1;如果不这样做,Microsoft.VisualBasic.Left()返回的文本中将包含空格;Microsoft.VisualBasic.Mid()语句中将intLocation加上1的原因与此相同。

在这个实例中,我省略了第一个参数,因为从字符串开头搜索时不需要指定该参数;如果要从其他位置开始搜索,则需要通过第一个参数指定从第几个字符开始搜索。

6.删除字符串开头和结尾的空格

在前一个示例中,我通过将函数InStr()返回的值加1或减1,以避免返回的姓或名中包含空格。使用字符串时,经常会遇到字符串开头或结尾存在空格的情况。Visual Basic包含下列三个函数,可用于将字符串开头或结尾的空格删除(这些函数不能删除字符串中间的空格):

例如,假设在前一个示例中没有将intLocation的值减1,即使用了下面的语句:

strFirstName = Microsoft.VisualBasic.Left(strFullName, intLocation)

strFirstName将包含文本“James ”,注意到名字后面有空格。要删除该空格,可使用函数Trim()或RTrim(),如下所示:

strFirstName = Trim(Microsoft.VisualBasic.Left(strFullName, intLocation))

提示:除非要保留字符串某一端的空格,否则应使用Trim(),而不是RTrim()或LTrim()。

7.替换字符串中的文本

经常需要用一些文本替换字符串中的某些文本。例如,有些人在句子结束处键入了两个空格,虽然均衡字体的使用使得没有必要再这样做。可以使用循环和前面讨论的字符串操作函数,用一个空格来替换所有的双空格,但有一种更简单的方式:Replace()函数。Replace()函数的基本调用语法如下:

Replace(expression, findtext, replacetext)

参数expression是要在其中搜索的文本,如字符串变量;参数findtext指定要在expression中查找的文本,replacetext参数指定用于替换findtext的文本。请看下面的代码:

Dim strText As String = "Give a man a fish"

strText = Replace(strText, "fish", "sandwich")

上述代码执行完毕时,strText将包含字符串“Give a man a sandwich”。Replace()是一个功能强大的函数,可以节省许多行代码,应尽可能使用它,而不要自己创建替换函数。

日期是是一种独特的数据类型。从某种程度上说,它们像字符串,可以拼接和分解。在另一些情况下,日期更像数字,可以对它们执行加法或减法运算。经常要对日期执行数学运算(如将日期增加几天或判断两个日期之间相隔的月份数),但不是使用普通的算术运算符,而是使用专门为日期设计的函数。

日期很常用。使用数据类型Date可以创建存储日期的变量。为Date类型的变量赋值有几种方法。将字符串变量设置为字面字符串时,需要用引号将后者括起;当将数值变量设置为字面值时,不需要使用引号:

Dim strMyString As String = "This is a string literal"

Dim intMyInteger As Integer = 420

将Date变量设置为字面日期时,用#将字面值括起,如下所示:

Dim dteMyBirthday As Date = #7/22/2010#

启用了Option Strict时,不能将字符串直接赋给Date变量。例如,如果让用户在文本框中输入日期,并将用户的输入赋给Date变量,必须像下面这样做:

dteMyDateVariable = CDate(txtBirthDay.Text)

将Date变量的值放到文本框中时,也必须将日期转换为字符串(同样,仅在Option Strict被启用时)。有关数据类型转换函数的更详细信息,请参阅第11章。

需要注意的是,Date变量总是存储日期和时间。请看下面的代码:

Dim dteBirthday As Date = #7/22/2012#

Debug.WriteLine(dteBirthday)

上述代码将生成如下输出:

7/22/2012 12:00:00 AM

这个例子打印出了时间 12:00:00 AM,虽然没有为该变量指定时间。这是只指定了日期时,Date 变量包含的默认时间。虽然 Date 变量总是存储日期和时间,但有时您只关心日期或时间。稍后将介绍如何使用函数Format()来只获取Date变量中的日期或时间部分。

注意:Visual Basic包含一个名为DateTime的结构,其成员让用户能够执行与这里讨论的函数类似的功能。据Microsoft讲,这两种方法之间没有优劣之分。DateTime结构较为复杂,因此我选择向读者介绍Date变量。

要将指定的日期或时间增加一定的时间(如一天或三个月),可使用函数DateAdd(),该函数的语法如下:

DateAdd(interval, number, date) As Date

注意,这三个参数都是必不可少的。第一个参数是个枚举值(预定义的值列表),指定要添加的单位(月、日、小时、分钟等)。表12.3列出了interval的可能取值。第二个参数指定要加上多少个时间间隔;最后一个参数是日期。将number设置为负数将从date减去指定的时间间隔数。例如,要在日期7/22/69的基础上加上6个月,可使用下述语句:

Dim dteMyBirthday As Date = #7/22/1969#

dteMyBirthday = DateAdd(DateInterval.Month, 6, dteMyBirthday)

表12.3 DateAdd()函数中参数 Interval的可能取值

执行第二条语句后,dteMyBirthDay将包含日期 1/22/1970 12:00:00 AM。

注意:可以使用与枚举对应的字面字符串(如表12.3所示),而不是枚举名。例如,上述语句可重写为:

dteMyBirthday = DateAdd("m", 6, dteMyBirthday)

下列是一些DateAdd()函数调用示例及其返回的日期。

注意:加上月份时,Visual Basic 不会超越得到的日历月份。例如, DateAdd(“m”, 1, #1/31/2010#)返回日期 2/28/2010,因为 2月没有 31号,因此Visual Basic使用该月份的最后一天。

函数DateAdd()让您能够从指定的日期或时间加上或减去时间;使用函数DateDiff()可以确定两个日期或时间之间的间隔。函数DateDiff()的基本语法如下:

DateDiff(interval, Date1, Date2) As Long

参数 interval 的可能取值与函数 DateAdd()的参数 interval 相同(参见表 12.3)。函数DateDiff()返回一个数字,指出两个指定的日期之间有多少个间隔。例如,下面的代码打印9,即两个日期相差的周数:

Dim dteStartDate As Date = #10/10/2010#

Dim dteEndDate As Date = #12/10/2010#

Debug.WriteLine(DateDiff(DateInterval.WeekOfYear, dteStartDate,

 dteEndDate))

如果第二个日期比第一个日期早,返回的将是负数。下面是一些函数调用示例及其返回的值,帮助读者理解函数DateDiff()的工作原理:

DateDiff(DateInterval.Year, #7/22/1969#, #10/22/2001#) ' Returns 32

DateDiff(DateInterval.Month, #3/3/1992#, #3/3/1990#) ' Returns -24

DateDiff(DateInterval.Day, #3/3/1997#, #7/2/1997#) ' Returns 121

注意到第二个函数调用返回−24。每当传递给函数DateDiff()的第一个日期比第二个日期晚时,都将返回一个负数。这在判断两个日期的早晚很有帮助:只需使用DateDiff()比较它们,然后根据返回的是正数还是负数来判断哪个日期更晚。

有时,只知道日期的某一部分非常有用。例如,您可能让用户输入生日,然后根据其出生的月份执行相应的操作。要检索日期的组成部分,可使用函数DatePart(),该函数的基本语法如下:

DatePart(interval, date) As Integer

同样,interval的可能取值与函数DateAdd()和DateDiff()的参数interval相同。下面的示例演示了函数DatePart()的用法:

DatePart(DateInterval.Month, #7/22/2010#) ' Returns 7

DatePart(DateInterval.Hour, #3:00:00 PM#) ' Returns 15 (military format)

DatePart(DateInterval.Quarter, #6/9/2010#) ' Returns 2

前面提到,有时需要使用Date变量中的日期或时间。另外,有时还要控制日期或时间的显示格式。这些都是使用函数 Format()来实现的。函数 Format()的功能非常强大,除日期和时间外,可能够格式化其他各种内容,如金额和字符串。这里无法全面介绍函数 Fromat(),但将介绍如何使用Format()来输出Date变量的日期和时间部分。

函数Format()的基本语法如下:

Format(expression, style)

参数expression是要格式化的表达式,而style是一个用于指定格式的字符串。例如,要显示日期的月份部分,可在style中使用M。实际上,使用单个M时,将用1位显示1-9月,用两位显示10-12月份;如果使用两个M,将总是将月份显示为两位。使用三个M时,将显示月份名的简称,使用四个M时,将返回完整的月份名,而不管月份名包含多少个字符。注意,小写字母m用于返回分钟,而不是月份。下面的示例演示了这一点:

Format(#1/22/2009#, "MM") ' Returns 01

Format(#1/22/2009#, "MMM") ' Returns Jan

Format(#1/22/2009#, "MMMM") ' Returns January

还有用于年、日、小时、分钟甚至a.m和p.m.的格式化字符。可用的格式化字符非常多,建议读者通过 Visual Basic帮助文档了解它们。因为这里旨在介绍如何提取日期或时间,因此将演示一些常用的格式:

正如读者看到的,逗号、分号和句点不是格式化字符,而是用于将按原样显示。

这些内容对读者来说足够了,但强烈建议读者自己研究函数 Format(),它是一个功能强大且很有用的函数。

Visual Basic能够获取当前的系统日期和时间,这是通过DateTime结构实现的。例如, DateTime结构有很多成员,其功能与这里讨论的很多日期函数类似,它还有其他的成员。其中一个是Today,它返回当前的系统日期。例如,要将当前的系统日期赋给一个新的Date变量,可用如下语句:

Dim dteToday As Date = DateTime.Today

要获得当前的系统日期和时间,可使用DateTime的Now属性,如下所示:

Dim dteToday As Date = DateTime.Now

将DateTime.Today和DateTime.Now存储到内存中。在应用程序中,以后可能需要获取系统日期及时间,而这是目前为止获取该信息的最简单方法。

我经常需要判断一个值是否为日期。例如,如果让用户将其生日输入到文本框中,需要在执行任何日期函数之前,确定输入的是否是日期。Visual Basic为此专门提供了一个函数:IsDate()。该函数接受一个表达式,如果该表达式为日期,则返回True,否则返回False。

如果文本框的内容为日期,下面的语句将打印True;否则打印False:

Debug.WriteLine(IsDate(txtBirthday.Text))

学会使用各种数据对于成为成功的Visual Basic开发人员非常重要。正如在社会活动中需要了解基础数学一样,即使编写最简单的应用程序,也需要在代码中执行基本的数学运算。掌握算术运算符以及运算符优先级顺序,对于使用Visual Basic执行数学运算大有帮助。

布尔逻辑是Visual Basic中的一种特殊运算,它将简单或复杂的表达式的最终结果都表示为 True 或 False。在接下来的几章,读者将学习如何在代码中创建循环和进行决策;读者在这里学到的布尔逻辑对于成功地使用循环和决策结构非常重要。布尔逻辑的使用频率甚至比算术运算还高。

字符串和日期的操作需要特别注意。在本章中,读者学习了如何对这两种数据进行部分数值的提取以及将数据片段组合成新数据。字符串的操作很简单,只要使用一些字符串函数,就可以很快熟悉其用法。而日期的操作有点复杂。即使是熟练的开发人员有时也需要参考在线帮助。读者在本章学习了一些基础知识,但不要畏惧自我实践。

问:我应该总是使用括号来保证运算符按期望进行吗?

答:Visual Basic总是按照运算符优先级顺序对表达式进行计算,所以当表达式中的优先级顺序正确时,不必使用括号。然而,使用括号可确保表达式按期望的方式计算,且可以使其他人更易读懂表达式。所以确实应该这样做。

问:每当我使用日期时,都使用同一种自定义的日期格式。是否有实现这一点的建议?

答:我会创建一个全局常量,用于表示要使用的格式,并在调用Format()时使用该常量,如下所示。这样,如果要修改格式,只需修改该常量的值,从而所有Format()调用都将使用新格式。

Const gc_MyUSDateFormat As String = "MMM.d, yyyy"

Debug.WriteLine(Format(dteStartDate, gc_MyUSDateFormat))

然而,需要注意的是,如果应用程序将在使用不同日期格式的国家使用,应使用名称日期格式,如General Date、Long Date或Short Date,这可确保使用适用于用户所在地区的日期格式。

1.哪个运算符用于执行乘方运算?

2.用什么运算符求除法运算的余数?

3.在下面的表达式中,首先执行哪个运算——加法还是乘法?x= 6 + 5 * 4。

4.下述表达式的结果为True还是False:((True Or True) And False) = Not True

5.执行逻辑否定的布尔运算符是什么?

6.将字符串追加到另一个字符串后面的过程称为什么?

7.哪个函数用于返回指定日期的月份?

8.哪个函数返回两个日期之间的间隔?

1.^运算符。

2.Mod运算符。

3.5 * 4最先执行。

4.该表达式为True。

5.Not运算符。

6.拼接。

7.函数DatePart()。

8.函数DateDiff()。

1.创建一个项目,该项目包含一个窗体,窗体上只有一个文本框。当用户在文本框中输入名、中间名和姓时,将它们解析成三个变量,每部分对应一个变量。

2.创建一个项目,该项目包含一个窗体,窗体上只有一个文本框。假设用户在文本框中输入了一个有效的生日,使用日期函数告诉用户已经多大,以天为单位。

在本章中,读者将学习:

使用If...Then语句进行决策;

使用Else和ElseIf扩展If...Then语句;

使用Selcet Case评估表达式的多种可能取值;

使用GoTo语句跳转代码执行流程。

在第10章,读者学习了将代码分成多个过程,这样就可以以任意要求的顺序来调用它们。这有助于组织代码,但仍需要在过程中有选择性地执行代码过程和语句集合。决策机制可实现这一点。决策结构是能够根据条件——如变量的值——执行或忽略代码的编码结构。Visual Basic包括了两个可用来执行任意类型的分支决策的结构:If…Then…Else和Select Case。

在本章中,读者将学习如何使用Visual Basic提供的决策结构在Visual Basic代码中进行完善而高效的决策。另外,还将学习如何在过程中使用 GoTo 语句来跳转执行流程以及在什么情况下不合适使用它。每个应用程序都有可能用到决策结构,因此越快掌握这些技能,就越容易创建健壮的应用程序。

编程中最常使用的决策结构是If...Then结构。简单的If...Then结构如下所示:

If expression Then

...' code to execute when expression is True.

End If

If...Then 结构使用布尔逻辑计算表达式的值为 True 还是 False。表达式可能很简单(如If x = 6 Then),也可能很复杂(如 If x = 6 And y >10 Then)。如果表达式的值为True,则执行If语句和End If语句之间的代码;如果表达式的值为 False,Visual Basic将跳到End If语句后继续执行,而不执行 If和End If语句之间的代码。

读者至少在Picture Viewer项目中添加了一个 If…Then结构。下面执行如下步骤,创建一个新项目。

1.创建一个新的Windows应用程序,将其命名为Decisions。

2.在“解决方案资源管理器”中右击 Form1.vb,选择“重命名”,将默认窗体名改为DecisionsForm.vb,并将窗体的Text属性设置为Decisions Example。

3.双击工具箱的Textbox项,添加一个新文本框到窗体中。如下设置文本框的属性:

4.双击工具箱的Button项,在窗体中添加一个新按钮。如下设置按钮的属性:

现在窗体应如图13.1所示。

图13.1 将使用If...Then语句判断文本框中输入的文本是否是数字

接下来在按钮的 Click()事件中添加代码。代码将使用一个简单的 If...Then 结构以及 Visual Basic函数 IsNumeric(),来判断在文本框中输入的文本是否是数字。现在双击按钮来访问其Click事件,然后输入下列代码:

If IsNumeric(txtInput.Text) Then

 MessageBox.Show("The text is a number.")

End If

逐行进行分析,将发现这段代码很简单。看第一条语句,前面提到,简单的If...Then语句应如下所示:

If expression Then

在您输入的代码中,表达式为:

IsNumeric(txtInput.Text)

IsNumeric()是一个 Visual Basic函数,它判断指定的字符串是否为数字,如果是则返回True,否则返回False。在这里,您将文本框的内容传递给函数 IsNumeric(),并命令Visual Basic根据结果做出决策。如果IsNumeric()返回True,将继续执行If语句后面的代码,即显示一条消息;如果 IsNumeric()返回False,将跳到End If语句处执行,即什么也不显示。

如果在简单的If…Then结构中只有一行代码需要执行,可将其放在Then之后,从而省略End If语句。例如,上述代码可修改为如下所示:

If IsNumeric(txtInput.Text) Then MessageBox.Show("The text is a number.")

虽然这种代码可行,但一种更好的做法是使用End If语句。我强烈建议读者这样做,因为这样可提高代码的可读性且不容易出错。

如果要在表达式的值为False时执行一些代码,可在 If和End If之间加入Else语句,如下所示:

注意:如果希望只在表达式为False而不是True时执行代码,可以对表达式使用Not运算符,如下所示:

If Not(expression) Then

关于布尔逻辑,请参见第12章。

通过包含Else子句,可在表达式为True时执行一组语句,在表达式为False时执行另一组语句。在刚才创建的示例中,如果用户输入的是数字,将显示一条消息;如果输入的不是数字,将不会有任何反馈。将代码做如下修改,可确保用户总是收到一条消息:

如果用户输入的是数组,将显示消息“The text is a number”,仅此而已。Visual Basic遇到Else语句后,将跳转到End If处,因为Else语句中的代码仅在表达式为False时才执行。同样,如果用户输入的文本不是数字,将显示消息“The text is not a number”,仅此而已;表达式为False时,将直接跳到Else语句处执行。

单击工具栏中的“全部保存”按钮保存所做的工作,然后按F5运行项目。在文本框中输入一些文本并单击按钮,将出现一个对话框,指出输入的文本是否是数字,如图13.2所示。

图13.2 If...Then 语句在决策时提供了极大的灵活性

可以继续输入其他文本然后单击按钮。完成后,从“调试”菜单中选择“停止调试”。

提示:务必熟悉 If...Then 结构;在每个创建的项目中很可能至少包含一个If...Then结构。

If…Then…Else给决策提供了极大的灵活性。Visual Basic有一条语句可进一步拓展这种结构的功能,如果没有它可能需要嵌套 If…Then 结构(将一个 If…Then 结构放在另一个If…Then 结构中),使用这种语句可极大地减少代码量。具体地说,ElseIf 语句让您能够在If…Then语句为False时评估另一个表达式。

下面是一个基本的ElseIf结构:

上述代码的功能与下面的嵌套If…Then语句相同:

通过使用ElesIf语句,不但可以减少代码,还可使复杂的If…Then决策结构更容易理解得多。需要指出的是,可以使用多条ElseIf语句,还可以使用Else语句来处理余下的其他所有情况。例如:

注意:If语句及其所有ElseIf语句之间是互斥的,只有其中的一条语句被执行。

前面提到,可以使用嵌套的If...Then语句进一步细化决策过程。下述代码就是一个这样的结构:

使用嵌套 If...Then语句时需要牢记的一点是,每条 If…Then语句都必须有对应的End If语句。正如前面指出的,这种规则有一个例外,即If…Then语句只执行一条语句,且该语句与If…Then位于同一行时。我个人不提倡将要执行的语句同If语句放在同一行,因为这使得代码难以调试。

使用Select Case语句对表达式进行多值判断

使用If...Then结构时,有时需要做很多额外的工作才能处理好决策情况。一个例子是需要根据表达式的多个可能值(不仅仅是 True 或 False)执行不同的操作。例如,假设要根据用户的年龄执行不同的操作,将可能使用类似于下面的代码:

可以看到,这种结构难以理解。如果不从上往下分析(就像编译器那样),将难以理解整体情形。例如,仅看最后一条ElseIf语句,将意味该ElseIf语句的代码将在年龄小于21时执行;然而,认识到前一条ElseIf语句涵盖了小于18岁的所有情况后,将知道最后一条ElseIf语句仅当年龄在18~20时才执行。如果读者不明白这一点,花些时间理解其中的逻辑,直到明白为止。

对于这个示例,需要明白的重点是,每条ElseIf语句评估的是同一个表达式(lngAge),但考虑的是该表达式为不同值的情形。Visual Basic包含一个更好的决策结构,可用于考虑同一个表达式的多种可能取值:Select Case。

典型的Selcet Case结构如下:

Case Else用于定义当表达式的值不为任何Case语句中的值时,将执行的代码。Case Else是可选的。

Select Case语句让您能够进行复杂的表达式比较。例如,可以在单条Case语句中指定多个比较,它们之间用逗号分隔。请看下面的示例:

Visual Basic遇到Case语句中的逗号后,它将表达式同用逗号分隔的每项进行比较。如果表达式同其中的任何一项匹配,将执行该Case语句的代码。对于非常复杂的结构,这可极大地减少Case语句的数量。

另一种高级比较是使用关键字 To。在这种情况下,Visual Basic检查表达式的值是否在To指定的范围之内。下面是一个这样的例子:

关键字To也可用于字符串,例如:

下面是前面有关年龄的示例,但使用的是Select Case语句:

Select Case语句使得这种决策更容易理解。同样,有关Select Case的要点是,它用于评估单个表达式的多种可能取值。

下面创建一个项目,使用Select Case结构进行复杂的表达式评估。这个简单的应用程序通过一个组合框向用户显示一个动物列表。当用户单击按钮时,程序显示列表中被选中的动物(如果有动物被选中的话)有几条腿。首先创建一个新的Windows应用程序项目,将其命名为Select Case Example,然后执行下列步骤。

1.在“解决方案资源管理器”中右击Form1.vb,选择“重命名”,然后将窗体的名称改为SelectCaseExampleForm.vb。接下来,将窗体的Text属性设置为Select Case Example(必须单击窗体才能查看它的设计属性)。

2.双击工具箱的Combo box项,在窗体中添加一个新组合框。按如下设置组合框的属性:

3.将一些元素添加到列表中。为此,单击组合框的Items属性,然后单击该属性的“生成”按钮打开“字符串集合编辑器”。输入图 13.3 所示的文本;在每个列表元素结束时按回车键,使下一个元素在新的一行上。

图13.3 这里输入的每一行将在运行时成为列表的元素

4.添加一个Button控件。当用户单击按钮时,使用一个Select Case结构来判断用户选择了哪种动物,然后告诉用户这种动物有几条腿。双击工具箱的Button项,在窗体中添加一个新按钮。按如下设置按钮的属性:

窗体现在应如图13.4所示。单击工具栏上的“全部保存”保存项目。

图13.4 这个例子只使用了一个组合框和一个按钮控件

剩下的便是添加代码。双击Button控件访问它的Click事件,然后输入下列代码:

这段代码执行以下功能。

Select Case结构将组合框 cboAnimals的内容同一组预先定义的值进行比对。按照值出现在列表中的顺序来执行每条Case语句。因此,表达式最先与Bird进行比较。如果组合框中的内容是Bird,则调用该Case语句后的MessageBox.Show()语句,然后跳到End Select语句处执行。

如果组合框的内容不是Bird,Visual Basic将判断其内容是不是Horse、Dog或Cat。如果组合框包含这些值中的任何一个,将调用该Case语句后的MessageBox.Show()语句,然后跳到End Select语句处执行。

后面的每个Case语句都以相同的方式评估。如果没有找到匹配的Case语句,则执行Case Else语句中的MessageBox.Show()语句。如果既没找到匹配的Case语句,也没有Case Else语句,将不执行任何代码。

注意:Select Case语句在进行比较时区分大小写。如果用户输入的是 horse, Select Case将不会认为它与Horse相同。

可以看出,通过在单条Case语句中考虑表达式的多个可能取值,可以减少Case语句数和重复的代码(如果不这样,将在针对腿数相同的每种动物的Case语句中使用相同的代码)。另外在列表中添加新动物时,只需在已有的Case语句中添加该动物的名称即可。

现在按F5运行项目,然后执行下列步骤。

1.从列表中选择一种动物,然后单击按钮。

2.清除组合框中的内容,然后单击按钮。

3.完成后,选择菜单“调试”>“停止调试”停止运行项目,然后单击工具栏上的“全部保存”。

读者可能对Select Case的功能感到惊讶。我知道的最酷的功能是,使用Select Case来判断一组单选按钮中的哪个被选中。

单选按钮被选中时,其Cheched属性为True。基本上,这意味着需要检查组中每个单选按钮的Checked属性,直到找到一个该属性为True的单选按钮。Visual Basic文档推荐使用像下面这样的If…Then结构:

在我看来,这些ElseIf语句过于零乱。要检查一系列单选按钮的Checked属性,将这些属性都同一个值(True)进行比较。

如果将True看作一个表达式,将控件的Checked属性视为可能的取值,可以使用Select Case结构代替If…Then结构,如下所示:

在我考虑,这要清晰得多。使用 If…Then、Select Case或两者可以完成任何可想象得到的决策任务。这种技能可用于创建最清晰、可读性最强的决策结构。

注意:可以嵌套Select Case结构。另外,还可以将Select Case结构嵌套到If…Then结构中,反之亦然。可以以任何您认为合适的方式嵌套决策结构。

决策结构用于选择性地执行代码。遇到决策语句后,Visual Basic计算表达式的值,并根据结果执行不同的代码。然而,要实现跳转并不一定非得使用决策结构,Visual Basic包含一条GoTo语句,可用于跳转到当前过程中指定位置处执行。

介绍如何使用 GoTo 语句前,需要指出的是,在大多数情况下使用 GoTo 语句都是一种糟糕的做法。充斥 GoTo 语句的代码难以理解和调试,因为执行流程过于复杂。这样的代码常被称为意大利面条式代码,应不惜一切代价避免。在99%的使用GoTo语句的情况下,都有解决问题的更佳方法,稍后将介绍一个这样的示例。尽管如此,和其他所有语句一样,GoTo语句也是一种工具。虽然它不如其他 Visual Basic语句那样使用频繁,但它仍是一个可供您使用的工具,只要明智地使用,它仍然很有用。

要跳转到过程中的特定位置,必须首先使用编码标签定义跳转位置。编码标签不同于放在窗体上的标签,要创建编码标签,将光标放在过程中的一个新行中,然后输入标签的名称和冒号,再按回车键。代码标签不能包含空格,也不能是 Visual Basic保留字。例如,不能创建名为Print的代码标签,因为Print是Visual Basic中的保留字;然而,可以创建名为PrintAll的代码标签,因为它不是保留字。代码标签相当于一个指针,可使用 GoTo 语句跳转到它指向的位置。下面的示例使用GoTo语句跳转到代码标签处执行:

这个过程的功能如下。

声明一个名为intCounter的Integer变量,并将其初始化为0。

定义一个名为 IncrementCounter 的代码标签,可以使用一条或多条 GoTo 语句跳转到该标签处执行。

将intCounter的值加1。

使用 If…Then语句判断 intCounter是否超过 5 000。如果没有,GoTo语句强行跳转到标签IncrementCounter处执行,在这里再次将intCounter的值加1,并判断它是否大于 5 000,从而形成循环。

这个代码段可行,读者可以检测它。然而,这是糟糕的代码。前面说过,GoTo 语句通常可用更佳的编码方法取代。在这个例子中,可使用 Visual Basic的循环结构来代替,这将在第14章介绍。在大多数情况下,使用这些循环结构足够创建所需的循环,因此应避免使用GoTo语句来创建循环。事实上,滥用GoTo语句的情形之一是,使用它来代替Visual Basic的内部循环结构。下面的循环可用来代替这个例子中使用的GoTo语句:

这里的讨论可能导致您发出疑问,为何要使用GoTo语句?GoTo语句的一种常见用途是,在过程中创建单个出口。正如读者知道的,可以使用Exit Sub或Exit Function随时强行离开过程。退出过程之前,常常需要执行清理代码。在很长的过程中,可能有很多退出语句。然而,这样的过程将难以调试,因为并非在任何情况下都将执行清理代码。所有的过程都只有一个入口,使所有过程都只有一个出口是有意义的。为此,可使用 GoTo 语句跳转到出口,而不是使用Exit语句来退出过程。下面的过程演示了如何使用GoTo语句来创建单个出口:

注意:一种创建单个出口的更佳方法是,将过程的代码放在一个Try…Catch…Finally块中,这将在第15章介绍。如果读者对使用行业普遍接受的最佳实践创建最健壮的代码感兴趣,建议阅读我编写的《Practical Standards for Microsoft Visual Basic .NET Programmers》第二版(Micorosoft Press 2002年出版)。

在本章中,读者学习了如何使用Visual Basic的决策结构在Visual Basic代码中进行决策。学习了如何使用If...Then语句在表达式为真时执行代码,以及用Else在表达式为假时执行代码。对于更复杂的决策,读者学习了如何在决策结构中使用ElseIf来进一步添加比较以及使用嵌套的If...Then结构来提高灵活性。

除了 If…Then外,读者还学习了如何使用Select Case创建强大的决策结构,以判断单个表达式的多种可能值。读者学习了如果在单条Case语句中检查多种可能的取值,这可以极大地提高可读性以及减少冗余代码。最后,读者学习了创造性地使用Select Case来获得有用的结果。

决策结构通常是应用程序的支柱。如果不能根据变化的情况来执行特定的代码,代码将非常线性化,局限性非常大。务必熟悉决策结构并努力在任何给定的情况下使用最好的结构。越善于创建决策结构,就能够越快地编写出可靠而易懂的代码。

问:如果要在仅当If...Then语句中的表达式是假而不是真时执行代码,应如何做?是否需要将代码置于Else子句中,而在Then后面不提供任何代码?

答:这是布尔逻辑可以解决的问题。所要做的无非就是使表达式值为真时运行您的代码。为此可使用Not运算符,如下所示:

If Not expression Then

问:Case语句的创建顺序有多重要?

答:这完全依情况而定。在根据选中的动物来显示有几条腿的例子中,Case语句的顺序无关紧要。然而,如果要比较数值,如在有关年龄的示例中,Case语句的顺序非常重要:如果不小心,可能导致Case语句根本不会被评估。例如,如果在判断变量是否等于6之前判断它是否小于12,意味着变量为6时,第一个比较结果为True,因此第二个比较根本就不会进行。

1.要判断表达式为True还是False,应使用什么决策结构?

2.在两种决策结构中判断表达式为True还是False时,使用的是什么逻辑?

3.如果想让代码在If…Then语句中的表达式为False时执行,应包含一个什么子句?

4.判断对错:如果在If…Then语句中的表达式为True时只执行一条语句,可以不需要End If语句。

5.要对有多种可能取值的的表达式进行判断,应使用哪种决策结构?

6.要在单条Case语句中放置多个可能的取值,应使用什么分隔它们?

7.在单个Case结构中,可能执行多条Case语句的代码吗?

8.判断对错:可以使用GoTo语句跳转到其他过程中执行。

9.要使用GoTo语句跳转到其他地方执行,必须创建什么来指定要跳转到的位置?

1.If...Then结构。

2.布尔。

3.Else。

4.对,但代码的可读性较差且更难以调试。

5.Select Case结构

6.逗号(,)。

7.不,绝不可能。

8.错。GoTo语句只能跳转到当前过程的其他地方执行。

9.代码标签。

1.创建一个项目,让用户能够在文本框中输入文字。使用If...Then结构判断是输入的是“圆”、“三角形”、“正方形”还是“五边形”,并显示输入的形状的边数。如果文本与这些形状都不匹配,提示用户必须输入有效的形状。

2.重新编写下面的代码,只使用一个If…Then结构,且不能包含GoTo语句:

在本章中,读者将学习:

使用For...Next执行确定次数的循环;

使用Do...Loop执行基于条件的循环。

读者经常会遇到需要反复执行相同代码的情况。可能需要将某些代码重复执行确定的次数,当某个条件满足时(表达式为True)一直执行代码,或一直执行代码直到某个条件成立(表达式为True)。Visual Basic包含一些结构,让您能够很容易地定义和执行这些重复的代码,这就是循环。本章将介绍如何使用两种主要的循环结构,使代码更简洁、更快、更高效。

最简单的循环是For...Next循环,从最初的BASIC语言起,它就一直存在。使用For...Next循环,可以将计数器置为某个初始值,然后让Visual Basic开始循环。随后Visual Basic将执行循环内的代码,并将计数器增加指定的值,然后重复循环直到计数器到达指定的上限。下面是For...Next循环的通用语法:

For语句设置并启动循环。For语句的组成部分如表14.1所示。

表14.1 For...Next语句的组成部分

续表

每条For语句都必须是对应的Next语句。在Next语句中,可以不指定Countervariable的名称,但应该指定,因为这使得代码更容易理解。下面提供了一些简单的For…Next循环示例,并对其功能进行了解释。

上述代码声明了一个名为intCounter的Integer变量,然后使用For语句开始循环。该循环将intCounter初始化为1,打印intCounter的值,将intCounter加1,然后继续循环。由于省略了step,因此每执行一次循环,变量intCounter的值都加1。循环将执行100次,在打印输出窗口中打印1~100。

下述循环的功能与前一个循环相同:

注意到在Next语句中没有指定循环使用的计数器的名称。这是完全合法的,但不是良好的编程习惯。请看下面的示例:

上述代码在一个循环中执行另一个循环。如果在Next语句中省略变量名,代码也将运行,但从程序员的角度看,这将难以阅读和理解。

在For…Next语句中,step用于指定每循环一次计数器变量增加的值。正如读者看到的,如果省略了 step,计数器变量将每次增加 1。如果要让计数器变量每次增加 1,则无需使用 step(使用它不会使代码更容易理解)。然而,如果要让计数器变量增加的值不是1,必须使用step。

下面是一个简单的使用step的For…Next循环示例:

上述代码与第一个示例极其相似,但每当到达Next时,都将intCounter增加4而不是1。该循环将总共执行25次,而不是100次。要创建倒计数的For…Next循环,可将step指定为负数,如下所示:

该循环将intCounter初始化为100,并在每次到达Next时将intCounter的值减1。该循环执行到intCounter减少到1(End值)为止。需要指出的是,step不一定非得是整数,也可以使用 0.5 这样的数字。如果这样做,计数器变量的数据类型必须支持小数,因此不能使用Integer。

虽然初始化For…Next循环时就知道循环的起点和终点(必须指定start和end,否则无法创建循环),但有时需要在到达终点前结束循环。要退出For…Next循环,可使用Exit For语句,如下所示:

Visual Basic遇到Exit For语句后,将跳到当前循环的Next语句后面的语句处执行,即结束循环。在这个示例中,condition可以是变量或任何表达式。condition通常是在循环中不断变化的东西;如果condition不发生变化,则对其进行判断没有任何意义。例如,您可能遍历一个文件列表,试图找到特定的文件。一旦找到该文件,就没有必要再搜索下去,因此退出循环。

Visual Basic新增的一种功能是,能够在到达Next语句之前进入下一次For…Next循环。为此,可使用Continue For语句,如下所示:

下面创建一个过程,它包含了一个For...Next循环,从100倒数到0并将窗体的透明度设置为计数器值(窗体将渐渐消失)。

创建一个新的Windows应用程序项目,将其命名为Fading Form,然后执行下列步骤。

1.右击“解决方案资源管理器”中的Form1.vb,选择“重命名”,然后将默认窗体名改为FadingFormForm.vb。接下来,将窗体的Text属性设置为Fading Form(必须单击窗体才能查看它的设计属性)。

2.双击工具箱的Button项,在窗体中添加一个按钮。按如下设置按钮的属性:

窗体应如图14.1所示。

图14.1 简单的项目也能实现相当酷的功能

剩下的便是编写代码了。双击按钮访问它的Click()事件,然后输入下列代码:

现在读者应该能看懂这段代码。下面是对这段代码的解释。

第一条语句创建一个 Single 变量。这里使用 Single 变量是因为 Opacity 的取值为0~1,Integer不支持小数点。事实上,我刚开始编写这段代码时没有注意,使用了Integer变量,但代码不能运行。试试就明白了!我花了一两分钟才发现问题所在。如果读者将这种情形视为一个谜题,就开始享受到编程的乐趣了。

接下来的语句开始For…Next循环。变量被初始化为1,step表明每次重新循环时该变量的值都将减少0.05。

第三行将窗体的 Opacity 属性设置为变量的值。接下来的一行(注释后面的代码)调用窗体的Refresh()方法,该方法将重绘窗体。如果不这样做,Windows可能不会在循环之间重绘窗体。可以将Refresh()方法注释掉(在语句的前面加上注释符,这样Visual Basic将把它视为注释而不执行它),看看将发生什么。

接下来的语句(Sleep()语句)让Visual Basic暂停运行。括号内的数字是要等待的毫秒数,这里为 200。这是一个很不错的函数!如果没有这个函数,可能需要使用一个 For...Next 循环来进行暂停,但暂停时间将取决于用户计算机的速度。通过使用Sleep(),可确保在任何计算机上都暂停相同的时间。

Next语句使得跳转到For语句处执行:将变量减小并检测是否达到终点。

循环结束后,窗体将不可见。最后一条语句将窗体的Opacity属性设置为1,以显示窗体。

单击工具栏中的“全部保存”,然后按F5键运行项目。窗体首次显示时,它是正常的。单击按钮,注意到窗体将逐渐消失(如图14.2所示)。

图14.2 如果不使用循环,将需要很多代码

如果不用循环,而编写每行改变透明度的代码,将需要复制这些语句 20 次!使用一个简单的For...Next循环,只需几行代码就能完成相同的任务。

当知道循环要执行的次数时,使用 For...Next 循环。这并不是说在设计时必须确切地知道循环要执行几次;而是说在开始循环时必须知道循环要执行的次数。可以使用变量来定义For...Next循环的任何参数,如下列代码所示:

提示:编写高效代码的关键之一是避免冗余。如果发现自己在重复输入相同(或类似)的代码行,很有可能可以使用循环。

在有些情况下,并不知道循环要执行的确切次数——甚至在循环开始执行时也不知道。可以在For…Next中指定比循环次数大的上限,并在循环内部检查终止条件,在满足条件时使用Exit For语句退出循环。然而,这种方法的效率很低,通常也是不可行的。需要创建这样的循环时,可使用Do...Loop。

Do...Loop有多种版本,最基本的Do...Loop版本的语法如下所示:

Do

 [Statements]

Loop

Do

没有退出机制或指定条件的Do...Loop循环是无限循环。在最简单的Do...Loop循环中,没有任何语句指定循环什么时候停止。有时可能需要无限循环(如游戏编程),但通常是在满足一定条件时就退出循环。和For...Next循环一样,任何时候都可以使用Exit Do语句来退出Do...Loop循环。例如,可以扩展前面讨论的Do...Loop循环,在其中包含一条Exit Do语句,如下所示:

在这段代码中,循环将持续执行,直到expression的值变为True。通常,表达式都是基于变量的,该变量的值将在循环中被修改。很显然,如果表达式的值一直不变,循环将不会终止。

可以使用关键字While或Until在Do…Loop结构中添加一个表达式,下面是一个使用关键字While的简单Do…Loop循环:

Do While expression

 [Statements]

Loop

只要expression的值为True,该循环就将继续执行。如果循环开始时expression的值就为False,Do While和Loop语句之间的代码将不会执行——一次也不执行。

下面是一个类似的使用关键字Until的Do...Loop循环:

Do Until expression

 [Statements]

Loop

这种循环的行为不同于使用关键字 While 的循环。使用关键字 Until 定义的循环将不断执行,直到expression为True;只要expression为False,循环就将进行。这几乎与While的行为相反。如果循环开始时 expression就为True,语句Do Until和Loop之间的代码将不会执行——一次也不执行。

注意到While和Until都可能导致循环根本不执行。这是因为expression放在Do语句中,意味着进入循环以及每次迭代循环前都将判断它的值。可以将While和Until放在Loop语句中,而不是Do语句中,这意味着首次判断expression的值之前,将执行循环一次。这样的循环总是至少会执行一次,需要注意的是这改变了循环的行为。下面是将关键字While放在Loop语句中的循环:

Do

 [Statements]

Loop While expression

同样,只要 expression的值为True,该循环都将不断执行。循环Do…Loop While和Do While…Loop 之间的差别在于,循环至少会执行一次,因为在判断 expression 的值之前,第一次循环已经完成。同样,这样的循环总是至少会执行一次,而不管 expression 的值如何。下面的循环将关键字Until放在Loop语句中,而不是Do语句中:

 Statements]

Loop Until expression

该循环不断执行到expression的值为True为止。然而,该循环中的代码至少会执行一次,因为在第一次循环完成后才判断expression的值。

注意:在Do…Loop循环中,可以使用Continue Do语句跳转到Do语句处执行,就像已经到达Loop语句一样。

下面创建一个使用Do...Loop的例子。在这个项目中,要找出能被3整除的前10个数。虽然知道要找到10个数字,但并不知道需要判断多少次,因此,Do...Loop循环是最好的选择。

新建一个Windows应用程序项目,将其命名为No Remainders,然后执行下列步骤。

1.将窗体名改为NoRemaindersForm.vb,并将其Text属性设置为No Remainders。

2.在窗体中添加一个按钮,并按如下设置按钮的属性。

3.在窗体中添加一个ListBox控件,并按如下设置它的属性:

现在窗体应如图14.3所示。

图14.3 还有什么控件能比列表框更能胜任显示结果列表呢

双击新添加的按钮来访问它的Click()事件,然后输入下列代码:

将这段代码进行分解将使理解更容易。

前两条语句只是创建了两个整型变量。变量intSeek是将要测试能否被3整除(即没有余数)的数字。变量intFound是计数器;每找到一个能被3整除的数字,该变量就加1。

Do语句开始循环。该循环在找到 10个数字(intFound等于 10)前将不断执行。

第12章指出过,可使用Mod运算符确定余数。这里使用Mod来判断intSeek除以3后是否有余数,以判断intSeek能否被3整除。

如果当前的数字能被3整除,则将其加到结果列表框中,并将intFound加1。

下一条语句将intSeek加1,以便检测下一个数字。

最后一条语句是Loop,它使得跳转到Do语句处执行;如果intFound等于10,则不执行循环,而是跳转到Loop语句后面的语句处执行;如果intFound小于10,则继续执行循环。

单击工具栏上的“全部保存”保存项目,然后按F5运行项目。单击Find Numbers按钮,结果很快就能出来(如图14.4所示)。

在这里,Do...Loop循环是最好的选择,因为不知道要判断多少个数字(也就是循环将迭代多少次)。如果只在1到100之间查找,则For...Next循环将是更好的选择。

图14.4 Visual Basic执行数学函数很快

注意:Visual Basic还支持另一种循环:While…End While循环。这种循环几乎与 Do...Loop 循环相同:其功能与 Do...Loop 循环相同,但使用的语法不同。然而,Do...Loop更被人们广泛接受(While…End While来自早期的Visual Basic版本)。强烈建议读者不要使用While…End While循环,而使用Do...Loop。

循环是一项强大的功能,让您能够编写更紧凑的代码。紧凑的代码更小、更高效,且通常——但并非总是——更具可读性。在本章中,读者学习了在循环将执行次数已知的情况下编写For...Next循环。记住,使用For...Next循环时,不必在设计时知道迭代的次数,但在运行时必须知道。读者学习了如何使用step来增加或减少For...Next循环中的计数器,以及使用Exit For提早结束循环。

在本章中,读者还学习了如何使用强大的Do...Loop循环。Do...Loop循环让您能够创建非常灵活的循环,几乎能够应对任何需要循环的情况。根据需要,可以使用 While 或 Until在Do...Loop循环中对表达式进行判断。读者知道,在Do语句中判断表达式将导致循环的行为与在Loop语句中判断表达式不同。如果For...Next循环不能胜任,可使用Do…Loop循环。

除学习循环的细节外,读者还直接看到了一个问题有多种解决方案。通常有一种方法是明显优于其他方法的,但您不一定总是能够得到。有时有一种方法只是稍微优越一点,或多种方法同样适用。专业程序员对任何给定的问题总能找到最佳的方法。随着时间的推移,您也能够做到。

问:有一种循环胜过另一种循环的具体例子吗?

答:通常,必须遍历一系列元素时(如引用数组中所有的元素),For…Next循环是最好的选择。

问:应关注两类循环的性能差异吗?

答:对于如今的高速处理器,在任何情况下,这两类循环之间的性能差异往往没有循环的可读性和功能性重要。在性能非常重要的情况下,使用所有能够想到的方法编写循环,对它们进行比较,然后选择最快的循环。

1.在For…Next循环中,如果要将计数器变量增加除1外的其他值,可使用哪个关键字?

2.判断对错:要使用For...Next循环时,必须在设计时就知道循环变量的起始值与终止值。

3.什么语句用于结束For语句开始的循环?

4.可以嵌套循环吗?

5.如果不知道循环将要发生多少次,应该创建哪种循环?

6.如果在Do…Loop循环的Loop语句中对表达式进行判断,循环中的代码有没有可能从不执行?

7.使用什么语句可以不判断Do或Loop语句中的表达式就结束Do…Loop循环?

1.Step。

2.错。只需在运行阶段知道即可。

3.Next。

4.可以。

5.Do循环。

6.不可能,代码至少会执行一次。

7.Exit Do。

1.在No Remainders项目中创建一个文本框,让用户输入一个数字。找出可被用户输入的数字整除的前10个数字。

2.使用两个嵌套的For...Next循环调整标签的宽度和高度。外层循环将标签的Width属性从1增加到100,内层循环将标签的Height属性从1增加到100。不要对最终结果感到惊讶,它非常奇怪。

在本章中,读者将学习:

在代码中添加注释;

识别两种基本错误;

使用断点;

使用“即时”窗口;

创建结构化错误处理程序。

没有人能够编写完美的代码。读者可能已经很熟悉那些使代码不能正确执行的问题——称为错误(bug)。对于Visual Basic新手来说,代码中可能包含相当多的错误。精通Visual Basic后,代码中的错误将减少,但绝不会消失。本书不能教您如何调试每个可能遇到的编译错误或运行错误;调试既是一种技巧也是一门艺术。然而,在本章中,读者将学习用于跟踪和纠正大多数代码错误的基本技巧。

下面首先创建一个新的Windows应用程序项目,将其命名为Debugging Example,然后执行下列步骤来编译项目。

1.在“解决方案资源管理器”中右击 Form1.vb,选择“重命名”,然后将窗体名改为DebuggingExampleForm.vb。接下来,将窗体的Text属性设置为Debugging Example(必须单击窗体才能查看它的设计属性)。

2.双击工具箱的TextBox项,在窗体中添加一个新文本框。如下设置文本框的属性:

3.双击工具箱的Button项,在窗体中添加一个新按钮,并如下设置它的属性:

现在窗体应如图15.1所示。

图15.1 这个简单的界面将帮助读者学习调试技巧

这个小项目将把文本框中输入的文本除以 100。编写代码来实现这种功能时,将碰到很多有意制造的各种错误,并学习如何修正这些错误。现在单击工具栏中的“全部保存”保存项目。

从一开始就减少错误并使已有的错误更容易跟踪的最简单方法之一是,在代码中添加注释。代码注释是 Visual Basic知道不是代码并忽略的文本行。将项目编译成可发布组件时,注释行将从代码中剥离,因此注释不会影响性能。Visual Basic的代码窗口将注释显示为绿色,这使过程更容易阅读和理解。应在每个过程的开头添加注释,以说明过程的用途。另外,应贯穿过程地添加注释,详细描述代码的功能。

提示:注释是要给人而不是计算机阅读的。因此尽量让注释容易阅读。记住,难以理解的注释还不如没有注释。另外,注释是一种文档。就像应用程序的文档必须清晰一样,代码注释也应遵循良好的编写原则。

要创建注释,在注释文本前面加上撇号(')。例如,下面是一条简单的注释:

'This is a comment because it is preceded with an apostrophe.

注释也可以放在代码行末尾,如下所示:

Dim intAge as Integer ' Used to store the user's age in years.

这条语句中,撇号右边的所有文本以及撇号都是注释。通过在过程中添加注释,就不必依靠记忆来描述过程的用途和机制。如果不得不看已经很久没碰过的代码,或者必须接触别人的代码,您将感激注释。

现在双击按钮 Perform Division访问它的Click事件,并添加下列两行代码(实际上是注释):

' This procedure divides 100 by the value entered in

' the text box txtInput.

注意到输入撇号字符时,注释文本将变为绿色。

创建代码注释时,尽可能做到以下几点:

指出代码的用途(为什么而不是如何做);

清晰地指出代码背后的想法和逻辑;

让人注意代码中重要的转折点;

减少读者在其脑袋里模拟代码执行的情形;

输入代码时就进行注释。如果等待代码完成后再做,可能就不会再回过头来添加注释。

基本上,代码中可能有两种错误:编译错误和运行错误。编译错误(常被称为生成错误)是指导致Visual Basic编译器不能继续处理代码的代码;Visual Basic不会编译有编译错误的项目。例如,如果语句调用过程时使用了错误的参数,将产生编译错误。运行错误是指编译时不会发生但在项目运行时将发生错误的代码。运行错误通常是由于对变量执行非法操作引起的。

为说明这一点,请看下面这条语句,它不会产生编译错误:

intResult = 10 / intDenominator

在大多数情况下,这条语句也不会导致运行错误。然而,如果intDenominator为0呢?10除以0是没有定义的,结果不能赋给intResult(intResult是一个Intege变量)。如果这段代码运行时 intDenominator的值为 0,Visual Basic将返回运行错误。运行错误被称为异常,当异常发生时,称为引发异常(也就是说,运行错误发生时,Visual Basic将引发异常)。异常被引发时,将在引起异常的语句处停止运行,Visual Basic将显示一条错误消息。可以编写特殊的代码来处理异常,使异常引发时Visual Basic不会终止执行,这将在本章后面介绍。

现在,将下列语句添加到Click()事件中,在两行注释的下面:

Dim lngAnswer As Long

lngAnswer = 100 / CLng(txtInput.Text)

MessageRox.Show("100/" & txtInput.Text & " is " & lngAnswer)

这里故意错误地拼写了函数名MessageBox.Show(),按上面所示键入代码。虽然错误地拼写了函数名,但Visual Basic不会马上返回错误。然而,注意到Visual Basic函数名下方显示了一条蓝色波浪线。将鼠标指向带下划线的文本,Visual Basic将指出错误的性质,如图15.2所示。

图15.2 Visual Basic在代码窗口中使用波浪线突出编译错误

按F5运行项目,Visual Basic将显示一条消息,指出发现编译错误,并询问是否继续执行上一次成功的生成。由于代码不能被正确执行,因此没有必要继续运行,所以,单击“否”返回代码编辑器。看看“错误列表”(如果没有显示,选择菜单“视图”>“错误列表”显示它)。当前项目的所有编译错误都将显示在“错误列表”中,如图 15.3 所示。要查看导致错误的代码行,可双击“错误列表”中的错误项。

图15.3 使用“错误列表”很容易找出编译错误

编译错误很严重,它们使代码不能编译因而完全阻止了代码的执行;必须先解决编译错误才能够编译和运行项目。现在,双击“错误列表”中的编译错误,直接进入发生错误的代码。

将R改为B,使函数名变为MessageBox,从而修正这个错误。修改完后,按F5运行项目。Visual Basic将不再返回编译错误;您成功地调试了一个问题!

现在,单击Perform Division按钮,将出现另一个错误,如图 15.4所示。

图15.4 运行错误使程序在出错的行上停止执行

这次产生的错误是运行错误或异常。如果发生异常,就知道代码编译没有问题,因为如果存在编译错误的话,代码将不能编译和执行。这个异常是一个无效转换(Invalid Cast)异常。无效转换通常发生在试图使用一个变量来执行函数,而变量的数据类型与指定的运算不兼容时。Visual Basic用黄色背景和黄色箭头突出出错的语句。

这时,您知道该语句存在错误,且错误与数据类型有关。从“调试”菜单选择“停止调试”,停止运行项目并返回代码编辑器。

Visual Basic包含很多调试工具,可以帮助您跟踪和消除错误。在这一节,读者将学习使用断点和“即时”窗口,这两个工具是任何调试武器的基础。

异常可以导致过程停止执行,同样,您也可以通过创建断点在任何一条语句处停止执行。如果 Visual Basic在执行的过程中遇到断点,将在断点处停止。断点让您能够及时地查询或修改变量值,还让您能够分步地执行代码:每次执行一行。

下面在语句 lngAnswer =中创建一个断点,以帮助排除异常。

添加断点很简单。只要在要停止执行的语句左边的灰色部分单击。这样做时,Visual Basic将显示一个红色圆圈,表示这条语句有个断点(如图 15.5 所示)。要清除这个断点,可以单击红色圆圈(但现在不要这样做)。

注意:断点可以随项目一起保存。不必在每次打开项目时重设断点。

图15.5 断点让您能够控制代码的执行

单击语句 lngAnswer =左边的灰色部分,创建如图 15.5所示的断点。设置断点后,按F5运行程序。单击Perform Division按钮。当Visual Basic遇到断点时,代码的执行将停止——在有断点的语句执行前,并显示出设置了断点的过程。另外,光标也放在包含当前断点的语句处。注意到断点的红色圆圈上有一个黄色箭头(如图 15.6 所示)。这个箭头标识了下一条将执行的语句。因为这条语句设置了断点,因此黄色箭头在红色圆圈上(黄色箭头并不总是在红色圆圈上,但它总是出现在将要执行的下一条语句的灰色部分中)。

图15.6 黄色箭头指示将要执行的下一条语句

当代码在断点处停止执行时,您可以做很多事情。表15.1列出了很多常用的操作。现在按F5继续执行项目。同样,发生了溢出异常。

表15.1 可在断点处执行的操作

断点本身并不足以对过程进行调试。除断点外,通常还要使用“即时”窗口来调试代码。“即时”窗口是一个Visual Studio IDE窗口,通常只在项目处于运行模式时才显示。如果“即时”窗口没有显示,可按Ctrl + G显示它。使用“即时”窗口,可以键入代码语句,随后Visual Basic将立即执行它们(这也是名称“即时”窗口的由来)。下面使用“即时”窗口来调试有问题的语句。

在“即时”窗口中输入下面的语句,然后按回车键:

? txtinput.text

虽然这不直观,但字符“?”已经在编程中使用多年了,它表示print。这条语句将在“即时”窗口中输出文本框的Text属性的内容。

注意,“即时”窗口输出了文本“”。这表示文本框包含一个空字符串(也叫零长字符串)。引发异常的语句试图使用CLng()函数将文本框的文本内容转换为Long。CLng()函数要求传给它数据,但文本框中没有数据(Text 属性为空)。因此发生溢出异常,因为 CLng()不知道如何将空字符串转换为数字。

注意:通常,发生溢出异常时,应检查引用的变量或属性,确保它们包含的数据可用于引发异常的语句中。您将经常发现代码试图执行不适合于数据的操作。

防止这种错误发生有很多方法。最明显的一种方法是,确保在使用CLng()前文本框包含了值。下面就这样做。

Visual Basic现在支持动态代码编辑。这意味着可在调试代码时修改它们,而不用停止项目、修改代码再运行以测试修改后的代码。

将光标放在Dim语句的末尾并按回车键。然后,输入下面的代码语句:

If txtInput.Text = "" Then

 Exit Sub

End If

前面说过,黄色箭头用于指示将要执行的下一条语句。它表示如果这时您继续执行代码,引发异常的语句将再次执行。我们希望执行新添加的语句。执行下列步骤,指定新代码作为接下来要执行的语句。

1.单击黄色箭头并按住鼠标。

2.将黄色箭头拖曳到以 If txtInput.Text打头的语句左边。

3.松开鼠标。

这时黄色箭头将指示下一条要执行的语句为If语句(如图15.7所示)。

图15.7 可以拖曳黄色箭头来改变将执行的下一条语句

现在,按F5继续运行程序。这次Visual Basic不会再引发异常,也不会在断点处停止执行,因为刚添加的语句导致执行到包含断点的语句前就离开了过程。

接下来,执行下列步骤。

1.在文本框中输入您的姓名,然后单击Perform Division按钮。由于文本框不再是空的,因此执行将通过退出测试的语句,并在断点处停止。

2.按F5继续执行代码,这次还会产生异常。然而,该异常与以前引发的异常不同,它是无效转换异常。

3.按Ctrl + G打开“即时”窗口,并在其中输入下列语句(完成后按回车键):

? txtinput.text

“即时”窗口将输出您的姓名。

您消除了没有给CLng()提供任何数据的问题,但其他地方又出现了问题。

按F5继续运行代码,详细看一下异常说明。在“异常”窗口的底部是“查看详细信息”链接。现在单击该链接,Visual Basic将打开一个“查看详细信息”窗口,显示了关于该异常的更多信息,如图15.8所示。

图15.8 “查看详细信息”窗口提供了修复异常的重要信息

“查看详细信息”窗口中显示:“从字符串‘您的姓名’到类型“Long”的转换无效”。它显然不接受传递给CLng()函数的数据。

当前,可能没有合理的方法能够将字母文本转换为数字;CLng()需要的是数字。可以通过下列步骤测试这一点。

1.在“调试”菜单中选择“停止调试”。

2.按F5键来运行项目。

3.在文本框中输入一个数字,然后单击按钮。代码将执行到断点处停止。

4.按F8键执行这条语句。这次没有产生错误!按F5键继续执行,Visual Basic最后将显示消息框。单击“确定”关闭消息框,然后关闭窗体以停止运行项目。

提示:可以使用“即时”窗口来修改变量的值。

因为CLng()需要的是数字,但文本框没有提供强制输入数字的功能,因此必须在代码中考虑这种情况。Visual Basic有一个名为 IsNumeric()的函数,它在提供的参数为数字时返回True,否则返回False。可使用该函数来确保只将数字传递给CLng()函数。在最后一条If…Then语句的后面添加下面的语句:

If Not (IsNumeric(txtInput.Text)) Then Exit Sub

该语句将文本框的内容传递给函数IsNumeric()。如果函数返回False(文本不是数字),则退出过程。现在过程将如图 15.9所示。现在,第一个 If…End If语句是多余的,新语句将捕获用户没有在文本框输入任何东西的情况。

图15.9 包含数据有效性验证的最终代码

按 F5 运行项目,在文本框中输入一些非数字文本,然后单击按钮。如果读者输入的代码没有错误,将不会发生任何异常。刚才创建的数据有效性测试将防止不合适的数据传递给函数CLng()。至此,该过程就调试好了!

“即时”窗口是一个功能强大的调试工具。可以使用它来设置和获悉变量和属性的值。甚至还可以使用“即时”窗口来调用函数,例如,在中断模式下,可以在“即时”窗口中输入下述代码,Visual Basic将打印该函数调用的结果:

? IsNumeric(txtInput.Text)

还可以在调试期间在代码中打印数据。为此,可使用Debug对象的WriteLine()方法,如下所示:

Debug.WriteLine(lngInteger1 + lngInteger2)

使用方法Debug.WriteLine()时,括号中的内容将打印到“即时”窗口中。可以打印字面文本、数字、变量和表达式。在需要知道变量的值,但不想使用断点停止执行代码时, Debug.WriteLine()最有用。例如,假设有很多语句操纵同一个变量,可在代码中使用Debug.WriteLine()打印该变量在关键位置的值。这样做时,应同时打印一些文本,以便能够知道打印的是什么。例如:

Debug.WriteLine("Results of area calculation = " & sngArea)

还可以使用Debug.WriteLine()在代码中创建检查点,如下所示:

“即时”窗口有很多创造性用途,应得心应手地使用它,这将帮助您完成艰难的调试。请牢记,“即时”窗口不能用于编译好的组件;创建可发布组件时,编译器将忽略对 Debug对象的调用。

在调试过程中发生异常时,Visual Basic可停止执行,这很有用。当 IDE处于运行中而代码停止运行时,您将收到一条错误消息,并指出出错的代码。然而,当项目作为编译后的程序运行时,没有处理的异常将使程序终止(也就是在桌面上崩溃)。这是应用程序最不希望遇到的。幸运的是,可以编写专门用于处理异常的代码,防止异常导致执行终止(并终止运行编译好的程序)。异常处理代码告诉Visual Basic如何处理异常,而不是依赖于Visual Basic的默认行为——终止应用程序。

注意:Visual Basic通过On Error语句支持非结构化错误处理。这种错误处理方法虽然在Visual Basic中仍得到支持,但已被摒弃。在遗留或示例代码中,读者可能看到使用这种错误处理的过程,但Microsoft强烈建议在全新的代码中使用Try…Catch…Finally结构来处理异常。

Visual Basic以 Try…Catch…Finally结构的形式支持结构化异常处理(处理错误的正式方法)。创建结构化错误处理代码一开始可能难以理解,与大部分编程原则一样,运用它才能更好地理解它。

创建一个新的Windows应用程序项目,将其命名为Structured Exception Handling,然后执行下列步骤来创建项目。

1.在“解决方案资源管理器”中右击Form1.vb,选择“重命名”,然后将默认窗体名改为 StructuredExceptionHandlingForm.vb。接下来,将窗体的 Text 属性设置为 Structured&nbsp;Exception Handling。

2.在窗体中添加一个新按钮,并按如下设置它的属性:

双击该按钮,将下列代码添加到它的Click()事件中。当您输入Try语句时,Visual Basic将自动创建语句End Try和Catch ex As Exception。

正如读者看到的,Try…End Try结构有起始语句和结束语句,就像循环和决策结构一样。Try…End Try结构用于封装可能引起异常的代码;并让您能够处理引发的异常。表 15.2对该结构的各个组成部分进行了解释。

表15.2 Try…End Try结构的组成部分

按F5运行项目,然后单击按钮。接下来,看一下“即时”窗口的内容(按Ctrl + G显示它)。“即时”窗口应包含下列文本行:

Try

Finally

Done Trying

下面是对程序的解释。

1.Try块开始执行,首先执行Try部分内的代码。

2.由于没有异常发生,因此Catch部分中的代码被忽略。

3.执行完Try部分内的所有语句后,接着执行Finally部分中的代码。

4.当Finally部分内的所有语句都执行完后,跳到紧接在End Try后面的语句处执行。

现在选择菜单“调试”>“停止调试”,停止运行程序。

理解了Try…End Try结构的基本机制后,下面在该结构中添加语句,使异常发生并得到处理。

将过程的内容改为下列代码:

再按F5运行项目。单击按钮,然后看看“即时”窗口。这次“即时”窗口的输出应为:

Try

A first chance exception of type 'System.OverflowException' _

occurred in Structured Exception Handling.EXE

Catch

Finally

Done Trying

注意到这次执行了Catch部分中的代码。这是因为设置lngResult的语句导致了溢出异常。如果这条语句没有放在Try块中,Visual Basic将引发异常,并显示一个错误对话框。然而,由于这条语句被放在Try块中,因此异常被捕获。捕获的意思是当异常发生时,Visual Basic直接执行Catch部分的代码。不一定非得要使用Catch部分,如果省略了Catch部分,捕获的异常将被忽略。还要注意Finally部分中的代码在Catch部分的代码后执行。记住,Finally部分中的代码总是执行,不管是否有异常发生。

捕获异常使应用程序不会因发生异常而崩溃,这是件好事,但它只是错误处理的一部分。通常还要通知用户(以友好的方式)发生了异常。可能还要告诉用户发生的是哪种异常。为此,必须通过某种方式知道引发了什么异常。如果要编写代码来处理特定的异常,这也很重要。Catch语句让您能够指定一个变量来存储Exception对象的引用。使用Exception对象,可以获得有关异常的信息。下面是将异常放在Exception对象中的语法:

Catch variablename As Exception

修改Catch部分,使其如下:

Catch ex As Exception

 Debug.WriteLine("Catch")

 MessageBox.Show("An error has occurred: " & ex.Message)

Exception对象的Message属性包含描述发生的异常的文本。运行程序并单击按钮,Visual Basic将显示自定义的错误消息,如图15.10所示。

图15.10 结构化异常处理让您能够决定在异常发生时如何办

注意:和其他代码结构一样,Visual Basic有一条语句可用于退出Try…End Try结构,这就是Exit Try。然而,如果使用Exit Try语句,将跳转到Finally部分执行,然后继续执行End Try语句后面的语句。

有时能够预知可能引发的异常。例如,您可能编写代码打开一个文件,而这个文件并不存在。在这种情况下,可能希望程序在异常引发时执行特定的操作。当您预计到特定的异常时,可以创建专门的Catch语句来处理这种异常。

上一节提到,可以使用Catch语句来获取当前异常的信息,例如:

Catch objException As Exception

通过创建通用的Exception变量,该Catch语句可以捕获Try部分引发的所有异常。要捕获特定的异常,只需将异常变量的数据类型改为特定的异常类型。还记得前面编写的那段代码吗?试图将空字符串传递给 CLng()函数时,它将导致 System.InvalidCastException 异常。可以使用Try…End Try结构来处理这种异常,代码如下:

注意到这个Try…End Try结构中有两条Catch语句。第一条Catch语句只捕获溢出异常;它不会捕获其他类型的异常。第二条Catch语句则不关心引发的异常是哪种,而是捕获所有的异常。第二条Catch语句捕获除溢出异常外的所有异常,因为Catch部分是自顶向下执行的,与Select Case结构中的Case语句很像。如果需要的话,可以增加更多的Catch部分来捕获其他特定类型的异常。

在下面的示例中,将在第 11小时创建的Picture Viewer项目的基础上继续开发。首先来看一下要捕获的异常。执行下列步骤来引发异常。

1.按F5运行项目。

2.单击工具栏中的Open Picture按钮显示Select Picture对话框。

3.在文本框“文件名:”中输入*.*,然后按回车。这将修改过滤器,以便能够选择非图像文件。在硬盘上找到一个非图像文件,扩展名为.txt、.ini或.pdf的文件都可以。

4.找到非图像文件后,单击以选中它,然后单击“打开”。

这将导致内存不足异常,如图15.11所示。这是试图加载非图像文件时图片框引发的异常。您的第一反应可能是:“为什么要担心这个问题?没有人会这样做。”在编程的时候,需要花费大量的时间编写代码以防止用户可能导致的错误。这并不公平,通常也并不有趣,但这是现实。

图15.11 您不希望有未被处理的异常发生

选择工具栏中的“停止调试”,停止运行项目。下面将显示该OpenPicture()过程的新代码,这要比一步一步讲解修改过程更容易。将代码改为如下所示:

上述代码在过程中添加了一个错误处理程序,它负责捕获和处理内存不足异常。按F5运行项目,然后执行刚才的步骤加载一个非图像文件。现在,应用程序将显示一个更友好的自定义消息对话框,而不是收到由IDE引发的异常,这样应用程序将不会崩溃,如图15.12所示。

图15.12

虽然已经消除了因用户选择的不是有效图片文件而导致内存不足异常的可能性,但您应该知道修改后的这段代码存在的一些问题。

如果该过程中的其他代码导致了内存不足异常,错误消息将误导用户。可以只将加载选中图片到图片框中的语句放到Try…End Try结构中,从而解决这个问题。

如果这个过程发生了另一种类型的异常,该异常将被忽略。可以创建通用的 Catch块来捕获其他所有异常。

可以看到,通过添加Try…End Try结构来处理异常的机制比较简单,困难的是知道要捕获哪种异常以及在异常发生时如何处理。

在本章中,读者学习了调试应用程序的基础知识。学习了在过程中加入大量有用的注释,以使调试更容易。然而,无论注释多么好,代码仍然会有错误。

读者学习了两种基本错误:编译错误和运行错误(异常)。编译错误比较容易解决,因为编译器会准确地告诉您哪一行有编译错误,而且通常会提供关于如何修正错误的有用信息。而异常如果处理不当的话,将使应用程序崩溃。读者学习了如何使用断点和“即时”窗口来追踪异常。最后,读者学习了如何使用Try…End Try结构来创建结构化错误处理程序,使应用程序更健壮。

没有书可以教会您编写没有错误代码的所有知识。然而,本章介绍了追踪和清除程序中多种错误的基本技巧。随着编程水平的提高,读者的调试能力也将提高。

问:异常发生时,应提醒用户还是让代码继续运行?

答:如果编写了处理特定异常的代码,则可能没有必要通知用户。然而,如果代码不知道如何处理发生的异常,就应将有关异常的信息提供给用户,让他们能够准确报告问题,这样您就可以修复它。

问:应用程序中每条语句都需要注释吗?

答:不需要。但应考虑对程序中的每一个决策结构和循环结构进行注释。通常这部分代码是过程成功的关键,而它们的功能不太明显。

1.什么错误将阻止Visual Basic编译和运行代码?

2.运行错误又叫什么?

3.什么字符用于表示注释?

4.要使代码执行在特定语句处停止,可设置什么?

5.解释出现在代码编辑器灰色区域中的黄色箭头与红色圆圈。

6.在中断模式下,哪个IDE窗口可用于显示变量的内容?

7.判断对错:必须在Try…End Try结构指定Catch部分。

1.编译错误。

2.异常。

3.撇号(')字符。

4.断点。

5.黄色箭头表示在调试过程中将要执行的下一条语句;红色圆圈表示断点——到达这里后停止执行。

6.“即时”窗口。

7.错。如果省略了Catch部分,异常将被忽略。

1.在将lngAnswer设置为除法表达式结果的代码示例中,将lngAnswer从Long类型改为Single类型(并将该变量重命名为sngAnswer)。然后删除在除法运算前测试文本框内容的If语句。是否会发生与变量为Long类型时相同的异常?为什么?

2.重写将 lngAnswer设置为除法表达式结果的代码示例,把代码放在Try…End Try结构中。将执行数据有效性检查的If语句删除,并创建Catch部分来处理可能引发的异常。

本章将介绍:

使用类来封装数据和代码;

将类与标准模块进行比较;

创建对象接口;

将对象的特征暴露为属性;

将函数暴露为方法;

根据类实例化对象;

将对象引用绑定到变量;

释放对象引用;

理解对象的生命周期。

在第3章,读者学习了对象的特征。接下来,读者又学习了如何操纵对象,如窗体和控件。使用对象的优势在于能够设计和实现自定义的对象。在本章中,读者将学习如何使用类(而不是标准模块)创建自己的对象,还将学习如何为对象定义模板以及如何创建自定义的属性和方法。

注意:仅通过阅读一章内容就成为类编程专家是不可能的。然而,阅读本章后,读者将掌握创建类和由类生成对象所需的知识;将本章当成面向对象编程的入门课程。强烈建议读者熟悉本书介绍的知识后,再阅读专门介绍面向对象编程的其他书籍。

类让您能够运用面向对象编程(OOP)的技巧来开发应用程序(第 3 章简单地介绍了OOP)。类是定义对象的模板。虽然读者可能不知道,但在本书的编程中读者已经使用过类。在 Visual Basic项目中创建一个新窗体时,实际上创建了一个定义窗体的类;运行时的窗体实例由该类生成。由定义好的类(如Visual Basic的Form类)生成对象只是面向对象编程的优点之一,要真正领略面向对象编程的魅力,必须创建自己的类。

使用类进行编程的思想与传统的编程思想截然不同。适当的类编程技巧将使程序更好,不管是结构方面还是可靠性方面。类编程迫使您更全面地考虑代码和数据的逻辑,从而能够创建复用性和扩展性更强的基于对象的代码。

根据类生成的对象是数据和代码的封装体;也就是说,对象由其代码和其使用的所有数据组成。例如,可能需要对组织中的员工进行跟踪,因此必须存储每个员工的许多信息,包括姓名、雇用日期和职位等。除此之外,假设需要一些方法来增加和删除员工,且希望所有这些信息和功能对应用程序中的许多函数都是可用的。可以使用标准模块来操纵这些数据作,但这样做需要许多变量数组以及很多管理这些数组的代码。

更好的方法是将所有员工数据和功能(添加或删除例程等)封装在一个可复用的对象中。封装是将数据和代码整合到一个实体——对象——中的过程。然后,应用程序和外部的应用程序可以通过统一的接口——Employee对象的接口——使用这些员工数据。接口是暴露的功能集——实质上就是定义方法、属性和事件的代码例程。

数据和代码的封装是类的重要概念。通过将数据和操纵数据的例程封装在单个对象中,可使应用程序中需要操纵数据的代码不受数据维护的影响。例如,假设公司的政策发生了变化,添加新员工到系统中时,必须生成一条纳税记录,并打印一个表格。如果数据和代码例程没有封装在对象中,而是分布在代码的不同地方,就必须修改包含用于创建新员工记录的代码的每个模块。若使用类创建对象,则只需修改一个地方的代码:类中。这样,只要不修改对象的接口(稍后将介绍),所有使用该对象来创建新员工的例程都能够立即体现这种政策变化。

在Visual Studio设计环境中如何显示以及如何编写代码方面,类和标准模块是相似的。然而,在运行阶段,类和标准模块的行为截然不同。在标准模块中,所有模块级数据(静态变量和模块级变量)由模块中所有的过程共享。另外,模块数据不会有多个实例。对于类,对象根据类实例化,每个对象都有自己的一组数据。

正如第11章介绍的,标准模块中的模块级变量只在应用程序的生命周期内存在。然而,类的模块变量只在对象的生命周期内存在。可以根据需要创建和销毁对象,对象被销毁时,其所有数据也将被销毁。

类和标准模块在很多方面都不同,而不仅仅在其数据的行为方面。定义标准模块时,其公有函数和过程对应用程序中的其他模块立刻可用。然而,类的公有函数和过程对程序来说并非立刻可用。在运行阶段,您的代码并不与类模块的代码交互,而是根据类来实例化对象。每个对象都作为类“模块”,因此有一组模块数据。将类暴露给其他应用程序时,包含类代码的应用程序称为服务器,创建并使用对象实例的应用程序称为客户。在包含类的应用程序中使用这些类时,该应用程序既是服务器也是客户。在本章中,将根据类实例化对象的代码称为客户代码。

首先创建一个新的Windows应用程序项目,将其命名为Class Programming Example,然后执行下列步骤来创建项目。

1.将窗体名改为ClassExampleForm.vb,并将窗体的Text属性设置为Class Programming Example。

2.选择菜单“项目”>“添加类”为项目添加一个新类,将类保存为clsMyClass.vb,如图16.1所示。

图16.1 像添加其他对象文件一样将类添加到项目中

要根据类创建对象,类必须暴露接口。前面提到,接口是一组暴露的功能(属性、方法和事件)。客户代码通过接口与根据类生成的对象进行通信。有些类暴露有限的接口,而有些类暴露复杂的接口。类接口的内容和数量完全由您决定。

类的接口由一个或多个下列成员组成:

属性;

方法;

事件。

例如,假设要创建一个Employee类(也就是用于生成Employee对象的类),首先必须决定希望客户代码如何与对象交互。需要同时考虑对象包含的数据和对象能够执行的功能。您可能希望客户代码能够检索员工的姓名和其他信息,如性别、年龄和雇用日期。要让客户代码能够从对象获得这些信息,对象必须为每项信息暴露一个接口成员。第3章提到,对象暴露的值称为属性。因此,这里讨论的每项信息都将暴露为Employee对象的一个属性。

除属性外,还可暴露函数——如函数Delete和AddNew。这些函数可能很简单,也可能很复杂。例如,Employee对象的Delete函数可能很复杂,它需要执行删除员工所需的全部操作,包括将员工从所在的部门删除、通知会计部门将该员工从薪水册中删除、通知安全部门撤销该员工的访问权限等。第3章提到,对象对外暴露的函数称为方法。

属性和方法是最常用的接口成员。虽然设计属性和方法对您来说可能不熟悉,但使用它们并不陌生,几乎前面每章都使用过属性和方法。下面将学习为自定义的对象创建属性和方法的技巧。

为使客户能够和对象进行更多的交互,可以暴露自定义事件。自定义的对象事件与窗体或文本框的事件类似。然而,对于自定义事件,可以完全控制以下信息:

事件名;

传递给事件的参数;

事件何时触发。

注意:创建自定义事件很复杂,本章只介绍自定义属性和方法。

属性、方法和事件构成对象的接口。接口是客户程序和对象之间的合约。客户和对象之间的任何通信都必须通过接口进行,如图16.2所示。

图16.2 客户通过接口与对象交互

客户与对象通过接口进行交互的技术细节由Visual Basic处理。您只负责定义对象的属性、方法和事件,使对象的接口合理、一致,并暴露客户高效使用对象所需的全部功能。

1.将对象的特征暴露为属性

属性是对象的特征。属性可以是只读的,也可以是既可读又可写的。例如,您可能允许客户查询存储组件路径的属性的值,但不允许客户修改它,因为运行的组件的路径不能修改。

要在类中添加属性,有两种方法。第一种是声明公有变量。被声明为公有的变量立即成为类的属性(实际上它只是像属性,从技术上来说并不是属性,而是字段)。例如,假设在类的声明区域中有下列语句:

Public Quantity as Integer

客户可以使用如下所示的代码来读写该字段:

objMyObject.Quantity = 42

虽然这种方法可行,但存在着严重的局限性。

不能在字段值改变时执行代码。例如,如果要将对Quantity的修改写入数据库,如何实现呢?由于客户程序能够直接访问变量,因此没有办法知道变量的值何时改变;

不能阻止客户代码修改字段,因为客户代码可以直接访问变量;

可能最大的问题是,如何控制数据的有效性?例如,如何保证Quantity不会被设置为负数?

使用公有变量无法解决这些问题。因此,不应该暴露公有变量,而应使用属性过程来创建类属性。

属性过程让您能够在属性改变时执行代码,验证属性值的有效性,规定属性是只读的、只写或可读写的。属性过程的声明与标准函数或子程序的声明类似,但有一些重要区别。属性过程的基本结构如下所示:

属性声明中的第一个单词指明了属性的作用域(通常是Public、Private或Friend)。使用Public声明的属性对于类外部的代码是可用的(可被客户代码访问)。如果应用程序将对象暴露给其他应用程序,Public方法在应用程序外部是可见的。声明为Friend的过程与Public过程相似,但在应用程序外部不可用。声明为private的属性只在类内部可用。紧接在作用域标识符后的是Property,它告诉Visual Basic,您要创建一个属性过程,而不是子程序或函数。接下来是属性名和数据类型。

在类中输入下列语句:

Private m_intHeight As Integer

Public Property Height() As Integer

输入这些语句后,按回车键提交。Visual Basic为您填充余下的过程模板,如图 16.3所示。

您可能想知道为什么要创建一个与属性过程同名(当然使用的前缀不同)的模块级变量。毕竟我刚刚讲到,使用模块级变量作为属性会出现问题。原因是属性必须从某个地方获得它的值,而模块级变量通常是存储属性的最好地方。属性过程将封装这个变量。注意,变量声明为Private而不是Public。这意味着类外的代码不能查看或修改该变量的内容;对于客户代码来说,这个变量是不存在的。

属性声明语句和End Property语句之间是两个结构:Get结构和Set结构。下面将分节讨论这两个结构。

图16.3 Visual Basic创建属性过程模板

使用Get结构创建可读的属性

Get结构用于放置在客户读取时返回属性值的代码。

可以把Get结构当成函数,函数返回的值将成为属性值。在语句Get和End Get之间添加下述语句:

Return m_intHeight

这条语句在客户代码查询Height属性时返回变量m_intHeight的值。

使用Set结构创建可写的属性

在Set结构中放置代码,用以接收来自客户代码的新属性值。

在语句Set和End Set之间添加下面的语句:

m_intHeight = Value

仔细查看Set语句,将发现它类似于子程序声明,其中Value是一个参数。Value中包含客户代码传递给属性的值。刚才输入的语句将新值赋给相应的模块级变量。

正如读者看到的,属性过程封装了模块级变量。客户代码设置属性时,Set 结构将新值存储到变量中;客户查询属性值时,Get结构返回模块级变量的值。

到目前为止,属性过程(包括Get和Set结构)与直接声明公有变量并没有什么区别(只是属性过程需要更多代码)。然而,看一下下述Set结构变体:

这个Set结构限制客户只能将Height属性设置为大于或等于10的值。如果小于10的值传递给该属性,属性过程将退出,而不会设置m_intHeight。不仅可以执行数据有效性验证;还可以添加需要的任意代码,甚至调用其他过程。在属性中添加验证语句,使 Set 结构与上述代码相同。现在,过程应如图16.4所示。

图16.4 这是一个属性过程,包含数据有效性验证

创建只读或只写属性

有时需要创建可读而不可修改的属性,这样的属性称为只读属性。在第3章讨论虚构的Dog对象时,谈到了创建一个NumberOfLegs属性。对于这样的对象,可能希望将属性暴露为只读的——客户代码可以获得腿数,而不能修改它。要创建只读属性,可使用关键字ReadOnly声明属性过程,并删除Set…End Set部分。例如,如果希望将前面创建的属性过程定义为只读过程,可如下声明:

提示:也可创建只写属性——只能设置而不能读取的属性,虽然这很少用。为此,使用关键字WriteOnly而不是ReadOnly,并删除Get…Endt Get而不是Set…End Set部分。

2.将函数暴露为方法

与作为对象特征的属性不同,方法是对象暴露的函数。方法不过是暴露的代码例程。方法可以返回一个值,但不一定非得这样。方法比属性更容易创建,因为可以像常规子程序和函数那样定义它们。要在类中创建一个方法,可创建一个公有的子程序或函数。现在,在类中创建下面的过程(在语句End Property的下一行输入这些代码):

同常规子程序和函数一样,使用Function定义的方法返回一个值,而使用Sub定义的不返回值。要使方法为类私有的(从而对客户代码不可见),只需将方法声明为 Private,而不是Public。

获得对象的引用并将其赋给变量后,就可以使用对象变量来操纵对象,下面将这样做。

单击“ClassExampleForm.vb[设计]”标签以显示窗体设计器,然后双击工具箱中的Button项,在窗体中添加一个按钮。按如下设置按钮的属性:

接下来,双击按钮访问它的Click事件,并输入下列代码:

Dim objMyObject As Object

objMyObject = New clsMyClass()

MessageBox.Show(objMyObject.AddTwoNumbers(1, 2))

第一条语句创建一个Object变量(变量声明在第11章讨论过)。第二条语句有必要解释一下。由于变量位于等号的左边,因此可以断定是给该变量设置值。

然而,等号右边的代码读者可能不熟悉。要将一个对象引用赋给变量,但还没有创建对象。关键字 new命令Visual Basic创建一个新对象,new后面的文本是用于创建对象的类名(记住,类是对象模板)。最后一条语句调用该类的AddTwoNumbers()方法,并将结果显示在一个消息框中。

按F5运行项目,然后单击按钮检验是否正常工作。完成后,停止运行项目并保存项目。

对象可以包含任意数量的属性、方法和事件;每个对象都是不同的。编写代码对对象进行操作时,Visual Basic必须理解对象的接口,否则代码将不能运行。接口成员(对象的属性、方法和事件)的解析发生在对象变量与对象绑定时。绑定有两种形式:早期绑定(early binding,发生在编译时)和晚期绑定(late binding,发生在运行时)。要创建基于类的代码,理解绑定非常重要。虽然本章不能解释早期绑定和晚期绑定的具体细节,但将介绍执行每种绑定所需的知识。

注意:这两种绑定各有优点,但早期绑定通常更好,因为与使用早期绑定对象的代码相比,使用晚期绑定对象的代码要求Visual Basic做更多的工作。

1.晚期绑定对象变量

将变量定义为 Object 数据类型时(如下列代码所示),变量与对象的绑定就是晚期绑定:

Dim objMyObject As Object

objMyObject = New clsMyClass()

MessageBox.Show(objMyObject.AddTwoNumbers(1, 2))

注意:启用了Option Strict时,不能使用晚期绑定。有关Option Strict的内容请参阅第11章。

晚期绑定对象时,绑定发生在运行阶段,此时变量被设置为对对象的引用。引用对象的成员时,Visual Basic必须为指定的成员确定并使用内部 ID。幸运的是,因为Visual Basic处理了所有这些细节,您不需要知道成员的 ID。只要知道 Visual Basic需要知道成员的 ID才能使用它。晚期绑定对象变量(使用As Object声明变量)时,后台将发生如下过程。

1.Visual Basic获取您要调用的属性、方法或事件的特殊 ID(调度 ID,Dispatch ID),这需要消耗时间和资源。

2.如果成员有参数,则创建一个包含成员参数的内部数组。

3.使用第1步获得的ID调用成员。

上面的步骤需要很大的开销,严重影响了应用程序的性能。因此,使用绑定时并不优先选择晚期绑定。晚期绑定在某些情况下具有优势,但大部分都与在应用程序外部使用对象有关,而不是在项目中使用根据类生成的对象。

晚期绑定的主要缺点之一是,编译器无法对操作对象的代码进行语法检查。由于它使用的成员 ID 及参数要到运行时才能确定,编译器无法知道您是否正确地使用了成员——甚至无法知道该成员是否存在。这可能导致运行异常或其他无法预测的行为。将上述代码中的第三条语句修改为如下所示(故意拼错了方法AddTwoNumbers):

MessageBox.Show(objMyObject.AddtoNumbers(1, 2))

按F5运行项目,没有任何问题——至少到目前为止是这样的。虽然方法名拼写不正确, Visual Basic 仍对这个项目进行了编译,而没有发生任何编译错误。这是因为变量被声明为As object,而Visual Basic不知道该变量将包含什么。因此,它不能在编译阶段进行任何语法检查,而假定您使用该变量执行任何操作都是正确的。现在单击按钮创建对象并调用方法,将出现图16.5所示的异常。

正如第 15 章指出的,运行阶段异常比编译错误更严重,因为面对这种异常的是最终用户,且出现异常的情形各不相同。使用晚期绑定时,很容易出现这种问题,因此,晚期绑定存在引发异常的风险。正如读者将在下一节看到的,使用早期绑定可极大地降低这种风险。

选择菜单“调试”>“停止调试”,停止执行项目。

图16.5 这样的异常是晚期绑定的风险所在

2.早期绑定对象变量

如果Visual Basic在编译时可以确定成员的调度 ID,就没有必要在运行阶段查询所引用成员的 ID了。这使得对对象成员的调用更快。不仅如此,Visual Basic还能在编译时验证成员调用,降低代码出错的概率。

早期绑定发生在将变量声明为特定类型的对象,而不是Object类型时。变量被早期绑定时,Visual Basic将在编译时而不是在运行时查询对象成员的调度 ID。

下面是使用早期绑定的一些重要理由:

速度;

还是速度;

对象及其属性、方法将出现在智能感知下拉列表中;

编译器可以检查代码中的语法和引用错误,这样,许多问题在编译时就可以发现,而不是等到运行时才发现。

要使用早期绑定,对象变量必须声明为特定的对象类型(而不是As Object)。将代码中的Dim语句修改为如下所示:

Dim objMyObject As clsMyClass

提交这条语句后,Visual Basic将在错误的方法名下显示一条蓝色波浪线,如图 16.6所示。这是因为 Visual Basic现在知道变量的包含的对象类型,因此它能够也确实对所有成员引用执行了语句检查。由于找不到名为AddToNumbers的成员,因此将其标记为编译错误。按F5键尝试运行项目,您将发现Visual Basic确实将之视为编译错误。

将光标放在单词objMyObject和AddToNumbers之间的句点上,删除句点,然后再输入句点,Visual Basic将显示一个智能下拉列表,其中列出了类的所有成员,如图 16.7所示。选择成员AddTwoNumbers,以修复代码中的问题。

图16.6 对于早期绑定的对象,Visual Basic将执行语法检查

图16.7 对于早期绑定的对象,Visual Basic将为之显示智能下拉列表

3.在声明变量时创建新对象

可以在声明语句中使用关键字new来实例化一个新对象,如下所示:

Dim objMyObject As New clsMyClass()

这种方法避免了使用另一条语句创建新对象实例。然而,如果这样做,该变量将始终包含一个对象引用。如果有可能不需要对象,应避免在声明语句中使用关键字 new。请看下面的代码:

注意,实例化对象需要消耗资源。在这段代码中,如果 condition 为 False,将不会创建对象。如果将关键字new放到声明语句中,则只要执行这段代码,就将创建一个新对象,而不管condition的值如何。

当不再需要对象时,应销毁它以收回它占用的所有资源。对对象的最后一个引用被解除时,对象将自动销毁。虽然解除对象引用有两种方法,但其中一种要明显比另一种好。

释放对象引用的一种方法是在存储对象引用的对象变量超出其作用域。第11章提到过,当变量超出其作用域时将被销毁,对象变量同样如此。然而,让对象变量超出其作用域时,对象不一定完全销毁,对象占用的内存也不一定会完全释放。因此,依赖于作用域来释放对象不是个好办法。

要显式地释放对象,将对象变量设置为Nothing,如下所示:

objMyObject = Nothing

将对象变量设置为 Nothing,可保证对象引用完全释放。然而,如果还有其他变量引用该对象,该对象将不会销毁!最后一个引用被释放后,垃圾回收器将销毁对象(垃圾回收器将在第24章介绍)。将上述语句加入到过程中——放在显示消息框的语句后面。

如果没有正确地释放对象引用,应用程序可能发生资源泄漏:运行速度缓慢,消耗更多的资源。

只要有变量存储了对它的引用,根据类生成的对象就将一直存在。幸运的是,Visual Basic (更确切地说,是第24章将讨论的.NET框架)负责处理跟踪对象引用的细节;开发人员在创建或使用对象时不必关心这些细节。当对象的所有引用都被释放后,对象将被标记,最终被垃圾回收器销毁。

下面是关于对象生命周期的一些要点以及它们对应用程序的影响。

使用关键字new声明对象变量时,将创建(并引用)一个对象;例如:

Dim objMyObject = New clsMyClass()

使用关键字new将对象赋给对象变量时,将创建(并引用)一个对象;例如:

objMyObject = New clsMyClass()

当将已有的对象赋给对象变量时,该对象将被引用,例如:

objThisObject = objThatObject

将对象变量设置为Nothing时,对象引用将被释放(参见前一节);

当对象的最后一个引用被释放时,对象将被销毁。这项工作由第24章将讨论的垃圾回收器处理。

理解对象的生命周期很重要。现在您已经知道如何以及何时创建对象引用,但还需要知道如何显式地释放对象引用。只有当对象的所有引用都被释放后,对象才能被标记为可销毁的,它占用的资源才能被回收。

面向对象编程是一种高级方法,让您能够创建更加健壮的应用程序,而类编程是 OOP的基础。在本章中,读者学习了如何创建类,它是用于实例化对象的模板。还学习了如何创建由属性和方法组成的自定义接口,以及如何通过对象变量,使用已定义的类来实例化和操纵对象。

就OOP功能而言,Visual Basic无法与C++等语言相比。在本章中,读者学习了使用类进行对象编程的基本技巧。面向对象编程要求相当高的技能,要真正发挥OOP的优势,必须掌握本书介绍的概念。尽管如此,您在本章学到的知识将远远超出想象。OOP方法是一种编程方式,同时也是一种思维方式;考虑使用对象来构建项目,这样必须创建健壮的类。

问:总是应尽可能将代码放在类的标准模块中吗?

答:不必。与大多数事情一样,这没有绝对的原则。正确地编写类需要一些技巧与经验,而编写标准模块对初学者来说比较容易。如果要尝试编写类,鼓励您这样做。然而,不要认为必须将所有代码都放到类中。

问:要创建一个包含各种方法的通用类,最好的方法是什么?

答:如果要创建实用类,建议调用像clsUtility这样的类。创建一个全局变量来存储使用这个类实例化的对象;在程序的启动代码中,将全局变量设置为指向这个类的新实例。然后,便可以在整个应用程序中,使用这个全局变量来访问那些实用函数,而不用每次要调用这些函数时都实例化一个新对象。

1.要创建对象,必须先创建模板,这个模板称为什么?

2.面向对象编程的主要优点之一是,对象包含其数据和代码,这称为什么?

3.在标准模块中,公有变量和例程对其他模块中的代码总是可用的。类中的公有变量和例程是否也如此?

4.判断对错:根据类生成的每个对象都有自己的模块级数据。

5.如何创建客户代码可读取而不可修改的属性?

6.在类中储存属性的内部值的最佳方法是什么?

7.早期绑定与晚期绑定哪个更好?

8.如果对象变量声明为As Object,它将被早期绑定还是晚期绑定?

9.销毁对象引用的最佳方法是什么?

1.类。

2.封装。

3.不是,必须先实例化一个对象才能访问公有变量和方法。

4.对。

5.使用ReadOnly声明属性过程,并删除Set…End Set部分。

6.将内部值储存在一个模块级私有变量中。

7.早期绑定通常都比晚期绑定好。

8.晚期绑定。

9.将对象变量设置为Nothing。

1.在类DropsInABucket中添加一个新属性。将该属性定义为Long型,并将它设置为客户代码可读取而不可修改。最后,在窗体中添加一个按钮,当它被单击时,将属性的值打印到“即时”窗口中(这将默认为0)。完成后,修改代码使该属性总是返回1000000。

2.在窗体中添加一个按钮,用于创建两个clsMyClass()型的对象变量。对于其中一个变量,用关键字New创建类的一个实例。然后,将第二个变量设置为引用该对象,并将Height属性的值打印到“输出”窗口或显示在消息框中。

在本章中,读者将学习:

使用Message.Show()函数显示消息;

创建自定义对话框;

使用InputBox()从用户那里获取信息;

与键盘交互;

使用常见的鼠标事件。

窗体和控件是用户和应用程序进行交互的主要途径。然而,程序交互不仅是这些。例如,程序可以显示定制的消息给用户,还能够处理键盘击键和鼠标单击事件。在本章中,读者将学习如何在应用程序与用户之间创建完善和紧密的交互。另外,还将学习对键盘和鼠标进行编程,在窗体和控件固有的功能外扩展程序的可交互性。

消息框是小型对话框,显示消息给用户(以防不够清楚)。消息框通常用于告诉用户某个操作的结果,例如“文件已复制”或“找不到文件”等。当用户单击消息框中的按钮时,消息框将消失。大多数应用程序都有许多消息框,但开发人员不总是能正确地显示消息。显示消息给用户时,是在和用户交流,牢记这一点很重要。在本节中,不仅将介绍如何使用MessageBox.Show()函数来显示消息,还将介绍如何有效地利用这条语句。

MessageBox.Show()函数可用于告诉用户一些事情或询问用户一个问题。除显示文本外(这是这个函数的主要功能),该函数也可用来显示图标、一个或多个用户可单击的按钮。显示的文本可以是任意的,但图标和按钮必须从已定义的图标和按钮列表中选择。

MessageBox.Show()是一个被重载的方法。这意味着该方法有多种结构用以支持各种选项。在 Visual Basic中输入代码时,智能感知将显示一个滚动下拉列表,显示 21个重载的MessageBox.Show()函数调用,以帮助编写代码。下面是调用MessageBox.Show()的几种方式。

要显示指定的文本、在标题栏显示标题以及“确定”按钮,使用如下语法:

MessageBox.Show(MessageText, Caption)

要显示包含指定文本、标题、一个或多个按钮的消息框,使用如下语法:

MessageBox.Show(MessageText, Caption, Buttons)

要显示包含指定文本、标题、按钮和图标的消息框,使用如下语法:

MessageBox.Show(MessageText, Caption, Buttons, Icon)

在所有这些语句当中,MessageText是消息框中要显示的文本,Captions决定了显示在消息框标题栏中的文本,Butttons决定了用户看到的按钮,Icon决定消息框要显示什么图标(如果有的话)。请看下面的语句,它显示图17.1所示的消息框:

MessageBox.Show("This is a message.", "Hello There")

图17.1 一个简单的消息框

应确保显示的按钮对于消息是合适的。可以看到,如果省略Buttons,Visual Basic将只显示“确定”按钮。

注意:老式 MsgBox()函数(现在虽然得到支持,但不建议使用它)默认将项目名用作消息框的标题。MessageBox.Show()没有默认标题,因此您总是应该指定标题,否则消息框的标题栏将为空。

使用 Buttons 参数,可以在消息框中显示一个或多个按钮。Buttons 参数的类型是MessageBoxButtons,表17.1列出了可用的值。

表17.1 可用于MessageBoxButtons的枚举

因为Buttons参数是枚举类型,因此为该参数指定值时,Visual Basic将显示智能感知下拉列表。所以,记住这些值并不重要;那些最常用的值您很快就能记住。

Icon参数决定消息框中显示的符号。Icon参数是MessageBoxIcon 类型的枚举。表17.2列出了MessageBoxIcon最常用的值。

表17.2 MessageBoxIcon常用的枚举值

Icon参数也是枚举类型;因此,为该参数指定值时,Visual Basic也将显示智能感知下拉列表。您可能发现了,Exclamation和Warning的图标相同。我只能说,这是向Visual Basic早期版本的倒退,但这两个图标都可行。选择使用哪个图标都可以,但为保证一致,建议您始终使用同一个图标。

下面代码显示图17.2所示的消息框。

MessageBox.Show("I’m about to do something...","MessageBox sample", _

 MessageBoxButtons.OKCancel,MessageBoxIcon.Information)

图17.2 对普通消息使用Information图标

图17.3中的消息框所用的代码与上面的代码几乎相同,唯一的不同在于第二个按钮被指定为默认按钮。如果在消息框显示时用户按回车键,消息框将认为用户单击了默认按钮。在每个消息框中将哪个按钮作为默认按钮,都需要仔细考虑。例如,如果程序要执行用户可能不希望的行为,最好将“取消”按钮作为默认按钮——以防用户过快地按下回车键。下面是产生图17.3所示消息框的代码:

图17.3 默认按钮有蓝色边框

Error图标如图17.4所示。最好将Error图标用于一些罕见的情况,如异常发生时。滥用Error图标就像“狼来了”——当真正的问题发生时,用户可能不会注意。注意这里我只显示了“确定”按钮。如果发生某些情况而用户无法采取任何措施,不要显示给用户“取消”按钮。下面的语句生成图17.4所示的对话框:

MessageBox.Show("Something bad has happened!","MessageBox sample", _

 MessageBoxButtons.OK, MessageBoxIcon.Error)

图17.4 如果用户无法控制已经发生的事情,不要显示“取消”按钮

在图17.5中,消息框向用户提出了一个问题,因此消息显示了一个Question图标。另外,消息框设想用户可能会选择“No”,因此将第二个按钮设置为默认按钮。在下一节中,读者将学习如何判断用户单击的是哪个按钮。下面是用于生成图17.5所示消息框的语句:

图17.5 消息框可用于询问问题

可以看出,指定按钮和图标并不难。真正要注意的是,如何确定在给定情况下显示哪些按钮和图标是合适的。

您可能已经看出,许多消息框都很简单,只包含一个“确定”按钮。但对于另外一些消息框,必须确定用户单击了哪个按钮。难道让用户作出选择,您却什么也不做吗?

MessageBox.Show()函数将被单击的按钮作为 DialogResult 枚举值返回。DialogResult 的可能取值如表17.3所示。

表17.3 DialogResult枚举

注意:注意 DialogResult 值描述中的“通常由……发出”。创建自定义对话框时(稍后将介绍),可以将DialogResult值分配给任何选择的按钮。

要根据单击的按钮执行相应的操作,需要使用决策结构。例如:

可以看到,MessageBox.Show()函数提供了很多选择;它提供了极大的灵活性。

MessageBox.Show()方法非常容易使用,您可以创建各种形式的消息。真正的技巧在于在适当的时候显示适当的消息给用户。除了要考虑在消息中显示的图标和按钮外,在编写消息文本时,还应遵循下列原则。

使用正式语言。不要使用很长的词,避免使用缩写。尽量使文本能被立即理解、不要太花哨;消息框不是表现您文字功底的地方。

将消息限制在两三行内。冗长的消息不仅让用户很难读懂,且看起来可能很吓人。当消息框用于询问问题时,问题要尽量简洁。

不要让用户觉得他们做错了什么。用户确实可能犯错,但显示的消息不应让用户产生这种感觉。

检查所有消息文本的拼写。Visual Basic代码编辑器不检查消息文本的拼写,因此应在Microsoft Word之类的程序中输入消息,经过拼写检查后再粘贴到代码中。拼写错误将使用户对应用程序产生不好的印象;

避免使用术语。用户使用软件并不意味着他们是技术人员,用英语(或GUI使用的语句)进行解释;

保证按钮与文本匹配!例如,如果消息没有向用户提出问题,就不要显示“是”/“否”按钮。

大多数时候,MessageBox.Show()函数足以将消息显示给用户。但有时候,MessageBox.Show()函数不足以实现目的。假设要显示许多文本给用户,如某种日志文件,您将希望用户能够调整消息框的大小。

自定义对话框其实就是一个标准的模态窗体,但有一个差别值得注意:自定义对话框可以指定一个或多个按钮用于返回对话结果,这与用 MessageBox.Show()函数显示在消息框中的按钮返回对话结果一样。

下面创建一个自定义对话框。创建一个新的Windows应用程序项目,将其命名为Custom Dialog Example,然后执行下列步骤来创建项目。

1.将默认窗体名改为MainForm.vb,并将窗体的 Text属性设置为 Custom Dialog Box Example。

2.在窗体中添加一个新按钮,并如下设置它的属性:

3.接下来将创建自定义对话框。为此,选择菜单“项目”>“添加Windows窗体”,将一个新窗体添加到项目中。将新添加的窗体保存为CustomDialogBoxForm.vb。

4.将新窗体的Text属性改为This is a custom dialog box,并将其FormBorderStyle属性设置为FixedDialog。

5.在窗体中添加一个新文本框,并如下设置它的属性:

对于和标准消息框一样要返回结果的自定义对话框,必须指定返回对话框结果的按钮。这可以通过设置按钮的DialogResult属性来实现,如图17.6所示。

图17.6 DialogResult 属性决定按钮的返回值

6.在窗体中添加一个新按钮,并按下表设置其属性。该按钮将作为自定义对话框的取消按钮。

7.还要为自定义对话框创建确定按钮。为此,创建另一个按钮,并按如下设置其属性:

为一个或多个按钮指定对话结果是使窗体成为自定义对话框的第一步。第二步是如何显示窗体。第5章讲过,窗体是通过调用窗体变量的Show()方法来显示的。然而,要将窗体作为自定义对话框显示,应调用ShowDialog()方法。当窗体使用ShowDialog()方法显示时,具有下列特点:

窗体显示为模态窗体;

如果用户单击了一个按钮,且其 DialogResult 属性被设置为返回一个值,窗体将立即关闭,该值作为ShowDialog()方法调用的结果返回。

注意,不必编写代码来关闭窗体;单击返回对话结果的按钮将自动关闭窗体。这简化了创建自定义对话框的过程。

8.在“解决方案资源管理器”中双击frmMain.vb,返回到窗体设计器中的第一个窗体。

9.双击已创建的按钮,添加下列代码:

在ShowDialog()之后输入等号和空格时,是否注意到Visual Basic显示了一个智能感知下拉列表,用于列出可能的对话结果?这些结果对应于可分配给按钮的DialogResult属性的值。按F5运行项目,单击按钮,显示自定义对话框(如图17.7所示),然后单击对话框中的一个按钮。看到项目在正确运行后,停止运行项目并保存项目。

图17.7 ShowDialog()方法让您能够创建自定义消息框

注意:如果单击窗体右上角的“关闭”按钮(“×”),窗体将关闭,代码的行为将与单击了Cancel按钮一样,因为都执行了Else代码。

能够创建自定义对话框是一项强大的功能。通常调用MessageBox.Show()函数就足够了,但有时需要对消息框的外观和内容进行更多的控制,这时就需要创建自定义对话框。

方法MessageBox.Show()让您能够向用户提出“是”/“否”、“确定”/“取消”型问题,但不能从用户那里获得具体的输入,如文本或数字。需要从用户那里获得输入时,有两种选择:

创建一个包含一个或多个控件的窗体,用于收集数据;

使用函数InputBox()收集用户数据。

函数 InputBox()只能收集一个数据,因此不适合用于大部分数据输入的情况。然而,在有些情况下,只需要收集一个数据,此时创建一个自定义对话框显得有些小题大做。例如,假设有一个简单的应用程序,要求用户启动应用程序后输入姓名。为此,可以使用函数InputBox()让用户输入其姓名,而无需设计一个专用窗体。

函数InputBox()的基本语法如下:

InputBox(prompt, [title], [defaultresponse])

前两个参数与函数 MessageBox.Show()中相应的参数类似。第一个参数是向用户显示的提示,在这里可以指定说明或要向用户提出的问题。参数title指定出现在标题栏中的文本,同样,如果省略title,将显示项目的名称。下面的语句创建如图17.8所示的输入框:

strResult = InputBox("What is your favorite color?", "Enter Color")

图17.8 输入框让用户能够输入一项数据

最后一个参数defalutresponse让您能够指定在输入框的文本框中显示的默认文本。例如,下面的语句生成图17.9所示的输入框:

strResult = InputBox("How many eggs do you want to order?", "Order Eggs", "12")

图17.9 可以为用户指定默认值

有关函数 InputBox()的返回值,有两点需要牢记。首先,结果总是一个字符串;其次,如果用户单击“取消”按钮,将返回一个空字符串。InputBox()只能返回字符串,而不能返回数字,这是其局限性,但可以避开这种局限性。

下面创建一个项目,使用 InputBox()来获取用户的年龄。如果用户单击“取消”按钮或输入的不是数字,程序将进行相应的处理。首先创建一个名为 InputBox Example的Windows应用程序,然后执行下列步骤来创建该项目。

1.将窗体从默认名称改为MainForm.vb,然后将其Text属性设置为 InputBox Example。

2.在窗体中添加一个新按钮,并按如下设置其属性:

3.双击该按钮访问其Click事件,并输入如下代码:

除调用 InputBox()外,该过程中的代码都是读者熟悉的。在这个过程中,使用函数InputBox()请求用户输入其年龄。由于InputBox()返回一个字符串,因此使用一个字符串变量来存储结果。接下来,使用一个 If…Else If…Else…End If结构来判断函数调用 InputBox()的结果。首先检测返回的是否是空字符串,如果是,则认为用户单击了“取消”按钮;接下来检测结果是否是数字,如果是,则将其转换为整数并将其显示给用户。按F5测试该项目。

注意:如果用户没有输入任何内容,将返回一个空字符串,就像单击了“取消”按钮一样。使用InputBox(),没有办法确定用户是单击了“取消”按钮还是没有输入任何内容。

InputBox()是一个方便收集单个数据的函数。正如读者看到的,可以避开该函数返回一个字符串的局限性。牢记函数InputBox(),它经常能够派上用场。

虽然窗体上几乎每个控件都自己处理键盘输入,但有时候仍希望直接处理键盘输入。例如,您可能希望在用户按下或松开某个键时执行特定的操作。大多数控件都支持三个可用于直接处理键盘输入的事件。表17.4列出了这三个事件。

表17.4 处理键盘输入的事件

这三个事件以它们在表17.4中出现的顺序被触发。例如,假设用户在文本框获得焦点时按下一个键,下面是文本框将触发的事件。

1.用户按下键时,触发KeyDown事件。

2.一旦键被按下,将触发KeyPress事件;只要键被按住,这个事件将重复触发。

3.用户松开键时,触发KeyUp事件,完成击键事件周期。

如果要创建文本编辑器并响应用户的每次击键,将需要使用KeyPress事件。这是唯一适用的事件,因为用户按下键时,Windows将在文本框或其他数据输入控件中重复该键。如果用户按住键不放,KeyPress事件将不断触发;但每按一次键,KeyDown和KeyUp事件都只触发一次,而不管按键时间多长。

下面创建一个演示击键处理的项目。该项目有一个文本框,它拒绝接受非数字字符,因此将创建一个数字文本框。创建一个新的 Windows 应用程序项目,将其命名为 Keyboard Example,然后执行下列步骤来创建该项目。

1.在“解决方案资源管理器”中右击 Form1.vb,选择“重命名”,将默认窗体名改为KeyboardExampleForm.vb,并将窗体的Text属性设置为Keyboard Example。

2.在窗体中添加一个新文本框,并如下设置它的属性:

3.下面为文本框的 KeyPress 事件添加代码,使文本框忽略非数字键击。双击文本框访问其默认事件。

4.我们对事件TextChanged不感兴趣,因此在代码窗口右上角的事件列表中选择KeyPress。

5.删除TextChanged事件过程,因为不使用它。代码编辑器现在应如图17.10所示。

图17.10 KeyPress 事件是处理键盘输入的好地方

第4章说过,e参数包含与发生的事件相关的信息。在与键盘相关的事件中,e参数包含有关被按下的键的信息;处理用户键击时将使用这些信息。

按下的键可通过e参数的KeyChar属性获悉。下面编写代码,处理按下的键不是数字的键击。

在KeyPress事件中添加下列代码:

If Not (IsNumeric(e.KeyChar)) Then

e.Handled = True

End If

您可能对e对象的Handled属性充满好奇。如将该属性设置为True,将告诉Visual Basic您已经处理了键击,Visual Basic应忽略它(也就是不将它添加到文本框中)。要查看其效果,按F5键运行项目,然后在文本框中输入字母和数字,将发现只有数字出现在文本框中(如图17.11所示)。

图17.11 键盘事件让您能够根据需要处理键击

粘贴剪贴板中的数据时,并非每次击键都将触发KeyPress事件。因此,文本框中可能出现非数字字符。如果要绝对防止非数字字符出现在文本框中,还需要使用TextChanges事件。

提示:需要捕获按键事件的情况不是很多,但有时还是需要这样做。表7.4列出的三个击键事件让您完成所需的工作更容易,但有一个问题,就是需要仔细考虑选择哪个事件(例如,KeyPress还是KeyUp)。不同的事件适合不同的情况,最好首先选择最合乎逻辑的事件,再测试代码,并在必要时改用其他事件。

和键盘输入一样,大多数控件也支持鼠标输入:您不必编写代码来处理鼠标输入。但有时候,可能需要比控件提供的内置功能更多的控制。Visual Basic支持 7种可用于直接处理鼠标输入的事件,表17.5按发生顺序列出了这些事件。

表17.5 用于处理鼠标输入的事件

下面创建一个项目,演示使用 MouseMove 事件与鼠标进行交互。这个项目让用户能够在窗体上画图,就像在画图程序中那样。现在创建一个新的Windows应用程序项目,将其命名为Mouse Paint,然后执行下列步骤来创建该项目。

1.在“解决方案资源管理器”中右击 Form1.vb,选择“重命名”,将默认窗体名改为MainForm.vb,并将窗体的Text属性设置为Paint with the Mouse。

2.双击窗体访问它的默认事件:Load事件。将下列语句输入到Load事件中:

m_objGraphics = Me.CreateGraphics

您已经使用过graphics对象多次。这里所做的是将graphics对象设置为窗体的客户区域;在该对象上执行的任何画图操作都将显示在窗体上。因为鼠标每次在窗体上移动时都将绘制到该 graphics 对象上,因此,没有必要每次需要绘制时都创建新的 graphics 对象。所以将m_objGraphics定义为模块级变量,它只实例化一次——在窗体的Load事件中。

3.在窗体类的声明部分(语句Public Class MainForm和事件过程的Private Sub语句之间)输入下面的语句:

Private m_objGraphics As Graphics

前面提到,使用完对象后应将其销毁。在这个例子中,您希望这个对象在窗体的生命周期内一直存在。因此,在FormClosed事件中销毁该对象,该事件在窗体被卸载时发生。

4.打开对象列表(左上角的列表),从中选择“(MainForm事件)”;然后打开过程列表(右上角的列表),从中选择FormClosed。在FormClosed事件中输入下列语句:

m_objGraphics.Dispose()

现在过程应如图17.12所示。

图17.12 多处代码协同工作来实现一个目标

最后要添加的是在窗体上画图的代码,这些代码将放置到窗体的 MouseMove 事件中。首先,代码确保鼠标左键被按下。如果没有按下,就不能进行绘画;用户必须按住鼠标左键才能画图。接下来创建一个矩形。根据鼠标的坐标创建一个小矩形,并将其传递给 graphics对象的DrawEllipse()方法。这样做的效果是,在鼠标所处的位置画一个小圆圈。

5.再次从对象列表中选择“(MainForm事件)”,并从事件过程列表中选择MouseMove。在MouseMove事件中添加下列代码:

与其他所有事件一样,e 对象包含与事件相关的信息。在这个例子中,使用的是 e 对象的属性X和Y,它们是事件触发时的鼠标坐标。另外,还检查了e对象的Button属性,确保用户按下了鼠标左键。

现在项目完成了!单击工具栏中的“全部保存”保存项目,然后按 F5 运行项目。将鼠标在窗体上移动——这样什么也不会发生。现在,按住鼠标左键并移动鼠标,这将在窗体上画图(如图17.13所示)。

图17.13 捕捉鼠标事件提供了许多激动人心的可能性

注意到鼠标移动得越快,圆圈之间的间距就越大。这表明用户移动鼠标的速度可以快于 MouseMove 事件的触发速度,因此您无法捕获鼠标的每次移动。这一点很重要,要牢记。

窗体和控件为用户与应用程序交互提供了很大的灵活性。然而,交互不仅限于窗体上的控件。在本章中,读者学习了如何使用 MessageBox.Show()函数来创建包含信息的对话框,学习了如何指定图标、按钮以及如何将按钮指定为默认按钮。还学习了一些有价值的技巧,可帮助创建最佳的消息框。您将经常创建消息框,因此掌握这种技能非常重要。

有时候,简单的“确定”/“取消”或“是”/“否”问题不适用,还需要从用户那里获得其他数据。在本章中,读者学习了如何使用函数 InputBox()从用户那里获取一条数据,以及如何创建自定义对话框。虽然 InputBox()总是返回一个字符串,但读者学习了如何使用它来收集数字。

最后,读者学习了如何通过各种事件直接同键盘和鼠标交互。有时候,控件本身处理鼠标或键盘的能力不能满足要求。通过理解本章介绍的概念,您能够超越控件的内置功能,为用户提供丰富多彩的互动体验。

问:是否可以在窗体层面捕获按键事件,而不是在控件事件中捕获?

答:可以。然而,要在控件获得焦点时触发窗体的键盘相关事件,必须将窗体的KeyPreview 属性设为 True。控件的键盘事件仍将触发,除非在控件的 KeyPress 事件中将KeyPressEventArgs.Handled设为True。

问:如果要从用户那里收集两三条信息,使用多条InputBox语句是否合适?

答:可能不合适。在这种情况下,使用一个窗体或自定义对话框可能是更好的选择。

1.调用MessageBox.Show()时必须提供什么参数?

2.如果不提供MessageBox.Show()的Caption参数值,消息框的标题栏将显示什么?

3.在消息框中同时可以显示几个图标?

4.函数InputBox()总是返回什么类型的数据?

5.用户单击“取消”按钮时,InputBox()将返回什么?

6.事件KeyUp与KeyPress哪一个先触发?

7.在鼠标相关事件中如何判断哪个鼠标按钮被按下?

1.提示和对话框标题(caption)。实际上,从技术上说标题是可选的,但没有标题不太好,因此我认为标题是必不可少的。

2.不显示任何内容——标题栏为空。

3.一次只能显示一个图标。

4.函数InputBox()总是返回一个字符串。

5.用户单击“取消”按钮时,将返回一个空字符串。

6.KeyPress先于KeyUp事件被触发。

7.在事件中使用e.Button属性。

1.修改自定义对话框项目,将OK按钮设置为窗体的接受按钮。这样,用户按回车键将关闭对话框消失。然后,将 Cancel 按钮设为窗体的取消按钮,让用户也可以按 Escape 键来关闭窗体。

2.修改鼠标画图项目,使得用户每次开始画图时,都清除窗体。提示:在MouseDown事件中清除graphics对象。

在本章中,读者将学习:

理解Graphics对象;

使用画笔;

使用系统颜色;

使用矩形;

绘制形状;

绘制文本;

将窗体上的图形持久化;

创建一个绘图项目。

Visual Basic 2010提供了很多强大的绘图功能。然而,与这些功能伴随而来的是陡峭的学习曲线。绘图并不容易,并非坐下来花几分钟看一下在线帮助就能开始绘制图形。然而,学习相关的基本原理后,将发现画图并没有那么复杂。在本章中,读者将学习在窗体或其他图形表面绘制形状和文本的基本技能。将学习画笔、颜色和笔刷(用于帮助定义要绘制的图形的对象)。另外,还将学习如何将窗体上的图形持久化以及如何创建只存在于内存中的位图。

开始时,您可能不会想到要在屏幕上画图,而是倾向于使用Visual Basic提供的很多高级控件来构建界面。然而,随着应用程序的规模和复杂度提高,将发现越来越多的时候需要将自己的界面直接绘制到屏幕上;需要这样做的时候,就真的需要使用这种功能了。您甚至可能选择自己设计控件(使用Visual Basic可以这样做)。在本章中,读者将学习绘图和打印到屏幕上的基础知识。使用本章学到的技能,将能根据需要创建更细化的界面。

在Windows操作系统中,负责将文本、线条和图形等绘制到屏幕上的代码称为图形设备接口(Graphics Device Interface,GDI)。GDI处理来自Windows和应用程序的所有绘图指令,并输出到当前的显示设备。由于GDI要将输出显示到屏幕上,因此它负责处理安装在计算机上的显示驱动程序以及对驱动程序进行设置,如分辨率和颜色深度。这意味着应用程序(及其开发人员)不必关心这些细节;您只要编写代码告诉GDI要输出什么,GDI将执行所需的操作以产生输出。这种行为称为设备独立性,因为应用程序可以使用独立于显示设备的代码让GDI显示文本和图形。

Visual Basic代码主要通过Graphics对象与GDI通信。基本过程如下:

创建一个对象变量来存储对Graphics对象的引用;

将对象变量设置为有效的Graphics对象(新创建的或已有的);

调用Graphics对象的方法进行绘制或输出。

如果要直接绘制到窗体或控件上,可调用该对象的CreateGraphics()方法获得对图形表面的引用。例如,要创建一个Graphics对象以绘图到文本框,可使用下列代码:

Dim objGraphics As Graphics = TextBox1.CreateGraphics

调用CreateGraphics()方法时,将对象变量设置为对窗体或控件的客户区域的Graphics对象引用。窗体的客户区域是窗体边框和标题栏内的灰色区域,而控件的客户区域通常是整个控件。使用 Graphics 对象进行的所有绘制和输出都将发送到客户区域。在前面的代码中, Graphics对象引用了文本框的客户区域,因此所有在Graphics对象上执行的绘制方法都将直接在文本框上进行绘制。

注意:直接绘制到窗体或控件上时,对象并不会持久化绘制在它上面的图形。如果窗体被遮掩,如被其他窗体覆盖或将窗体最小化,下次窗体绘制时,将不会包含已绘制在它上面的图形。稍后将介绍如何将窗体上的图形持久化。

不一定要将Graphics对象设置为窗体或控件的客户区域;也可将其设置为只存在于内存中的位图。出于性能方面的考虑,您可能想使用内存位图来存储临时图像,或存储临时创建的复杂图形,然后再发送给可见元素(如窗体或控件)。为此,必须首先创建新位图。

要创建新位图,可使用如下语法,声明一个存储新位图引用的变量:

variable = New Bitmap(width, height, pixelformat)

参数width和height是新位图的宽度和高度。然而,pixelformat参数的意义不那么明显。这个参数指定位图的颜色深度,也可能指定位图是否有alpha图层(用于位图的透明部分)。表 18.1 列出了 pixelformat 的一些常见取值(有关所有的可能取值及其含义,请参见 Visual Basic在线帮助)。

表18.1 pixelformat的常见取值

例如,要创建一个颜色深度为24位的640×480新位图,可以使用下面的语句:

objMyBitMap = New Bitmap(640, 480,

Drawing.Imaging.PixelFormat.Format24bppRgb)

创建位图后,可以使用FromImage()方法创建一个引用位图的Graphics对象,如下所示:

objGraphics = Graphics.FromImage(objMyBitMap)

现在,使用objGraphics进行的所有绘制都将作用于内存位图。要让用户看到位图,必须将位图发送到窗体或控件。稍后的“将窗体上的图形持久化”一节将这样做。

注意:不再需要Graphics对象时,应调用它的Dispose()方法,确保Graphics对象占用的所有资源都被释放。

画笔是定义线条特征的对象。画笔用于定义颜色、线条宽度和线条样式(实线、虚线等)。画笔可用于本章将介绍的每个绘图方法。

Visual Basic提供了很多定义好的画笔,您还可以创建自己的画笔。要创建自己的画笔,使用如下语法:

penVariable = New Pen(color, width)

创建画笔后,可以设置其属性来调整它的外观。例如,所有Pen对象都有一个DashStyle属性,它决定了使用画笔画出的线条外观。表18.2列出了DashStyle的可能取值。

表18.2 DashStyle的可能取值

DashStyle枚举位于Drawing.Drawing2D命名空间中。因此,要创建一支绘制深蓝色点线的画笔,可使用如下代码:

Dim objMyPen As Pen

objMyPen = New Pen(Drawing.Color.DarkBlue, 3)

objMyPen.DashStyle = Drawing.Drawing2D.DashStyle.Dot

通过第二个参数传递的3以像素为单位指定了新画笔的宽度。

Visual Basic提供了很多标准画笔,可以通过 System.Drawing.Pens类来使用它们,如下所示:

objPen = System.Drawing.Pens.DarkBlue

使用稍后将讨论的方法来绘画时,可以使用自定义的画笔,也可以使用系统定义的画笔——这由您决定。

有时候,您可能修改了Windows主题,或修改了桌面的图像或颜色。您可能还没有意识到,Windows允许用户定制几乎所有Windows界面元素的颜色。Windows允许修改的颜色称为系统颜色。要修改系统颜色,右击桌面,从弹出菜单中选择“属性”打开“显示属性”对话框,然后单击“外观”选项卡,如图18.1所示。要修改特定元素的颜色,可以单击“高级”按钮,选择要修改的颜色属性,然后选择一种新颜色(如果使用的不是Windows XP,对话框可能稍有不同)。

图18.1 “显示属性”对话框让用户能够为大多数 Windows 界面元素指定颜色

使用“显示属性”对话框修改系统颜色时,所有已加载的应用程序都将根据设置修改其外观。另外,启动新应用程序时,其外观也将匹配您的设置。如果必须编写代码来管理这种行为,将必须编写大量的代码,您可能想避免这样做。然而,使应用程序调整外观以匹配用户选择的系统颜色很容易,因此没有理由不这样做。大部分情况下,这是由添加到窗体中的控件自动完成的。

要指定界面颜色使其与用户系统颜色一致,可以将元素的颜色属性设置为一种系统颜色,如图18.2所示。例如,如果要保证按钮的颜色与用户的系统颜色匹配,应将Button控件的BackColor属性设置为系统颜色Control。表18.3列出了最常用的系统颜色,完整的列表可参阅在线帮助。

图18.2 使用“系统”调色板来指定系统颜色

表18.3 系统颜色

当用户使用“显示属性”对话框修改系统颜色时,Visual Basic将自动更新使用系统颜色的对象的外观;不必编写任何代码来实现这一点。创建新窗体并在窗体上添加控件时,Visual Basic将自动地将适当的系统颜色分配给适当的属性,因此通常不需要考虑这些。

要知道,系统颜色不仅可分配给逻辑相关的属性,也可以将系统颜色分配给任何颜色属性,画图时也可以使用系统颜色。这让您可以绘制与用户系统颜色匹配的自定义界面元素。然而,需要注意的是,如果使用系统颜色进行绘制,当用户修改系统颜色时 Visual Basic不会自动更新颜色;您必须使用新的系统颜色重新绘制元素。另外,如果通常不使用系统颜色的属性使用了系统颜色,可能显示奇怪的颜色组合,如前景色和背景色都是黑色,这取决于用户的颜色设置。

注意:用户修改系统颜色并不只是出于美观。我与一位色盲程序员共事,他修改系统颜色旨在能够更好地看清屏幕上显示的东西。如果您不允许应用程序根据用户的颜色偏好进行调整,可能会给用户带来不必要的困难,甚至可能使色盲患者或有视力问题的用户无法使用您的程序。

学习如何绘制形状前,需要理解矩形的概念,因为它与Visual Basic编程有关。矩形是一种用于存储边框坐标的结构,这些坐标用于绘制形状。矩形不一定用于绘制矩形(虽然可以)。显然,正方形可位于矩形内,但圆形和椭圆也可以。图 18.3 表明,矩形可以包围大多数形状。

图18.3 矩形用于定义大多数形状的边框

绘制大多数形状都必须有一个矩形。传递给绘制方法的矩形被用作边界矩形;适当的形状(圆形、椭圆等)总是绘制在边界矩形的边框内。创建矩形很容易。首先,将一个变量定义为Rectangle,然后设置该对象变量的属性X、Y、Width和Height。X和Y值是矩形左上角的坐标,属性Height和Width的含义是不言自明的。

下列代码创建了一个矩形,其左上角坐标为 0,0,宽度为 100,高度为 50。注意,这段语句只是定义了一个矩形,并没有将它绘制到屏幕上:

Rectangle对象让您能够在它的初始化构造函数中传递属性X、Y、Width和Height。使用这种方法,只要一行代码就可以创建与上面相同的矩形:

Dim rectBounding as New Rectangle(0,0,100,50)

定义好矩形后,可以使用它做很多事情。最有用的可能是使用一条语句放大或缩小矩形。使用Inflate()方法放大或缩小矩形。Inflate()最常用的语法如下所示:

object.Inflate(changeinwidth, changeinheight)

使用这种调用方式时,矩形的宽度和高度被放大(矩形的左上角保持不变)。要使宽度或高度保持不变,将相应的参数指定为0。要缩小矩形,将参数指定为负数。

如果要进行大量绘画,将使用大量的Rectangle对象,因此读者尽可能深入地了解它们。

学习Graphics对象、画笔和矩形后,将发现绘制形状相当简单。形状是通过调用Graphics对象的方法来绘制的。大多数方法都要求一个矩形(用做形状的边界框)和一支画笔。在本节中,读者将学习如何绘制不同的形状。

注意:这里只讨论如何绘制最常见的形状。Graphics对象包含很多方法可用于绘制其他形状。

要绘制线条,可使用Graphics对象的DrawLine()方法。DrawLine()是少数几个不需要矩形的绘制方法之一。DrawLine()的基本语法如下:

object.DrawLine(pen, x1, y1, x2, y2)

正如前面讨论的,object是一个Graphics对象,pen是一个Pen对象。x1和y1是线条起点的坐标,x2和 y2是终点的坐标;Visual Basic使用指定的画笔在这两个端点之间绘制一条线段。

要绘制矩形(包括正方形),可使用Graphics对象的DrawRectangle()方法。您可能已经想到, DrawRectangle()方法接受一支画笔和一个矩形作为参数。下面是以这种方式调用DrawRectangle()方法的语法:

object.DrawRectangle(pen, rectangle)

如果没有Rectangle对象(且您也不想创建),可使用下面的格式调用DrawRectangle()方法:

object.DrawRectangle(pen, X, Y, width, height)

圆形和椭圆的绘制通过调用DrawEllipse()方法来实现。如果您熟悉几何知识,将知道圆形实际上是一个长和宽相等的椭圆。这就是没有用于专门绘制圆形的方法的原因:DrawEllipse()完全可以胜任这项工作。与 DrawRectangle()方法一样,DrawEllipse()接受一支画笔和一个矩形作为参数。矩形用作椭圆的边界——矩形的宽是椭圆的宽,矩形的高是椭圆的高。DrawEllipse()的语法如下所示:

object.DrawEllipse(pen, rectangle)

在没有定义Rectangle对象(且不想创建)的情况下,可用如下语法调用DrawEllipse():

object.DrawEllipse(pen, X, Y, Width, Height)

要清除Graphics对象的表面,可调用Clear()方法,并将用于绘制表面的颜色传递给它,如下所示:

objGraphics.Clear(Drawing.SystemColors.Control)

在Graphics对象上绘制文本与绘制形状类似,绘制方法甚至也包含“Draw”一词,而不是“Print”。要在Graphics对象上绘制文本,调用DrawString()方法。DrawString()的基本格式如下所示:

object.DrawString(stringoftext, font, brush, topX, leftY)

这些参数当中有些您可能没见过。stringoftext 参数的含义不言自明:它是要在 Graphics对象上绘制的文本。topX和leftY参数表示绘制位置的坐标;它们表示字符串左上角的位置,如图18.4所示。

图18.4 DrawString()中指定的坐标表示输出文本的左上角位置

参数brush和font的含义不那么明显。这两个参数都可以是对象。笔刷与画笔类似,但画笔描述的是线条样式,而笔刷描述的是填充样式。例如,画笔和笔刷都有颜色,但画笔有一个定义线条样式的属性,如虚线或实线,而笔刷有一个定义填充图案的属性,如实心、阴影型、波浪型或网格型。绘制文本时,实心笔刷通常足够。可以像创建画笔一样创建笔刷,也可以通过System.Drawing.Brushes类获得标准笔刷。

Font(字体)对象定义了文本的格式特征,包括字符集(Times New Roman,Courier等)、大小(点数)和样式(粗体、斜体、正常和下划线等)。要创建一个新Font对象,可用如下所示的代码:

Dim objFont As Font

objFont = New System.Drawing.Font("Arial", 30)

这段代码中的 Arial 是我的计算机上安装的一种字体的名称。实际上,Arial 是所有Windows系统都会安装的字体之一。如果提供的字体名在计算机上不存在,Visual Basic将使用与指定字体最接近的默认字体。第二个参数是文本的大小。如果要使用非正常样式,可以将样式值作为第三个参数,如下所示(注意逻辑Or,它在第12章讨论过):

objFont = New System.Drawing.Font("Arial Black", 30, _

FontStyle.Bold or FontStyle.Italic)

除创建Font对象外,也可以使用已有对象(如窗体)的字体。例如,下面的语句使用当前窗体的字体将文本输出到Graphics对象上:

objGraphics.DrawString("This is the text that prints!", Me.Font, _

 System.Drawing.Brushes.Azure, 0, 0)

您可能发现,有时需要使用本章介绍的方法来绘制到窗体。然而,您可能记得,在前几章,在窗体上绘图时(实际上是在引用窗体的Graphics对象上绘制)时,绘制的元素并没有持久化;下次绘制窗体时,绘制的元素将消失。例如,如果用户将窗体最小化或其他窗口覆盖了窗体,当窗体重绘时,所有被覆盖的已绘制元素都将消失。要解决这种问题,有以下两种方法:

将所有绘制到窗体的代码都放在窗体的Paint事件中;

绘制到内存位图,并在窗体的Paint事件中将内存位图复制到窗体中。

如果只绘制几个元素,将绘制代码放在 Paint 事件中是个不错的办法。然而,如果有很多绘制代码,图形可能是根据用户输入进行绘制的,因此不能一次性重建它们,在这种情况下,第二种方法显然更好。

下面运用学到的绘制到窗体的技能来创建一个项目。在这个项目中,将使用绘制到内存位图的方法,在每次窗体重绘时将图形持久化。

注意:这次要创建的项目可能是到目前为止最难的。我将对项目创建过程的每一步进行解释,但对那些已经讨论过的对象和方法将不再赘述。

为更有趣些,我使用随机数来决定要绘制到窗体的字体大小和文本的X、Y坐标。在Visual Basic 中,生成随机数的最简单方法是使用 System.Random 类。要生成指定范围内的随机数(如1~10之间的随机数),可采取三个步骤。

1.创建一个类型为System.Random的对象变量。

2 .创建一个新的 Random 类实例,并传递要用于生成随机数的种子值。我使用Now.Millisecond,这样将使用一个伪随机数作为种子值,因为这个值将每隔1毫秒变化一次,每次运行该应用程序时,它都可能不同。

3.调用Random类的Next()方法,并将上限和下限传递给它。方法Next()将返回一个位于指定范围内的随机数。

首先新建一个Windows应用程序项目,将其命名为Persisting Graphics,然后执行下列步骤来创建该项目。

1.在“解决方案资源管理器”中右击Form1.vb,选择“重命名”,然后将默认窗体名改为MainForm.vb,并将窗体的Text属性设置为Persisting Graphics Example。

2.窗体的界面包含一个文本框和一个按钮。当用户单击按钮时,文本框的内容将以随机的字体大小绘制到窗体的随机位置。添加一个新文本框,并按如下设置它的属性:

3.在窗体中添加一个新按钮,并按如下设置它的属性:

现在添加代码让程序运行起来。

前面说过,所有绘制都将使用内存位图进行,然后将其复制到窗体中。将在多个地方引用该位图,因此应将其声明为模块级变量。

4.双击窗体访问它的Load事件,并在过程Form_Load的声明前添加下面的语句,不要将该语句放在窗体的Load事件过程中:

Private m_objDrawingSurface As Bitmap

5.要使位图变量起作用,必须让它引用一个Bitmap对象。进行这种初始化的好地方是窗体的Load事件,因此,将光标放在Load事件中,然后输入下列代码:

现在的过程应如图18.5所示。

该过程中的第一条语句初始化随机数生成器(将在另一个过程中使用随机数)。接下来的语句创建了一个新位图。因为位图的内容将被发送给窗体,因此将窗体的客户区域的大小作为新位图的大小。最后一条语句调用了一个还没有创建的过程。

图18.5 确保代码与这里的一致

6.将光标放在End Sub语句的末尾,并按回车键几次创建几个新行。下面编写初始化位图的代码。这些代码将使用系统颜色Control来清除位图,然后绘制大小与位图相同的椭圆。我在代码中添加了注释,让读者能够理解代码的作用;这里涉及的所有概念都讨论过。完整地输入下面这段代码:

现在过程应如图18.6所示。

如果现在运行项目,将发现窗体上什么也没有绘制。这是因为绘制是针对内存中的位图的,您还没有添加将位图复制到窗体上的代码。实现这种功能的地方是窗体的 Paint 事件,这样窗体每次重新绘制时,位图的内容都将被发送给窗体。这确保绘制的元素总是显示在窗体上。

7.下面创建窗体的 Paint 事件处理程序。为此,从代码编辑器左上角的对象列表中选择“(MainForm事件)”,然后从右上角的列表中选择 Paint,再将下列代码添加到 Paint 事件中:

图18.6 确保输入的代码正确

Paint事件的参数e有一个属性引用了窗体的Graphics对象。然而,使用参数e不能修改该Graphics对象(它是只读的),因此必须创建一个新的Graphics对象,并将其设置为引用窗体的Graphics对象。方法DrawImage()将位图中的图像绘制到Graphics对象的表面,因此最后一条语句只是将位于内存中的位图内容发送给窗体。

现在如果运行项目,将发现椭圆显示在窗体上。另外,可以用另一个窗口来覆盖这个窗体或将它最小化,当窗体重新显示时,椭圆总是出现在窗体上——也就是说,图形被持久化。

8.最后需要完成的工作是,编写代码将文本框中输入的内容绘制到窗体上。文本将以随机大小绘制在随机位置。返回窗体设计器,双击按钮访问它的 Click 事件。然后添加下列代码:

其中的注释应使这段代码很容易读懂。然而,最后一条语句需要讨论一下。窗体的Invalidate()方法使客户矩形无效。该操作告诉 Windows,窗体的外观已经不准确,因而需要重绘,这将触发窗体的Paint事件。由于Paint事件包含了将内存中的位图内容复制到窗体上的代码,因此使窗体无效将使得文本被显示。如果这里不调用 Invalidate(),文本将不会显示在窗体上(但它仍将绘制到内存位图)。

注意:如果根据窗体的大小来绘制元素,需要在窗体的 Resize 事件中调用Invalidate();因为窗体大小的改变不会触发窗体的Paint事件。

现在项目完成了!单击工具栏中的“全部保存”保存项目,然后按 F5 运行项目。椭圆将立即被绘制到窗体上。在文本框中输入一些文本,然后单击按钮。再单击一次。每次单击按钮时,文本都将使用相同的画笔绘制到窗体上,但字体和位置不同,如图18.7所示。

图18.7 文本被绘制在窗体上,与常规形状一样

不是每个项目都需要加入画图功能。然而,需要这样做时,就要求您具备绘图能力。在本章中,读者学习了在图形表面画图的基础技巧。图形表面可能是窗体、控件、内存位图或其他众多表面之一。您知道所有绘制都是使用Graphics对象完成的,知道如何为窗体或控件创建Graphics对象,以及如何为存在于内存中的位图创建Graphics对象。

大多数画图方法都需要画笔和矩形,您能够使用本章学到的方法来创建矩形和画笔。学习画笔与矩形后,画图方法本身非常简单易用。有了Graphics对象后,绘制文本也很简单。

持久化窗体上的图形有点复杂,可能给很多新Visual Basic编程人员自学时带来困惑。然而,读者创建了一个将窗体上的图形持久化的示例,在以后的项目中这样做时,读者将知道如何利用涉及的技术。

不要期望通过一章的学习就能够创建像Adobe Photoshop那样的应用程序。然而,读者现在已具备扎实的基础知识。如果想创建能够执行很多画图功能的项目,应深入研究Graphics对象。

问:如果要绘制很多线条,其中每条的起点都与另一条的终点相连。是否每绘制一条都需要调用DrawLine()?

答:Graphics对象有一个DrawLines()方法,它接受一系列点。该方法绘制将这一系列点连接起来的线条。

问:有没有填充形状的方法?

答:Graphics对象包含绘制填充形状的方法,如FillEllipse()和FillRectangle()。

1.使用什么对象绘制到表面?

2.要设置Graphics对象以直接绘制到窗体,应调用窗体的什么方法?

3.什么对象定义线条的特征?什么对象定义填充图案?

4.如何使颜色属性随用户的Windows设置调整?

5.使用什么对象定义要绘制形状的边界?

6.调用什么方法绘制椭圆?圆形呢?

7.调用什么方法在Graphics表面输出文本?

8.为确保图形在窗体上持久化,必须在什么事件中绘制到窗体?

1.Graphics对象。

2.CreateGraphics()方法。

3.线条用Pen对象定义;填充特征用Brush对象定义。

4.使用系统颜色。

5.Rectangle对象。

6.两种图形都用DrawEllipse()方法绘制。

7.DrawString()方法。

8.窗体的Paint事件。

1.修改本章的示例,将字体改为除Arial外的其他字体。如果不确定计算机安装了哪些字体,打开“开始”菜单并选择“控制面板”,再在控制面板中双击“字体”。

2.创建一个项目,绘制一个与窗体等大的椭圆,与本章创建的椭圆类似。然而,在窗体的Paint事件直接绘制椭圆。确保窗体大小改变时椭圆被重绘。提示:在窗体的Resize()事件中使窗体无效。

感谢Sams出版社所有工作人员为本书所做的工作,没有他们的付出,本书不可能付梓。

图书在版编目(CIP)数据

Visual Basic 2010入门经典/(美)福克奥斯(Foxall,J.)著;梅兴文译.--北京:人民邮电出版社,2011.4

ISBN 978-7-115-25145-9

Ⅰ.①V… Ⅱ.①福…②梅… Ⅲ.①BASIC语言—程序设计 Ⅳ.①TP312

中国版本图书馆CIP数据核字(2011)第048616号

版权声明

James Foxall: sams Teach Yourself Visual Basic 2010 in 24 Hours

ISBN: 0672331136

Copyright © 2010 by Sams Publishing.

Authorized translation from the English languages edition published by Sams.

All rights reserved.

本书中文简体字版由美国Sams出版公司授权人民邮电出版社出版。未经出版者书面许可,对本书任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。

Visual Basic 2010入门经典

♦著 [美] James Foxall

译 梅兴文

责任编辑 傅道坤

♦人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京  印刷

♦开本:787×1092 1/16

印张:23.25

字数:578千字  2011年5月第1版

印数:1-000册  2011年5月北京第1次印刷

著作权合同登记号 图字:01-2011-0736号

ISBN 978-7-115-25145-9/TP

定价: 元

读者服务热线:(010)67132705 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

相关图书

DirectX 12 3D 游戏开发实战
DirectX 12 3D 游戏开发实战
Python和NLTK自然语言处理
Python和NLTK自然语言处理
scikit-learn机器学习(第2版)
scikit-learn机器学习(第2版)
C++编程自学宝典
C++编程自学宝典
数据科学实战手册(第2版)
数据科学实战手册(第2版)
Scala实用指南
Scala实用指南

相关文章

相关课程