#はじめに
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ではこの動作を分かってないと、少し痛い目に遭います(笑)。
同時に、イベントハンドラのライフ管理さえしっかりと行えば、かなり使い物になると思います。