HIDARI日記(右)

そのときどき興味ある技術を中心にだらだら書いてます。内容は個人の見解であり、所属する企業を代表するものではありません。

ListBoxのItemTemplateに設定したDataTemplateにRelativeSourceのメソッドなりコマンドなりをバインドしよう

メソッド(CallMethodAction)の場合

まずはListBoxの項目がダブルクリックされたらViewModelのCommand(or メソッド)を呼ぶ - かずきのBlog@hatenaのサンプルを.確実に動くコードで基本を整理.

エッセンシャル WPF:Windows Presentation Foundation (Programmer’s SLECTION―Microsoft .net Development Series)

エッセンシャル WPF:Windows Presentation Foundation (Programmer’s SLECTION―Microsoft .net Development Series)

ListBoxということで,まずは中に表示するためのクラスを定義.丸パクリで申し訳無さを感じつつもPersonクラスで.

// 名前だけを持つPersonクラス
public class Person
{
    public string Name { get; set; }
}

で,それを扱うViewModelを作成.ItemsSourceにバインドするためのコレクションはObsevableCollectionクラスを使用しています.

public class MainWindowViewModel
{
    // バインディングソースにするコレクション
    public ObservableCollection<Person> People { get; private set; }

    public Person SelectedPerson { get; set; }

    public MainWindowViewModel()
    {
        // ざっくりと初期化.25個のPersonを用意しておく.
        this.People = new ObservableCollection<Person>(
            Enumerable.Range(1, 25).Select(i => new Person { Name = "Hidari" + i })
        );
    }

    // 動作の確認がしたいだけなのでMessageBoxを表示するだけの実装
    public void Execute()
    {
        if (this.SelectedPerson == null)
        {
            MessageBox.Show("ぬるですやん…");
            return;
        }

        MessageBox.Show(this.SelectedPerson.Name);
    }
}

で,View(XAML)側は以下.わかりやすくするために全体を示しますね.

と,その前に1点だけ注意. xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" を宣言してることからも分かる通り,事前に以下のアセンブリの参照をプロジェクトに追加しておくのを忘れずに.

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:vm="clr-namespace:WpfApplication4"
        Title="MainWindow"
        SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <ListBox ItemsSource="{Binding Path=People}"
                 SelectedItem="{Binding Path=SelectedPerson}"
                 HorizontalContentAlignment="Stretch">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding Path=Name}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseDoubleClick">
                                <ei:CallMethodAction MethodName="Execute" TargetObject="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Mode=OneWay}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </ContentControl>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

上記のコードでは,CallMethodActionにDataContextに設定したMainWindowViewModelで定義したExecuteメソッドをバインドしている.CallMethodActionの部分は以下の通り.

  • TargetObjectでは先祖要素を遡り一番初めに出てきたListBoxのDataContextを指定.これによってTargetObjectにMainWindowViewModelが設定される.
  • MethodNameでTargetObject(MainWindowViewModel)で定義されたExecuteメソッドを指定する.

ここで例えばRelativeSourceをListBoxItemに設定した場合,そのDataContextに設定されるのはPersonクラスであり,Personクラスに定義されたExecuteメソッドが呼ばれることになる.この場合の該当部分のコードは以下のようになる.

まずこちらがExecuteメソッドを定義した状態のPersonクラス

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

    public void Execute()
    {
        MessageBox.Show("This is a Person class.");
    }
}

で,こっちがView側の修正した部分.

<i:EventTrigger EventName="MouseDoubleClick">
    <ei:CallMethodAction MethodName="Execute" TargetObject="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Mode=OneWay}"/>
</i:EventTrigger>

コマンド(InvokeCommandAction)の場合

メソッドを使った上記のサンプルをコマンドを使うパターンに書き換えます(Personクラスは一番最初のサンプルから変わっていないので省略).

まずはViewModel.

こちらでは事前に簡単なRelayCommandクラスを用意して使っています.実装はMVVM入門 その1「シンプル四則演算アプリケーションの作成」 言語: C#, XAML Visual Studio 2010 用のものを使っていると考えていただければほぼ間違いないかと. 他は極力使い回しする方向で.

public class MainWindowViewModel
{
    public ObservableCollection<Person> People { get; private set; }

    public Person SelectedPerson { get; set; }

    public MainWindowViewModel()
    {
        this.People = new ObservableCollection<Person>(
            Enumerable.Range(1, 25).Select(i => new Person { Name = "Hidari" + i })
        );
    }

   #region Command
    private RelayCommand _HogeCommand;

    public RelayCommand HogeCommand
    {
        get
        {
            if (this._HogeCommand == null)
            {
                _HogeCommand = new RelayCommand(Execute, CanHoge);
            }

            return _HogeCommand;
        }
    }
   #endregion

    // ラムダ式にしたい気持ちもありました
    private bool CanHoge()
    {
        return true;
    }

    // 使い回し
    public void Execute()
    {
        if (this.SelectedPerson == null)
        {
            MessageBox.Show("ぬるですやん…");
            return;
        }

        MessageBox.Show(this.SelectedPerson.Name);
    }
}

で,次にView.変更したのはEventTriggerの中だけ.

<ListBox.ItemTemplate>
    <DataTemplate>
        <ContentControl Content="{Binding Path=Name}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseDoubleClick">
                    <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}},Path=DataContext.HogeCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ContentControl>
    </DataTemplate>
</ListBox.ItemTemplate>

ここでの肝は,CallMethodActionではメソッド名をMethodNameで指定してたところを,CommandのPathで DataContext.HogeCommand と指定すること.Pathの始めはDataContextとなるから混乱しやすい.注意していきたい.

おわりに

初歩の初歩で躓いてたので書いてみました. 間違いなどありましたら,教えていただけると幸いです.