1週間ほど掛けて、おおまかなところが動いてきたのでアルファ版で公開します。
何ができるかというと、Xamarin.Forms の XAML ファイルを動的にロードして、エミュレータ上に表示させます。現在のところ、Xamarin Studio にも Visual Studio にも GUI ベースのデザイナがないので、XAML コードを手打ちしています。まあ、手打ち自体は構わないのですが、画面の表示を確認するのに、いちいちビルドしてエミュレータなどにデプロイしないと画面が分かりません。勢い、StatckLayout などの汎用的な画面を作ることが多いのですが、それにしても色とか微妙な文字の配置とかが面倒です。そこで、XAML 記述を HTTP 経由で持ってきて、エミュレータ内で再構築するプレビューツールを作りました。エミュレータ上で画面をリロード(HTTP 経由で XAML ファイルを再びロード)することで、画面の状態が更新できます。これにより、ひとまず、XAMLのコードを書く→デプロイ→デザインの確認→XAMLのコード直し、というサイクルがなくなり、XAMLのコードを書く→画面をリロードする→XAMLの手直し、の手順だけで良くなります。ちょっと、使ってみた感じでは、表示している文字変更とか色変更が手軽にできるので、Xamarin Studio で本格的なデザイナが作られるまでのツナギになるとは思います。
■ダウンロード
moonmile/XFormsPreviewer
https://github.com/moonmile/XFormsPreviewer
まだ、NuGet を作っていないので、github から直接取ってきてください
■構成
- Host/XFormsPreviewerHost ? XAML データを提供する簡易ホスト
- Sample/XFormsSampleApp — サンプルプロジェクト(XAMLファイルが含まれる)
- Viewer/XFormsPreviewer — 簡易プレビューア
- XFormsProvider ? Xamarin製XAMLのパーサ
- XFormsProvider.Test ? テストプロジェクト
プレビューアが、簡易ホストを経由してサンプルプロジェクト内の XAML ファイルを表示させます。プレビューア自体は、iOS/Android/Windws Phoneエミュレータなので、適当にカスタマイズしてリロードしてください。今後はもうちょっとプレビューア/簡易ホストをもう少しリッチにしていきましょう。
■使い方
応答する XAML ファイルを指定する。
簡易ホストを書き換えて、応答する XAML ファイルを指定します。/get/item のような形式で、プレビューアから呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 12 | public PreviewHost() { this .Port = 10150; this .LoadPaths = new Dictionary< string , string >(); // TODO: change load xaml file // ここで、レスポンスを返す XAML ファイルを指定。 // 作成中のプロジェクトフォルダを指定すればok. // 後で WPF あたりで作成してドラッグ&ドロップ. // mac のためにコマンドラインで使えるようにしておく。 var basefolder = @"..\..\..\Sample\XFormsSampleApp\XFormsSampleApp\" ; this .LoadPaths.Add( "0" , basefolder + "MainPage.xaml" ); } |
簡易クライアントを管理者モードで立ち上げる。
Windows 8.1 の場合は、ローカルサーバーを立ち上げるときに指定ポートを明示的に開ける必要があります。面倒な場合は、コマンドプロンプトを管理者モードで立ち上げてください。
プレビューアで要求ボタンを書き換える。
新しくボタンを作ってもよいし、既存のボタンを書き換えてもよいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /// <summary> /// HTTP クライアント経由で XAML ファイルをロードする /// http://hostname:10150/get/[num] 形式で取得する /// [num] 部分は XFormsPreviewHost と合わせる /// </summary> /// <param name="sender"></param> /// <param name="e"></param> async void OnClickSample0( object sender, EventArgs e) { var hc = new HttpClient(); try { var res = await hc.GetStringAsync( this .Uri + "0" ); var page = PageXaml.LoadXaml(res); await this .Navigation.PushAsync(page); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } |
呼び出しは /get/item 形式です。XAML データのローディングは非常に簡単ですPageXaml.LoadXaml(res) のように LoadXaml メソッドを呼び出すことで Page オブジェクト(内部で設定している ConentPage オブジェクトなど)を受け取ることができます。ここでは Navigation を使って PushAsync メソッドで画面遷移をさせます。これにより、ボタンを押すごとに XAML データのリロードができます。
ホスト名を書き換える。通常、エミュレータからは名前解決ができないと思うので、IP アドレスを直書きしています。このあたりは、別途設定ファイルから読み込ませる予定です。
1 2 3 4 5 6 7 8 9 10 | public MainPage() { InitializeComponent(); this .Port = 10150; // TODO: Change IP address of XFormsPreviewHost.exe on machine. // XAML ファイルをホスティングしている PC の IP アドレスを書く。 // エミュレータから名前解決ができれば、マシン名でもOK. this .Hostname = "172.16.0.9" ; } |
一度作ったプレビューアは何度も再利用ができます。/get/item コマンドだけ変更しなければ、簡易ホストのほうで、自由に応答 XAML ファイルを変更できます。
Visual Studio のほうでは、XAML のインテリセンスが効かないので、Xamarin Studio で XAML ファイルを編集します。属性などをコード補間してくれるので、多少は間違いがすくないかも。
■Type Provider 風に提供
XFormsPreviewer/XFormsProvider/PageXaml.fs at master ・ moonmile/XFormsPreviewer
https://github.com/moonmile/XFormsPreviewer/blob/master/XFormsProvider/PageXaml.fs
パーサ自体は F# で書いています。PCL の場合 FSharp.Core の競合があって懸念点が多いのですが、各種のワーニングは無視です。警告がいやだったので、最初は C# で書いていたのですが、今後のメンテを考えると F# のほうが良いかなと(単なる趣味…かもしれません)。
すごい大ざっぱに書いているので、タグ名とプロパティ名の対応を取ってない(DTDを考慮していない…というか、そもそも Xamarin.Forms の XAML に DTD が用意されていないのが問題では?…とも思ったけど、MS製XAML http://schemas.microsoft.com/winfx/2006/xaml/presentation のほうの記述もないので、そういうものかもしれない)のですが、まあツナギなので動けばよいという考えて作っています。が、どうせならば、F# の Type Provider と互換をとりたいですよね。FsXaml な感じで、WPF 製 XAML と互換を取るぐらいまでは作る予定です。最終的には、Windows Store 製 XAML や Android の axml, Xcode の storyboard の互換までいきたい。
■できないこと
本格的にデザイナ/プレビューアとして使うには、足りない機能が満載なのですが、
- static resource の参照ができない。
- 外部 UI モジュールを呼び出せない。
- x:Name を参照できない。
- ICommand が動かない。
- MVVM の Binding が動かない。
- デザイン時の Data Binding がないので、ListView が表示できない。
の項目ができません。このあたりは、おいおい実装していく予定です。これを実装すると、動的に XAML をロードするだけで、本体に View がいらないという状態になって、本格的に MVVM での View と ViewModel の疎結合が実現できそうです。