F#でWPF --- スライダーCommand

今回はdouble値をCommandで送るSliderを作成します。
SliderにはCommandプロパティが用意されていないため、Behaviorを作成します。

Behaviorの基本は下記記事を参照してください。
F#でWPF --- チェックボックスCommand --- Behavior利用 - 何でもプログラミング

ValueChangedイベント

SliderにはValueChangedイベントがありますが、ViewModelからのValueの変更でも発行されてしまいます。
ループ構造は予期せぬ挙動を招く可能性がありますので、今回はViewModel側からのValueの変更とユーザーからのValueの変更を分離するよう実装します。
f:id:any-programming:20170204214004p:plain

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


作成するアプリケーション

スライダーとスライダーの値を表示するテキストボックスからなります。
f:id:any-programming:20170204232747p:plain

アプリケーションコード

SliderのIsSnapToTickEnabledをtrueにすると、TickFrequencyの値でスライダーを刻むことができるようになります。(TickFrequencyのデフォルトは1です。)

F#で利用しているDataContextは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング

またF#でWPFプロジェクトを作成する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング

Xaml

<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