用过VB6的人都会对控件数组念念不忘,因为控件数组在处理多个控件统一事件上确实很方便。.NET Framework没有引入控件数组这一概念,这是因为.NET Framework的类型系统很完善,可以实现控件数组原来的功能。只是这样凭空增加了一些麻烦,我们不是需要在Handls字句后面写一长串控件名称,就是要用写数遍AddHandler或C#的+=语句给多个控件绑定同一个事件。为了减轻没有控件数组日子的痛苦,我写了这个替代方案:共享事件容器。在编写它的过程中,我用了泛型,但其实不用泛型也可以做到,只是泛型能够约束容器内控件的类型,减少可能的运行时错误。
在看实现方法之前,先来看看共享事件容器是怎么操作的。首先你先要放一些相同类型控件到窗体上,这些就是你的控件数组元素了,假设是button1,button2和button3。然后在窗体的构造函数或Load事件中添加如下代码:
ba = New ShareEventsContainer(Of Button) '创建一个共享事件容器
ba.AddRange(New Button() {Button1, Button2}) '用AddRange可以添加多个控件
ba.Add(Button3) '当然Add方法也是支持的了
这就是共享事件容器了,就像是一般的集合一样,你可以用数组的语法操纵其中的对象。接下来就是重头戏了,我们要将容器内的全部控件的Click事件都绑定到同一个方法上,比如这个方法
Public Sub Buttons_Click(ByVal sender As Object, ByVal e As EventArgs)
MsgBox("Hello! I'm " & CType(sender, Button).Name)
End Sub
则接着在刚才初始化共享事件容器语句的后面写事件绑定代码:
ba.AddHandler("Click", New EventHandler(AddressOf Buttons_Click))
有点像VB的语法,其实我就是取了个神似。这里事件名称是要用字符串表示的,可别写错了。而事件处理程序的委托也与一般的事件绑定语句不一样,这时必须将委托类型写出来,还得写正确了。VB和C#2.0的委托类型推定在这时是不起作用的。你还可以写更多的AddHandler,绑定其他事件。
好了,就这么简单,事件已经绑定好了。更进一步的是,将来继续往这个容器内添加新的按钮,他们的事件也会自动绑定到这时预定的所有处理程序上,而从容器中移除控件,其事件会自动解除绑定。你还可以在任意地方用AddHandler和RemoveHandler方法添加和删除新的事件处理程序,支持多播事件方法。
如果你觉得这个方法值得用用,那就可以看下面的实现代码了:VB2005 BETA1
Imports System.Collections.Generic
Imports System.Reflection
'包含委托的列表类型
Imports DelegateList = System.Collections.Generic.List(Of System.MulticastDelegate)
''' <summary>
''' 提供一个共享事件容器,容器内的控件可以共享事件处理过程
''' </summary>
''' <typeparam name="T">控件的类型,不支持Menu</typeparam>
''' <remarks>
''' 共享事件容器在事件处理上类似于Visual Basic 6.0或更早版本的控件数组,
''' 它可以将多个控件的事件绑定到同一个处理过程上,方便进行功能一致的操作。
''' </remarks>
Public Class ShareEventsContainer(Of T)
Inherits Collection(Of T)
'保存当前共享事件容器所预定的全部事件
'为了支持多播事件,必须采用这种结构的存储方式
Private events As Dictionary(Of String, DelegateList)
Private controlType As Type
''' <summary>
''' 初始化一个默认的共享事件容器
''' </summary>
Public Sub New()
MyBase.New()
controlType = GetType(T)
events = New Dictionary(Of String, DelegateList)
End Sub
''' <summary>
''' 初始化一个共享事件容器,并添加初始的控件
''' </summary>
''' <param name="controls"></param>
Public Sub New(ByVal ParamArray controls() As T)
MyClass.New()
If controls IsNot Nothing Then
AddRange(controls)
End If
End Sub
''' <summary>
''' 为容器内所有控件绑定统一的事件处理过程
''' </summary>
''' <param name="eventName">要绑定的事件名称,应当是T类型所支持的事件</param>
''' <param name="handler">对应于事件,并且类型正确的委托变量</param>
Public Sub [AddHandler](ByVal eventName As String, _
ByVal handler As MulticastDelegate)
If events.ContainsKey(eventName) Then
events(eventName).Add(handler)
Else
events.Add(eventName, New DelegateList)
events(eventName).Add(handler)
End If
Dim ev As EventInfo = controlType.GetEvent(eventName)
Try
For Each item As T In Me
ev.AddEventHandler(item, handler)
Next
Catch ex As NullReferenceException When ev Is Nothing
Throw New ArgumentException("所请求的事件不存在", "eventName")
Catch ex As ArgumentException
Throw New ArgumentException("事件处理的程序与事件的委托类型签名不符", "handler")
End Try
End Sub
''' <summary>
''' 为容器内所有控件解除绑定统一的事件处理过程
''' </summary>
''' <param name="eventName">要绑定的事件名称,应当是T类型所支持的事件</param>
''' <param name="handler">对应于事件,并且类型正确的委托变量</param>
Public Sub [RemoveHandler](ByVal eventName As String, _
ByVal handler As MulticastDelegate)
If events.ContainsKey(eventName) Then
events(eventName).Remove(handler)
Dim ev As EventInfo = controlType.GetEvent(eventName)
Try
For Each item As T In Me
ev.RemoveEventHandler(item, handler)
Next
Catch ex As NullReferenceException When ev Is Nothing
Throw New ArgumentException("所请求的事件不存在", "eventName")
Catch ex As ArgumentException
Throw New ArgumentException("事件处理程序与事件的委托类型签名不符", "handler")
End Try
End If
End Sub
'在删除项目时,解除该项目的事件绑定
Private Sub WhenRemoveItem(ByVal index As Integer)
Dim ev As EventInfo
For Each eventName As String In events.Keys
ev = controlType.GetEvent(eventName)
For Each handler As MulticastDelegate In events(eventName)
ev.RemoveEventHandler(Me(index), handler)
Next
Next
End Sub
'在插入新项目时,为该项目绑定所有事件
Private Sub WhenInsertItem(ByVal index As Integer, ByVal item As T)
Dim ev As EventInfo
For Each eventName As String In events.Keys
ev = controlType.GetEvent(eventName)
For Each handler As MulticastDelegate In events(eventName)
ev.AddEventHandler(item, handler)
Next
Next
End Sub
Protected Overrides Sub RemoveItem(ByVal index As Integer)
WhenRemoveItem(index)
MyBase.RemoveItem(index)
End Sub
Protected Overrides Sub ClearItems()
For i As Integer = 0 To Me.Count - 1
WhenRemoveItem(i)
Next
MyBase.ClearItems()
End Sub
Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T)
WhenInsertItem(index, item)
MyBase.InsertItem(index, item)
End Sub
Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As T)
WhenRemoveItem(index)
WhenInsertItem(index, item)
MyBase.SetItem(index, item)
End Sub
''' <summary>
''' 添加多个项目到共享事件容器中
''' </summary>
''' <param name="items">包含要添加项的可列举容器</param>
Public Sub AddRange(ByVal items As IEnumerable(Of T))
For Each item As T In items
Add(item)
Next
End Sub
End Class
其实我写的也有些仓促,还没有仔细测试,估计还有不少毛病。不过这个思路的确可以简化不少使用控件数组方面的麻烦,我今后也会不断改进和完善它的。