かずきのBlog@hatena

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

Xamarin.FormsのListViewでタップされた項目をスマートにViewModelに渡す方法

EventToCommandBehaviorを使います。コードはこちらを参考に。

github.com

この時、こういうBehaviorを作っておくとListViewの選択がされなくなって捗ります。

using Xamarin.Forms;

namespace PrismUnityApp2
{
    public class NotSelectableListViewBehavior : Behavior<ListView>
    {
        protected override void OnAttachedTo(ListView bindable)
        {
            base.OnAttachedTo(bindable);

            bindable.ItemSelected += this.Bindable_ItemSelected;
        }

        protected override void OnDetachingFrom(ListView bindable)
        {
            base.OnDetachingFrom(bindable);
            bindable.ItemSelected -= this.Bindable_ItemSelected;
        }

        private void Bindable_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            ((ListView)sender).SelectedItem = null;
        }
    }
}

SelectedItemをItemSelectedでnullにしてるのですが、こうすることで選択時に色がつかなくなります。 色がついてると同じアイテムを2回選択したときにイベントが飛ばないので強制的に選択をしないようにしているという感じですね。

あとは、SelectedItemChangedEventArgsから選択項目を抜き出すConverterを作って

using System;
using System.Globalization;
using Xamarin.Forms;

namespace PrismUnityApp2
{
    public class SelectedItemChangedEventArgsToSelectedItemConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var args = (SelectedItemChangedEventArgs)value;
            return args.SelectedItem;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

こんな風にListViewに対して設定すればOKです。

<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:local="clr-namespace:PrismUnityApp2"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PrismUnityApp2.Views.MainPage"
             Title="MainPage">
  <ContentPage.Resources>
    <ResourceDictionary>
      <local:SelectedItemChangedEventArgsToSelectedItemConverter x:Key="SelectedItemChangedEventArgsToSelectedItemConverter" />
    </ResourceDictionary>
  </ContentPage.Resources>
  <Grid>
    <ListView ItemsSource="{Binding People}">
      <ListView.Behaviors>
        <local:NotSelectableListViewBehavior />
        <local:EventToCommandBehavior EventName="ItemSelected"
                                      Converter="{StaticResource SelectedItemChangedEventArgsToSelectedItemConverter}"
                                      Command="{Binding SelectedCommand}"/>
      </ListView.Behaviors>
    </ListView>
  </Grid>
</ContentPage>

大事なのは、ViewModel側のCommandでnullは受け付けないようにするところです。これをやらないと無駄にコマンドが実行されます。 (初回表示のときや、ItemSelectedイベントでnullをセットしたときとかにイベントが発生するので)

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace PrismUnityApp2.ViewModels
{
    public class MainPageViewModel : BindableBase, INavigationAware
    {
        public IEnumerable<Person> People { get; } = Enumerable.Range(1, 100)
            .Select(x => new Person { Name = $"tanaka {x}" })
            .ToArray();

        public DelegateCommand<Person> SelectedCommand { get; }

        public MainPageViewModel()
        {
            this.SelectedCommand = new DelegateCommand<Person>(
                x => Debug.WriteLine($"{x.Name} selected."), 
                x => x != null);
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }

    public class Person
    {
        public string Name { get; set; }
    }
}

以上、簡単にですが最近得たノウハウ。