I have ListView
(virtualization is on by default), which ItemsSource
is bound to ObservableCollection<Item>
property.
When data are populated (property is set and notification is rised) I see 2 layout spikes in profiler, second one happens after call listView.ScrollIntoView()
.
My understanding is:
ListView
loads data via binding and createsListViewItem
for items on screen, starting from index 0.- Then I call
listView.ScrollIntoView()
. - And now
ListView
does that second time (creatingListViewItem
s).
How do I prevent that de-virtualization from happening twice (I don't want one before ScrollIntoView
to occur)?
I tried to make a repro using ListBox
.
xaml:
<Grid>
<ListBox x:Name="listBox" ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Fill" VerticalAlignment="Top" HorizontalAlignment="Center" Click="Button_Click" />
</Grid>
cs:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class ViewModel : NotifyPropertyChanged
{
public class Item : NotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
}
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
void Button_Click(object sender, RoutedEventArgs e)
{
var list = new List<ViewModel.Item>(1234567);
for (int i = 0; i < 1234567; i++)
list.Add(new ViewModel.Item());
list.Last().IsSelected = true;
_vm.Items = new ObservableCollection<ViewModel.Item>(list);
listBox.ScrollIntoView(list.Last());
}
}
Debug - Performance Profiler - Application Timeline... wait a bit, click button, wait a bit, close window. You will see 2 layout passes with VirtualizingStackPanel
. My aim is to have just one and I don't know how.
The problem with repro is to simulate load (when creating ListViewItem
is expensive), but I hope it's more clearly demonstrate the problem now.
ListView
, scrolling to the last selected item). Ideally I am seeking for the way to store/restoreListView
state in MVVM application (selection is handled, but scroll position is not, it's somehow handled withScrollIntoView
), that double de-virtualization is kind of XY-problem (but I am ok withScrollIntoView
, only de-virtualization two times is the problem). – QuietenListView
it's scroll position before loading it's content somehow. Could you somehow subclassListView
and prevent rendering until you have finished the load pass (perhaps an IsLoadingContent flag) so that you can assign the items and also mark which item needs to be selected and brought into view? – Lifeanddeath