分享

2.1 使用VS.NET设计用户界面

 nxhujiee 2010-04-07

用户界面(User Interface,UI)是一个软件技术的专用术语。通俗地说,用户界面就是程序的使用者可以看到的程序外观,对于运行于个人电脑上的程序而言,用户界面指的就是程序的窗体(Form)。图2-1是著名的MP3播放软件WINAMP的用户界面。

文本框: 
图2-1 MP3播放器的用户界面在.NET下,运行在个人电脑上的应用程序由“Windows Form”类型的项目生成。Windows Form类似于以前使用VB 6、Delphi、VC 6开发出来的窗体,其特点是拥有丰富的用户交互功能,比如可以最大化、最小化,可以通过拖动标题栏而拖动整个窗体,单击窗体左上角图标会弹出系统菜单,按Alt+F4组合键可以关闭当前窗体……Windows Form通过调用.NET Framework和Windows操作系统的API(Application Programming Interface,应用程序编程接口),还可以方便地使用操作系统所提供的任意功能。

在.NET下,还有另外一种用户界面,称之为“Web Form”。一看见“Web”,读者一定就会猜出这是运行在因特网上的窗体。其实Web Form本身就是Web网页,它不能像Windows Form一样独立存在,而只能运行在浏览器(比如微软的Internet Explorer)中。

为什么要把 “网页”叫做“Web Form”呢?Web Form与传统的使用HTML设计的网页有何不同?

经常上网的人都知道网页页面和标准的Windows窗体还是有许多差别的。Web页面由于必须显示在浏览器中,所以其功能受到浏览器的限制,无法做到像标准的Windows窗体那样功能强大而且使用方便。例如,按照目前的技术水平,要开发一个全部运行在浏览器中的图像处理软件(如Photoshop)是很困难的,不管是程序运行速度还是用户操作友好性,都受到HTML及浏览器的天然限制。Windows Form主要运行在单机上,而Web Form主要运行在因特网环境中,这两种差异极大的环境决定了两者的不同。

但微软公司一直在努力弥合开发Web应用程序和桌面应用程序之间的差别,Web Form就是把Windows Form的事件驱动原理应用于开发因特网应用程序的一种尝试。在.NET下,开发因特网应用程序(即Web网站)的技术称为ASP.NET。在ASP.NET中,把Web页面看成是一个窗体,可以像开发Windows Form应用程序一样,直接用控件在网页上“画”出网页布局,并可以针对各种控件的事件进行编码。正是由于在ASP.NET中设计Web网页与设计Windows Form几乎一样,所以,ASP.NET中把Web网页称为“Web Form”。

 提示

