事件使你可以将函数调用与用户作相关联,在 GUI 编程中非常重要。 事件也可以由应用程序或作系统触发。
处理事件
使用 GUI 库(如 Windows 窗体或 Windows Presentation Foundation)时,应用程序中的大部分代码都运行,以响应库预定义的事件。 这些预定义事件是 GUI 类的成员,例如窗体和控件。 可以通过引用感兴趣的特定命名事件(例如 Click 类的事件 Form )和调用 Add 方法,将自定义行为添加到预先存在的事件(如按钮单击),如以下代码所示。 如果从 F# Interactive 运行此函数,请省略对 . 的 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)调用。
open System.Windows.Forms
let form = new Form(Text="F# Windows Form",
Visible = true,
TopMost = true)
form.Click.Add(fun evArgs -> System.Console.Beep())
Application.Run(form)
方法的类型 Add 为 ('a -> unit) -> unit. 因此,事件处理程序方法采用一个参数,通常是事件参数,并返回 unit。 上一个示例将事件处理程序显示为 lambda 表达式。 事件处理程序也可以是函数值,如以下代码示例所示。 下面的代码示例还演示了事件处理程序参数的使用,这些参数提供特定于事件类型的信息。
MouseMove对于事件,系统将传递一个System.Windows.Forms.MouseEventArgs对象,该对象包含X指针的位置和Y位置。
open System.Windows.Forms
let Beep evArgs =
System.Console.Beep( )
let form = new Form(Text = "F# Windows Form",
Visible = true,
TopMost = true)
let MouseMoveEventHandler (evArgs : System.Windows.Forms.MouseEventArgs) =
form.Text <- System.String.Format("{0},{1}", evArgs.X, evArgs.Y)
form.Click.Add(Beep)
form.MouseMove.Add(MouseMoveEventHandler)
Application.Run(form)
创建自定义事件
F# 事件由实现 IEvent 接口的 F# 事件类型表示。
IEvent 是一个接口,它结合了另外两个接口 System.IObservable<'T> 和 IDelegateEvent 的功能。 因此, Event具有其他语言的委托的等效功能,以及附加 IObservable功能,这意味着 F# 事件支持事件筛选,并使用 F# 一流函数和 lambda 表达式作为事件处理程序。
此功能在事件模块中提供。
若要在与任何其他 .NET Framework 事件一样运行的类上创建事件,请向类添加一个 let 绑定,该绑定将定义为 Event 类中的字段。 可以将所需的事件参数类型指定为类型参数,或将其留空,并让编译器推断相应的类型。 还必须定义将事件公开为 CLI 事件的事件成员。 此成员应具有 CLIEvent 属性。 它声明为属性,其实现只是对事件的 Publish 属性的调用。 类的用户可以使用 Add 已发布事件的方法添加处理程序。 该方法的参数 Add 可以是 lambda 表达式。 可以使用 Trigger 事件的属性引发事件,将参数传递给处理程序函数。 下面的代码示例对此进行了说明。 在此示例中,事件的推断类型参数是元组,表示 lambda 表达式的参数。
open System.Collections.Generic
type MyClassWithCLIEvent() =
let event1 = new Event<string>()
[<CLIEvent>]
member this.Event1 = event1.Publish
member this.TestEvent(arg) =
event1.Trigger(arg)
let classWithEvent = new MyClassWithCLIEvent()
classWithEvent.Event1.Add(fun arg ->
printfn "Event1 occurred! Object data: %s" arg)
classWithEvent.TestEvent("Hello World!")
System.Console.ReadLine() |> ignore
输出如下所示。
Event1 occurred! Object data: Hello World!
此处演示了模块提供 Event 的其他功能。 下面的代码示例演示了创建事件和触发器方法的基本用法 Event.create ,以 lambda 表达式的形式添加两个事件处理程序,然后触发该事件来执行这两个 lambda 表达式。
type MyType() =
let myEvent = new Event<_>()
member this.AddHandlers() =
Event.add (fun string1 -> printfn "%s" string1) myEvent.Publish
Event.add (fun string1 -> printfn "Given a value: %s" string1) myEvent.Publish
member this.Trigger(message) =
myEvent.Trigger(message)
let myMyType = MyType()
myMyType.AddHandlers()
myMyType.Trigger("Event occurred.")
前面的代码的输出如下所示。
Event occurred.
Given a value: Event occurred.
处理事件流
可以使用模块中的Event函数以高度自定义的方式处理事件流,而不是使用 Event.add 函数为事件添加事件处理程序。 为此,请使用正向管道(|>)和事件作为一系列函数调用中的第一个值,并将 Event 模块函数用作后续函数调用。
下面的代码示例演示如何设置仅在特定条件下调用处理程序的事件。
let form = new Form(Text = "F# Windows Form",
Visible = true,
TopMost = true)
form.MouseMove
|> Event.filter ( fun evArgs -> evArgs.X > 100 && evArgs.Y > 100)
|> Event.add ( fun evArgs ->
form.BackColor <- System.Drawing.Color.FromArgb(
evArgs.X, evArgs.Y, evArgs.X ^^^ evArgs.Y) )
可观测模块包含对可观测对象进行作的类似函数。 可观测对象类似于事件,但如果事件本身被订阅,则仅主动订阅事件。
实现接口事件
开发 UI 组件时,通常首先创建从现有窗体或控件继承的新窗体或新控件。 事件经常在接口上定义,在这种情况下,必须实现接口来实现该事件。 该 System.ComponentModel.INotifyPropertyChanged 接口定义单个 System.ComponentModel.INotifyPropertyChanged.PropertyChanged 事件。 以下代码演示如何实现此继承接口定义的事件:
module CustomForm
open System.Windows.Forms
open System.ComponentModel
type AppForm() as this =
inherit Form()
// Define the propertyChanged event.
let propertyChanged = Event<PropertyChangedEventHandler, PropertyChangedEventArgs>()
let mutable underlyingValue = "text0"
// Set up a click event to change the properties.
do
this.Click |> Event.add(fun evArgs ->
this.Property1 <- "text2"
this.Property2 <- "text3")
// This property does not have the property-changed event set.
member val Property1 : string = "text" with get, set
// This property has the property-changed event set.
member this.Property2
with get() = underlyingValue
and set(newValue) =
underlyingValue <- newValue
propertyChanged.Trigger(this, new PropertyChangedEventArgs("Property2"))
// Expose the PropertyChanged event as a first class .NET event.
[<CLIEvent>]
member this.PropertyChanged = propertyChanged.Publish
// Define the add and remove methods to implement this interface.
interface INotifyPropertyChanged with
member this.add_PropertyChanged(handler) = propertyChanged.Publish.AddHandler(handler)
member this.remove_PropertyChanged(handler) = propertyChanged.Publish.RemoveHandler(handler)
// This is the event-handler method.
member this.OnPropertyChanged(args : PropertyChangedEventArgs) =
let newProperty = this.GetType().GetProperty(args.PropertyName)
let newValue = newProperty.GetValue(this :> obj) :?> string
printfn "Property {args.PropertyName} changed its value to {newValue}"
// Create a form, hook up the event handler, and start the application.
let appForm = new AppForm()
let inpc = appForm :> INotifyPropertyChanged
inpc.PropertyChanged.Add(appForm.OnPropertyChanged)
Application.Run(appForm)
如果要在构造函数中连接事件,则代码会更加复杂,因为事件挂钩必须位于 then 其他构造函数中的块中,如以下示例所示:
module CustomForm
open System.Windows.Forms
open System.ComponentModel
// Create a private constructor with a dummy argument so that the public
// constructor can have no arguments.
type AppForm private (dummy) as this =
inherit Form()
// Define the propertyChanged event.
let propertyChanged = Event<PropertyChangedEventHandler, PropertyChangedEventArgs>()
let mutable underlyingValue = "text0"
// Set up a click event to change the properties.
do
this.Click |> Event.add(fun evArgs ->
this.Property1 <- "text2"
this.Property2 <- "text3")
// This property does not have the property changed event set.
member val Property1 : string = "text" with get, set
// This property has the property changed event set.
member this.Property2
with get() = underlyingValue
and set(newValue) =
underlyingValue <- newValue
propertyChanged.Trigger(this, new PropertyChangedEventArgs("Property2"))
[<CLIEvent>]
member this.PropertyChanged = propertyChanged.Publish
// Define the add and remove methods to implement this interface.
interface INotifyPropertyChanged with
member this.add_PropertyChanged(handler) = this.PropertyChanged.AddHandler(handler)
member this.remove_PropertyChanged(handler) = this.PropertyChanged.RemoveHandler(handler)
// This is the event handler method.
member this.OnPropertyChanged(args : PropertyChangedEventArgs) =
let newProperty = this.GetType().GetProperty(args.PropertyName)
let newValue = newProperty.GetValue(this :> obj) :?> string
printfn "Property {args.PropertyName} changed its value to {newValue}"
new() as this =
new AppForm(0)
then
let inpc = this :> INotifyPropertyChanged
inpc.PropertyChanged.Add(this.OnPropertyChanged)
// Create a form, hook up the event handler, and start the application.
let appForm = new AppForm()
Application.Run(appForm)