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

F#でWPFやるときのTipsとか(その1)の続編です。

添付プロパティの作り方

F#で添付プロパティを作るには、添付プロパティの本体はプロパティではなくフィールドに保持する必要があるようです。

// ダメな例
type Sample private () =
  static member SomeValueProperty =
    DependencyProperty.RegisterAttached("SomeValue", typeof<string>, typeof<Sample>, FrameworkPropertyMetaData("", Sample.OnSomeValueChanged))

  static member GetSomeValue(obj: DependencyObject) = obj.GetValue(Sample.SomeValueProperty) :?> string
  static member SetSomeValue(obj: DependencyObject, value: string) = obj.SetValue(Sample.SomeValueProperty, value)

  static member OnSomeValueChanged =
    PropertyChangedCallback(fun sender e ->
      // プロパティが変更されたときの処理
    )

このコードはコンパイルは通りますが、この添付プロパティにXAML内でBindingしようとすると、

'Button' コレクション内で 'Binding' を使用することはできません。'Binding' は、DependencyObject の DependencyProperty でのみ設定できます。

というエラーになってしまいます。

プロパティではなくフィールドを使うとうまくいきます。

type Sample private () =
  // 一旦staticなフィールドに保持しておいて、
  static let someValueProperty =
    DependencyProperty.RegisterAttached("SomeValue", typeof<string>, typeof<Sample>, FrameworkPropertyMetaData("", Sample.OnSomeValueChanged))

  // プロパティの値として保持したフィールドを設定
  static member SomeValueProperty = someValueProperty

  static member GetSomeValue(obj: DependencyObject) = obj.GetValue(Sample.SomeValueProperty) :?> string
  static member SetSomeValue(obj: DependencyObject, value: string) = obj.SetValue(Sample.SomeValueProperty, value)

  static member OnSomeValueChanged =
    PropertyChangedCallback(fun sender e ->
      // プロパティが変更されたときの処理
    )

添付プロパティがF#で書けるため、WPFのかなりの部分がF#のみで完結できると思われます。 Full F#でWPFがかなり現実味を帯びてきました。 足りないのは各種ユーティリティなので、その辺の再実装が苦でない人であれば、十分選択肢に入ってくる環境はすでに整ったと言えるでしょう。

別Windowの開き方

今のところ、一番手軽に別Windowを開くには、XAML Type Providerを使うのがいいでしょう。

まずはXAMLを作る必要がありますが、F#のプロジェクトではXAMLのアイテムテンプレートがないため、「General」の「XMLファイル」を選んでファイルの拡張子をxamlに変更します。 注意点として、この方法で追加したファイルは「ビルドアクション」が「None」になっているので、「Resource」に変更しておく必要があります。

「F# Empty Windows App (WPF)」テンプレートでプロジェクトを作った場合、

type OtherView = XAML<"OtherWindow.xaml">

としてViewを表す型を作っておいて、何らかのコマンド内でこの型のオブジェクトを生成して Show (もしくは ShowDialog)を呼び出します。

member this.OnClick = this.Factory.CommandSync (fun () ->
  let view = OtherView()
  view.Root.Show()
)

ちなみに、「F# Empty Windows App (WPF)」テンプレートで導入されるFsXaml.Wpfは古い(0.9.9)ため、パッケージを更新(現時点では2.1.0)するとビルドが通らなくなります。 ビルドを通すためには、Root プロパティへのアクセスを消してください。

member this.OnClick = this.Factory.CommandSync (fun () ->
  let view = OtherView()
  view.Show()
)

App.fsもコンパイルエラーになるので、そちらの Root も削除しましょう。

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