目標
ここまではカウント数を1
だけ変動させることができましたが、次はステップ数も画面上で変更できるようにします。
ステップ数コントロールの配置
Designer
プロジェクトのMainForm.cs
を選択してフォームデザイナを表示します。
ツールボックスからNumericUpDown
コントロールを追加し、以下のようにプロパティを設定します。
-
Name
プロパティをnumericUpDownStep
とします。 -
Modifier
プロパティをPublic
とします。 - その他お好みで文字色やサイズなどを変更します。
NumericUpDown
コントロールの配置
ステップ数の反映
それでは変動数を固定値の1
から、先ほど追加したコントロールの値となるように書き換えてみましょう。
NumericUpDown
コントロールの数値はValue
プロパティで取得できます。
ただし、その数値の型はdecimal
なので注意が必要です。
decimal
は16バイトの10進浮動小数点数を表す型なので、整数だけでなく一部の小数を表すこともできます。
NumericUpDown
コントロールのDecimalPlaces
プロパティで小数点以下の桁数を指定することもできますが、今回、カウント数は整数とするのでDecimalPlaces
は初期値の0
のままとします。
したがって、Value
プロパティの値は丸めを気にせずにint
へ変換することができます。
module MainForm
open App.Designer
type View() as this =
inherit MainForm()
let mutable count = 42
let setCount nextCount =
count <- nextCount
this.labelCount.Text <- $"現在のカウント数は %d{count} です。"
do setCount count
let subscriptions = [|
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
- setCount (count + 1))
+ setCount (count + (this.numericUpDownStep.Value |> int)))
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
- setCount (count - 1))
+ setCount (count - (this.numericUpDownStep.Value |> int)))
|]
override this.Dispose(disposing) =
if disposing then
subscriptions
|> Array.iter (fun subscription -> subscription.Dispose())
base.Dispose disposing
これにより、ステップ数を自由に変更できるようになりました。
ステップ数
しかし、ボタンの表示はステップ数に関わらず「+1」「-1」のままです。
この問題は後で解決します。
依存の逆転
さて、今回ステップ数をdecimal
型としてnumericUpDownStep
コントロールのValue
に保持しています。
しかし、実際にはステップ数は整数なのでした。つまり、int
型で保持するのが理想です。
そこで、カウント数のときと同じように、コントロールに状態を持たせるのではなく、変数を用意してステップ数の状態を格納させましょう。
まずステップ数を格納するための変数step
を変更可能な変数として定義します。
合わせてsetStep
関数も用意し、初期値の反映も忘れずに行います。
let mutable step = 1
let setStep nextStep =
step <- nextStep
this.numericUpDownStep.Value <- nextStep |> decimal
do setStep step
ボタンがクリックされたときの処理で、直接numericUpDownStep.Value
を参照する代わりにstep
変数を使用します。
let subscriptions = [|
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
setCount (count + step))
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setCount (count - step))
|]
最後に、numericUpDownStep
コントロールのValue
プロパティが変更されたことを購読し、step
変数が常に最新の値になるように上書きしましょう。
this.numericUpDownStep.ValueChanged
|> Observable.subscribe (fun e ->
setStep (this.numericUpDownStep.Value |> int))
最終的に次のようなコードになります。
module MainForm
open App.Designer
type View() as this =
inherit MainForm()
let mutable count = 42
let setCount nextCount =
count <- nextCount
this.labelCount.Text <- $"現在のカウント数は %d{count} です。"
do setCount count
let mutable step = 1
let setStep nextStep =
step <- nextStep
this.numericUpDownStep.Value <- nextStep |> decimal
do setStep step
let subscriptions = [|
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
setCount (count + step))
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setCount (count - step))
this.numericUpDownStep.ValueChanged
|> Observable.subscribe (fun e ->
setStep (this.numericUpDownStep.Value |> int))
|]
override this.Dispose(disposing) =
if disposing then
subscriptions
|> Array.iter (fun subscription -> subscription.Dispose())
base.Dispose disposing
試しに動かしてみましょう。
ステップカウンター
step
変数やsetStep
関数を導入することにより、カウント数と同様の方法でステップ数を管理することができました。
しかし、step
変数の導入前と導入後を見比べてみると、むしろstep
変数を導入する前のほうがコードが短いため単純に感じられます。
もう少しコードを整理してみましょう。
モデルの抽出
前回、アプリケーションの状態の本質を表すようなデータをモデルと呼ぶことにしました。
今回の例で言うと、アプリケーションのモデルはカウント数とステップ数の2つです。
そこで、このモデルを明示的にコードで表してみます。
F#のレコードを用いてModel
型を定義します。
type Model =
{ Count: int
Step: int }
このモデルの初期値を次のように定義できます。
let initialModel =
{ Count = 42
Step = 1 }
このモデルを使って、ここまでで定義してきたstep
とcount
をまとめてmodel
という1つの変数に統一できます。
また、setStep
関数とsetCount
関数をまとめてsetModel
という1つの関数に統一できます。
やってみましょう。
let mutable model = initialModel
let setModel nextModel =
model <- nextModel
this.labelCount.Text <- $"現在のカウント数は %d{model.Count} です。"
this.numericUpDownStep.Value <- model.Step |> decimal
do setModel model
ところで、「+1」「-1」ボタンのテキストをステップ数の変更に合わせるのもここで行っておきましょう。
let mutable model = initialModel
let setModel nextModel =
model <- nextModel
this.labelCount.Text <- $"現在のカウント数は %d{model.Count} です。"
this.numericUpDownStep.Value <- model.Step |> decimal
+ this.buttonIncrement.Text <- $"+%d{model.Step}"
+ this.buttonDecrement.Text <- $"-%d{model.Step}"
do setModel model
最後に、アプリケーションの状態を変更するときは統一的にsetModel
関数を使うように変更します。
let subscriptions = [|
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
setModel
{ model with
Count = model.Count + model.Step })
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setModel
{ model with
Count = model.Count - model.Step })
this.numericUpDownStep.ValueChanged
|> Observable.subscribe (fun e ->
setModel
{ model with
Step = this.numericUpDownStep.Value |> int })
|]
レコード式を用いると、既存のレコードから一部のフィールドのみを変更することができます。
最終的に次のようなコードになります。
module MainForm
open App.Designer
type Model =
{ Count: int
Step: int }
let initialModel =
{ Count = 42
Step = 1 }
type View() as this =
inherit MainForm()
let mutable model = initialModel
let setModel nextModel =
model <- nextModel
this.labelCount.Text <- $"現在のカウント数は %d{model.Count} です。"
this.numericUpDownStep.Value <- model.Step |> decimal
this.buttonIncrement.Text <- $"+%d{model.Step}"
this.buttonDecrement.Text <- $"-%d{model.Step}"
do setModel model
let subscriptions = [|
this.buttonIncrement.Click
|> Observable.subscribe (fun e ->
setModel
{ model with
Count = model.Count + model.Step })
this.buttonDecrement.Click
|> Observable.subscribe (fun e ->
setModel
{ model with
Count = model.Count - model.Step })
this.numericUpDownStep.ValueChanged
|> Observable.subscribe (fun e ->
setModel
{ model with
Step = this.numericUpDownStep.Value |> int })
|]
override this.Dispose(disposing) =
if disposing then
subscriptions
|> Array.iter (fun subscription -> subscription.Dispose())
base.Dispose disposing
実行結果
ここまででアプリケーションの状態を1つのモデルに集約することができました。
これにより、アプリケーションが予期せぬ情報を画面に表示しているとき、次の2つの観点から問題の切り分けができます。
- 現在のアプリケーションの状態(すなわち型
Model
の値)は正しいか? - アプリケーションの状態を正しく描画できているか?(すなわち
setModel
関数)