共用方式為


事件 (F#)

事件可讓您產生函式呼叫與使用者動作的關聯,而且對 GUI 程式設計而言十分重要。事件也可以由應用程式或作業系統觸發。

處理事件

當您使用 GUI 程式庫如 Windows Forms 或 Windows Presentation Foundation (WPF) 時,應用程式中的大部分程式碼都會執行以回應程式庫預先定義的事件。這些預先定義的事件是 GUI 類別的成員,例如表單和控制項。您可以參考特定重要具名事件 (例如 Form 類別的 Click 事件),以及叫用 Add 方法,將自訂行為加入至預先存在的事件 (例如按一下按鈕事件),如下列程式碼所示。如果您從 F# Interactive 執行此程式碼,請省略 Run 的呼叫。

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 事件,系統會傳遞 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# 事件是以 F# Event 類別表示,而這個類別會實作 IEvent 介面。IEvent 介面本身合併兩個其他介面 (IObservable<T>IDelegateEvent) 的功能。因此,Event 具有相當於其他語言中委派的功能,加上來自 IObservable 的額外功能,這表示 F# 事件支援事件篩選,以及使用 F# 第一級函式和 Lambda 運算式做為事件處理常式。這個功能是在事件模組中提供。

若要在類別上建立其運作方式與任何其他 .NET Framework 事件類似的事件,請將 let 繫結 (將 Event 定義為類別中的欄位) 加入至類別。您可以指定所需的事件引數型別做為型別引數,或是空過,讓編譯器推斷適當型別。您也必須定義將事件公開為 CLI 事件的事件成員。這個成員應該要有 CLIEvent 屬性。它會宣告為屬性,而它的實作就只是呼叫事件的 Publish 屬性。您類別的使用者可以使用已發行事件的 Add 方法加入處理常式。Add 方法的引數可以是 Lambda 運算式。您可以使用Trigger事件引發的事件,將引數傳遞至處理常式函式的屬性。下列程式碼範例會說明這點。在此範例中,推斷的事件型別引數是 Tuple,代表 Lambda 運算式的引數。

open System.Collections.Generic

type MyClassWithCLIEvent() =

    let event1 = new Event<_>()

    [<CLIEvent>]
    member this.Event1 = event1.Publish

    member this.TestEvent(arg) =
        event1.Trigger(this, arg)

let classWithEvent = new MyClassWithCLIEvent()
classWithEvent.Event1.Add(fun (sender, 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.add 函式來加入事件的事件處理常式,則可以使用 Event 模組中的函式,以高度自訂的方式處理事件資料流。若要這麼做,請使用正向管道 (|>) 與事件做為一連串函式呼叫中的第一個值,並且使用 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) )

Observable 模組包含可在可預見物件上作業的類似函式。可預見物件與事件類似,但是只有在已訂閱可預見物件時,才會主動訂閱事件。

實作介面事件

當您開發 UI 元件時,通常先建立新的表單或新的控制項是繼承自一個現有的表單或控制項。事件通常定義於介面,並在此情況下,您必須實作來實作事件介面。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 %s changed its value to %s" args.PropertyName 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 %s changed its value to %s" args.PropertyName 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)

請參閱

參考

Lambda 運算式:fun 關鍵字 (F#)

Control.Event 模組 (F#)

Control.Event<'T> 類別 (F#)

Control.Event<'Delegate,'Args> 類別 (F#)

其他資源

成員 (F#)

事件和委派