F#でWPF --- メッセージボックス
作成するアプリケーション
ボタンを押すと"Hello"のメッセージボックスが表示されるアプリケーションを作成します。
MVVMを用いない場合
下記の様なシンプルなコードになります。
しかしViewが分離できていません。
button.Click |> Event.add (fun _ -> MessageBox.Show("Hello"))
MVVM化
ViewModelのMessageプロパティが変更されたらViewがMessageBox.Showを呼ぶ実装にします。
MessageDialogBehavior
上記機能を実現するためにBehaviorを利用します。
BehaviorBaseは下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
open System.Windows open System.Windows.Input type MessageDialogBehavior() = inherit BehaviorBase<Window>() static member val MessageProperty = DependencyProperty.Register("Message", typeof<string option>, typeof<MessageDialogBehavior>) member this.Message with get() = this.GetValue(MessageDialogBehavior.MessageProperty) :?> string option and set(x:string option) = this.SetValue(MessageDialogBehavior.MessageProperty, x) static member val ClosedProperty = DependencyProperty.Register("Closed", typeof<ICommand>, typeof<MessageDialogBehavior>) member this.Closed with get() = this.GetValue(MessageDialogBehavior.ClosedProperty) :?> ICommand and set(x:ICommand) = this.SetValue(MessageDialogBehavior.ClosedProperty, x) override this.OnAttached control = [ DependencyProperty.changed<string option> MessageDialogBehavior.MessageProperty this |> Observable.subscribe (Option.iter (fun x -> MessageBox.Show(x) |> ignore if this.Closed <> null then this.Closed.Execute())) ]
アプリケーションコード
アプリケーション名はBehaviors.exeです。
MessageDialogBehaviorはnamespace Behaviorsに定義されています。
F#で利用しているDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
またF#でWPFプロジェクトを作成する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング
<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"> <i:Interaction.Behaviors> <local:MessageDialogBehavior Message="{Binding Message}" Closed="{Binding ClearMessage}" /> </i:Interaction.Behaviors> <Grid> <Button Content="メッセージボックス" Command="{Binding ShowMessage}" HorizontalAlignment="Left" Margin="40,20,0,0" VerticalAlignment="Top" Width="100" /> </Grid> </Window>
F#
open System open System.Windows type Model = { Message : string option } type Msg = | ShowMessage | ClearMessage let updateModel model msg = match msg with | ShowMessage -> { model with Message = Some "Hello" } | ClearMessage -> { model with Message = None } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ Message = None }, updateModel, id) Application().Run(window) |> ignore 0
F#でWPF --- ファイルダイアログCommand
今回はファイルダイアログを開いて選択されたパスをCommandで送る機能を実装します。
この機能を実装するにあたり、Actionを利用します。
Action
Behaviorと同じく、System.Windows.Interactivity.dllを利用します。
Behaviorの詳細は下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
ActionはTriggerと共に用いられ、例えばボタンがクリックされた時にCommandを実行する記述は下記の様になります。(挙動はButtonのCommandを利用するのと同じです。)
<Button> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <i:InvokeCommandAction Command="{Binding DoSomething}" /> </i:EventTrigger> </i:Interaction.Triggers> </Button>
OpenFileDialogAction
独自のActionを作成するには、TriggerActionを継承しInvoke関数をオーバーライドします。
open System.Windows open System.Windows.Input open System.Windows.Interactivity open Microsoft.FSharp.Linq.NullableOperators open Microsoft.Win32 type OpenFileDialogAction() = inherit TriggerAction<FrameworkElement>() static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<OpenFileDialogAction>) member this.Command with get() = this.GetValue(OpenFileDialogAction.CommandProperty) :?> ICommand and set(x:ICommand) = this.SetValue(OpenFileDialogAction.CommandProperty, x) static member val FilterProperty = DependencyProperty.Register("Filter", typeof<string>, typeof<OpenFileDialogAction>) member this.Filter with get() = this.GetValue(OpenFileDialogAction.FilterProperty) :?> string and set(x:string) = this.SetValue(OpenFileDialogAction.FilterProperty, x) override this.Invoke parameter = let dialog = OpenFileDialog(Filter = this.Filter) if dialog.ShowDialog() ?= true && this.Command <> null then this.Command.Execute dialog.FileName
作成するアプリケーション
ボタンを押してファイルダイアログを開き、パスを選択するとテキストボックスにパスが表示されるアプリケーションを作成します。
アプリケーションコード
アプリケーション名はActions.exeです。
OpenFileDialogActionはnamespace Actionsに定義されています。
F#で利用しているDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
またF#でWPFプロジェクトを作成する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング
<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:Actions;assembly=Actions" Title="MainWindow" Height="80" Width="250"> <Grid> <Button Content="ファイルダイアログ" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:OpenFileDialogAction Command="{Binding SetPath}" Filter="F#|*.fs" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> <TextBlock Text="{Binding Path}" HorizontalAlignment="Left" Margin="115,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> </Grid> </Window>
F#
open System open System.Windows type Model = { Path : string } type Msg = SetPath of string let updateModel model msg = match msg with | SetPath x -> { model with Path = x } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ Path = "" }, updateModel, id) Application().Run(window) |> ignore 0
F#でWPF --- スライダーCommand
今回はdouble値をCommandで送るSliderを作成します。
SliderにはCommandプロパティが用意されていないため、Behaviorを作成します。
Behaviorの基本は下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
ValueChangedイベント
SliderにはValueChangedイベントがありますが、ViewModelからのValueの変更でも発行されてしまいます。
ループ構造は予期せぬ挙動を招く可能性がありますので、今回はViewModel側からのValueの変更とユーザーからのValueの変更を分離するよう実装します。
SliderBehavior
ViewModelからのValueの変更の場合、一時変数に入力をセットしておきます。
ValueChangedイベントが発行されたときに一時変数の値と同じならViewModelからの変更、異なる場合はユーザーからの変更と考えられます。
ViewModelからのValueの値がMinimum~Maximunに収まっていない場合、収まった値がSlider.Valueに設定されてしまう(ValueChangedが発行されてしまう)ので、事前にMinimum~Maximumに収めています。
BehaviorBaseは下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
DependencyProperty.changedは下記記事を参照してください。
F#でWPF --- テキストボックスCommand --- 1文字変更毎に発行 - 何でもプログラミング
type SliderBehavior() = inherit BehaviorBase<Slider>() static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<SliderBehavior>) member this.Command with get() = this.GetValue(SliderBehavior.CommandProperty) :?> ICommand and set(x:ICommand) = this.SetValue(SliderBehavior.CommandProperty, x) static member val ValueProperty = DependencyProperty.Register("Value", typeof<double>, typeof<SliderBehavior>) member this.Value with get() = this.GetValue(SliderBehavior.ValueProperty) :?> double and set(x:double) = this.SetValue(SliderBehavior.ValueProperty, x) override this.OnAttached control = let mutable value = control.Value [ control.ValueChanged.Subscribe (fun x -> if value <> x.NewValue then value <- x.NewValue if this.Command <> null then this.Command.Execute(value)) DependencyProperty.changed<double> SliderBehavior.ValueProperty this |> Observable.subscribe (fun x -> let clamp min max = function | x when x < min -> min | x when max < x -> max | x -> x value <- x |> clamp control.Minimum control.Maximum control.Value <- value) ]
作成するアプリケーション
スライダーとスライダーの値を表示するテキストボックスからなります。
アプリケーションコード
SliderのIsSnapToTickEnabledをtrueにすると、TickFrequencyの値でスライダーを刻むことができるようになります。(TickFrequencyのデフォルトは1です。)
F#で利用しているDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
またF#でWPFプロジェクトを作成する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング
<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="80" Width="220"> <Grid> <Slider IsSnapToTickEnabled="True" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="150"> <i:Interaction.Behaviors> <local:SliderBehavior Command="{Binding SetValue}" Value="{Binding Value}" /> </i:Interaction.Behaviors> </Slider> <TextBlock Text="{Binding Value}" HorizontalAlignment="Left" Margin="165,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> </Grid> </Window>
F#
open System open System.Windows type Model = { Value : double } type Msg = SetValue of double let updateModel model msg = match msg with | SetValue x -> { model with Value = x } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ Value = 0.0 }, updateModel, id) Application().Run(window) |> ignore 0
F#でWPF --- テキストボックスCommand --- 1文字変更毎に発行
下記記事ではキーボードのフォーカスが外れた時にCommandを実行するよう実装しました。
F#でWPF --- テキストボックスCommand - 何でもプログラミング
今回は1文字変更する度にCommandを実行するよう実装します。
TextChangedイベント
TextBoxにはTextChangedイベントがあり、1文字変更される度に発行されます。
正確にはTextの値が変更されると発行されます。
ここで問題となるのが、ViewModel側でTextを変更した場合もTextChangedが発行されてしまうことです。
ループ構造は予期せぬ挙動を招く可能性がありますので、今回はViewModel側からのTextの変更とユーザーからのTextの変更を分離するよう実装します。
TextBoxBehavior
BehaviorBaseは下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
ViewModelからのTextの変更の場合、一時変数に入力をセットしておきます。
TextChangedイベントが発行されたときに一時変数の値と同じならViewModelからの変更、異なる場合はユーザーからの変更と考えられます。
DependencyProperty.changedは後程説明します。
type TextBoxBehavior() = inherit BehaviorBase<TextBox>() static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<TextBoxBehavior>) member this.Command with get() = this.GetValue(TextBoxBehavior.CommandProperty) :?> ICommand and set(x:ICommand) = this.SetValue(TextBoxBehavior.CommandProperty, x) static member val TextProperty = DependencyProperty.Register("Text", typeof<string>, typeof<TextBoxBehavior>) member this.Text with get() = this.GetValue(TextBoxBehavior.TextProperty) :?> string and set(x:string) = this.SetValue(TextBoxBehavior.TextProperty, x) override this.OnAttached control = let mutable text = control.Text [ control.TextChanged.Subscribe(fun _ -> if text <> control.Text then text <- control.Text if this.Command <> null then this.Command.Execute(control.Text)) DependencyProperty.changed<string> TextBoxBehavior.TextProperty this |> Observable.subscribe (fun x -> text <- x control.Text <- x) ]
DependencyProperty.changed
ViewModelからの変更を検知するため、DependencyPropertyのIObservableを作成します。
変更の検知にはDependencyPropertyDescriptorのAddValueChangedを利用します。
IObservableでイベントを発行するにはobserverのOnNextを呼びます。
またSubscribeの返り値としてRemoveValueChangedを呼ぶIDisposableを作成します。
RemoveValueChangedが呼ばれない限りコントロールがメモリに残り続けてしまうため、利用側はDisposeを忘れないようにしてください。
open System open System.Windows open System.ComponentModel module DependencyProperty = let changed<'a> property (obj : DependencyObject) = { new IObservable<'a> with member this.Subscribe observer = let handler = EventHandler(fun _ _ -> observer.OnNext(obj.GetValue(property) :?> 'a)) let descriptor = DependencyPropertyDescriptor.FromProperty(property, property.OwnerType) descriptor.AddValueChanged(obj, handler) { new IDisposable with member this.Dispose() = descriptor.RemoveValueChanged(obj, handler) } }
アプリケーションコード
下記記事と同じアプリケーションを作成します。
ただし今回は文字変更の度にテキストブロックに反映されます。
F#でWPF --- テキストボックスCommand - 何でもプログラミング
<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="250"> <Grid> <TextBox HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"> <i:Interaction.Behaviors> <local:TextBoxBehavior Command="{Binding SetText}" Text="{Binding Text}" /> </i:Interaction.Behaviors> </TextBox> <TextBlock Text="{Binding Text}" HorizontalAlignment="Left" Margin="13,37,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> </Grid> </Window>
F#
open System open System.Windows type Model = { Text : string } type Msg = SetText of string let updateModel model msg = match msg with | SetText x -> { model with Text = x } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ Text = "" }, updateModel, id) Application().Run(window) |> ignore 0
F#でWPF --- テキストボックスCommand
今回はstringを送るCommandをTextBoxに追加します。
TextBoxにはCommandプロパティが用意されていないため、Behaviorを作成します。
Behaviorの基本は下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
作成するアプリケーション
キーボードのフォーカスが外れた時に、テキストボックスの文字列がテキストブロックに反映されるアプリケーションです。
TextBoxBehavior
LostKeyboradFocusのイベントでCommandを実行するようにします。
ついでにReturnでキーボードのフォーカスが外れるようにします。
BehaviorBaseは下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング
type TextBoxBehavior() = inherit BehaviorBase<TextBox>() static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<TextBoxBehavior>) member this.Command with get() = this.GetValue(TextBoxBehavior.CommandProperty) :?> ICommand and set(x:ICommand) = this.SetValue(TextBoxBehavior.CommandProperty, x) override this.OnAttached control = [ control.LostKeyboardFocus.Subscribe(fun _ -> if this.Command <> null then this.Command.Execute(control.Text)) // Returnでキーボードフォーカスを外す control.KeyDown.Subscribe(fun x -> if x.Key = Key.Return then Keyboard.ClearFocus()) ]
アプリケーションコード
アプリケーション名はBehaviors.exeでTextBoxBehaviorはnamespace Behaviorsに定義されています。
TextBoxのTextはデフォルトでTwoWayなので、setterにアクセスしないようOneWayにします。
F#で利用しているDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
またF#でWPFプロジェクトを作成する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング
<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> <TextBox Text="{Binding Text, Mode=OneWay}" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"> <i:Interaction.Behaviors> <local:TextBoxBehavior Command="{Binding SetText}" /> </i:Interaction.Behaviors> </TextBox> <TextBlock Text="{Binding Text}" HorizontalAlignment="Left" Margin="13,37,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> </Grid> </Window>
F#
type Model = { Text : string } type Msg = SetText of string let updateModel model msg = match msg with | SetText x -> { model with Text = x } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ Text = "" }, updateModel, id) Application().Run(window) |> ignore 0
EventTriggerとInvokeCommandActionで実装
System.Windows.InteractivityのEventTriggerとInvokeCommandActionを利用してLostKeycoardFocus時にCommandを送ることができます。
またRelativeSourceのFindAncestorモードを利用することにより、TextBoxのTextプロパティをCommandParameterにバインドすることが可能です。
この例ではReturn入力時にキーボードフォーカスが外れることはありません。
<TextBox Text="{Binding Text, Mode=OneWay}" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"> <i:Interaction.Triggers> <i:EventTrigger EventName="LostKeyboardFocus"> <i:InvokeCommandAction Command="{Binding SetText}" CommandParameter="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}"/> </i:EventTrigger> </i:Interaction.Triggers> </TextBox>
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で作成した方が使いまわしが可能となります。
F#でWPF --- チェックボックスCommand
今回はチェックボックスのCommandを利用したアプリケーションを作成します。
作成するアプリケーション
チェックを入れるとダイアログの背景がグレーに、外すと白になるアプリケーションを作成します。
CommandParameter
チェックの状態をCommandと共に送るために、CommandParameterにIsCheckedの値を設定するようにします。
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
IsChecked
IsCheckedはデフォルトでTwoWayです。今回はsetterを利用しないのでModeをOneWayにします。
冗長ではありますが一貫した実装ができるため、入力は全てCommandにバインドし、その他は全てOneWayのバインドにすることを推奨します。
IsChecked="{Binding IsDark, Mode=OneWay}"
コード全体
F#で利用しているDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
またF#でWPFプロジェクトを作成する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="MainWindow" Height="100" Width="200"> <Grid Background="{Binding Background}"> <CheckBox Content="背景を暗く" IsChecked="{Binding IsDark, Mode=OneWay}" Command="{Binding SetIsDark}" CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}" HorizontalAlignment="Left" Margin="15,25,0,0" VerticalAlignment="Top"/> </Grid> </Window>
F#
open System open System.Windows open System.Windows.Media type Model = { IsDark : bool } member this.Background = match this.IsDark with | true -> Brushes.LightGray | false -> Brushes.White type Msg = SetIsDark of bool let updateModel model msg = match msg with | SetIsDark x -> { model with IsDark = x } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ IsDark = false }, updateModel, id) Application().Run(window) |> ignore 0