【发布时间】:2020-02-20 10:22:26
【问题描述】:
我有一组项目 (~12.000) 我想在 ListView 中显示。这些项目中的每一个都是一个视图模型,它具有一个不属于应用程序包的分配图像(它位于本地磁盘上的“外部”文件夹中)。而且由于 UWP 的限制,我不能(afaik 和测试)将Uri 分配给ImageSource,而必须使用SetSourceAsync 方法。因此,应用程序的初始加载时间太长,因为所有ImageSource 对象都必须在启动时初始化,即使用户看不到图像(列表在启动时未过滤)和结果内存消耗约为 4GB。将图像文件复制到应用程序数据目录可以解决问题,但对我来说不是解决方案,因为图像会定期更新,会浪费磁盘空间。
项目显示在ListView 中,该ICollectionView 使用分组的ICollectionView 作为源。
现在我想我可以在每个组上实现IItemsRangeInfo 或ISupportIncrementalLoading 并推迟视图模型的初始化,以便仅加载要显示的图像。我对此进行了测试,但它似乎不起作用,因为在运行时组上都没有调用接口的方法(如果这不是真的并且可以实现,请在此处纠正我)。当前(不工作)版本使用自定义ICollectionView(用于测试目的),但DeferredObservableCollection 也可以实现IGrouping<TKey, TElement> 并用于CollectionViewSource。
有什么方法可以实现延迟初始化或使用Uri 作为图像源,或者我必须在实现的ListView 上使用“普通”集合或自定义ICollectionView 作为ItemsSource期望的行为?
应用的当前目标版本:1803(内部版本 17134) 应用程序的当前目标版本:Fall Creators Update (Build 16299) 两者(最低版本和目标版本)都可以更改。
创建图片源代码:
public class ImageService
{
// ...
private readonly IDictionary<short, ImageSource> imageSources;
public async Task<ImageSource> GetImageSourceAsync(Item item)
{
if (imageSources.ContainsKey(item.Id))
return imageSources[item.Id];
try
{
var imageFolder = await storageService.GetImagesFolderAsync();
var imageFile = await imageFolder.GetFileAsync($"{item.Id}.jpg");
var source = new BitmapImage();
await source.SetSourceAsync(await imageFile.OpenReadAsync());
return imageSources[item.Id] = source;
}
catch (FileNotFoundException)
{
// No image available.
return imageSources[item.Id] = unknownImageSource;
}
}
}
ICollectionView.CollectionGroups 属性返回的结果组的代码:
public class CollectionViewGroup : ICollectionViewGroup
{
public object Group { get; }
public IObservableVector<object> GroupItems { get; }
public CollectionViewGroup(object group, IObservableVector<object> items)
{
Group = group ?? throw new ArgumentNullException(nameof(group));
GroupItems = items ?? throw new ArgumentNullException(nameof(items));
}
}
包含每个组的项目的集合的代码:
public sealed class DeferredObservableCollection<T, TSource>
: ObservableCollection<T>, IObservableVector<T>, IItemsRangeInfo //, ISupportIncrementalLoading
where T : class
where TSource : class
{
private readonly IList<TSource> source;
private readonly Func<TSource, Task<T>> conversionFunc;
// private int currentIndex; // Used for ISupportIncrementalLoading.
// Used to get the total number of items when using ISupportIncrementalLoading.
public int TotalCount => source.Count;
/// <summary>
/// Initializes a new instance of the <see cref="DeferredObservableCollection{T, TSource}"/> class.
/// </summary>
/// <param name="source">The source collection.</param>
/// <param name="conversionFunc">The function used to convert item from <typeparamref name="TSource"/> to <typeparamref name="T"/>.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> is <see langword="null"/> or
/// <paramref name="conversionFunc"/> is <see langword="null"/>.
/// </exception>
public DeferredObservableCollection(IList<TSource> source, Func<TSource, Task<T>> conversionFunc)
{
this.source = source ?? throw new ArgumentNullException(nameof(source));
this.conversionFunc = conversionFunc ?? throw new ArgumentNullException(nameof(conversionFunc));
// Ensure the underlying lists capacity.
// Used for IItemsRangeInfo.
for (var i = 0; i < source.Count; ++i)
Items.Add(default);
}
private class VectorChangedEventArgs : IVectorChangedEventArgs
{
public CollectionChange CollectionChange { get; }
public uint Index { get; }
public VectorChangedEventArgs(CollectionChange collectionChange, uint index)
{
CollectionChange = collectionChange;
Index = index;
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
// For testing purposes the peformed action is not differentiated.
VectorChanged?.Invoke(this, new VectorChangedEventArgs(CollectionChange.ItemInserted, (uint)e.NewStartingIndex));
}
//#region ISupportIncrementalLoading Support
//public bool HasMoreItems => currentIndex < source.Count;
//public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
//{
// Won't get called.
// return AsyncInfo.Run(async cancellationToken =>
// {
// if (currentIndex >= source.Count)
// return new LoadMoreItemsResult();
// var addedItems = 0u;
// while (currentIndex < source.Count && addedItems < count)
// {
// Add(await conversionFunc(source[currentIndex]));
// ++currentIndex;
// ++addedItems;
// }
// return new LoadMoreItemsResult { Count = addedItems };
// });
//}
//#endregion
#region IObservableVector<T> Support
public event VectorChangedEventHandler<T> VectorChanged;
#endregion
#region IItemsRangeInfo Support
public void RangesChanged(ItemIndexRange visibleRange, IReadOnlyList<ItemIndexRange> trackedItems)
{
// Won't get called.
ConvertItemsAsync(visibleRange, trackedItems).FireAndForget(null);
}
private async Task ConvertItemsAsync(ItemIndexRange visibleRange, IReadOnlyList<ItemIndexRange> trackedItems)
{
for (var i = visibleRange.FirstIndex; i < source.Count && i < visibleRange.LastIndex; ++i)
{
if (this[i] is null)
{
this[i] = await conversionFunc(source[i]);
}
}
}
public void Dispose()
{ }
#endregion
}
【问题讨论】:
标签: c# uwp collectionviewsource icollectionview