読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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; }
    }
}

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