はじめに
MVVMデザインパターンでListBoxを操作する場合、ListBoxのSelectedIndexやSelectedItemを操作する事例が多いですが、ViewとViewModelの関係をもっと疎結合にし、簡潔に扱えないかなという事で上記2プロパティを使わない方法を紹介します。
今回もMVVMフレームワークにはLivetを使います。
View側のコード
<Grid>
<ListBox ItemsSource="{Binding PersonListView}" />
</Grid>
ItemsSourceを指定するだけです
ViewModel側のコード
選択された項目を得る
ListBoxのバインドターゲットとしては、ListCollectionView型を指定します。ListCollectionViewは、System.Windows.Dataネームスペースにあります。
Livetのスニペットである「lpropn」を利用して作成します。
# 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の中身のリストは含まれていません。リストの実体を並べ替えて表示したり、フィルターで絞ったりする事ができます。
PersonListはListBoxに表示するリストの実体です。
さて、コード部を以下のように実装します。肝の部分のみの説明なので、エラー処理、解放処理などは省略しています。
/// <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を渡しています。
その後、PersonListViewのCurrentChangedにイベントハンドラを設定しています。
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ではこの動作を分かってないと、少し痛い目に遭います(笑)。
同時に、イベントハンドラのライフ管理さえしっかりと行えば、かなり使い物になると思います。