正确使用Event

一、如何定义和引发事件

定义事件有两种语法,一种是显式指定事件处理方法的委托类型:

[modifier] Event EventName As HandlerType

另一种是直接在定义语句中写出事件处理方法的参数,这是一种隐式指定委托类型的方法:

[modifier] Event EventName(args)

在这种定义下VB会根据参数表生成一个嵌套定义的委托类型。我建议大多数情况使用第一种定义方法,因为第一种方法可以将事件处理方法的委托类型独立出来,防止无意中创建大量浪费的类型。比如下面两个事件:

Public Event Test(ByVal sender As Object, ByVal e As EventArgs)
Public Event Test1(ByVal sender As Object, ByVal e As EventArgs)

这两个事件具有同样的参数结构,因此他们的委托类型应当是一样的。但是VB还没有智能到那个地步,这两句语句将产生两个不同的委托类型。更深一步,这种参数的处理过程已经被系统定义为System.EventHandler,所以没必要创建任何新的委托类型,只需要这样写:

Public Event Test As EventHandler
Public Event Test1 As EventHandler

只有一种情况可以随意使用参数表形式定义事件,那就是实现定义在接口中的事件时。比如接口中有这个事件:

Public Interface ITest
Event Test As EventHandler
End Interface

则实现这个接口的事件定义可以写成

Public Event Test(ByVal sender As Object, ByVal e As EventArgs) Implements ITest.Test

这里不会产生新的委托类型,因为VB能够判断事件的委托类型来自于接口中的定义。用显示指定委托类型的方式定义事件的另一个好处是让C#中使用该事件的代码变得简洁些。

在引发事件方面,很多VB的初学者到处随意使用RaiseEvent来引发事件,这是非常不正确的。应该总是在一个叫OnXXX(XXX是事件的名称)的受保护的虚方法中引发事件。比如引发上面例子中Test事件,应该写一个这样的方法:

Protected Overridable Sub OnTest(e As EventArgs)
RaiseEvent Test(Me, e)
End Sub

在真正需要引发事件的地方调用OnTest方法来引发事件。为什么要这么做?因为只有这样,才能允许该类型的子类控制该事件的行为。如果不是这样写,子类就无法获知父类何时引发事件,更无法在事件引发的时候执行自定义的代码。至于为什么必须起名为OnXXX,这可能是.NET类库一致的约定。请一定要习惯这种写法,在进一步的开发中,我们将逐渐发现这种写法的思想所在。

二、事件参数的设计

我们看到.NET类库中对象的事件总是提供一个Object类型的参数sender和一个XXXEventArgs类型的参数e。这是整个.NET Framework统一的设计。XXXEventArgs是一个继承自System.EventArgs的类型,他表示通过事件传递的数据,而sender则表示事件的引发者。我们设计事件处理方法的时候,如果没有特殊情况,应当总是提供两个参数:一个Sender参数和一个继承自EventArgs的类型的参数。这种设计可以最大限度的让所有事件处理过程有相似的签名。你将会发现,当你的设计需求发生改变时,这种设计可以最大限度地减小整个系统的修改。比如若事件设计成这样:

Event MyEvent(sender As Object, index As Integer, product As String)

当需求改变,需要再多传递一个Decimal类型的price参数时,系统中所有响应这个事件的过程都需要增加一个参数,那将是一场灾难。如果将所有要传递的数据封装在一个继承自EventArgs的类型——MyEventArgs中,代码就能写成这样:

Event MyEvent(sender As Object, e As MyEventArgs)

那么当需求改变的时候,只需要修改一下MyEventArgs的成员即可,响应事件的方法定义不需要做任何变化。
多数情况下,事件是不需要传递参数的。因此用系统定义的System.EventHandler是非常好的选择,没必要为每个事件编写一个新的EventHandler或者EventArgs类型。

如果需要通过事件进行参数回传(就是说,事件的处理方法要将某些数据传递给事件的引发者),那么同样可以用处理方法的参数来完成,只需要将自定义EventArgs类型中需要回传的属性设置为可写(提供Set访问器)即可。一般不需要用某些教材中建议的那样,设计ByRef的参数来实现回传。因为EventArgs能够满足大部分设计的需要。

至于Sender参数,应该始终让调用者传递Me变量以便事件的处理方法能够正确识别事件的引发者。

结论

事件设计是面向对象的重要内容,设计一个良好的事件通常包括:显式指定事件处理方法的委托类型,一般情况采用系统设定的System.EventHandler,用名为OnXXX的受保护的虚方法引发事件以及用自定义的继承自EventArgs的类型包装事件传递的数据。

EventTrigger是WPF触发器的一种类型,它根据事件的引发来启动一组操作。以下结合示例介绍其使用方法。 ### 基本使用示例 当鼠标指针进入`ListBoxItem`时,`MaxHeight`属性在0.2秒时间内以动画方式增大为值90;当鼠标离开该项时,该属性在1秒时间内还原为原始值。代码示例如下: ```xml <ListBox> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Style.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="MaxHeight" To="90" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="MaxHeight" Duration="0:0:1"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle> <ListBoxItem Content="Item 1"/> <ListBoxItem Content="Item 2"/> </ListBox> ``` 上述代码中,定义了`ListBoxItem`的样式,在样式的触发器中使用`EventTrigger`。当`MouseEnter`事件触发时,启动一个故事板,其中的`DoubleAnimation`将`MaxHeight`属性在0.2秒内动画到90;当`MouseLeave`事件触发时,启动另一个故事板,`DoubleAnimation`将`MaxHeight`属性在1秒内恢复到原始值,由于动画能够跟踪原始值,所以`MouseLeave`动画无需指定`To`值 [^2]。 ### 与命令绑定使用示例 在VM部分代码中定义命令,然后在XAML中使用`EventTrigger`触发命令。 VM部分代码: ```csharp public class StudentViewModel : NotificationObject { public StudentViewModel() { Clicked = new ActionCommand(this.Click); } public ICommand Clicked { get; private set; } public void Click(object arg) { //为了演示需要,在这里用了一个MessageBox //应尽量避免在VM中揉杂UI交互功能 MessageBox.Show((arg as Student).StudentName); } } ``` XAML部分代码(假设已正确绑定`StudentViewModel`): ```xml <Button> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <cmd:EventToCommand Command="{Binding Clicked}" PassEventArgsToCommand="True"/> </i:EventTrigger> </i:Interaction.Triggers> <Button.Content>Click Me</Button.Content> </Button> ``` 上述代码中,在VM里定义了`Clicked`命令,在XAML的按钮上使用`EventTrigger`监听`Click`事件,当按钮被点击时,触发`EventToCommand`将事件传递给`Clicked`命令 [^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值