现在我们看看这样一个问题。我们想要做这样一个控件,他继承自Window.Forms.TextBox下面,也就是说它支持TextBox的所有功能,但是我们需要一个新的功能,就是当用户按下回车之后,我们判断一下这个TextBox里面的文字,如果是空字符串的话,就显示一个MessageBox,默认的内容是“Empty connect is not validated.”。同时我们希望用户可以选择是否显示这个MessageBox。我们用此前的办法来做。
我们的类很快就可以完成。
Public Class MyTextBox
Inherits TextBox
Public Event EnterKeyPress(ByVal sender As Object, ByRef Cancel As Boolean, ByRef Message As String)
Private Sub MyTextBox_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
'Do something then user press ENTER KEY
If Asc(e.KeyChar) = 13 Then
Dim Cancel As Boolean = False
Dim Message As String = "Empty connect is not validated."
'Send the event with parameters
RaiseEvent EnterKeyPress(Me, Cancel, Message)
'Show message box depend on the return parameters
If Cancel = False Then
MsgBox(Message)
End If
End If
End Sub
End Class
而且我们很容易就可以使用这个控件了。
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
Me.ClientSize = New System.Drawing.Size(292, 273)
Me.Name = "Form1"
Me.Text = "Form1"
End Sub
#End Region
Friend WithEvents MyTextBox1 As New MyTextBox
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Controls.Add(Me.MyTextBox1)
Me.MyTextBox1.Location = New Point(100, 100)
End Sub
Private Sub MyTextBox1_EnterKeyPress(ByVal sender As Object, ByRef Cancel As Boolean, ByRef Message As String) Handles MyTextBox1.EnterKeyPress
End Sub
End Class
现在看来一切正常,但是无聊的客户突然来了一个Mail,他们希望我们能够在显示MessageBox的时候,将TextBox的背景色变成红色,并且允许有可能在其它的时候会变成绿色。也就是说变色也是要使用者可以自由设定的。这样我们就要在Event参数列表里面加入一个新的背景色参数,并且修改所有使用了这个Event的Handle函数。
Public Class MyTextBox
Inherits TextBox
Public Event EnterKeyPress(ByVal sender As Object, ByRef Cancel As Boolean, ByRef Message As String, ByVal BackColor As Color)
Private Sub MyTextBox_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
'Do something then user press ENTER KEY
If Asc(e.KeyChar) = 13 Then
Dim Cancel As Boolean = False
Dim Message As String = "Empty connect is not validated."
Dim Color As Color = Color.Red
'Send the event with parameters
RaiseEvent EnterKeyPress(Me, Cancel, Message, Color)
'Show message box depend on the return parameters
If Cancel = False Then
MsgBox(Message)
End If
End If
End Sub
End Class
然后修改使用的地方,也许实际上我们只是让它显示成默认的红色,我们也需要修改使用Event的地方,因为Event的定义已经不同了。
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
Me.ClientSize = New System.Drawing.Size(292, 273)
Me.Name = "Form1"
Me.Text = "Form1"
End Sub
#End Region
Friend WithEvents MyTextBox1 As New MyTextBox
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Controls.Add(Me.MyTextBox1)
Me.MyTextBox1.Location = New Point(100, 100)
End Sub
Private Sub MyTextBox1_EnterKeyPress(ByVal sender As Object, ByRef Cancel As Boolean, ByRef Message As String, ByVal Color As Color) Handles MyTextBox1.EnterKeyPress
End Sub
End Class
现在我们只有这样一处使用这个Event的地方,但是假设我们有100个Form,每个Form都有20个这样的TextBox(记住,你做的是通用控件,他可能会被用在任何的地方,会被使用上千次)。那么我们就要这样修改2000次。其中可能只有10个时需要把背景色变成别的颜色,比如耦合色。但是我还是要修改2000个函数的。
当我们终于修改完成之后,客户有发来一封稍有歉意的Mail,说他们还希望能够设定MessageBox的按钮状态和显示属性,大部分是OKCancel按钮和Information形式,个别的是OK按钮和Error形式。看来我们有需要修改我们的TextBox类和那2000个函数了。
而后,用户又来了一个Mail……天哪,看来我们有必要把Mail Service关掉了。因为这样修改对于项目是毁灭性的。我们有必要重新审视一下我们的TextBox类的建立问题了。
我们现在关键的问题就是参数,我们增加一点Event的功能,就需要修改Event的参数列表,然后修改所有使用这个Event的Handle函数。但是很多的时候,真正修改函数内部的地方可能很少,甚至没有。我们重复的劳动就是一个个修改Handle函数的参数列表。
那么我们看看.NET是怎么做的,我们看看Form.Closing事件Handle出来的函数。
Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
End Sub
我们把注意力集中到第二个参数,它不是我们前面熟悉的EventArgs,而是CancelEventArgs。而我们看看这个CancelEventArgs里面都有什么。我们看到了e.Cancel。.NET把我们此前做的那个Cancel参数通过第二个参数的成员属性传递进来了。这样我们就通过设定e.Cancel来达到我们原先的处理。
这里可能有的人会产生疑问,e参数被声明为ByVal,它应该不能被返回到RaiseEvent调用处的啊?但是事实是可以的。原因是.NET在处理类的实例参数的时候,传进来的不是实例本身,而是实例的指针。而我们ByVak的只不过是保存这个指针地址变量的一个副本。所以即使是ByVal,如果这个参数是类的实例,也可以把成员传递回去。
这样做的好处就是如果还有新的参数加入,我们只需要修改这个CancelEventArgs类和相应的RasiseEvent,调用的HandleEvent如果不需要对应这个新的参数,就没有必要修改了。这个就是面向对象的好处,封装了操作,只通过接口来连接。也就是软件工程里面所说的松耦合。
现在我们已找这个办法修改自己的TextBox类。首先我们要做好这个EventArgs类,模仿CancelEventArgs,我们做一个EnterKeyPressEventArgs。
Public Class EnterKeyPressEventArgs
Inherits EventArgs
Private m_Cancel As Boolean
Private m_Message As String
Private m_BackColor As Color
Public Property Cancel() As Boolean
Get
Return m_Cancel
End Get
Set(ByVal Value As Boolean)
m_Cancel = Value
End Set
End Property
Public Property Message() As String
Get
Return m_Message
End Get
Set(ByVal Value As String)
m_Message = Value
End Set
End Property
Public Property BackColor() As Color
Get
Return m_BackColor
End Get
Set(ByVal Value As Color)
m_BackColor = Value
End Set
End Property
Public Sub New()
m_Cancel = False
m_Message = "Empty connect is not validated."
m_BackColor = Color.Red
End Sub
End Class
这个类继承自EventArgs,通过私有变量和公共属性来传递参数。为什么不用共有变量呢,有原因的,我心准备在专门写一篇文章说说这个问题。反正这样私有变量加公共属性的办法比公共变量要好。
然后我们修改TextBox类,调用RaiseEvent的地方就可以使用这个类了。
Public Event EnterKeyPress(ByVal sender As Object, ByVal e As EnterKeyPressEventArgs)
Private Sub MyTextBox_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
'Do something then user press ENTER KEY
If Asc(e.KeyChar) = 13 Then
'Create the args instance
Dim ekpea As New EnterKeyPressEventArgs
'Send the event with parameters
RaiseEvent EnterKeyPress(Me, ekpea)
'Show message box depend on the return parameters
If ekpea.Cancel = False Then
Me.BackColor = ekpea.BackColor
MsgBox(ekpea.Message)
End If
End If
End Sub
我们使用的时候,也可以相应的修改成。
Private Sub MyTextBox1_EnterKeyPress(ByVal sender As Object, ByVal e As MyTextBox.EnterKeyPressEventArgs) Handles MyTextBox1.EnterKeyPress
'Some codes ...
e.Cancel = False
e.Message = "My message ... "
e.BackColor = Color.Green
'Some codes ...
End Sub
这样即便客户又来信说,我们需要加入一个功能,设定这个MessageBox的按钮类型,默认是VBOKOnly和Information。我们需要修改的地方首先是这个EventArgs。
Public Class EnterKeyPressEventArgs
Inherits EventArgs
Private m_Cancel As Boolean
Private m_Message As String
Private m_BackColor As Color
Private m_ButtonStyle As MsgBoxStyle
Public Property Cancel() As Boolean
Get
Return m_Cancel
End Get
Set(ByVal Value As Boolean)
m_Cancel = Value
End Set
End Property
Public Property Message() As String
Get
Return m_Message
End Get
Set(ByVal Value As String)
m_Message = Value
End Set
End Property
Public Property BackColor() As Color
Get
Return m_BackColor
End Get
Set(ByVal Value As Color)
m_BackColor = Value
End Set
End Property
Public Property Style() As MsgBoxStyle
Get
Return m_ButtonStyle
End Get
Set(ByVal Value As MsgBoxStyle)
m_ButtonStyle = Value
End Set
End Property
Public Sub New()
m_Cancel = False
m_Message = "Empty connect is not validated."
m_BackColor = Color.Red
m_ButtonStyle = MsgBoxStyle.OKOnly Or MsgBoxStyle.Information
End Sub
End Class
然后修改RaiseEvent的一句话。
MsgBox(ekpea.Message, ekpea.Style)
而使用的Handle函数只要使用默认值,就不需要修改了。