Here is a simple non-MVVM solution that will work with a VirtualizingStackPanel - tested with 100,000 items. We work directly with the ListView's ScrollViewer. Will work even when the ListView is resized.
1. Xaml
<Window
x:Class="PagingVirtualListView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Paging Virtualized ListView"
Height="300" Width="500" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView x:Name="PeopleList" ItemsSource="{Binding Persons}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Avatar" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding AvatarUrl}" Margin="10" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Column="1" Margin="10" VerticalAlignment="Center">
<Button Content="PGUP" Click="OnPgUpClick" Padding="10 5" Margin="10"/>
<Button Content="PGDn" Click="OnPgDnClick" Padding="10 5" Margin="10"/>
</StackPanel>
</Grid>
</Window>
2. The code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PeopleList.Loaded += PeopleList_Loaded;
DataContext = this;
Mock();
}
ScrollViewer lvScrollViewer;
public ObservableCollection<Person> Persons { get; set; }
= new ObservableCollection<Person>();
private Random rand = new Random();
private void Mock()
{
for (int i = 0; i < 100000; i++)
{
Persons.Add(new Person
{
AvatarUrl = "http://www.freepngimg.com/download/happy_person/2-2-happy-person-free-download-png.png",
Name = $"Person {i}",
Age = rand.Next(20, 60)
});
}
}
private void PeopleList_Loaded(object sender, RoutedEventArgs e)
{
PeopleList.Loaded -= PeopleList_Loaded;
lvScrollViewer = PeopleList.GetVisualChild<ScrollViewer>();
}
private void OnPgUpClick(object sender, RoutedEventArgs e)
{
lvScrollViewer.ScrollToVerticalOffset(
Math.Max(0, lvScrollViewer.VerticalOffset - lvScrollViewer.ViewportHeight));
}
private void OnPgDnClick(object sender, RoutedEventArgs e)
{
lvScrollViewer.ScrollToVerticalOffset(
Math.Min(lvScrollViewer.VerticalOffset + lvScrollViewer.ViewportHeight, Persons.Count - 1));
}
}
public static class HelperExtension
{
public static T GetVisualChild<T>(this Visual referenceVisual) where T : Visual
{
Visual child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
{
child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
if (child != null && (child.GetType() == typeof(T)))
break;
else if (child != null)
{
child = GetVisualChild<T>(child);
if (child != null && (child.GetType() == typeof(T)))
break;
}
}
return child as T;
}
}
public class Person
{
public string AvatarUrl { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
Enjoy.