ListBoxのMVVMを意識した使い方


はじめに

 MVVMデザインパターンでListBoxを操作する場合、ListBoxSelectedIndexSelectedItemを操作する事例が多いですが、ViewとViewModelの関係をもっと疎結合にし、簡潔に扱えないかなという事で上記2プロパティを使わない方法を紹介します。

 今回もMVVMフレームワークにはLivetを使います。

View側のコード

MainWinodw.xaml
<Grid>
    <ListBox ItemsSource="{Binding PersonListView}" />
</Grid>

ItemsSourceを指定するだけです

ViewModel側のコード

選択された項目を得る

 ListBoxのバインドターゲットとしては、ListCollectionView型を指定します。ListCollectionViewは、System.Windows.Dataネームスペースにあります。

Livetのスニペットである「lpropn」を利用して作成します。

MainWindowViewModel.cs
#region PersonListView 個人一覧(ListBox)
private ListCollectionView _PersonListView ;
/// <summary>
/// 個人一覧(ListBox)
/// </summary>
public ListCollectionView PersonListView 
{
    get
    { return _PersonListView ; }
    set
    { 
        if (_PersonListView  == value)
            return;
        _PersonListView  = value;
        RaisePropertyChanged();
    }
}

/// <summary>
/// 個人リスト
/// </summary>
private List<string> PersonList;
#endregion

 ListCollectionViewはこれ自体にListBoxの中身のリストは含まれていません。リストの実体を並べ替えて表示したり、フィルターで絞ったりする事ができます。

 PersonListListBoxに表示するリストの実体です。

 さて、コード部を以下のように実装します。肝の部分のみの説明なので、エラー処理、解放処理などは省略しています。

MainWindowViewModel.cs
/// <summary>
/// 初期化
/// </summary>
public void Initialize()
{
    this.PersonList = new List<string>() 
    {
        "佐藤","高橋","渡辺","山本","小林"
    };

    this.PersonListView = new ListCollectionView(this.PersonList);
    this.PersonListView.CurrentChanged += PersonListView_CurrentChanged;
}

/// <summary>
/// リストの位置が変わった
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void PersonListView_CurrentChanged(object sender, EventArgs e)
{
    var lv = sender as ICollectionView;
    if (lv.CurrentPosition < 0)
    {
        System.Diagnostics.Trace.WriteLine("選択無し");
        return;
    }
    var name = lv.CurrentItem as string;
    System.Diagnostics.Trace.WriteLine(
        string.Format("CurrentChanged:位置={0},名前={1}",
        lv.CurrentPosition,
        name));
}

 Initalize()の中で、PersonListを初期化した後、ListCollectionViewを作成し、PersonListを渡しています。
 その後、PersonListViewCurrentChangedにイベントハンドラを設定しています。
 ListBoxへバインドを行うと、リストボックスから発生するIndexの変更がこのハンドラへ伝わります。
 CurrentChangedイベントのライフ管理を十分に行えば、ListCollectionViewを使った方が楽だと思います。

ViewModel側から項目を選択する

 ViewModel側からの項目選択は簡単です。

this.PersonListView.MoveCurrentToPosition(index);

indexは先頭を0とした番号です
またこのようにインスタンスを指定した設定ができます。

var item = this.PersonList[2];
this.PersonListView.MoveCurrentTo(item);

他にも有用なメソッドで移動ができます。
ICollectionView のメソッドを調べてみて下さい。

さいごに

 使う上での注意事項として、ViewModel側でListCollectionViewのインスタンスを作成した直後はCurrentPositionが-1ですが、ListBoxへバインドすると、自動的にCurrentPositionが0(先頭に位置付く)になります。
 UnitTestではこの動作を分かってないと、少し痛い目に遭います(笑)。
 同時に、イベントハンドラのライフ管理さえしっかりと行えば、かなり使い物になると思います。