昨日はiseebiせんせいのMvvmCrossなお話でした。
今日はXamarin.Formsの軽い話です。
注意ごと:
Xamarin.Forms自体についての説明はほとんどしません。Xamarin.Formsが
- Xamarin製のクロスプラットフォームなUIシステムで
- Android、iOS、Windows Phoneで動いて
- C#/F#のコードまたはXAMLベースの書式で書ける
長いので、結果だけ見たい方はオチへとどうぞ。
Xamarin Sketches✨
Evolve 2014で発表されたXamarin Studioの新機能に、Xamarin Sketches というのがあります。
いわゆるREPL環境です。
今回はAndroid向けとしてXamarin Android Playerを使って作業を進めますが、基本的にiOS向けでiOS Simulatorをターゲットとしても同じことができるはずです。
Xamarin Sketchesを使っているところ
SketchesではToolkitとして通常のXamarin環境と、Xamarin.Formsをサポートしています。
Xamarin.Formsを選択すると、RootPageというTabbedPageオブジェクトが自動生成された状態でアプリが開始します。
この状態でコードを書いていき、RootPage.Childrenに要素を追加などすると即時Android Player上の見栄えが更新されていきます。
なかなか楽しいです。
楽しいです。
が、残念ながらSketchesは今のところ(最新版=Xamarin Studio 5.6.3 build 3)はC#コードによるXamarin.Forms定義記述しかサポートしていません。
どちらかというとXAMLでビューを書いていきたい勢としては悔しいところです。
なので、XAMLでフォーム要素を書いてリアルタイムプレビューを実現してみたというのが今回のお話です。
破戒すべき全ての符(下調べ)
Xamarin.Formsのビューを定義する.xamlファイルは、プロジェクトのビルド時、対応付く.xaml.csファイルと共に処理されます。XAMLの書式に従ってC#側のpartial class定義が何か作成されて.xaml.csのものとあわせてXamarin.Forms用クラスとして生成される気がしますが、今回このあたりは深く追っていません。
なんとなくWPFのXAMLビュー定義と似たような処理がされているはずという読みだけで進みます。
XAMLからのビュー生成はどのようにしておこなわれるのでしょう。
しばらくXamarin.Forms.Xaml名前空間を睨みつけていると、うっすら見えてきた気がします。
Xamarin.Forms.Xaml.Extensionsにいくつかの拡張メソッドが生えていて、その中のLoadFromXamlメソッドにinflate対象のXamarin.Forms要素へ該当するクラスの型を渡してやると、紐付いたXAML(xml)をリソース領域から引っ張り出してきてロードする、という感じでしょうか。
じゃあこれを使えば…!と思うのですが、Xamarin Sketchesでは名前付きのクラスを内部に記述してXAMLのロードまでできるような辻褄を合わせるのが難しそうです。じゃあXamarin.Forms.Xaml.XamlLoader::Load()を直接呼び出すか、いやこれそもそも名前空間直下のprivateなクラスだしそう簡単にはいかないし、うーむ。だめですね。
諦めましょう。
...
失意の中でふて寝していたところ、不意に前述のLoadFromXamlが非公開のオーバーロードを持っている気がして目が覚めました。
あとはリフレクション芸でどうにかなる。
私が勝利を確信した瞬間でした。
無限の検証(Unlimited Verification Works)
状況の調査・検証にはXamarin Studioのデバッガでのステップ実行とImmediate windowがおおいに役立ちました。
まずは厳かな心持ちでXamarin.Forms.Xamlアセンブリを指定、読み出します。
var asm = Assembly.Load(new AssemblyName("Xamarin.Forms.Xaml, Version=1.2.3.0, Culture=neutral, PublicKeyToken=null"));続いて、GetTypes()で得られた中からExtensionsクラスを引っ張り出します。
var type = asm.GetTypes().Where(t => t.FullName == "Xamarin.Forms.Xaml.Extensions").First();続いて、publicでないメソッドを拾ってみます。
var methods = type.GetMethods(BindingFlags.NonPublic);きっとその中に含まれるLoadFromXamlのうち、非公開のほうをいい感じに引っ張り出せるように小細工をします。
var method = (MethodInfo)((TypeInfo)type).DeclaredMembers.Where(t => !((MethodInfo)t).Attributes.HasFlag(System.Reflection.MethodAttributes.Family)).First();ここでメソッドが取得できたら、あとはもう少し! ジェネリクスの解決をおこなってContentPageを食わせられるメソッド参照を得ます。
MethodInfo generic = method.MakeGenericMethod(typeof(ContentPage));あとは、天の杯(ヘブンズフィール)に至るための器たるContentPageインスタンスを生成し、それをいい感じにコンテンツで満たしてもらいます。
var page = new ContentPage{Padding = new Thickness(0,20,0,0)};
var res = generic.Invoke(page, new object[]{page, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<ContentPage .... "});
もらいます…が、結構容赦なくマスター(Xamarin Studio)がバタバタと斃れていきます(クラッシュレポートいっぱい投げました)。
天の鎖
挙動を見ていると、どうもSketchesでのコード実行自体ではなく、Xamarin Studio内でのオブジェクトプレビュー(構造のダンプ表示)部分でクラッシュしているようでした。
前述のように一時変数を用意して段階を踏んでいくと、途中段階のオブジェクトダンプに差し掛かったところでXamarin Studioがクラッシュします。
なるほどそれなら全部ひとつのステートメントで処理してしまえば良いか、と放った一撃が以下のメソッドチェーンお化けです。
var s = (単純につないだだけでこんなに読みづらいんだなぁと感動しました。
((MethodInfo)(
((TypeInfo)(
(Assembly.Load(new AssemblyName("Xamarin.Forms.Xaml, Version=1.2.3.0, Culture=neutral, PublicKeyToken=null"))
.GetTypes()
.Where(t => t.FullName == "Xamarin.Forms.Xaml.Extensions")
.First()
)
)).DeclaredMembers
.Where(t => !((MethodInfo)t).Attributes.HasFlag(System.Reflection.MethodAttributes.Family))
.First()
)).MakeGenericMethod(typeof(ContentPage))
).Invoke(page, new object[]{page, xaml}) != null;
後で思うと、C# 6.0の宣言式をうまく使うとなんとかなったかもしれません。
結果はこのようになりました。
実際に編集している模様の動画を貼り付けておきます。
いやー、フルスペックの.NET環境というのは良いですねぇ。
Xamarin StudioのAlpha版を使えばお手元で簡単に環境を再現できると思います。
当然XAMLのコード補完は効きませんが、ほらそのへんはトレーニングと思って脳を鍛えて下さい!!!11
私は元々Xamarin.Formsにビジュアルエディタは不要で基本的にはライブプレビューが出来ればよいと思っているので、この結果で割と満足しました。
まあXAMLといえばデータバインディング、という意味では実用性ほとんど無いのですけど。どのパラメータを指定するとどのようにビューが解釈されるか、といったエリアに使えれば十分かなーと思っています。
以上、Xamarin.Formsの軽い話でした。
明日の担当はmatatabiさんです。
0 件のコメント:
コメントを投稿