かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Xamarin.FormsでListViewのItemTemplate内のボタンにPageにバインドされているVMにあるCommandをバインドする方法

こういうの悩みますよね。例えば以下のようなViewModelがあるとします。こいつのAlertCommandに選択項目を渡したいというケースです。

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xamarin.Forms;

namespace App52
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ObservableCollection<string> Items { get; } = new ObservableCollection<string>(Enumerable.Range(1, 100)
            .Select(x => $"Item{x}"));

        public Command AlertCommand { get; }

        public MainPageViewModel()
        {
            this.AlertCommand = new Command<string>(x =>
            {
                Debug.WriteLine($"Execute: {x}");
            });
        }
    }
}

こういう感じの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:App52="clr-namespace:App52;assembly=App52"
             x:Class="App52.MainPage"
             x:Name="Root">
  <ContentPage.BindingContext>
    <App52:MainPageViewModel />
  </ContentPage.BindingContext>
  <ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <StackLayout Orientation="Horizontal">
            <Label Text="{Binding}" />
            <Button Text="Alert"
                    Command="{Binding Source={x:Reference Root}, Path=BindingContext.AlertCommand}"
                    CommandParameter="{Binding}" />
          </StackLayout>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

x:Referenceでページ自体をSourceに指定して、そこからVMのCommandをPathで指定しています。あとは、CommandParameterに渡したい要素をBindingしてやるだけ。これで動くようになります。めでたしめでたし。