読者です 読者をやめる 読者になる 読者になる

F#でWPFやるときのTipsとか(その1)

F# WPF

最近、Full F#でWPFしてるので、Tipsてきなものをまとめようと思います。 その2はTipsがたまればあるかもしれませんが、過度な期待はしないでください。

プロジェクトの作り方

基本的には、Pure F# WPF GUIアプリ開発に向けてに書いてある通りです。 細かい注意点があるので書いておきます。

.NET Frameworkの選択に関する注意点

.NET Frameworkを4.5.1や4.5.2などのような3桁のものを選ぼうとすると、プロジェクト作成に失敗します。 また、4.6を選んでも4.5として作られるので、その点にも注意しましょう。

初回以外での作成

言うまでもないことかもしれませんが一応。 一回でも「F# Empty Windows App (WPF)」を使ってプロジェクトを作った場合、それ以降は「オンライン」の方ではなく、「インストール済み」のテンプレートを選ぶことになります。

MainView.xaml.fs

「F# Empty Windows App (WPF)」というテンプレートでプロジェクトを作ると、下記の内容でMainView.xaml.fsというファイルが生成されます。

namespace ViewModels

open System
open System.Windows
open FSharp.ViewModule
open FSharp.ViewModule.Validation
open FsXaml

type MainView = XAML<"MainWindow.xaml", true>

type MainViewModel() as self = 
    inherit ViewModelBase()    

不要な型

実際にこのテンプレートで作る際、MVVMで作る場合は MainView は不要です。 こいつはC#でのコードビハインドで作る場合に使うものに相当するため、MVVMで行く場合は消してしまって大丈夫です。

当然、C#でのコードビハインドで作る場合は MainViewModel の方が不要なので、消してしまいましょう。 ただし、生成されるコードは完全にMVVMでやること前提なコードになっているので、正直お勧めしません。

細かいことですが、MainViewModel の定義の = の後ろに1つ、その下の行に4つ、空白文字が紛れ込んでいるのも注意しましょう。 気になる人は消してしまうといいでしょう。

App.fs

MainView.xaml.fs同様にテンプレートによって作られるファイルです。

module main

open System
open FsXaml
open System.Windows

type App = XAML<"App.xaml">

[<STAThread>]
[<EntryPoint>]
let main argv =
    App().Root.Run()

モジュール名

なぜか小文字で main というモジュールになっています。 細かい部分ですが、Main とか App とかに変えたほうがいいでしょう。

XAML Type Providerについて

テンプレートで作られる構成がMVVMスタイルで作ることを意識したものになっていますので、あまりXAML Type Providerの出番はないのですが、Type Providerの使い方の例として触れておきます。

コード生成の代替としてのType Provider

C#でのコードビハインドではdesigner.csファイルが自動生成されますが、F#はXAML Type Providerの力によりそういったファイルは生成されません。 その代わりに、type HogeView = XAML<"xamlファイル名", true> のようにしてビュー用の型をXAMLから生成することになります。

テンプレートによって生成されたプロジェクトを弄って、ちょっと使って見ましょう。

不要なファイルを消す

XAML Type Providerを使う場合は次のファイルは不要です。消してしまいましょう。

MainWindow.xamlを書き換える

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ViewModels;assembly=FsEmptyWindowsApp3"
    xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
    Title="MVVM and XAML Type provider" Height="200" Width="400">
    <Grid>
        <Label x:Name="Message"/>
    </Grid>
</Window>

Message という名前を持った Label を追加しました。

App.fsを書き換える

App.fsを書き換え、先ほど追加したラベルにテキストを設定してみます。

module App

open System
open FsXaml
open System.Windows

type MainView = XAML<"MainWindow.xaml", true>

[<STAThread>]
[<EntryPoint>]
let main argv =
    // XAML Type Providerによって生成された型のインスタンスを生成し、
    let view = MainView()
    // Messageというプロパティにアクセスし、Contentに文字列を設定
    view.Message.Content <- "Hello XAML Type Provider!"

    // Application.RunにXAMLのRoot要素であるWindowオブジェクトを渡して、
    // アプリケーションを起動
    let app = Application()
    app.Run(view.Root)

これでビルドして実行すると、「Hello XAML Type Provider!」と表示されたウィンドウが開きます。

XAML Type Providerの力によって、自動生成コードなしでビューにアクセスできました。

イベントの登録

これだけだと、全部XAMLに書けばいいじゃん、という話になってしまうので、ボタンを追加してボタンにイベントを登録してみましょう。

MainWindow.xamlの内容を変更します。

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ViewModels;assembly=FsEmptyWindowsApp3"
    xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
    Title="MVVM and XAML Type provider" Height="200" Width="400">
    <StackPanel>
        <Label x:Name="Message"/>
        <Button x:Name="Btn">Please Click!</Button>
    </StackPanel>
</Window>

Grid は面倒なので StackPanel に変え、Label の下に Btn という名前でボタンを追加しました。

そして、app.Run(view.Root) の前にイベントを追加するコードを書きます。

view.Btn.Click.Add(fun x ->
    view.Message.Content <- "Clicked!")

これをビルドして実行すると、先ほど作った画面にボタンが追加されたウィンドウが開きます。 そして、ボタンをクリックするとラベルの内容が変わります。

こんな感じで、画面の要素に名前を付けておけば、かなり簡単に画面が作れることが分かります。 まぁ、ちょっとした画面であればXAML Type Providerも選択肢に入れてもいいかな、という感じでしょうか。