サブルーチン駅前4時29分集合

プログラミングと散歩と数学に興味のある男のブログです。基本的に知識は浅いです。

Xamarin.FormsのDataTemplateの内側から外側のBindingContextを参照した~い!

 こんにちは。先日腕時計が壊れた、たねろうです。6月になりましたが、関東はまだ梅雨入りしないですね。普通に暑い晴れの日が多いです。周囲の人でエアコンをつけ始める人も多いですが、僕は寒がりなので例年梅雨明けまでつけないことが多いです。

さて、本題ですが、Xamarin.FormsでCollectionViewなどのDataTemplateの内側から外にあるBindingContextのViewModelを参照する方法がわかったのでメモしておこうと思います。

 CollectionViewで表示するUIのひとつひとつに、削除ボタンを付けておく場合などにCommandを親のViewModelに持たせておいて共通化するということはよくあると思います。例を出すと、BindingContextにMainPageViewModelをもつMainPageの中に、ItemViewModelのリストをItemsSourceとするCollectionViewがあり、DataTemplateでその様式を指定しているとします。ここで、各DataTemplateの中に要素を削除するための削除ボタンをつけておく場合、各ItemViewModelにCommandを持たせるよりも、ItemViewModelのリストを持っているMainPageViewModelにCommandを持たせておいたほうが削除の処理が楽になりますし、ItemViewModelの数だけCommandを持たせる必要もなくなります。ここでは、MainPageViewModelがDeleteCommandを持っているとします。

今の状況を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"
             xmlns:local="BlogTest"
             x:Class="BlogTest.MainPage">
    <ContentPage.Resources>
        
        <DataTemplate x:Key="Item">
            <Label Text="これはItemです"/>
            <Button Text="削除"
                    Command=""/> //ここをどう指定するか
        </DataTemplate>
    </ContentPage.Resources>

    <ContentPage.BindingContext>
        <local:MainPageViewModel />
    </ContentPage.BindingContext>

    <StackLayout>
        <Label Text="メインページです"/>
        //ItemListはItemViewModelのリストです
        <CollectionView ItemsSource="{Binding ItemList}" 
                                   ItemTemplate="Item"/>
    </StackLayout>

</ContentPage>

さて、ここでDataTemplateの内側からMainPageViewModelのDeleteCommandを参照するにはどうすればよいかというと、どうやらRelativeSourceを使うと上手くいくようです。

Command="{Binding Source={RelativeSource AncestorType={x:Type local:MainPageViewModel}},Path=DeleteCommand}"

Commandをこう指定することによって、「MainPageViewModel型をBindingContextに持つ親を探し、あったらMainPageViewModelのDeleteCommandを代入する」という指定をすることができます。ここで、Xamarinのドキュメントによると、x:Typeの指定により、挙動が変わるようです。今回の場合のように、Viewから派生した型(Label, Entry, Editor, Buttonなど)ではない型を指定した場合、「その型をBindingContextにもつ要素を親から探す」挙動になりますが、Viewから派生した型を指定したときは、「その型の要素を親から探す」ことになります。

 また、DataTemplateの内側から親を探す場合、DataTemplateがCollectionViewなど、Templateを適用する要素の子として配置された状態で親を探します。したがって、DataTemplateがどこに適用されているかで親の位置が変わったりするので注意が必要みたいです。

 このあたりの挙動がよくわかってなかったので、分かってよかったです。