I have the following view-model class:
public class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private bool _isSelected;
public bool IsSelected {
get => _isSelected;
set {
if (value == _isSelected) { return; }
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
}
}
public int Data { get; }
public ViewModel(int data) => Data = data;
}
and the following view:
<Window x:Class="MVVMScrollIntoView.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataGrid Name="dg">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Window>
I set the ItemsSource
of the DataGrid in the code-behind as follows:
var data = Enumerable.Range(1, 100).Select(x => new ViewModel(x)).ToList();
dg.ItemsSource = data;
Selecting/deselecting rows in the data grid is propogated to the view-model instances, and changes from code to the view-model's IsSelected
property are propogated back to the data grid.
But I want that when the IsSelected
property is set via code within the view model:
data[79].IsSelected = true;
the selected data grid row should also scroll into view, presumably using the data grid's ScrollIntoView
method.
My original thought was to listen in the view code-behind for the SelectionChanged
event:
dg.SelectionChanged += (s, e) => dg.ScrollIntoView(dg.SelectedItem);
But this doesn't work, as SelectionChanged
is only triggered on visible items when virtualization is on.
Turning virtualization off is a successful workaround:
<DataGrid Name="dg" EnableRowVirtualization="False">
...
but I'm worried about the performance implications for large lists (20K+ items), so I would prefer not to do this.
What's the MVVM way of doing this?
You may not like referencing View controls in your ViewModels, but this will work.
Create a reference to the DataGrid
in your ViewModel
and invoke the ScrollIntoView
command in the Data
setter.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (value == _isSelected) { return; }
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
//Invoke Scroll
DataGrid.ScrollIntoView(this);
}
}
public int Data { get; }
public ViewModel(int data) => Data = data;
//DataGrid Reference
public DataGrid DataGrid { get; set; }
}
Then just add the reference when you construct the ViewModels
var data = Enumerable.Range(1, 100).Select(x => new ViewModel(x) { DataGrid = dg }).ToList();
It seems like the best way to avoid virtualization and having to call from the View in this case.