F#でWPF --- チェックボックスCommand --- Behavior利用
下記記事ではCommandParameterを利用してboolを渡すCommandを実装しました。
F#でWPF --- チェックボックスCommand - 何でもプログラミング
今回はBehaviorという機能を利用して同じCommandを実現します。
参照追加
Behaviorを利用するには、System.Windows.Interactivityを参照に追加する必要があります。
もしXamlエディタで
The type .... is built with an older version of the Blend SDK
のようなエラーが表示される場合は、ver4.0のSystem.Windows.Interactivity.dllを参照してみてください。
Xaml
BehaviorはInteraction.Behaviorsに追加することにより利用できます。
CheckBoxBehaviorはこれから実装するクラスになります。
CheckBoxBehaviorは作成するアプリケーション(Behaviors.exe)のnamespace Behaviors内に定義されています。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:Behaviors;assembly=Behaviors" Title="MainWindow" Height="100" Width="200"> <Grid Background="{Binding Background}"> <CheckBox Content="背景を暗く" IsChecked="{Binding IsDark, Mode=OneWay}" HorizontalAlignment="Left" Margin="15,25,0,0" VerticalAlignment="Top"> <i:Interaction.Behaviors> <local:CheckBoxBehavior Command="{Binding SetIsDark}" /> </i:Interaction.Behaviors> </CheckBox> </Grid> </Window>
CheckBoxBehavior
System.Windows.Interactivity.Behavior
OnAttachedをオーバーライドして独自の処理を追加していきます。
AssociatedObjectプロパティからターゲットを取得できます。
Bindingできるようにするため、CommandのプロパティはDependencyPropertyとして定義しています。
定義の記述は冗長なので、スニペットや自動生成を利用した方がよいです。
namespace Behaviors open System.Windows open System.Windows.Interactivity open System.Windows.Controls open System.Windows.Input type CheckBoxBehavior() = inherit Behavior<CheckBox>() static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<CheckBoxBehavior>) member this.Command with get() = this.GetValue(CheckBoxBehavior.CommandProperty) :?> ICommand and set(x:ICommand) = this.SetValue(CheckBoxBehavior.CommandProperty, x) override this.OnAttached() = let control = this.AssociatedObject control.Click |> Event.add (fun _ -> if this.Command <> null then this.Command.Execute(control.IsChecked.Value))
動作確認
F#側は下記記事のものを変更しないで利用できます。
F#でWPF --- チェックボックスCommand - 何でもプログラミング
より安全なBehavior
OnAttachedの中ではイベントをSubscribeすることが多く、依存関係を考えておかないとメモリリークの原因になることがあります。
これを回避するため、BehaviorのOnDetachとAssociatedObjectのUnloadedでUnsubscribeするベースクラスを用意します。
継承先はコントロールを入力とし、IDisposable listを返り値とするOnAttachedを実装します。
[<AbstractClass>] type BehaviorBase<'a when 'a :> FrameworkElement>() = inherit Behavior<'a>() let mutable subscriptions : IDisposable list = [] let unsubscribe() = subscriptions |> List.iter (fun x -> x.Dispose()) subscriptions <- [] abstract OnAttached : 'a -> IDisposable list override this.OnAttached() = base.OnAttached() subscriptions <- this.AssociatedObject.Unloaded.Subscribe(fun _ -> unsubscribe()) :: this.OnAttached this.AssociatedObject override this.OnDetaching() = unsubscribe() base.OnDetaching()
BehaviorBaseを利用したCheckBoxBehavior
type CheckBoxBehavior() = inherit BehaviorBase<CheckBox>() static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<CheckBoxBehavior>) member this.Command with get() = this.GetValue(CheckBoxBehavior.CommandProperty) :?> ICommand and set(x:ICommand) = this.SetValue(CheckBoxBehavior.CommandProperty, x) override this.OnAttached control = [ control.Click.Subscribe(fun _ -> if this.Command <> null then this.Command.Execute(control.IsChecked.Value)) ]
継承してMyCheckBoxを作成することとの違い
今回の利用ではCheckBoxを継承して新たにCommandプロパティを追加することと動作自体は変わりません。
ただし、コントロールのベースクラスであるFrameworkElementを拡張したい時などはBehaviorで作成した方が使いまわしが可能となります。