かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

F# + WPF + MVVM で足し算アプリ

さて、今日はF#のシーケンスあたりでもやろうかと思ってMSDNとにらめっこしてましたが、気が変わったのでF#でWPFMVVMパターンな凄く小さなアプリケーションを作ってみることにしました。

作るもの

仕様は、以下のような感じです。

  • テキストボックスが2つあって数字を入力できる
    • 入力値の検証はしない
  • ボタンをおすと足し算をして結果をテキストブロックに表示する

これ以上ないくらいシンプルです。一応M V VMの役割分担は以下のようにしました

  • Model
    • 足し算ロジック
  • ViewModel
    • Viewからの入力を受けとる
    • Modelに計算処理を委譲する
    • コマンドでViewからのアクションを受け取る
  • View
    • 入力用のインターフェースとボタンを持ってる

妥協点

この試みを行う段階でいくつか妥協?した点があります。

  • MVVMのベースライブラリとしてはPrismを採用しました。(ここまでF#は超大作になってしまう)
  • ViewのプロジェクトはC#のプロジェクト(F#のプロジェクトでXAMLとか信じられない)

ということで作ってみます

まず、F#のプロジェクトを作成します。名前はWpfMVVMFsにしました。
このプロジェクトに以下の参照を追加します。

  • WindowsBase
  • PresentationCore
  • PresentationFramework
  • System.Xaml

そして、プロジェクトのプロパティからWindowsアプリケーションに変更します。

続けてViewのプロジェクトを作成します。これはC#WPFユーザコントロールライブラリを選択します。名前はWpfMVVMFs.Viewsにしました。最初デフォルトで作成されているUserControl1.xamlは削除します。
このプロジェクトにはNuGetを使ってPrismの参照を追加します。

Prismの参照を追加したら、WpfMVVMFsプロジェクトにWpfMVVMFs.Viewsプロジェクトを参照に追加します。

ここまで出来たら画面が出る所まで作ってしまいます。まず、WpfMVVMFsプロジェクトにApp.fsというファイルを作成します。ここにアプリケーションクラスを追加します。App.fsファイルはProgram.fsよりソリューションエクスプローラ上で上に持っていきます。これはF#が前に定義されてるものじゃないと使えないという制限があるからです。上への持っていきかたはApp.fsを選択した状態でAlt+↑で移動できます。

次に画面を作ります。画面だけは・・・C#のプロジェクトで普通にWPFのWindowを作成します。C#のコードには一切触らないのでよしとします。MainWindow.xamlを作成します。
そしてWpfMVVMFsプロジェクトのApp.fsを以下のようにします。

namespace WpfMVVMFs

open System.Windows
open WpfMVVMFs.Views

// Appクラス エントリポイントみたいなもの
type App() as this =
    inherit Application()

    do
        // Startupイベントでウィンドウを表示
        this.Startup.Add <| fun e ->
            let w = MainWindow()
            w.Show()

次に、Program.fsでAppクラスを使ってWPFのアプリケーションを開始させる処理を書きます。

open System
open WpfMVVMFs

[<STAThread>]
do
    let app = App()
    app.Run() |> ignore

これで、何もないWindowが表示されるようになります。

次にViewModelとModelを作ります。WpfMVVMFs.ViewModelsという名前でF#のクラスライブラリのプロジェクトを作成します。デフォルトで作成されてるF#のファイルは削除したあとにPrismへの参照を追加します。NuGetが使えれば楽なのですが、悲しいことにF#のプロジェクトはサポートしてくれてないみたいなので、Viewのプロジェクトで参照している奴を拝借して追加します。PrismのDLLはソリューションフォルダのpackagesの下にあるので、それを追加しました。後は、WPF関連のDLLも参照に追加します。

  • WindowsBase
  • PresentationCore
  • PresentationFramework
  • System.Xaml

そしたらまず、Modelを作っていきます。Modelは別プロジェクトにしても良かったのですが、めんどくさいのでViewModelのプロジェクトに入れてしまいます。Calc.fsという名前でファイルを作成したら、以下のコードを書きます。足し算するだけなので簡単です。

namespace WpfMVVMFs.Models

type Calc() =
    member this.Add(x, y) = x + y

次にViewModelクラスを作成します。これはMainWindowViewModel.fsという名前でファイルを作成して以下のように記述します。Prismのクラスをふんだんに使っています。

namespace WpfMVVMFs.ViewModels

open System
open Microsoft.Practices.Prism.ViewModel
open Microsoft.Practices.Prism.Commands
open WpfMVVMFs.Models

// PrismのINotifyPropertyChangedの実装クラスを使う
type MainWindowViewModel() as this =
    inherit NotificationObject()

    // モデル
    let model = Calc()

    // 計算結果
    let mutable answer = 0

    // 左辺値
    let mutable lhs = 0

    // 右辺値
    let mutable rhs = 0

    // 計算コマンド
    let calcCommand = DelegateCommand(fun() -> this.Calc())

    // 計算結果のプロパティ
    member this.Answer
        with get() = answer
        and set(v) =
            answer <- v
            base.RaisePropertyChanged("Answer")

    // 左辺値のプロパティ
    member this.Lhs
        with get() = lhs
        and set(v) =
            lhs <- v
            base.RaisePropertyChanged("Lhs")

    // 右辺値のプロパティ
    member this.Rhs
        with get() = rhs
        and set(v) =
            rhs <- v
            base.RaisePropertyChanged("Rhs")

    // 計算コマンドのプロパティ
    member this.CalcCommand with get() = calcCommand

    // 計算処理
    member this.Calc() =
        this.Answer <- model.Add(lhs, rhs)

ViewModelが出来たのでViewのプロジェクトとメインのWpfMVVMFsプロジェクトからViewModelのプロパティへの参照を追加しておきます。そして、Viewを作ります。見た目には今回は拘らないので手書きXAMLでさくっと行きました。BindingだけはVisual Studioのプロパティエディタの力をかりました。便利ですよねやっぱり。

<Window x:Class="WpfMVVMFs.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:WpfMVVMFs.ViewModels;assembly=WpfMVVMFs.ViewModels"
        Title="F# MVVM" Height="300" Width="300">
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>
    <StackPanel>
        <TextBox Text="{Binding Path=Lhs}" />
        <TextBlock Text="+" />
        <TextBox Text="{Binding Path=Rhs}" />
        <TextBlock Text="=" />
        <TextBlock Text="{Binding Path=Answer}" />
        <Button Content="計算" Command="{Binding Path=CalcCommand}" />
    </StackPanel>
</Window>

さて、これで完成です!実行すると、以下のようなウィンドウが表示されます。

正確にテキストボックスに整数値を入れて計算ボタンを押すと、見事計算結果が表示されました!

感想

この程度ではF#でもC#でも大差ない感じです。もっとロジックがあるとF#の強みがいきてくると思うのですが、この時点ではIDEのサポートがプアな状態でMVVMアプリを作ってるという感じであまり有難味はありません。でもまあネタとしては満足です。

過去記事