今、手元で作っている WinXamlProvider は、Windows Store 8.1 と Windows Phone 8.1 で使っている XAML を F# で動かそうという試みのひとつです。
WinXamlProvider
http://github.com/moonmile/WinXamlProvider
Windows Store アプリを作る F# プロジェクトが Microsoft から提供されればいいのですが、そんな雰囲気もない。正当な方法としては、自前で F# プロジェクトのテンプレートを作ればよいのですが、どうやら *.target のほうを大きく変えないと駄目っぽくて、頓挫中です。おそらく MSBuild あたりを詳細に調べないとうまくいかなそうなのと、XAML ファイルから C#/VB/C++ コードに落としている箇所に、うまく F# を追加しないといけません。MVVM パターンのみでやるか、ここでやっているように実行時にコードビハイドをバインディングする方法をとれば、ビルド時に XAML からコードビハイドを出力する必要はありません。あるいは、F# の Type Provider を作って、ビルド時に静的に作る方法でもよいでしょう。WPF の XAML 用の Type Provider は FsXaml
https://github.com/fsprojects/FsXaml を使えばよいんですが、WinStore 用の XAML がありません。なぜ、WinStore の XAML に私がこだわるのかといえば、Surface RT のような WinRT タブレットでの使い手を想定しているためです。ええ、ユニバーサルアプリにして Windows Phone 8.1 でもうまく動くようになったのは「おまけ」ですから。
■プロジェクトを分ける
現状では、WinStore と WinPhone ではメインプロジェクトに F# を据えることが難しいので、
- フロントエンドを C# プロジェクトで作る
- バックエンドを F# プロジェクトで作る
ということにします。この方式は、Xamarin.Forms を使って iOS/Android を作るときと同じで、バックエンドのロジック部分をライブラリ化して、可搬性を高めるという方式です。ただし、バックエンド部分が PCL(Portable Class Library) になってしまうので、使えるライブラリに制限がでてきます。

こんな風に、C# プロジェクトでは簡単に参照できる Windows.Xaml.UI.* も、PCL の中ではできません。
直接 F# プロジェクトのファイルを開いて、ターゲットの書き換えと、
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile32</TargetFrameworkProfile>
ターゲットプラットフォームに Windows, Version=8.1 を無理やり追加すると Windows.UI.Xaml の namespace は参照できるようになるのですが、
<ItemGroup>
<TargetPlatform Include="Windows, Version=8.1" />
<TargetPlatform Include="WindowsPhoneApp, Version=8.1" />
<Compile Include="Class1.fs" />
</ItemGroup>
実行時に
{"ファイルまたはアセンブリ 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。":"Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null"}
という謎なエラーを出力します。確かに、無理に追加しただけなので、ロード自体がうまくいっていないっぽいのです。ここは別途修正するとうまくいのかも。
実験したプロジェクトは https://github.com/moonmile/FsApp です。
■Windows.UI.Xaml をラッピングする
PCL から直接 Windows.UI.Xaml を扱うことはできませんが、リフレクションを使うとアクセスができます。

既にフロントのほうでは、Windwos.UI.Xaml に必要なアセンブリはロードされているハズですから、それめがけてちまちまとインターフェースを作っていく方法ですね。
この方法自体は、Xamarin.Forms の拡張レンダラーとか、MvvmCross のサービスの作り方だとかと似た感じで、Xamarin.Forms や MvvmCross の場合は、適当なインターフェースを使って呼び出すようにしていますが、面倒なので、直接リフレクション呼び出しをしています。インターフェースを使うと、F# の場合に型キャストでややこしいってのもあるので。
■プロジェクト構成
今、電卓アプリのサンプルを作っている途中ですが、構成はこんな感じです。

- WinStore/Phone のユニバーサルアプリを作成
- F# PCL を作成
- NuGet で WinXamlProvider をインストール(製作中)
- WinStore/Phone で XAML ファイルを動的にロードするように設定(調節中)
- Page クラスのコンストラクタで、BindInit を呼び出し。
のようにすると、特定の Xaml/Page が F# の Page クラスにバインドされます。4 の手順は、動的に XAML をロードする必要があって、こうなっています。元ネタの XAML はバイナリ形式の *.xbf ファイルになって手がだせないので、テキスト形式でコンテンツ埋め込みをさせています。
一応、動作させると、