初学.NET的人不适合一上来就直接学习ASP.NET。由于Web Form是在Windows Form技术的基础上并结合了现有的Web网站开发技术出现的更为复杂的新技术,所以,学习掌握Windows Form的基本原理,掌握一种.NET语言(推荐C#和VB.NET两者之一)编程技能是学习ASP.NET技术的基础。初学者切不可一开始就抱着本《C# Web Form编程》之类的书狂啃不休,不掌握必要的基础,您会发现学到一定的时候就学不下去了。

学习ASP.NET的另一个基础是掌握现有的Web开发技术,主要是HTML和JavaScript,以及Web应用程序的基本原理。

本书所有内容只涉及Windows Form技术,读者如果对开发因特网应用程序感兴趣,可以在学习本书之后,再去学习ASP.NET。

本章介绍Windows Form的基本原理,并学习使用VS .NET这一功能强大的集成开发环境高效地设计用户界面。

2.1.1 窗体的使用

Windows Form应用程序的核心就是窗体(Form),先看一个示例。

1.类和命名空间

打开VS .NET,从“文件”菜单中选“新建/项目…”,如图2-2所示。

图2-2 新建一个Windows应用程序

注意选中“Visual Basic项目”下的“Windows应用程序”模板,在名称处输入“UseForm”。单击【确定】按钮,VS .NET将会自动创建一个窗体,显示在屏幕中央。请切换到代码视图(参见第1章中的相关内容),观察VS .NET自动生成的代码框架。

Public Class Form1

Inherits System.Windows.Forms.Form

'此处略去Windows窗体生成器生成的代码

End Class

从上述代码中可以看到窗体其实是一个类,名字叫做“Form1”。在.NET中编程用到的所有东西都放在特定的类中,类是所有面向对象程序中最基本的具有独立性的构成元素(在以C语言为代表的计算机语言开发出来的结构化程序中,最基本的构成元素是函数)。2.2节将介绍面向对象编程的更多知识。

注意上述代码中的第2句,“Inherits”是VB.NET中的一个关键字(Key Word,即编程语言中表达特殊意义的单词),它表明类Form1从另一个类System.Windows.Forms.Form继承而来,这个类由.NET Framework提供,它其中有许多已写好的程序代码,这些代码能完成前面提到的标准Windows窗体应该具有的功能(比如让窗体最大化和最小化)。通过继承这些现有代码,软件工程师就不再需要自己实现这些功能,而只需使用现成的类,这就是面向对象编程所带来的高开发效率。把许许多多完成各种功能的类集中放在一起,就构成了类库(Class Library)。.NET就提供了一个庞大的类库,参见图2-3。

图2-3 .NET Framework基本类库

读者可能会觉得奇怪,类名字“Form1”只有一个单词,为什么它继承的类有那么长的以英文句点分隔的字符串——“System.Windows.Forms.Form”?这就涉及.NET类库中类的组织方式问题。

在.NET Framework中,类的存放不是杂乱无章的,而是分门别类的,比如开发Web网页的类就放在System.Web这个大类别下。相应地,用于开发Windows Form的类就放在System. Windows.Forms这个大类别下。

诸如System.Windows.Forms这样的大类别称为名字空间(Name Space),可以把名字空间看成是功能彼此类似或相关的类的集合。

名字空间是可以嵌套的,大的可以装下几个小的,比如System这个顶级的名字空间下就可以有Web、Data、XML等几个小的名字空间,而Runtime这个名字空间下还有Remoting、Serialization等好几个子名字空间。嵌套的名字空间形成一个复杂的分层结构,最底层是具体的类。

当需要明确地表达一个类时,应采用以下格式:

最大的名字空间.子名字空间.孙名字空间.….类

最右边的是类名,所以,“System.Windows.Forms.Form”这个字符串表明Form是一个类,它的最顶级名字空间是System,次级名字空间为Windows,三级名字空间为Forms。通过使用这样的完整字符串,就可以在庞大的类库中快速定位一个类。

为了不在编程时输入一个长长的字符串,VB.NET引用了一种“提前”导入名字空间的Imports语句。

在代码窗口的最上方输入

Imports System.Windows.Forms

然后,就可以把代码中的

Inherits System.Windows.Forms.Form

改为

Inherits Form

则程序仍然会正确运行。

这种在代码开头使用Imports语句导入特定名字空间的方法在编写代码时是非常常见的。在后面的章节中,许多实例都采用了提前导入名字空间的方法。读者在手动敲入代码时如果发现VS .NET提示“未定义类型XXX”(参见图2-4),则往往是由于没有导入名字空间的缘故。

图2-4 未定义名字空间引发的错误

解决方法是在类代码之外使用Imports语句导入名字空间,如图2-5所示。

可以在随机文档MSDN中查找.NET Framework中所有类各自所属的名字空间。

图2-5 导入名字空间解决“类型未定义”错误

 试一试

在MSDN中查找StreamReader这个类属于哪个名字空间。

2.类和由类创建的对象

前面说过,Form1其实是一个类,但我们看到的窗体并不是类本身,而是由Form1类生成的对象。

类和对象两者是一种什么关系?为理解这一点,我们进行一个练习。

把一个按钮拖动到窗体(窗体名Form1)上,在其Click事件代码中书写以下代码:

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

Dim frm As New Form1

frm.ShowDialog()

End Sub

运行之后,多次单击按钮,看看发生了什么(参见图2-6)!

文本框: 
图2-6 显示的多个窗体可以看到,每次单击按钮都会在屏幕上多出一个窗体,并且这些窗体都“长得”一模一样。这说明了什么?

类Form1定义了窗体的外观,但真正看到的是以Form1作为模板创建的Form1对象。一个类可以创建多个以它为模板的对象,类和对象之间是一对多的关系。为了便于理解,可以把类看成是一个印,它可以在纸上盖出多个章,盖出的章就是“对象”,不管盖多少次,印本身是不会变的,盖出来的章样子也是类似的。

理解类和由类创建的对象这两个概念非常重要,2.2节将学习类的更多知识。

上述代码中使用New这个关键字创建类Form1的对象frm,再调用frm对象的ShowDialog()方法让它显示出来。

一个对象可以拥有属性和方法,属性往往代表了对象的性质,而方法往往代表了对象的功能。Form1对象的ShowDialog()方法完成的功能是把窗体显示出来,并等待用户关闭。

一个对象拥有多少种属性和方法是由类来决定的。读者可能会奇怪,在类Form1的代码中,没有看见有ShowDialog()这样单词啊?它从哪儿冒出来的?

别忘了,Form1继承自Form类,而ShowDialog()这个方法的功能由Form类所实现,具体的代码放在.NET Framework中,在VS .NET中不能直接看到。

3.Form类的常用属性

熟悉Windows操作的人都知道,一个标准的窗口有很多特性,而System.Windows.Forms.Form作为所有可见窗体的父类,实现了标准的Windows窗体的所有功能,这主要是通过设置Form类的特定属性值实现的,许多属性可以用属性窗口进行设置。以下是使用代码来设置这些属性的方法(假设所有的代码都位于同一窗体,用关键字Me代表窗体):

(1)修改窗体标题

Me.Text="新窗体标题"

 试一试

在Form_Load()过程中加上以上代码,然后编译运行。

(2)隐藏窗体

Me.Visible=False

将其改为True则会再显示窗体。

(3)设置窗体背景颜色

Me.BackColor = Color.Yellow

以上代码把窗体背景改为黄色。

请注意在.NET中颜色用System.Drawing名字空间中的Color类型表示,其完整的声明为“System.Drawing.Color”。

.NET提供了许多种预先定义的颜色,如上面的代码所示,可以使用以下的方式指定一种预定义颜色:

Color.颜色名称

如果预定义颜色不能满足需要,则可以通过指定RGB值来定义一种特殊的颜色。RGB分别代表Red(红色)、Green(绿色)、Blue(蓝色)三个分量,每个分量的取值范围是0~255。从色彩学可以知道,红、绿、蓝是三原色,这三种颜色按不同的比例混合,可以生成人肉眼能看到的几乎所有颜色。在计算机上,每个颜色有256种取值,所以,一共能表示“256′256′256”种颜色。

对于纯色有以下公式:

(R,G,B)=(255,0,0) 纯红色

(R,G,B)=(0,255,0) 纯绿色

(R,G,B)=(0,0,255) 纯蓝色

(R,G,B)=(255,255,255) 纯白色

(R,G,B)=(0,0,0) 纯黑色

如果使用RGB的方式来选定颜色,可以使用Color类型的FromARGB()方法:

Me.BackColor = Color.FromArgb(255, 0, 0)

以上代码把窗体背景颜色改为红色。

 试一试

在Form_Load()过程中加上以上代码,然后编译运行,改改RGB的值,看看效果。

(4)让窗体不能动态改变大小

Me.FormBorderStyle = FormBorderStyle.FixedSingle

 试一试

(1)在Form_Load()过程中加上以上代码,然后编译运行;

(2)将FormBorderStyle设为其他值,再运行看看结果;

(3)在代码窗口中选中FormBorderStyle,然后按F1键,调出MSDN,了解MSDN对这一属性的描述;

(4)让窗体在启动时在屏幕上自动居中。

只需设置窗体的StartPosition属性即可。

Me.StartPosition = FormStartPosition.CenterScreen

StartPosition属性还有其他的取值,请读者自行查阅MSDN。

Form类拥有很多属性,本节仅介绍了几个最常用的属性,请您通过MSDN了解其他属性。

4.Form类的常用方法

Form类提供了许多方法,其中最常用的有以下几个:

(1)关闭与隐藏窗体

Me.Close() '关闭窗体

Me.Hide() '隐藏窗体

 技术内幕

关闭一个窗体是指不再使用这个窗体,.NET虚拟机把这个窗体对象标记为不再使用,所占用的内存可以被回收。一个窗体被关闭之后,就不能再使用它了。

隐藏一个窗体是指这个窗体暂时从屏幕上消失,在需要的时候可以通过将其Visible属性设置为True而重新显示在屏幕上。

窗体隐藏的特点是窗体对象仍然存在于内存中,当再次显示时,其中所有控件的属性值仍然保存隐藏之前的原始状态。创建一个窗体需要花费许多时间,而重新显示一个窗体则速度非常快。所以,为了加快程序的响应时间,可以预先创建一个窗体对象但不显示(Visible=False),需要时设置Visible=True显示它,这会给用户此程序运行速度很快的感觉。

 技术探索

使用Timer控件进行计数,让窗体显示1分钟,再隐藏1分钟。

(2)显示窗体

可以使用窗体对象的Show()、ShowDialog()方法显示窗体,但这两者是有区别的,请做以下实验。

从“项目”菜单中选择“添加Windows窗体”命令(参见图2-7)。

图2-7 新建一个窗体

单击【打开】按钮,将会在项目中添加一个新窗体Form2,可以在项目管理器中看到这个新窗体。

打开第一个窗体Form1,确定窗体上有一个按钮Button1,双击Button1切换到代码视图,给其Click事件编码:

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

Dim frm As New Form2

frm.ShowDialog()

End Sub

编译运行之后,单击按钮Button1,Form2窗体将会显示在屏幕上。

注意,这时可以试着再单击Form1,您会发现Form1的标题栏始终是灰的,无法被激活。只有先关闭Form2,才能再次单击Form1上的Button1。两个窗体之间如果存在这种关系,则称Form2 按“模式”方式显示。

现在把代码中的frm.ShowDialog()改为frm.Show(),再运行看看,现在,可以随意地用鼠标单击激活Form1和Form2了。这种情况称Form2按“非模式”方式显示。

何时用模式,何时用非模式,取决于程序的具体要求。一个实例是Microsoft Word的“插入图片”窗体就必须是模式的,而“查找”窗体就必须是非模式的,读者可以打开Word试一试。

读者可能会奇怪,在解决方案资源管理器中Form1、Form2看上去都是地位平等的,为什么程序运行时会自动显示Form1,而不会自动显示Form2,要显示Form2居然还要编码?

这涉及到“程序主窗体”的概念。使用VB的早期版本创建一个新项目时,默认情况下第一个创建的窗体称为主窗体,后创建的窗体是辅助窗体,关闭主窗体会导致程序的结束,关闭辅助窗体则不会结束整个程序。VB.NET延续了这个概念。当程序运行时,会首先显示主窗体(当然可以通过给Sub Main()编码手动改变VB.NET的默认行为),所以,主窗体往往又称为“启动窗体”,但这种称呼的含义是模糊的,定义并不严格。

可以定义项目中的任何一个窗体为启动窗体,方法是在“解决方案资源管理器”中的项目节点上单击鼠标右键,选择“属性”(参见图2-8)。

图2-8 设定启动对象

如图2-8所示,从“启动对象”下拉列表中选择一个窗体对象,则此窗体将会在程序运行时自动显示,关闭此窗体会导致程序关闭,而项目中的其他窗体对象必须先被创建(New)之后,再调用其Show()或ShowDialog()方法显示。

 技术内幕

主窗体的真实含义

由于VB.NET在后面悄悄地进行了一些工作,所以主窗体的真实含义不好理解。C#就明确得多了。

打开VS.NET,创建一个C#的Windows应用程序,切换到代码视图,可以看到以下代码:

/// <summary>

/// 应用程序的主入口点

/// </summary>

[STAThread]

static void Main()

{

Application.Run(new Form1());

}

在Application.Run()中指定的窗体就是主窗体。当主窗体关闭以后,Application.Run()函数执行完毕,Main()函数结束,整个程序也就终止了。

5.Form类的事件

事件(Event)是面向对象编程中的一个很重要的概念。可以这样通俗地理解,事件用于表明在某个对象内部某些事情发生了,其他对象可以决定是否对这些事情进行响应。

举一个简单的例子,当鼠标在一个窗体上单击时,会激发窗体类的Click事件,如果程序员为这个Click事件写了一些代码,那么,程序运行时只要鼠标单击了这个窗体,Click事件就会发生,程序员写的代码就会被运行。

下面看两个实例。

实例1:跟踪鼠标的移动

新建一个“Windows应用程序”项目,取名为“MouseLocation”,往窗体上拖入一个Label控件,设置其Text属性为“(0,0)”,如图2-9所示。

双击图2-9所示主窗体内部,将会切换到代码视图,光标默认位于Form_Load()过程中,如图2-10所示。

 

图2-9 主窗体 图2-10 给特定的事件编码

在代码视图左上角的组合框中选中“(Form1 Events)”,在右上角的组合框中选择“MouseMove”(如图2-10所示),则VS .NET会自动生成处理MouseMove事件的框架代码,并且光标会自动定位于Sub Form1_MouseMove()过程内部:

Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As _

System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove

文本框: 
图2-11 实例运行结果

End Sub

在Sub Form1_MouseMove()过程内部输入以下代码:

Label1.Text="(" & CStr(e.X) & "," & CStr(e.Y) & ")"

现在编译并运行程序(参见图2-11)。

可以看到随着鼠标的移动,标签的数字也跟着变动,能及时地显示当前鼠标的位置坐标。

理解这个示例的关键有两点。

(1)当鼠标在窗体上移动时,MouseMove事件会被激发,会调用我们给MouseMove事件写的代码:

Label1.Text = "(" & CStr(e.X) & "," & CStr(e.Y) & ")"

MouseMove事件激发多少次,这段代码就会执行多少次。

(2)当MouseMove事件激发时,当前鼠标的位置坐标XY被封装到了MouseMove事件的参数e中,可以用e.X和e.Y取出这个值。

为了在标签上显示(0,0)这样格式的信息,需要把整数e.X和e.Y用VB.NET提供的内部函数CStr()转为字符串变量,然后再连在一起,这就是我们所写的代码的作用。

 提示

有关函数的概念将在2.2节介绍。

 技术内幕

窗体坐标和屏幕坐标

在计算机中使用直角坐标系来确定一个点(参见图2-12)。

与数学中使用的直角坐标系不同,计算机中使用的直角坐标系Y轴正向是向下的。

窗体坐标是以窗体为依据的,窗体内部区域左上角为原点。而屏幕坐标是以显示器所显示的区域为标准的,屏幕上左上角为原点,如图2-13所示。

实例MouseLocation中输出的是窗体坐标值。

第10章10.1.4节中对坐标系有更多的介绍。

实例2:在窗体上输出文字

在实例MouseLocation中是使用Label控件输出结果的,能不能不使用控件而直接在窗体上输出文字?完全可以。

新建一个“Windows应用程序”项目,往窗体上增加一个按钮,设置其Name属性为“btnOutputStr”,将Text属性改为“输出字串”,如图2-14所示。

给按钮btnOutputStr的Click事件编码如下:

Private Sub btnOutputStr_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles btnOutputStr.Click

Dim g As Graphics

'获取绘图对象

g = Me.CreateGraphics

'以红色输出字串

g.DrawString("您好!世界!", New Font("宋体", 16), Brushes.Red, 10, 10)

'释放绘图对象

g.Dispose()

End Sub

运行结果如图2-15所示。

 

图2-14 设计好的程序界面 图2-15 在窗体上输出文字

简要解释一下所写的代码:

要在窗体上输出,必须获取代表窗体可输出区域的Graphics对象。可以把这个对象看成是一块画布,有了画布,就可以用红色画刷(Brushes.Red),以16点大小的宋体字,在窗体坐标(10,10)处输出字符串“您好!世界!”。

使用完绘图对象g之后,使用Dispose()方法回收对象占用的资源。

程序运行时,单击按钮可以正确地输出字符串。但现在试一试,把窗体最小化,再还原,发生了什么?原来看得见的“您好!世界!”不见了,为什么会这样呢?

原来Windows中所有的程序窗体都是“画出来的”,当一个窗体最小化时,Windows负责把此窗体挡住的其他窗体重绘出来;当窗体还原时,Windows就负责在原来的位置重新把此窗体画出来。为了正确地完成这个重绘过程,Windows必须知道它需要重绘窗体上的哪些东西。

Windows知道应该重绘窗体和窗体上的控件,但在运行中使用绘图方法输出的内容(如上面的代码所示)就不会自动重绘了,这就是字符串“神秘失踪”的原理。

怎么解决这个问题呢?

Windows在每次重绘窗体的过程中,窗体对象都会引发一个Paint事件,因此可以在此事件中再次调用绘图代码。将原来的代码移入到Form_Paint()事件中,如下所示。

Private Sub Form1_Paint(ByVal sender As Object, _

ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint

Dim g As Graphics

'获取绘图对象

g = e.Graphics

'以红色输出结果

g.DrawString("您好!世界!", New Font("宋体", 16), Brushes.Red, 10, 10)

'释放绘图对象

g.Dispose()

End Sub

编译运行之后,发现窗体先最小化再还原之后,文字会正确地显示。结合前面所讲的内容,您一定会明白其中的道理。

 提示

10.1.2节中详细介绍了图形重绘原理。

现在运行修改过的程序,细心的读者会发现程序一运行就有文字输出,不再需要单击按钮了,这是因为窗体第一次显示在屏幕上时,Paint事件也会发生。所以,现在可以从代码中删除Sub btnOutputStr_Click()过程,然后到窗体视图中把按钮删除。

 提示

仅删除按钮本身并不会同步删除其事件响应代码,这部分代码需要手动删除。

新代码的另一个改变是获取Graphics绘图对象的语句变了,它可以直接从参数e中获取。这是因为Paint事件主要用于绘图,所以,为了方便,其参数e中就直接包含了绘图对象g,不再需要调用窗体对象的CreateGraphics()方法。

 技术探索

绘图对象Graphics拥有相当丰富的功能,请查阅MSDN,试着在窗体上输出一幅图片。

除了已介绍的MouseMove事件和Paint事件,常用的Form类的事件还有以下几个。

(1)Load事件:当窗体第一次创建时发生。

(2)Click/DoubleClick事件:鼠标在窗体上单击/双击时发生。

(3)KeyPress/KeyDown/KeyUp事件:从键盘输入时发生,通过事件提供的参数,可以知道用户按下了键盘的哪个键,其中KeyDown和KeyUp事件可以判断组合键(如Ctrl+C)。

(4)MouseDown/MouseUp事件:按下鼠标键时发生,可以知道按下的是左键还是右键,以及按下时是否同时还按了键盘的组合键(如Ctrl、Alt、Shift)。

(5)Closing与Closed事件:当窗体关闭时发生。特别是Closing事件,可以通过设置其参数e.Cancel=True来禁止用户单击窗体右上角的关闭按钮来关闭窗体。

 技术探索

在MSDN中查看Form类的各种事件,了解其含义和用途。

2.1.2 常见控件使用

上一节介绍了窗体Form类的常用属性、事件和方法。一个空白的Form并没有多大用,Form最常见的是作为一个“容器”,在其中可以加上各种各样的控件,从而构成一个可视化的程序用户界面。

本节将介绍三个常见界面控件:菜单、状态条和工具栏的使用方法。在本书中不会对所有的控件做详细介绍,而希望能通过若干典型的控件介绍一些重要的基础知识。掌握了这些知识,读者就可以通过其他资料来自行学会使用更多的控件了。

1.设计菜单(Menu)

菜单可谓是Windows应用程序中最重要的界面元素之一。有两种主要的菜单形式:

(1)标准的下拉式菜单,这种菜单一般位于窗口的最上方,分为若干个菜单组,每个组下又有多个菜单项,有的菜单项还有子菜单项。

文本框: 
图2-16 两种菜单组件的图标(2)弹出式菜单,这种菜单一般通过单击鼠标右键弹出。

在VS .NET中,MainMenu组件用于创建下拉式菜单,ContextMenu组件用于创建弹出式菜单,如图2-16所示。

在VS .NET中设计菜单非常简单。以下拉式菜单为例,把一个MainMenu组件从工具箱中拖到窗体上,窗体上部就会出现一个菜单的雏形,并有一个选中的灰色方框可直接输入菜单文本,如图2-17所示。

每输入完一项,可以按回车键确定。

图2-17 菜单设计器

当需要修改某一项菜单时,直接用鼠标单击选中,然后在屏幕右下方的属性窗口设置菜单项的各种属性。

VS .NET提供的菜单设计器非常易于使用,以下是一些使用技巧。

(1)菜单分隔线:可以直接在输入区中输入减号“-”生成,亦可以在菜单项属性窗口中设置Text属性为减号“-”。

(2)可以直接用鼠标拖动菜单项以调整相对位置。

(3)选中菜单项单击鼠标右键,会弹出一个快捷菜单,其中提供了一些常用的命令(参见图2-18)。

“剪切”一个菜单,然后在需要插入的位置选择“粘贴”命令,可以快速地在多个菜单组之间移动子菜单项。

(3)菜单项有许多常用属性,可以在菜单项属性窗口中进行设置(参见图2-19)。

 

图2-18 菜单设计器的弹出式菜单 图2-19 菜单项属性窗口

这里简单介绍一下常用属性的用法(表2-1)。

表2-1 菜单项常用属性

实现功能

 

设置属性

禁用菜单项

让选定的菜单项不可用(即灰掉)

Enabled=True

复选菜单项

选中后在菜单项前会加一个小对钩,可以同时有多个菜单项打上此标记

Checked=True

单选菜单项

选中后会在菜单项前加一个小圆点,一个菜单组中一次只能有一个菜单项被选中

RadioCheck=True

隐藏菜单项

让一个菜单项不显示(仍是存在的,只不过程序运行时在菜单中看不到此项)

Visible=False

(4)弹出式菜单:使用ContextMenu组件设计,其设计方法与MainMenu基本是一样的,但其特点是运行时并不会自动显示此菜单,需要右击特定的控件才能出现。

在VS .NET提供的许多控件中,都有一个ContextMenu属性,只需在这些控件的属性窗口中设计其ContextMenu属性为指定的ContextMenu组件名字即可将控件与弹出式菜单相关联。

 试一试

(1)新建一个“Windows应用程序”项目,然后,试着建立本章实例MyEditor的菜单(参见图2-20)。

图2-20 “我的文本编辑器”菜单

(2)随意设计一个弹出式菜单,设置右击窗体弹出此菜单。

给菜单项编码也非常简单,只需在菜单设计器中双击某个菜单项,即可切换到代码视图。VS .NET会自动生成菜单项Click事件的框架代码,如下所示:

Private Sub mnuOpen_Click(ByVal sender As System.Object, _

ByVale As System.EventArgs) Handles mnuOpen.Click

'在此输入代码

End Sub

 提示

在VS .NET中,双击某个指定的控件会自动生成其默认事件的框架代码,然后就可以从代码编辑器右上方的下拉框中选取特定的事件,这是一个最基本、最常用的使用技巧,请注意掌握,以后本书不再重复介绍为某个事件编码的基本步骤。

 技术探索

(1)读者可能会感到好奇,VS .NET是如何生成这些菜单的?

请随意设计一个菜单,然后切换到代码视图,展开折叠的标有“Windows窗体设计器生成的代码”这部分代码,找到“Private Sub InitializeComponent()”这一过程,其中的代码解释了菜单生成的全部奥秘。要完全看懂这些代码,您需要至少学完本书前4章的内容。

(2)您一定见过许多漂亮的菜单,并可能想在 VS.NET提供的MainMenu组件中给菜单加上小图标,然而很遗憾,VS.NET 默认提供的 MainMenu 组件并没有此功能,请您利用 Google(http://www.google.com)到因特网上去找到解决此问题的方法。在许多专业技术论坛中也有对此问题的解答。

2.使用状态条(StatusBar)

状态条大家并不陌生,图2-21是Word 2003中的状态条。

图2-21 Word 2003提供的状态条

可以看到状态条主要用于显示各种信息,信息被划分为几个区域。每个区域被称为状态条的面板(Panel)。

(1)状态条示例介绍

在VS .NET中,提供了一个StatusBar控件用于在程序中显示一个状态条。请看图2-22所示的状态条使用实例的运行效果(示例项目UserStatusBar)。

图2-22所示为最简单的状态条,在窗体下部仅有一个简单的文本,此时状态条处于简单文本状态(SimpleText)。单击【切换状态条显示状态】按钮,可以看到如图2-23所示的窗体。

 

图2-22 仅有文本的状态条 图2-23 显示面板的状态条

图2-23所示为显示了多个面板的状态条。第一个面板仅显示了一些文本,第二个面板加了一个图标,第三个面板将显示一个进度条,第四个面板是一个时钟,显示了当前的时间。

先单击【将进度条放到状态栏中】,再单击【让进度条开始走/停】按钮,可以看到进度条在状态条上不停地“走”,如图2-24所示。

图2-24 不停地“走”的进度条

这个小例子虽然简单,却是本书所介绍的第一个复杂一些的程序。通过对这个例子的分析,相信读者一定能大大增加对VS .NET编程的兴趣。

(2)状态栏示例分析

现在来揭开这个示例程序的面纱,看看怎么实现上面介绍过的实例功能。

① 访问状态条对象与面板对象

首先要明白,状态条(StatusBar)中可以放置多个面板,每个面板都是一个StatusBarPanel对象,多个StatusBarPanel对象构成一个集合,作为状态条对象的Panels属性而存在。

当需要访问一个面板对象时,要通过其所属的集合对象访问。假设状态条对象StatusBar1中有三个面板,则以下代码设置第2个面板的文本为“这是第2个面板”:

StatusBar1.Panels(1).Text = "这是第2个面板"

 注意

.NET中集合的索引都是从0开始的,所以,Panels(0)代表第一个面板,而Panels(2)是第三个面板。

 提示

.NET的许多控件中都有集合属性,这些集合属性一般其名字后面都以“s”结尾,集合中往往存放着许多类型一致的对象。访问集合中对象的方法都是一致的,按以下代码格式进行:

控件对象名.集合属性名(要访问的对象在集合中的位置)

在.NET中,集合的位置称为索引,是从0开始的。

有关集合的更详细介绍,请参见第3章。

可以通过状态条对象的属性窗口给状态条增加面板,如图2-25所示。

在图2-25中,单击Panels属性右边的标有省略号的按钮,将会打开“StatusBarPanel集合编辑器”窗口,如图2-26所示。

 

图2-25 状态条对象的面板属性 图2-26 “StatusBarPanel集合编辑器”窗口

从图2-26中可以看到,使用“StatusBarPanel集合编辑器”窗口,可以方便地在左边列表框中添加和删除面板对象,在右边网格中则可以设置选中的面板对象的属性。

表2-2是StatusBarPanel对象的一些重要属性。

表2-2 StatusBarPanel的重要属性

 

 

Text

定义在面板上显示的文字

BorderStyle

定义面板的显示外观(边框是凸起还是凹下,或是根本没有边框)

Icon

定义图标(示例中第2个面板显示的小图片就是通过此属性提供的)

AutoSize

定义面板的大小是不是会随着Text所定义的文字长度而变化

当把一个状态条对象从工具箱拖到窗体上时,它默认是不显示面板的。要显示面板,请将其ShowPanels属性置为True(参见图2-25)。

状态条不处于面板状态时(即ShowPanels=False),要设置它上面的文字请使用其Text属性。

以下是示例程序中切换状态条显示状态的代码:

01 Private Sub btnChangeStatus_Click(ByVal sender As System.Object, _

02 ByVal e As System.EventArgs) Handles btnChangeStatus.Click

03 '隐藏进度条

04 Me.ProgressBar1.Visible = False

05 With Me.StatusBar1

06 .ShowPanels = Not .ShowPanels

07 If .ShowPanels = False Then

08 .Text = "这是简单文本状态"

09 Else

10 .Panels(0).Text = "这是显示面板状态"

11 End If

12 End With

13 End Sub

注意第5行和第12行引入了一个新的关键字——With。With关键字主要用于简化程序代码输入。其格式为:

With 对象名

'使用对象的各种代码

End With

在With块内部,对象的名字可以简写。

在前述的示例代码中:

With Me.StatusBar1

.ShowPanels = Not .ShowPanels

'……

End With

其实相当于以下代码:

Me.StatusBar1.ShowPanels = Not Me.StatusBar1.ShowPanels

读这种语句时,只需要把With后对象名字搬到代码中以英文句点“.”打头的部分就能“凑”出完整的代码。

另外一个是“Me”这个词语,Me在VB.NET中是一个关键字,代表对象自身。在本例中,这些代码是写在窗体内部的,所以,Me就代表窗体对象frmStatus。又由于状态条对象StatusBar1是放在窗体上的,所以,可以通过Me.StatusBar1来访问这个对象。其实,也可以不写Me,直接把“Me.StatusBar1”换成“StatusBar1”,程序仍然正确。那为什么多写一个Me呢?

读者只要在代码编辑器中试一试就明白了,输入一个Me,再加一个小句点,VS .NET会马上下拉出一个方法和属性清单。再输入“stat”几个字母,马上就可找到StatusBar1这个对象,直接按空格或Tab键都会让StatusBar1直接上屏。这是不是很方便?以后读者还会看见其他示例中的代码前也有一个Me,一般情况下去掉它对程序没有任何影响,但对解放读者的手指头可大有影响(参见图2-27)。

现在再来看看第6行代码:

06 .ShowPanels = Not .ShowPanels

前面说过,当ShowPanels属性为True时显示状态条对象会显示面板,如果需要不断地在“显示”和“隐藏”两种状态之间切换,可以按照以下处理逻辑来写代码:

如果 ShowPanels=True 那么

ShowPanels=False

否则

ShowPanels=True

图2-27 使用Me加快代码输入

换成VB.NET代码之后,就是这样:

If StatusBar1.ShowPanels = True Then

StatusBar1.ShowPanels = False

Else

StatusBar1.ShowPanels = True

End If

注意到True和False是互斥的,非此即彼,所以,可以使用一个Not运算符来把上面的4句变成一句(见示例中的第6行)。

Not运算符的作用就是取反:

Not False=True

Not True=False

示例中的第4个面板是一个时钟,有关显示时钟的代码相信读者已经知道了,只需取出当前时间,然后设置对应面板对象的Text属性即可。实际代码不再列出。

② 在状态栏上显示进度条

进度条(ProgressBar)往往直观地显示某个工作完成了多少,多用数字表达,它的使用很简单,只需要明了如表2-3所示的属性的含义即可。

表2-3 进度条属性含义

 

 

Minimum

最小值

Maximum

最大值

Value

当前值

举个实例:若Minimum=0,Maximum=100,则当Value=50时,表明当前工作完成了50%。

现在的问题是进度条与状态条是两个彼此独立的控件,怎样把它们两个放在一块?

答案是把进度条对象的Parent属性设为状态条对象,这样进度条对象就成了状态条对象的“儿子”,会乖乖地待在进度条对象内部。

 试一试

新建一个Windows应用程序项目,往窗体上加一个状态条和进度条,随便往状态条中加几个面板,然后编写代码将进度条的Parent属性设为状态条对象,看看发生了什么?

读者会发现进度条确实跑到状态条内部去了,但却把面板给挡住了,显然这样是不行的,还必须想办法精确定位进度条到指定的位置。

仔细观察一下示例程序的状态条(图2-28)。

图2-28 组合了进度条的状态条

读者会看到,进度条的长短和高度刚好和第3个面板一样,所以,如果能得到第3个面板的长度和高度,就知道该把进度条“缩小”并“平移”到多少才可以放进面板中。再看看图2-25所示的StatusBarPanel对象属性中,有一个Width属性刚好符合要求。

下一个问题是进度条的左上角坐标。如果把进度条的左上角坐标设为0,则进度条会把第一个面板给盖住,因此必须知道第3个面板的左上角坐标。很遗憾,StatusBarPanel对象没有一个对应的属性能直接提供这个数据,必须另想方法。

进度条要放到第3个面板上,而前两个面板的宽度是知道的,那么,很容易就有以下公式:

进度条的左上角距状态条左边框的距离= 第1个面板的宽度 + 第2个面板的宽度

 技术内幕

不是所有控件都能被设为其他控件的Parent的,可以这样做的控件称为“容器”控件(即可以容纳其他控件的控件)。当一个控件被加入到一个容器控件中时,它的位置坐标是以容器为准的。比如Form就是一个容器控件,当设置一个标签的Left属性为100时,其含义是该标签到窗体左边缘的距离是100像素,而不是到屏幕左边距的距离是100像素。因为窗体往往是可以在屏幕上移动的,因此标签到屏幕左边距的距离会不断改变,而标签相对于窗体的距离是不变的。因此,我们把标签相对于窗体的坐标值称为“相对坐标”(或“窗体坐标”),标签到屏幕边距的坐标称为“绝对坐标”(或“屏幕坐标”)。

现在,所有的问题都解决了,读者应该可以看得懂示例的代码了:

Private Sub btnAddProgressBar_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles btnAddProgressBar.Click

With Me.ProgressBar1

'设置左边距

.Left = Me.StatusBar1.Panels(0).Width + _

Me.StatusBar1.Panels(1).Width + 2

.Top = 2 '设置上边距

.Width = Me.StatusBar1.Panels(2).Width '设置宽度

.Height = Me.StatusBar1.Height - 4 '设置高度

.Parent = Me.StatusBar1 '将其加入到状态条中

.Visible = True '让其可视

End With

End Sub

注意,为了美观,应把进度条上下高度缩进2个像素,这样,状态条的边框就可以完整地显示出来,从而保持了面板外观的一致性。

 提示

如果能先弄明白程序中实现某个功能的思路是什么,再阅读这段代码就很容易理解了。解决某个功能的思路,一般是通过在程序代码中加上注释,或者是在软件所配的文档中说明。

3.使用工具条(ToolBar)

工具条是另一种常见的用户界面控件,其上可以放置多个按钮。图2-29是本书示例程序MyEditor的工具条。

图2-29 工具条

(1)给工具条添加按钮

工具条上的每一个按钮都是一个ToolBarButton对象,工具条上的所有按钮组成一个集合,以工具条对象的Buttons属性代表。

类似于访问状态条的面板对象,可以通过以下代码来访问工具条ToolBar1的第一个按钮的Text属性。

Me.ToolBar1.Buttons(0).Text = "New"

要给工具条添加按钮,请在工具条对象的Buttons属性上单击标有省略号的按钮,打开如图2-30所示的按钮编辑窗口。

图2-30 给工具条增加按钮

从图2-30可以看到,给工具条增加按钮,非常类似于给状态栏增加面板,其操作也是类似的。

 提示

在程序中访问状态条的面板与访问工具条的按钮,其方法都是一样的,掌握这种编程模式,就可将其应用到所有需要访问集合成员的场合,这将在后面的实例代码中不断看到。在学习中善于总结这种共性的东西,就可以举一反三,提高学习的效率。

(2)给工具条按钮增加图标

从图2-30中只看到ToolBarButton对象中有一个ImageIndex属性,并没有像状态栏的面板对象那样有一个Icon属性可以指定一个图标。那么,工具条按钮上的小图标从何而来?

这就用到了另一个控件ImageList。ImageList是一个专用于存放小图标的控件。使用时将其从工具箱中拖到窗体上,然后,打开其Images属性,会出现以下窗口(图2-31)。

图2-31 向ImageList控件增加图标

给ImageList控件增加完图标以后,再设置ToolBar控件的ImageList属性为包含了多个图标的ImageList控件,如图2-32所示。

这时,就可以从ToolBarButton集合编辑器中给特定的按钮添加图标,如图2-33所示。

 

图2-32 将ImageList控件与 图2-33 给工具条按钮添加图标

ToolBar控件联系起来

 试一试

(1)ToolBarButton的Style属性有四个取值:PushButton,ToggleButton,Seperator和DropDownButton,请尝试找出这四个值的作用。

(2)ToolBarButton有一个ToolTipText属性,请弄清楚这个属性的作用。

 提示

ImageList这个控件常被用来向许多其他控件提供图标,比如TreeView和ListView,在后面的内容中会看到这方面的介绍。

(3)确定单击的按钮

在窗体设计器上双击工具条对象,将会出现以下代码框架:

'工具条按钮事件

Private Sub ToolBar1_ButtonClick(ByVal sender As System.Object, _

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

Handles ToolBar1.ButtonClick

End Sub

那么如何编程确定用户单击的是哪个按钮?

注意到工具条对象的单击事件处理框架代码中有一个类型为ToolBarButtonClickEventArgs的参数e,通过它的Button属性就可以知道单击的按钮对象。

可以通过按钮对象的Text属性来具体区分是哪个按钮,比如:

If e.Button.Text = "新建" Then

'单击了“新建”按钮

'……

End If

但由于Text属性经常改变,所以,比较好的方式是通过控件的属性窗口给每个ToolBar的Tag属性赋一个惟一的字符串值,然后,通过此属性来区别开各个按钮。典型的代码如下所示:

Private Sub ToolBar1_ButtonClick(ByVal sender As System.Object, _

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

Handles ToolBar1.ButtonClick

Select Case e.Button.Tag

Case "New" '单击了"新建"按钮

Me.NewFile()

Case "Open" '单击了"打开"按钮

Me.OpenFile()

'……

End Select

End Sub

上面代码中的Select语句称为多条件分支语句,其格式为:

Select Case 变量名

Case 值1

'处理代码

Case 值2

'……

End Select

这个语句其实完全可以用条件语句来表达:

If 变量名=值1 then

'处理代码

End If

If 变量名=值2 then

'处理代码

End If

'……

显然,Select Case语句更清晰和明确。

 提示

2.2节将详细介绍VB.NET语法。

2.1.3 界面布局

在实际的程序中,往往一个窗体上会有多个控件,而窗体的大小也可以用鼠标动态地调整。窗体大小改变时,窗体上的控件尺寸也必须做相应调整,才能让用户界面有一个比较满意的效果。控件的Dock属性与Anchor属性就是起到这个作用的。

1.Dock属性

通过一个示例来说明Dock属性的作用。

请从工具箱上拖动一个RichTextBox控件到窗体上,然后编译运行,试着改变窗体的大小,会发现RichTextBox控件始终保持原有的大小不变,如图2-34所示。

选中RichTextBox控件,在其属性窗口中设置其Dock属性为Fill,参见图2-35。

 

图2-34 控件大小不随窗体大小而变 图2-35 设置Dock属性为Fill

再次编译运行程序,可以发现RichTextBox控件自动布满整个窗体区域,并可随着窗体大小的改变而自动改变大小。

 试一试

将Dock属性改为其他值,再看看效果。

2.Anchor属性

从工具箱中拖动一个按钮到窗体上,然后编译运行程序,改变窗体大小,可以看到按钮本身是不会改变大小和位置的。

现在选中按钮对象,在属性窗口中将其Anchor属性改为“Left,Right”,如图2-36所示。

现在,编译运行程序,改变窗体大小,可以看到按钮宽度会随着窗体的宽度变化而改变,如图2-37所示。但改变高度时,按钮宽度不受影响。

 

图2-36 Anchor属性 图2-37 按钮宽度与窗体宽度同步变化

 试一试

将Author属性设为其他值,编译运行,改变窗体大小,看看结果。

从上面的尝试中可以得出以下几个有用的组合,如表2-4所示。

表2-4 Anchor属性组合

Anchor属性值

窗体尺寸变化时对控件尺寸的影响

LeftRight

控件宽度与窗体宽度同步变化

Right,Bottom

控件与窗体右下角的距离保持不变

Bottom

控件与窗体下边距的距离保持不变

Left,Right,Top,Bottom

窗体尺寸大小的任何改变,都会使控件的尺寸做同步变化

表2-4中是以窗体为例的,事实上,Anchor属性对任何容器控件内的控件都适用。所谓容器控件就是指能放置其他控件的控件,如窗体(Form)、面板(Panel)、页控件(TabControl)等。当容器的尺寸改变时,容器内的控件按照其Anchor属性值对这种改变做出相应的变化。

 注意

(1)Dock与Anchor属性对于设计用户界面是非常重要的。

设计窗体时,应保证在800′600的屏幕分辨率下仍能显示完整的窗体,而在1024′768或更高的分辨率上也能让窗体显示正常,这时,就要根据分辨率大小而适当改变窗体大小。合理设置窗体中控件的Dock与Anchor属性值就显得十分重要,否则,这个工作就必须手动编码来完成,工作量不小。

(2)何时用Dock,何时用Anchor?

Dock属性主要用于容器控件(窗体除外)和拥有较大尺寸的控件,比如RichTextBox控件、树控件(TreeView)等;而Anchor则用在小尺寸控件中,如按钮(Button)、标签(Label)等。对容器控件很少需要用Anchor属性,多用Dock属性。

当然,何时用Dock,何时用Anchor并没有绝对正确的答案,需要在开发实践中灵活取舍,以达到满意的界面效果。

3.同时设置多个控件的共同属性

如果在窗体上同时有不止一个控件,则可以拖动鼠标画出一个框同时选中这些控件,如图2-38所示。

也可以先单击选中第一个控件,然后按住Ctrl键,再次单击其他控件。如果要从已选择的控件集合中去掉某个控件,只需再次单击它即可(注意要同时按下Ctrl键),如图2-39所示。

 

图2-38 用鼠标拖动选择多个控件 图2-39 选中的多个控件

一旦选中了多个控件,就可以使用“布局”工具条(参见图2-40)对这些控件进行对齐、平均分布等操作。

图2-40 VS .NET窗体设计器的布局工具条

针对选中的多个控件,VS .NET还会自动地抽取出所有这些控件都拥有的属性,显示在属性窗口中,这时,对任何一个属性所做的修改都会同时作用于所有选中的控件。

 试一试

在窗体上放置多个文本框,然后选中它们,在属性窗口中设置其Text属性为空,观察效果。

2.1.4 绘制图标

在Windows Form应用程序开发中,有许多地方(比如工具条按钮)需要用到图标。在VS .NET中,可以直接创建图标文件。

新建一个“Windows应用程序”,然后在“解决方案资源管理器”窗口中右击项目节点,选择“添加/添加新项”命令,出现如图2-41所示的对话框。

图2-41 添加图标文件

注意选中“图标文件”模板,单击【打开】按钮,可以看到图2-42所示的画面。

图2-42的左侧有一个调色板,在相应色块上单击鼠标左键可定义前景色,单击鼠标右键可设置背景色。在上方的工具条上提供了绘制直线、文字等工具。注意,VS .NET的主菜单中增加了一个新菜单——“图像”,其中提供了一些在工具条上没提供的功能(参见图2-43)。

 

图2-42 VS .NET的图标编辑器 图2-43 图像菜单

图标编辑器的使用很简单,用提供的绘图工具画好图标后,保存在磁盘上即可。

 技术内幕

Windows使用的图标最常用的有两种:16′16(长与宽均为16像素)的,常用于工具栏按钮图标;32′32的,大都用在普通按钮上。图标文件的后缀名为“.ico”。一般来说,图标颜色数不易过多,黑白两色的对比明确,但略显单调,256色的比较美观,但当图标尺寸为16′16时,人眼分辨不清,16色是较好的选择。

有许多专用的图标制作工具,可以到因特网上去搜索,也可以使用功能强大的绘图软件如CorelDraw、Photoshop等制作图标,但要注意颜色及尺寸大小的问题。

 试一试

到目前为止,读者已学习了许多使用控件来设计用户界面的知识,现在是实践的时候了。请从本书配套光盘的示例源码下找到MyEditor示例程序,这是一个文本编辑器,如图2-44所示。

图2-44 MyEditor用户界面

请试着用所学到的知识把这个界面设计出来。

 提示

(1)这个文本编辑器用到一个MainMenu控件绘制菜单,一个工具条(ToolBar),一个RichTextBox控件用于编辑文本,一个状态条(StatusBar)用于显示信息。

(2)当窗体大小改变时,RichTextBox会自动变换大小,占满整个窗体的空白区域,如何实现?

(3)StatusBar有三个面板,处于显示面板状态。

(4)工具栏上的按钮图标部分是VS .NET直接提供的,部分可使用本节介绍的VS .NET图标编辑器绘制。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多