F#でWPFアプリを作る方法

F#でGUIを扱うのはなかなか大変なのですが、できないことはありません…と思っていましたが、WPFアプリならばプロジェクトテンプレートがあるよ、ってのを最近知りました。

Visual Studio 2013 で「オンラインテープレートの検索」で「F# WPF」で検索してみます。すると、いつか F# で Windows アプリを作るテンプレートがあるんですね。

image

が…、WPF アプリと、WPF+MVVM アプリのプロジェクトをダウンロードしたものの、いまいちよく分からなくて途方に暮れます。WPF アプリのほうは中身が空っぽ(Emptyなのでそりゃそうなんですが)で先がわからなく、WPF+MVVMは、グリッドへの DataBind の例なんですが、ふつうの DataBind がどうするのかわかりません。

そんなわけで、上をベースにして簡単なサンプルを作りました。
moonmile/SampleFSharpUI
https://github.com/moonmile/SampleFSharpUI

これも簡単すぎて実務には足りないんですが、取っ掛かりぐらいにはなるかと。WPF + F# の組み合わせはふつうに XAML デザイナを使ってアプリを作れるので、C# と同じように作れます。ただし、コードビハインドの仕組みがないので、ボタンのクリックイベントは自前で用意する必要がありますね。以下、ざっと説明を。

■SampleFSharpUI.WPF

先の F# empty windows app をベースにして XAML にテキストボックスを並べます。テンプレートの XAML では Grid タグをつけ忘れているので、それを追加するところから。

image

MainWindow.xaml.fs のようなコードビハインドはなくて、App.fs にある

[code lang=”csharp”]
type MainWindow = XAML<"MainWindow.xaml">
[/code]

が、その役割を果たします。TypeProvider.XAML でコーディング時に MainWindow.xaml を解析して、クラス(type)に割り付けます。こうしておくと、

[code lang=”csharp”]
let window = MainWindow()
…
let x = Convert.ToInt32( window.text1.Text )
[/code]

な感じで x:Name=”text1” で指定した名前を、window.text1 のようにプロパティとして参照できるようになります。TypeProvider 自体がよくわからないのですが、なんかファイルか文字列を読み込んで型にして返してくれるものみたい。

ボタンのクリックイベントは

[code lang=”csharp”]
window.button1.Click.Add( fun _ ->
let x = Convert.ToInt32( window.text1.Text )
let y = Convert.ToInt32( window.text2.Text )
let ans = x + y
window.text3.Text <- ans.ToString()
)
[/code]

と書いていますが、window.button1.Click |> Event.add を使って

[code lang=”csharp”]
window.button1.Click
|> Event.add( fun _ ->
let x = Convert.ToInt32( window.text1.Text )
let y = Convert.ToInt32( window.text2.Text )
let ans = x + y
window.text3.Text <- ans.ToString()
)
[/code]

のように書くこともできます。まあ、普通にメソッドにしたほうがいい気も。

アプリの起動自体は

[code lang=”csharp”]
[<STAThread>]
(new Application()).Run(loadWindow()) |> ignore
[/code]

となって、最初の loadWindow を呼び出すわけですが、この書き方だとすべてグローバル変数扱いになってしまうので、実務的には適度にクラスかモジュール分けが必要ですよね。

■SampleFSharpUI.MVVM

MVVM では、INotifyPropertyChanged を継承した ViewModelBase クラスを作っておいて、DataModel クラスを作ってます。MVVM の ViewModel にあたるクラスなんですが、Model クラスがないのは気持ち悪いのでこんな名前にしています。

[code lang=”csharp”]
type DataModel() =
inherit ViewModelBase()
let mutable _X : int = 10
let mutable _Y : int = 20
let mutable _ANS : int = 30

member this.X
with get() = _X
and set(value) =
_X <- value
base.OnPropertyChanged "X"

member this.Y
with get() = _Y
and set(value) =
_Y <- value
base.OnPropertyChanged "Y"

member this.ANS
with get() = _ANS
and set(value) =
_ANS <- value
base.OnPropertyChanged "ANS"
[/code]

プロパティの get/set の並べ方はこんな感じで。クラスの最初に mutable を置かなければいけないのと(これでよかったっけ?)、OnPropertyChanged メソッドに「メソッド名」を渡さないといけないのがいまいちですね。CallerMemberName 属性を使って、コンパイル時にチェックしたいところです。

XAML へのバインドは C# と同じで Text=”{Binding X, Mode=TwoWay}” のように書けます。この方法で C# とビューが共有できます。

[code lang=”csharp”]
type MainWindow = XAML<"MainWindow.xaml">

let _model = new DataModel()

let loadWindow() =
let window = MainWindow()

// Your awesome code goes here and you have strongly typed access to the XAML via "window"

// 初期値
_model.X <- 0
_model.Y <- 0
_model.ANS <- 0
window.button1.Click.Add( fun _ ->
// データバインドで設定
_model.ANS <- _model.X + _model.Y
)
window.Root.DataContext <- _model
window.Root
[/code]

あとは、_model オブジェクトを作って DataContext にバインドすれば OK です。XAML のデザイン時に Binding でインテリセンスを効かせたいときは、C# のときと同じようにデザイン時のバインドを使えば OK です。

[code lang=”csharp”]
d:DataContext="{d:DesignInstance {x:Type local:DataModel}, IsDesignTimeCreatable=True}"
[/code]

実行結果はこんな感じ

image

■Window Store App ではどうするのか?

タイププロバイダで XAML が読めるんだから、ストアアプリの XAML も読めるはず…なんですが、F# でストアアプリを作ろうとすると XAML デザイナがうまく認識しません。さらに言うと、そのままでは、参照設定がうまく WinRT にほうにできません。どうやらコンパイル時の *.targets をうまく設定しないといけないんですよね。現時点ではうまくできてません。

XAML 用のデザイナは C:/Program Files (x86)/MSBuild/Microsoft/WindowsXaml/v12.0/ あたりを見ているので、ここに FSharp 版をつくらないとダメな模様

あとプロジェクトテンプレートのGUIDが BC8A1FFA-BEE3-4634-8014-F334798102B3 で、C:/Program Files (x86)/Microsoft Visual Studio 12.0/Common7/IDE/CommonExtensions/Microsoft/WindowsXamlFlavor/Microsoft.VisualStudio.Windows.UI.Xaml.Project.pkgdef になるので、これを参考にしながら F# 版を作らないとダメかも。

カテゴリー: F# パーマリンク

F#でWPFアプリを作る方法 への1件のコメント

  1. ピンバック: F# Weekly #17, 2014 | Sergey Tihon's Blog

コメントは停止中です。