WPFでF#も使いたい!

3 min read読了の目安(約2900字

はじめに

F#には現状、WPFを扱うテンプレートが存在しておらず、デフォルトの状態ではWPFでF#を用いるのは難しいですが、今回F#のみでウィンドウの表示に成功している例を見つけることができたので共有したいと思います。
参考:https://github.com/kalugvasy3/How-to-create-WPF-application-with-F-sharp-only-with-Dot-Net-Core-3.1

プロジェクトファイル(fsproj)

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <Page Remove="MainWindow.xaml" />
  </ItemGroup>

  <ItemGroup>
    <EmbeddedResource Include="MainWindow.xaml" />
    <Compile Include="MainWindow.fs" />
  </ItemGroup>

</Project>

WPFのcsprojと異なるのは、埋め込みリソースとしてMainWindow.xamlを含める必要があります。

MainWindow.fs

open System 
open System.Windows 
open System.Windows.Markup
open System.Reflection

[<STAThread>] 
[<EntryPoint>]
do Assembly.GetExecutingAssembly().GetManifestResourceNames() 
    |> Array.find(fun x -> x.Contains("MainWindow.xaml"))
    |> Assembly.GetExecutingAssembly().GetManifestResourceStream
    |> XamlReader.Load :?> Window
    |> Application().Run |> ignore

現在実行中のアセンブリをGetExecutingAssemblyメソッドで取得し、さらにGetManifestResourceNamesでリソース名称が格納された文字列配列を取得します。その中からMainWindow.xamlの文字列を含む要素をfind関数で取得します。見つからない可能性も加味してoption型で返すtryFind関数を使った方がいいかもしれません。

そして、取得したリソース名を使ってリソースのStreamを取得し、Stream(つまりXamlファイルの内容)からXamlReader.Loadメソッドを使ってオブジェクトを生成しています。Loadメソッドからの戻り値はObject型(F#ではobj)なので:?>演算子でSystem.Windows.Window型へダウンキャストを行っています。

MainWindow.xaml

特に通常と変わりない記述で問題ありません。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        MinWidth="450" MinHeight="266" Title="I want to use Windows Presentation Foundation with F#!" SizeToContent="WidthAndHeight">

    <Grid x:Name="gridAll" Margin="0,0,0,0"  RenderTransformOrigin="0.5,0.5"  >
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="Hello F#!" />
    </Grid>
</Window>

実行

実行結果

備考

ホットリロードはどうやら動かないみたいです。
Visual StudioのデザイナーはC#と同様に使用することができます。

終わりに

このままだとF#でWPFをやる!という時に逐一プロジェクトファイルなどを書き換える必要があるので、こちらのチュートリアルなどを参考にテンプレートを作ると便利です。

備考

GitHubのIssueにもサポートを望む声が少しあるようです。

また、ElmというJavaScriptにコンパイル可能な関数型言語のF#実装である、ElmishというライブラリがF#には存在していて、ElmishでWPFを書くElmish.WPFがあるので、こちらもいずれ試してみたいと思います。