AttachedProperty定義の記述量削減(F#)
下記記事にて、C#でAttachedPropertyの記述を削減してみました。
AttachedProperty定義の記述量削減 - 何でもプログラミング
今回はF#でAttachedPropertyの記述を減らしてみたいと思います。
C#版同様、プリプロセスやポストプロセスは利用しないで実装してみます。
AttachedPropertyManager
Register、Get、Set、Changedを公開するクラスになります。
C#の時と異なり、プロパティの変更通知はEventクラスを介して行います。
現状、member valで宣言されたものからのCallerMemberNameには、末尾に@が付与されているようです。
open System.Runtime.CompilerServices type AttachedProperty<'owner, 'target, 'value when 'target :> DependencyObject>() = let changed = Event<'target>() let mutable property : DependencyProperty = null member this.Changed = changed.Publish member this.Register(?defaultValue : 'value, [<CallerMemberName>]?propertyName : string) = assert (property = null) let name = propertyName.Value.Substring(0, propertyName.Value.Length - "Property@".Length) let metadata = PropertyMetadata(defaultValue |> Option.defaultValue(Unchecked.defaultof<'value>), PropertyChangedCallback(fun obj _ -> changed.Trigger(obj :?> 'target))) property <- DependencyProperty.RegisterAttached(name, typeof<'value>, typeof<'owner>, metadata) property member this.Get(obj : 'target) = obj.GetValue(property) :?> 'value member this.Set(obj : 'target, value : 'value) = obj.SetValue(property, value)
distinct
初回通知の時のみに処理を行う用に、distinctオペレータを定義します。
弱参照になるようConditionalWeakTableを利用しています。
open System.Runtime.CompilerServices module Event = let distinct (source : IEvent<'a>) = let ev = Event<'a>() let table = ConditionalWeakTable<'a, unit>() source.Add(fun x -> if table.TryGetValue(x) |> fst |> not then table.GetValue(x, (fun _ -> ())) ev.Trigger(x)) ev.Publish
動作確認
C#版同様、Rectangle上でマウスをDownするとコンソール出力されるアプリケーションを作成してみます。
OwnerTypeを毎回指定するのが面倒な場合は、さらに一段クラスか関数をかまして利用してください。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="clr-namespace:FsApp;assembly=FsApp" Title="MainWindow" Height="200" Width="200"> <Grid> <Rectangle Fill="White" local:Commands.MouseLeftButtonDown="{Binding Down}" /> </Grid> </Window>
F#
type ViewModel() = member this.Down = command (fun _ -> printfn "down") let main argv = let window = loadEmbeddedResource<Window> "MainWindow.xaml" window.DataContext <- ViewModel() Application().Run(window) |> ignore 0
namespace FsApp type Commands() = static let mouseLeftButtonDown = AttachedProperty<Commands, FrameworkElement, ICommand>() static do mouseLeftButtonDown.Changed |> Event.distinct |> Event.add(fun x -> x.MouseLeftButtonDown.Add(fun _ -> mouseLeftButtonDown.Get(x).Execute(null))) static member val MouseLeftButtonDownProperty = mouseLeftButtonDown.Register() static member GetMouseLeftButtonDown(x) = mouseLeftButtonDown.Get(x) static member SetMouseLeftButtonDown(x, y) = mouseLeftButtonDown.Set(x, y)