ちょっとばかし、ハマったのでメモ書きをしておく。
WPFのDataGridにデータバインドする
WPFでリスト表示をするときに、ListViewかDataGridを使うと思うのだが、ここで MVVM を使って ItemsSource にバインドする。
1 2 3 4 5 6 7 8 9 10 | < Grid > < Grid.RowDefinitions > < RowDefinition Height = "50" /> < RowDefinition Height = "*" /> </ Grid.RowDefinitions > < DataGrid Grid.Row = "1" x:Name = "lv" ItemsSource = "{Binding Items}" SelectedItem = "{Binding Item}" SelectedIndex = "{Binding ItemIndex}" > </ DataGrid > |
ViewModel を作って
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class ViewModel : ObservableObject { private List<商品> _items; public List<商品> Items { get { return _items; } set { SetProperty( ref _items, value, nameof(Items)); } } private 商品 _item; public 商品 Item { get { return _item; } set { SetProperty( ref _item, value, nameof(Item)); } } private int _itemIndex; public int ItemIndex { get { return _itemIndex; } set { SetProperty( ref _itemIndex, value, nameof(ItemIndex)); } } } |
ロード時に表示させる。
1 2 3 4 5 6 7 8 | private void MainWindow_Loaded( object sender, RoutedEventArgs e) { _vm = new ViewModel(); var ent = new Database1Entities(); _vm.Items = ent.商品.ToList(); this .DataContext = _vm; } ViewModel _vm; |
実行すると、こんな感じ。
行を選択するとバインドしている SelectedItem にオブジェクト(ここでは商品オブジェクト)が入るので、詳細データとかを別画面で表示するには便利だったりする。
SelectedIndex でもいいような気もするのだが、実は、DataGrid はヘッダをクリックするとソートする機能が初めから入っていて、結構便利。でもって、ソートしたときはインデックスが変わってしまうので、SelectedIndex では困るので、SelectedItem から選択したオブジェクトを拾うと良いという訳。渡している List<商品> と DataGird 自身が持っている Items の中身が異なる(ソートされている)のでずれがでてくる。
リストをリフレッシュすると落ちる
さて、普段は DataGrid の中身は変わらないので更新することはないのだが、なんらかの理由でリストを再描画させたいとしよう。このときに、ItemsSource にバインドしている ViewModel 側の Items を更新すればよいと思って、次な感じにすると、
1 2 3 4 5 | private void clickInit( object sender, RoutedEventArgs e) { var ent = new Database1Entities(); _vm.Items = ent.商品.ToList(); } |
~~~
System.NullReferenceException が発生しました
HResult=0x80004003
Message=オブジェクト参照がオブジェクト インスタンスに設定されていません。
~~~
なる例外が発生して落ちる。それも、_vm.Items に代入しようとしているところで落ちるので始末が負えない。
実は、初期表示をしていて、カーソルを DataGird にあてない状態(選択行が無い状態)の場合は落ちなくて、一度選択した後には例外が発生して落ちるのである。
かなり不思議な現象である。ネットでもあちこち困っている人がいるもの、ぴっちりとした解決策は無いように見える、が、
実は、このように Item に null を代入すると落ちなくなる。
1 2 3 4 5 6 | private void clickReload( object sender, RoutedEventArgs e) { var ent = new Database1Entities(); _vm.Item = null ; // ★ _vm.Items = ent.商品.ToList(); } |
そう、ViewModel を見るとわかるのだが、DataGrid の SelectedItem へのバインドが悪さをしている。Items に新しいリストを設定して、ItemsSource プロパティを更新してしまうと、SelectedItem が行先を見失う(?)ことになって NULL例外が発生するらしい。このため、先に SelectedItem に null を設定してやって、カーソルを外した後で ItemsSource に新しいリストを設定することになる。
おまけ
じゃあ、ObservableCollection を使って、一度クリアしたあとに1つずつ追加しけばいいじゃないか、と思うかもしれないが、実はそれでも落ちる。
1 2 3 4 5 6 7 | private void clickReload( object sender, RoutedEventArgs e) { var ent = new Database1Entities(); _vm.Item = null ; // ★ _vm.Items.Clear(); foreach ( var it in ent.商品) _vm.Items.Add(it); } |
普通の Items の更新と同じように、★部分の null の設定がないと、_vm.Items.Clear() 行で例外が発生して落ちてしまう。これも、SelectedItem が示し先を見失ってしまうためらしい。