な感じで動きます。
現在、Button, TextBlock, TextBox, Page クラスのプロパティしかバインドしていないので、かなり限定的ですが。
■リフレクションでバインド
詳細なコードは、WinXamlProvider にありますが、主要部分を抜き出すとこんな感じです。
type ParseXaml() =
let findName( page, propName ) =
let mi = page.GetType().GetRuntimeMethod("FindName", [|typeof<string>|])
let res = mi.Invoke( page, [|propName|])
res
let bindMethod( page:obj, bind:obj, target:obj, eventName:string, methodName:string) =
let ei = target.GetType().GetRuntimeEvent(eventName)
let dt = ei.AddMethod.GetParameters().[0].ParameterType
let mi = bind.GetType().GetRuntimeMethod(methodName,[|typeof<obj>; typeof<RoutedEventArgs>|])
let handler =
new Action<obj,obj>(
fun sender eventArgs ->
let e = new RoutedEventArgs(eventArgs)
mi.Invoke( bind, [|sender; e|]) |> ignore )
let handlerInvoke = handler.GetType().GetRuntimeMethod("Invoke",[|typeof<obj>; typeof<Type[]> |]);
let dele = handlerInvoke.CreateDelegate(dt, handler)
let add = new Func<Delegate, EventRegistrationToken> ( fun (t) ->
let ret = ei.AddMethod.Invoke(target, [|t|] )
ret :?> EventRegistrationToken
)
let remove = new Action<EventRegistrationToken>( fun(t) ->
ei.RemoveMethod.Invoke( target, [|t|]) |> ignore )
WindowsRuntimeMarshal.AddEventHandler<Delegate>( add, remove, dele)
let bindProperty( page:obj, bind:obj, propName:string, t:Type ) =
let pprop = findName( page, propName )
if pprop <> null then
let bi = bind.GetType().GetRuntimeProperty(propName)
match t.Name with
| "TextBlock" -> bi.SetValue( bind, new TextBlock(target = pprop))
| "TextBox" -> bi.SetValue( bind, new TextBox(target = pprop))
| "Button" -> bi.SetValue( bind, new Button(target=pprop))
| _ -> bi.SetValue( bind, new UIElement( target=pprop ))
Page.FindName が必要なので、findName 関数でリフレクションを使います。
bindMethod 関数は、XAML で記述してある Click=”OnClickButton” のイベントを、F# のクラスメソッドに倍度する仕組みですね。イベントハンドラの登録は、WindowsRuntimeMarshal.AddEventHandler を使います。残念ながら、元ネタのイベントを削除していないので(頑張ればできる目途は立ちそうなのですが)、元のイベントと F# のイベントメソッドの両方を呼び出してしまいます。まあ C# のイベントハンドラは空なので、そのままでいいでしょう。
このあたりは、頭がちぎれそうになるぐらいややこしいのですが、まあなんとか。いくつか C# のコードも出てくるので、それを参照に組み立てます。こまめに小さなメソッドにしたほうがわかりやすいですね。
プロパティ呼び出しもいちいちリフレクションします。
namespace Moonmile.WinXamlProvider.UI
open System
open System.Reflection
[<AllowNullLiteral>]
type BaseElement() =
member val target:obj = null with get, set
member this.getProp<'T>( propName:string ) =
let pi = this.target.GetType().GetRuntimeProperty( propName )
pi.GetValue(this.target) :?> 'T
member this.setProp( propName:string, value:obj ) =
let pi = this.target.GetType().GetRuntimeProperty( propName )
pi.SetValue( this.target, value)
これを使って、Windows.UI.Xaml を再構築します。
Button クラスを、こんな風にリフレクションでバインドします。
type Button() =
inherit ButtonBase()
member this.Flyout
with get() = base.getProp<FlyoutBase>("Flyout")
and set(value:FlyoutBase) = base.setProp("Flyout", value )
member this.CommandParameter
with get() = base.getProp<Object>("CommandParameter")
and set(value:Object) = base.setProp("CommandParameter", value )
member this.Command
with get() = base.getProp<Windows.Input.ICommand>("Command")
and set(value:Windows.Input.ICommand) = base.setProp("Command", value )
member this.ClickMode
with get() = base.getProp<ClickMode>("ClickMode")
and set(value:ClickMode) = base.setProp("ClickMode", value )
...
このあたりは手作業で書くと大変なので、適当なツールを作って、コード出力しています。
そのうち自動生成させてしまうつもり。
■F# で MainPage クラスを書いてみる
namespace WinXamlProvider.Lib
open System
open Moonmile.WinXamlProvider
open Moonmile.WinXamlProvider.UI
type MainPage() =
member val textMessage:TextBlock = null with get, set
member this.Button_Click(sender:obj, e:RoutedEventArgs) =
this.textMessage.Text <- "New F# message."
Button をクリックしたときに Button_Click イベントが呼び出されて、textMessage の内容を書き換えるコードです。まだ MVVM タイプの SetBinding を実装していないので、WinForm っぽい書き方になりますが、これで Widows Store アプリと Windows Phone アプリを F# で書くことが可能になります。
■今後は
Button.Click イベントあたりをバインドしてしまえば、直接 this.btn.Click.Add( … ) が使えるようになるので、コードビハイドのイベントコードを書かなく手済むようになります。これを早急に実装。
あとは、自動生成コードを修正して、Windows.UI.Xaml 以下の全てのクラスをバインドさせてしまう。