When working in an image browser program, you encounter a huge number of pictures (such as hundreds or thousands of pictures), loading all pictures at once will cause the interface to stutter or even the program crash.
This article introduces a method of dynamic paging loading pictures in WPF + Prism, combined with ScrollViewer scrollbar bottoming detection, to achieve a smooth experience of "scrolling, loading".
1. Interface design: 4×4 display + scroll bar
We want the interface to display 4×4 each time, with a total of 16 pictures, each with a border.
XAML Example
<ScrollViewer VerticalScrollBarVisibility="Auto" ScrollChanged="ScrollViewer_ScrollChanged"> <ItemsControl ItemsSource="{Binding Images}"> <> <ItemsPanelTemplate> <UniformGrid Columns="4"/> </ItemsPanelTemplate> </> <> <DataTemplate> <Border BorderBrush="Gray" BorderThickness="1" Margin="5"> <Image Source="{Binding}" Stretch="Uniform"/> </Border> </DataTemplate> </> </ItemsControl> </ScrollViewer>
ScrollViewer Pack ItemsControl, enable vertical scrolling.
Using UniformGrid layout, 4 columns are evenly distributed.
Each picture is wrapped in a layer with a Border, which is beautiful and clearly separated.
2. Backend logic: ViewModel pagination loading
ViewModel is responsible for managing picture lists and paging logic.
public class ImageBrowserViewModel : BindableBase { private const int PageSize = 16; private readonly List<string> allImagePaths = new(); public ObservableCollection<BitmapImage> Images { get; } = new(); private int currentPage = 0; public bool IsLoading { get; private set; } public void LoadAllImagePaths(string folderPath) { (); var extensions = new[] { ".jpg", ".png", ".bmp" }; var files = (folderPath) .Where(f => ((f).ToLower())) .ToList(); (files); currentPage = 0; (); } public async Task LoadNextPageAsync() { if (IsLoading) return; IsLoading = true; var nextImages = allImagePaths .Skip(currentPage * PageSize) .Take(PageSize) .ToList(); foreach (var path in nextImages) { await (() => { var bitmap = new BitmapImage(); (); = ; = new Uri(path); (); (); (() => (bitmap)); }); } currentPage++; IsLoading = false; } }
- LoadAllImagePaths: Record all image paths at once, but do not load the image content immediately.
- LoadNextPageAsync: Load the picture by page every time, use + to avoid interface lag.
The purpose of this code is to take out the image paths required for the current page from the allImagePaths list and store them in the nextImages list. The following is a line-by-line explanation:
Code snippet
var nextImages = allImagePaths .Skip(currentPage * PageSize) .Take(PageSize) .ToList();
Explain line by line
1. var nextImages
:
Declare a variable nextImages to store the list of image paths required for the current page.
2. allImagePaths
:
This is a list of all image paths, of type List<string>.
3. .Skip(currentPage * PageSize)
:
- currentPage is the index of the current page, starting from 0.
- PageSize is the number of pictures displayed per page, which is defined here as 16.
- currentPage * PageSize Calculates the number of all image paths before the current page.
- .Skip(currentPage * PageSize) Skip these paths, that is, skip all image paths before the current page.
4. .Take(PageSize)
:
Take out the PageSize paths from the skipped path, that is, take out the image path required for the current page.
5. .ToList()
:
Convert the retrieved path to a new list and assign it to nextImages.
Summarize
The purpose of this code is to take out the image paths required for the current page from allImagePaths and store them in the nextImages list. Specifically, it skips all image paths before the current page, then takes out the number of image paths required for the current page (PageSize) and stores these paths into the nextImages list.
3. Load new images when scrolling to the end
In the ScrollChanged event of ScrollViewer, check whether it is close to the bottom, and if so, request ViewModel to load the next page:
private async void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { var scrollViewer = (ScrollViewer)sender; if ( + >= - 50) // 50 pixels close to the bottom { if (DataContext is ImageBrowserViewModel vm && !) { await (); } } }
ExtentHeight is the total height, ViewportHeight is the current visible area height, and VerticalOffset is the current scrolling position.
When the scrolling approaches the bottom 50px, the loading is triggered.
4. Summary
Through the above method, we have achieved:
- Only a small number of images are loaded initially to quickly open the interface.
- When the user scrolls, the subsequent pictures are loaded on pages as needed.
- The interface does not stutter and experiences smoothness.
This design is particularly suitable for handling scenarios such as large-scale image browsing, video frame viewing, thumbnail managers, etc.
This is the end of this article about WPF implementing dynamic loading of image browsers. For more related WPF image browser content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!