UWP GridView增量加载图片不显示及逐批加载16张实现问题
问题根因
- XAML存在语法错误:Image节点的
x:Name="cover缺少闭合双引号,会导致XAML解析失败,控件无法正常渲染 - 数据类型不匹配:原
ItemsToShow继承自ObservableCollection<string>,集合存储的是base64字符串,但DataTemplate里Image的Source绑定的是Image属性,字符串对象不存在该属性,绑定直接失效;同时代码里读取item.Path时从未给该属性赋值,会触发空引用异常 - 增量加载逻辑混乱:每次触发加载都会全量扫描所有文件夹、遍历全部435个文件,没有记录当前加载位置;
HasMoreItems判断写死为1000,和实际文件总数不匹配;内层循环重复添加同一项,计数逻辑完全错误 - 性能与线程问题:所有文件IO操作重复执行,BitmapImage创建未做线程适配,容易触发跨线程访问异常,同时硬编码从集合类获取页面ProgressRing控件,耦合度极高容易出错
- 文件排序失效:原代码注释掉了获取文件修改时间的逻辑,排序依据的
LastModified属性全是默认值,实际没有按修改时间排序
正确实现方案
1. 修正XAML代码
补全XAML语法错误,配置GridView的增量加载参数,固定每次预取批次大小:
<GridView x:Name="komikGridView" Loaded="komikGridView_Loaded" IncrementalLoadingThreshold="2" DataFetchSize="16" SelectionMode="None" IsItemClickEnabled="False"> <GridView.Resources> <DataTemplate x:Key="DataTemplatekomikGridView"> <Grid x:Name="komikGrid1"> <Image x:Name="cover" Width="160" Height="235" Stretch="UniformToFill" Source="{Binding Image}" /> </Grid> </DataTemplate> </GridView.Resources> <GridView.ItemTemplate> <StaticResource ResourceKey="DataTemplatekomikGridView"/> </GridView.ItemTemplate> </GridView> <!-- 页面单独放置加载进度环,不要在集合类里直接操作UI元素 --> <ProgressRing x:Name="loading" Grid.RowSpan="2" Width="50" Height="50" Visibility="Collapsed" IsActive="False"/>
参数说明:DataFetchSize="16"指定每次预取16项,IncrementalLoadingThreshold="2"表示滚动到距离列表剩余2项时触发下一次加载。
2. 定义Book模型类
public class Book { public string Name { get; set; } public ImageSource Image { get; set; } }
3. 重写增量加载集合类
把一次性的文件扫描逻辑放到构造函数里仅执行一次,记录总文件数和当前加载位置,固定每批加载16张:
public class KomikIncrementalCollection : ObservableCollection<Book>, ISupportIncrementalLoading { private readonly List<StorageFile> _sortedThumbFiles = new List<StorageFile>(); private int _currentLoadedIndex = 0; private const int BatchSize = 16; // 固定每批加载16张 private bool _fileListInited = false; public bool HasMoreItems => _fileListInited && _currentLoadedIndex < _sortedThumbFiles.Count; // 构造函数里一次性完成文件夹扫描、文件排序,全局只执行一次 public KomikIncrementalCollection() { InitFileListAsync().ConfigureAwait(false); } private async Task InitFileListAsync() { StorageFolder installedLocation = ApplicationData.Current.LocalFolder; StorageFolder library = await installedLocation.CreateFolderAsync("library", CreationCollisionOption.OpenIfExists); StorageFolder komik = await library.CreateFolderAsync("komik", CreationCollisionOption.OpenIfExists); IReadOnlyList<StorageFile> files = await komik.GetFilesAsync(); // 正确按修改时间倒序排序 List<Temp> sortingFiles = new List<Temp>(); foreach (StorageFile file in files) { BasicProperties prop = await file.GetBasicPropertiesAsync(); sortingFiles.Add(new Temp { File = file, LastModified = prop.DateModified.DateTime, Name = file.Name }); } // 获取对应缩略图文件夹的文件列表 StorageFolder thumbfolder = await installedLocation.CreateFolderAsync("thumb", CreationCollisionOption.OpenIfExists); StorageFolder komikthumb = await thumbfolder.CreateFolderAsync("komik", CreationCollisionOption.OpenIfExists); foreach (Temp item in sortingFiles.OrderByDescending(x => x.LastModified)) { // 只添加存在对应缩略图的文件,避免加载时报错 if (await komikthumb.TryGetItemAsync(item.Name + ".png") is StorageFile thumbFile) { _sortedThumbFiles.Add(thumbFile); } } _fileListInited = true; } public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { CoreDispatcher coreDispatcher = Window.Current.Dispatcher; return Task.Run<LoadMoreItemsResult>(async () => { // 等待文件列表初始化完成 while (!_fileListInited) { await Task.Delay(10); } // 计算本次要加载的数量:取批次大小和剩余文件数的最小值,避免越界 int itemsToLoad = Math.Min(BatchSize, _sortedThumbFiles.Count - _currentLoadedIndex); List<Book> currentBatch = new List<Book>(); for (int i = 0; i < itemsToLoad; i++) { StorageFile thumbFile = _sortedThumbFiles[_currentLoadedIndex + i]; Book book = new Book { Name = thumbFile.DisplayName.Replace(".png", "") }; // 回到UI线程创建BitmapImage,避免跨线程异常 await coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => { BitmapImage bi = new BitmapImage(); using (IRandomAccessStream stream = await thumbFile.OpenAsync(FileAccessMode.Read)) { await bi.SetSourceAsync(stream); } book.Image = bi; }); currentBatch.Add(book); } // 回到UI线程把数据加入集合 await coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { foreach (Book item in currentBatch) { this.Add(item); } }); _currentLoadedIndex += itemsToLoad; return new LoadMoreItemsResult() { Count = (uint)itemsToLoad }; }).AsAsyncOperation(); } private class Temp { public StorageFile File { get; set; } public DateTime LastModified { get; set; } public string Name { get; set; } } }
4. 修正页面后台代码
在页面层控制加载状态,避免UI和数据逻辑耦合:
private async void komikGridView_Loaded(object sender, RoutedEventArgs e) { // 避免重复触发加载事件导致重复初始化 if (komikGridView.ItemsSource != null) return; loading.IsActive = true; loading.Visibility = Visibility.Visible; KomikIncrementalCollection collection = new KomikIncrementalCollection(); komikGridView.ItemsSource = collection; // 等待第一批次16张加载完成后隐藏进度环 while (collection.Count < 16 && collection.HasMoreItems) { await Task.Delay(10); } loading.IsActive = false; loading.Visibility = Visibility.Collapsed; }
实现效果说明
- 首次进入页面自动加载第一批次16张图片
- 滚动到距离列表底部2项位置时,自动加载下一批次16张图片,直到全部435张图片加载完成
- 所有文件扫描、排序逻辑仅在集合初始化时执行一次,不会重复扫描文件夹造成性能浪费
- 数据类型和绑定路径完全匹配,图片可以正常渲染
- 不会出现重复添加、索引越界、加载数量错误的问题
- 所有UI相关操作都在UI线程执行,不会触发跨线程访问异常
内容的提问来源于stack exchange,提问作者Rose
相关产品推荐
相关产品推荐

