События

События позволяют связывать вызовы функций с действиями пользователя и являются важным элементом в программировании графического интерфейса пользователя. События могут также инициироваться приложениями или операционной системой.

Обработка событий

При использовании библиотеки графического интерфейса пользователя, такой как Windows Forms или Windows Presentation Foundation (WPF), значительная часть кода в приложении выполняется в ответ на события, предопределенные в библиотеке. Эти предопределенные события являются членами классов графического интерфейса пользователя, таких как формы и элементы управления. Можно добавить произвольное поведение к уже существующему событию, такому как нажатие кнопки, сославшись на интересующее именованное событие (например, событие 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. В предыдущем примере обработчик события показан как лямбда-выражение. Обработчик события также может представлять собой значение функции, как в следующем примере кода. В следующем примере кода также показано использование параметров обработчика события, содержащих данные, зависящие от типа события. Для события 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# представлены типом события F#, который реализует интерфейс IEvent. IEvent — это интерфейс, System.IObservable<'T> который объединяет функциональные возможности двух других интерфейсов и IDelegateEvent. Следовательно, события Event обладают функциональными возможностями, эквивалентными возможностям делегатов в других языках, и дополнительно функциональными возможностями интерфейса IObservable; это означает, что события F# поддерживают фильтрацию событий и использование функций первого класса и лямбда-выражений языка F# в качестве обработчиков событий. Эта функция предоставляется в модуле событий.

Чтобы создать для класса событие, которое ведет себя точно так же, как любое другое событие платформы .NET Framework, добавьте в класс привязку let, определяющую событие Event как поле в классе. В качестве аргумента типа можно указать требуемый тип аргумента события или оставить его пустым, чтобы соответствующий тип был выведен компилятором. Необходимо также определить член события, предоставляющего это событие как событие CLI. Этот элемент должен иметь атрибут CLIEvent . Он объявляется как свойство и его реализация является просто вызовом свойства Publish события. Пользователи класса могут использовать метод Add опубликованного события для добавления обработчика. Аргумент метода Add может быть лямбда-выражением. Для вызова события можно использовать его свойство Trigger, передавая аргументы функции обработчика. Это показано в следующем примере кода. В этом примере выведенный аргумент типа для события — кортеж, представляющий аргументы для лямбда-выражения.

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 для создания события и метода-триггера, добавления двух обработчиков события в виде лямбда-выражений и последующего инициирования события для выполнения обоих лямбда-выражений.

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

Модуль "Наблюдаемый " содержит аналогичные функции, которые работают с наблюдаемыми объектами. Наблюдаемые объекты аналогичны событиям, но они активно подписываются на события только при создании подписки на такой объект.

Реализация события интерфейса

Разработка компонентов пользовательского интерфейса часто начинается с создания новой формы или нового элемента управления, наследуемых от существующих формы или элемента управления. События часто определяются в интерфейсе. В этом случае для реализации события необходимо реализовать интерфейс. Интерфейс 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)

См. также