WindowsストアアプリでXAMLを動的にロードする方法

XamlReader.Load method (Windows)
http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.markup.xamlreader.load.aspx

を使うと、XAML ファイルを動的にローディングできるんやで、ということは以前 XAML を調べていて分かったのだけどサンプルがいまいちだったので作ってみました。

ちなみに、Dev Center のサンプルは、

[code lang=”csharp”]
"<Ellipse Name="EllipseAdded" Width="300.5" Height="200"
Fill="Red" "http://schemas.microsoft.com/winfx/2006/xaml/presentation"/>";
[/code]

のところを、

[code lang=”csharp”]
"<Ellipse Name="EllipseAdded" Width="300.5" Height="200"
Fill="Red" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"/>";
[/code]

にして「xmlns=」を追加しないと動きません orz.

■XAML を文字列で指定する。

サンプルコードのように xaml 文字列を作成して、XamlReader.Load に渡すとルートのオブジェクトが帰ってきます。先の例では、Ellipse オブジェクトが取れるわけです。
これをどのようにすり替えるかというと、普通に Children.Add します。なので、ページを切り替えようと思って Frame.Navigate しようと思ってもうまくいきません。Navigate に渡すのはクラス自身なので、オブジェクトじゃないんですよね。仕方がないので、Page.Content の方をすり替えます。

[code lang=”csharp”]
private void OnClickString(object sender, RoutedEventArgs e)
{
// var xaml = "<TextBlock FontSize="50" Text="From XAML" "http://schemas.microsoft.com/winfx/2006/xaml/presentation" />";
string xaml = "<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><Ellipse Name="backbtn" Width="300.5" Height="200" Fill="Red" /></Grid>";
var doc = XamlReader.Load(xaml) as Grid;
this.Content = doc ;
// 短いXAML文字列だと FindName できる?
var el = doc.FindName("backbtn") as UIElement;
el.Tapped += (_,__) =>
{
this.Content = originalGrid;
};
}
[/code]

こんな風に、文字列を Page(this)のContent プロパティに設定すると画面が切り替わります。
戻すためにボタンをつけておいて、x:Name でつけること…は実はできないはずなのですができてしまいます。たぶん、XAML の文字列が短い場合は大丈夫なのかもしれません。次の HTTP プロトコルでデータを取ってくるときはうまくいかないのですから(MSDN にも x:Name は「使わないで」と書いてあるので)。

味気ない楕円の○が出ます。これをクリックすると元の画面に戻ることができます。

■HTTP プロトコルで XAML データをダウンロードして表示する

XAML 文字列で表示できたということは、インターネット上にあるデータを取ってくることもできます。HttpClient#GetStringAsync でがっつりともってきて表示すれば Ok です。

[code lang=”csharp”]
private async void OnClickHttp(object sender, RoutedEventArgs e)
{
var url = "http://moonmile.net/up/samplepage.xaml";
var cl = new HttpClient();
var xaml = await cl.GetStringAsync(new Uri(url));
var doc = XamlReader.Load(xaml) as Grid;
this.Content = doc;
// 戻るボタン
// HTTP からロードしたときは x:Name がスルーされない?
// var el = doc.FindName("backbtn") as UIElement;
var el = doc.Children.First<UIElement>( t => {
var btn = t as Button;
return (btn != null && ((string)btn.Content)=="back" )? true : false;
});
el.Tapped += (_, __) =>
{
this.Content = originalGrid;
};
}
[/code]

文字列が長いためか、x:Name が有効に働きません。仕方がないので。Button#Content で戻るボタンを取ってきているのですが、これは別な手段を考えたほうがいいでしょう。

こんな風に、Grid と Button, 楕円を並べた XAML を表示させることができます。ただし、すべての XAML がロードできるわけではなく、かなり制限があるようです。

  • x:Name が設定できないので、目的のコントロールを探すのがしんどい。
  • Stroyboard がうまく設定できない(実行エラーになる)のでアニメーションができない。
  • トリガーの設定はできるのか不明

な感じです。アニメーション&トリガーの組み合わせができないのは痛いですよね。おそらく、Storyboard 自身への x:Name と targetProperty のあたりでエラーになっているようです。まあ、コードビハイド(というのか?)で、独自に storyboard を追加してやって適度にイベントを設定すればそれらしいことはできるのですが、HTMLとSVGの組み合わせでアニメーションという具合にはいかなそうです。
あと、XAML への C# コードの埋め込みはできるのか?という問題がありますね。埋め込みができれば、View のままで色々できるので、Model と View の完全な分離(ダイナミックにリンクさせるという意味で)はかなり実用的になりそうなのですが。

■サンプルコード

WinStoreDynamicView
http://github.com/moonmile/WinStoreDynamicView

カテゴリー: C#, XAML パーマリンク