リフレクションを使ってXamarin.FormsにXAMLを動的ロードする

ふと、LoadFromXaml が public になれば、と思って探して、Xamarin.Forms のコードを見ていったら、

Load Xaml Dynamically At Runtime ? Xamarin Forums
https://forums.xamarin.com/discussion/87727/load-xaml-dynamically-at-runtime
Please make XamlLoader Public ? Xamarin Forums
https://forums.xamarin.com/discussion/87810/please-make-xamlloader-public

需要はあるようなないような。以前、動的にロードしたかったのは Xamarin Live Player 以前の頃だったので、既にあるからまあ特にいらんだろうという感じもするし、XAML をサーバーからダウンロードして切り替えることができたら、少しは違うかもしれないと思ったり。

サンプル

moonmile/XFormsXamlDynamicLoad: Xamarin.FormsでXAMLを動的にロードするサンプル
https://github.com/moonmile/XFormsXamlDynamicLoad

拡張メソッドを定義

ContentPageのLoadFromXamlメソッドが private ならば、自前で作ってしまえ、ってことで自前で作ってしまう。

static class ContentPageExtensions
{
    public static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml)
    {
        Load(view, xaml);
        return view;
    }
    private static void Load(object view, string xaml)
    {
        var t = Type.GetType("Xamarin.Forms.Xaml.XamlLoader, Xamarin.Forms.Xaml");
        var mi = t.GetRuntimeMethod("Load", new Type[] { typeof(object), typeof(string) });
        var obj = mi.Invoke(null, new object[] { view, xaml });
        return;
    }
}

自前のLoadFromXamlから、本物のXamarin.Forms.Xaml.XamlLoader.Loadを呼び出す。実は、XamlLoaderクラスはinternalなので呼び出せないのだが、Type.GetTypeでロードして、GetRuntimeMethodを使うと呼び出せるんですね。

PrivateObject クラス?(Microsoft.VisualStudio.TestTools.UnitTesting)
https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.privateobject.aspx

と同じことをやります。

メインページから遷移するときに、自前で XAML をロードさせます。コード内に書いちゃうとリソースで XAML にしたほうが楽だろう(実際そうだし)と思うのですが、似たような感じでネット上から XAML をダウンロードすることも可能ということで。

private void Button_Clicked(object sender, EventArgs e)
{
    var xaml = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
        xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml"">
<StackLayout>
<Label Text=""Hello Sub Page"" />
<Button Text=""click me!"" Clicked=""Button_Clicked"" />
<Label Text=""{Binding Count, StringFormat='{0} clicked'}"" />
</StackLayout>
</ContentPage>
";
    /// XAML を動的にロードする
    this.Navigation.PushAsync(new SubPage().LoadFromXaml(xaml));
}

SubPage クラスのほうは MVVM も効くので、ViewModel クラスだけで取り廻して XAML のほうは動的に切り替えることもできるかな、と。

public class SubPageViewModel : ObservableObject
{
    private int count = 0;
    public int Count
    {
        get { return count; }
        set { SetProperty(ref count, value, nameof(Count)); }
    }
}
public class SubPage : ContentPage
{
    public SubPage()
    {
        vm = new SubPageViewModel();
        this.BindingContext = vm;
    }
    SubPageViewModel vm;
    private void Button_Clicked(object sender, EventArgs e)
    {
        vm.Count++;
    }
}

実行

カテゴリー: 開発, XAML, Xamarin パーマリンク