イベント (F#)
イベントを使用すると、関数呼び出しをユーザーによる操作に関連付けることができます。イベントは、GUI プログラミングの重要な要素です。イベントは、アプリケーションまたはオペレーティング システムによってトリガーすることもできます。
イベントの処理
Windows フォームや WPF (Windows Presentation Foundation) などの GUI ライブラリを使用する場合、アプリケーションのコードの大半は、ライブラリによって定義されたイベントに応答して実行されます。これらの定義済みイベントは、フォームやコントロールなどの 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 です。したがって、イベント ハンドラー メソッドは、1 つのパラメーター (通常はイベント引数) を受け取り、unit を返します。前の例は、ラムダ式としてのイベント ハンドラーを示しています。イベント ハンドラーは、次のコード例に示すように、関数値である場合もあります。次のコード例は、イベントの種類に固有の情報を提供するイベント ハンドラー パラメーターの使い方も示しています。MouseMove イベントの場合、システムはポインターの X 位置および Y 位置を含む MouseEventArgs オブジェクトを渡します。
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 という他の 2 つのインターフェイスの機能を結合するインターフェイスです。したがって、Event は、他の言語のデリゲートに相当する機能と、IObservable からの追加機能を備えていることになります。つまり、F# のイベントでは、イベントのフィルター処理がサポートされるほか、F# のファースト クラスの関数とラムダ式をイベント ハンドラーとして使用できます。この機能は Event モジュールで提供されます。
他の任意の .NET Framework イベントと同様に動作するイベントをクラスに作成するには、クラスのフィールドとして Event を定義する let 束縛をクラスに追加します。目的のイベント引数の型を型引数として指定することも、指定せずにコンパイラによって適切な型を推論することもできます。CLI イベントとしてイベントを公開するイベント メンバーも定義する必要があります。このメンバーには CLIEvent 属性が必要です。これはプロパティと同様に宣言され、実装では、単にイベントの Publish プロパティを呼び出します。クラスのユーザーは、公開されたイベントの Add メソッドを使用してハンドラーを追加できます。Add メソッドの引数はラムダ式にすることができます。イベントを発生させるには、ハンドラー関数に引数を渡すイベントの Trigger のプロパティを使用できます。これを次のコード例に示します。この例では、イベントの型引数はタプルとして推論され、ラムダ式の引数を表します。
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 の基本的な使用例を示しています。イベントおよびトリガー メソッドを作成し、ラムダ式の形式で 2 つのイベント ハンドラーを追加して、イベントを発生させて両方のラムダ式を実行します。
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)
参照
関連項目
Control.Event<'Delegate,'Args> クラス (F#)