Udostępnij za pośrednictwem


Events (F#)

Events enable you to associate function calls with user actions and are important in GUI programming. Events can also be triggered by your applications or by the operating system.

Handling Events

When you use a GUI library like Windows Forms or Windows Presentation Foundation (WPF), much of the code in your application runs in response to events that are predefined by the library. These predefined events are members of GUI classes such as forms and controls. You can add custom behavior to a preexisting event, such as a button click, by referencing the specific named event of interest (for example, the Click event of the Form class) and invoking the Add method, as shown in the following code. If you run this from F# Interactive, omit the call to 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)

The type of the Add method is ('a -> unit) -> unit. Therefore, the event handler method takes one parameter, typically the event arguments, and returns unit. The previous example shows the event handler as a lambda expression. The event handler can also be a function value, as in the following code example. The following code example also shows the use of the event handler parameters, which provide information specific to the type of event. For a MouseMove event, the system passes a MouseEventArgs object, which contains the X and Y position of the pointer.

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)

Creating Custom Events

F# events are represented by the F# Event class, which implements the IEvent interface. IEvent is itself an interface that combines the functionality of two other interfaces, IObservable<T> and IDelegateEvent. Therefore, Events have the equivalent functionality of delegates in other languages, plus the additional functionality from IObservable, which means that F# events support event filtering and using F# first-class functions and lambda expressions as event handlers. This functionality is provided in the Event module.

To create an event on a class that acts just like any other .NET Framework event, add to the class a let binding that defines an Event as a field in a class. You can specify the desired event argument type as the type argument, or leave it blank and have the compiler infer the appropriate type. You also must define an event member that exposes the event as a CLI event. This member should have the CLIEvent attribute. It is declared like a property and its implementation is just a call to the Publish property of the event. Users of your class can use the Add method of the published event to add a handler. The argument for the Add method can be a lambda expression. You can use the Trigger property of the event to raise the event, passing argument to the handler function. The following code example illustrates this. In this example, the inferred type argument for the event is a tuple, which represents the arguments for the lambda expression.

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

The output is as follows.

Event1 occurred! Object data: Hello World!

The additional functionality provided by the Event module is illustrated here. The following code example illustrates the basic use of Event.create to create an event and a trigger method, add two event handlers in the form of lambda expressions, and then trigger the event to execute both lambda expressions.

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.")

The output of the previous code is as follows.

Event occurred.
Given a value: Event occurred.

Processing Event Streams

Instead of just adding an event handler for an event by using the Event.add function, you can use the functions in the Event module to process streams of events in highly customized ways. To do this, you use the forward pipe (|>) together with the event as the first value in a series of function calls, and the Event module functions as subsequent function calls.

The following code example shows how to set up an event for which the handler is only called under certain conditions.

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) )

The Observable module contains similar functions that operate on observable objects. Observable objects are similar to events but only actively subscribe to events if they themselves are being subscribed to.

Implementing an Interface Event

Frequently in developing UI components, one begins by inheriting from an existing control or form. Events may frequently be defined on an interface and in that case you must implement the interface to implement the event. The following code illustrates this. The INotifyPropertyChanged interface defines a single PropertyChanged event.

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"))

   // 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)

See Also

Reference

Lambda Expressions: The fun Keyword (F#)

Control.Event Module (F#)

Control.Event<'T> Class (F#)

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

Concepts

Events and Delegates

Other Resources

Members (F#)

Change History

Date

History

Reason

December 2011

Add example of implementing an interface with an event.

Information enhancement.

January 2011

Emphasize the use of Event rather than DelegateEvent.

Information enhancement.