** Henry 手记 - VB.NET Windows 窗体 引介(二) **
韩睿 ( 12/26/2002)
上一篇发表后,有读者来信和留言,认为是不是太简单了?其实, Henry是有一些私心的,本来我准备继续讨论Datagrid中的事件响应(如双击cell这样的复杂过程),但必须要涉及到事件处理方法的内容,正好本文的结构可以让Henry在适当扩充后达到这一目标。这虽然是个不复杂的问题,但写清楚还是不太容易的,写完此篇后,发现除结构上与原文一致外,已经面目全非了,为了保证对原作者的尊重,只好将此篇剔除出译作的范围,这里有 原文的链接 ,可供读者自行参考。并借此机会对原作者提供的帮助表示感谢。
**添加控件 **
如果没有控件,您的窗体还并不是真正有用。 .NET Framework 基本类型库提供了大量控件以备使用。你可以使用 button, checkbox, textbox, label... 如果对于 .NET Framework 您还是个新手,您还需要研究一下 Object 派生树。以 button 控件为例,其派生关系如图 1 所示:
System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Windows.Forms.Control
System.Windows.Forms.ButtonBase
System.Windows.Forms.Button
图 1 Button 控件派生树
所有控件都是派生自 Cotrol 类,向窗体中添加一个控件需要以下三个步骤:
( 1 )定义一个控件变量
( 2 )实例化控件
( 3 )把控件加入窗体中
因为 VB.NET 变量有不同的应用范围,在何处声明一个变量才是最重要的。在一个函数内声明的变量只能在函数内生存。如果您想让您的控件在整个类中都可用,请在类级别上定义它。
实例化一个控件就象实例化其它任何对象一样,使用 NEW 关键字。一旦您有一个实例化了的控件,你就可以将其加入窗体中去。因为 Form 类没有诸如 Add(Control) 这样的方法,你不能直接将控件添加到窗体上去。但是, Form 类从 Control 类继承了 Controls 属性。在 Form 类中,您可以使用此属性去获取该 Form 的 System.Windows.Forms.Form.ControlCollection 对象。这个 System.Windows.Forms.Form.ControlCollection 类代表了在窗体中包含的控件的集合。您可以使用它的 Add 方法往窗体中添加控件,用 Remove 方法把一个控件从窗体中移除。要理解这三个步骤,以添加一个 Button 控件为例将代码示例如下:
' 声明 button 变量
Dim Button1 As System.Windows.Forms.Button
' 实例化 button
Button1 = New System.Windows.Forms.Button()
' 将 button 加至窗体
Me.Controls.Add(Button1)
如果您不是只添加一个控件,而是要加入多个控件,一般不要使用 Add 方法去一个个添加,而是使用 AddRange 方法去添加一组 Control 对象。比如,您往窗体中添加一个 button(ID:button1) ,同时添加一个 textbox(textbox1) ,可以这样写:
Me .Controls.AddRange( New System.Windows.Forms.Control() { Me .TextBox1, Me .Button1})
您可以通过设置控件的多个属性去获得您所需要的控件。可以在将其加到窗体之前或之后设置这些属性,但必须是要实例化了控件之后。一个控件的属性通常有控制其大小与位置的 Left, Top, Width 和 Height 。一个 Button 属性还有 Text 属性用于显示 button 对象的文本等等。以下的代码是继续上一节的内容,补充初始化的代码:
Private Button1 As System.Windows.Forms.Button
Private TextBox1 As System.Windows.Forms.TextBox
1<system.diagnostics.debuggerstepthrough()> Private Sub InitializeComponent()
2
3Me .Button1 = New System.Windows.Forms.Button()
4
5Me .TextBox1 = New System.Windows.Forms.TextBox()
6
7Me .SuspendLayout()
8
9'Button1
10
11Me .Button1.Location = New System.Drawing.Point(200, 72)
12
13Me .Button1.Name = "Button1"
14
15Me .Button1.Size = New System.Drawing.Size(96, 56)
16
17Me .Button1.TabIndex = 0
18
19Me .Button1.Text = "Button1"
20
21'TextBox1
22
23Me .TextBox1.Location = New System.Drawing.Point(216, 168)
24
25Me .TextBox1.Name = "TextBox1"
26
27Me .TextBox1.Size = New System.Drawing.Size(64, 21)
28
29Me .TextBox1.TabIndex = 1
30
31Me .TextBox1.Text = "TextBox1"
32
33'Form1
34
35Me .AutoScaleBaseSize = New System.Drawing.Size(6, 14)
36
37Me .ClientSize = New System.Drawing.Size(392, 266)
38
39Me .Controls.AddRange( New System.Windows.Forms.Control() { Me .TextBox1, Me .Button1})
40
41Me .Name = "Form1"
42
43Me .Text = "Henry 的窗体"
44
45Me .ResumeLayout( False )
46
47End Sub
48
49_ 这里 Henry要解释几个一般不太注意的问题: _
50
51(1) 布局事件:在窗体中添加或移除控件,或在控件中添加或移除子控件,窗体 / 控件的边界改变,以及在发生其他可能影响窗体/ 控件的布局时,将会发生 Layout 事件。因此在设置初始化代码时,应该先禁止(挂起)此事件发生,在布局完毕后再恢复。要不然每添加、设置一个控件就会调用一次Layout事件,对窗体进行重新布局,影响工作效率。所以在上例中用 Me .SuspendLayout()来挂起Layout事件,在设置完毕后,用 Me .ResumeLayout( False )来恢复该事件。
52
53(2) VB.NET的习惯是使用集合、数组来处理以前的个体行为,我们可以从上例中看出。设置控件的位置时,没有用left、right、top等属性来定位,而是使用Location属性来设置该控件左上角的坐标。设置控件大小时,没有用Height、width等属性,而是使用Size。这也表现了VB.NET设计与使用上的一个变革思想,多使用集合,可以提高代码的简洁度,且便于阅读与管理。
54
55(3) Button1与Textbox1的Private声明在后文会详加解释。
56
57**** **事件处理 **
58
59先前版本的 VB 是事件驱动的,您可以很方便地编写响应事件的程序代码。比如,您想让按钮 Button1 响应用户的单击 (Click) 事件,所有要做的事就是创建一个名为 Button1_Click 的事件程序(实际上, VB 集成编译环境帮您完成了此工作)。在 VB.NET 中的事件处理方法,和以前是一样的。当您在设计窗口布署了控件后,可以点击它以告知 Visual Studio.NET 您想写一段事件处理程序。接下来您将看到如何人工编写该代码,也因此使您明白 VB.NET 的事件是如何工作的。
60
61对于 Windows事件驱动程序而言,不可避免要涉及三个要素:对象、事件、事件处理程序。
62
63那么对于我们这个例子,对应上述要素的各是什么呢?
64
65对象: Button1 事件:Click 事件处理程序:Button1_Click
66
67联系起来用语言描述就是:点击 Button1(对象),会激发Click(事件),调用Button1_Click(事件处理程序)。
68
69如果您想让某个对象能够响应事件,就要在声明它时使用 WithEvent 关键字( WithEvents 关键字指示被声明的对象变量引用可以引发事件的类实例)。例如,当您想让按钮 (button) 能够响应事件,就要将声明:
70
71Private Button1 As Button
72
73替换为:
74
75Private WithEvents Button1 As Button ‘这个声明在后文还会有变化,请读者注意
76
77在变量名前添加 WithEvent 。
78
79然后,您需要为每个事件创建一段事件处理程序以使对象能够在响应事件时调用。 Button1 要响应 Click 事件,您需要有一个 Button1_Click 过程,并用 Handles 标识“控件 . 事件”。
80
81Private Sub Button1_Click(ByVal sender As Object, _
82
83ByVal e As System.EventArgs) Handles Button1.Click
84
85' 在此写自己的代码
86
87End Sub
88
89Form 类和所有的控件类都从 Control 类继承下了事件。如果您查看一下 .NET Framework 参考中的 System.Windows.Forms.Control 类,您将会看到此类有 58 个事件,包括从 Component 继承下的 Disposed 事件(关于 Disposed 的继承关系,请看 Henry 的 另一篇文档 )。还命名了许多新的诸如 Click, DoubleClick, GotFocus, LostFocus, KeyUp, KeyDown 等事件。
90
91很多新手都会有这样的问题:事件处理程序中的参数 Sender 与 e 是什么意思?
92
93(1) sender as object,就一语道破它的来源与用途。object是支持 .NET 框架类层次结构中的所有类,并为派生类提供低级别服务。这是 .NET 框架中所有类的最终超类;它是类型层次结构的根。
94一般来说,sender在形参中表示引发事件的源头,即上文所说的三要素中的“对象”。如果在控件引发的事件中写代码的话(就象上例这样),一般都不需要再重新指派,因为它已经默认为是该控件了。当自己写代码来调用某事件程序时,就要注明sender是何物了。
95(2) e,表示的是事件数据,就是一个事件激发所需要的状态(参数)信息。在事件引发时不向事件处理程序传递状态信息的事件会将e设为Eventargs。如果事件处理程序需要状态信息,则应用程序必须从此类派生一个类来保存数据。比如Mousedown事件,系统需要判断mouse的位置、判断是左中右等哪个键点击了、判断点击了几下等等,因此该e就成为了System.Windows.Forms.MouseEventArgs类的实例。
96
97
98当几个控件的事件处理程序是相同时,可以把“对象名 . 事件名”加至 Handles 之后,即可在事件引发时,调用同一个事件处理程序。如果处理程序有所不同,可以通过 Sender 来进行标识,代码如下:
99
100Private Sub Button1_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) _
101
102Handles Button1.Click, Button2.Click
103
104Select Case sender.name
105
106Case "Button1"
107
108MsgBox("你点击的是button1")
109
110Case "Button2"
111
112<SPAN lang=EN-US style="FONT-SIZ</system.diagnostics.debuggerstepthrough()>