ちょっとばかし、ハマったのでメモ書きをしておく。
WPFのDataGridにデータバインドする
WPFでリスト表示をするときに、ListViewかDataGridを使うと思うのだが、ここで MVVM を使って ItemsSource にバインドする。
<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 を作って
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)); } } }
ロード時に表示させる。
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 を更新すればよいと思って、次な感じにすると、
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 を代入すると落ちなくなる。
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つずつ追加しけばいいじゃないか、と思うかもしれないが、実はそれでも落ちる。
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 が示し先を見失ってしまうためらしい。