C# 创建一个列表视图,其中LoadMoreItemsAsync位于滚动的末尾
我的Windows Phone 8.1应用程序中有一个C# 创建一个列表视图,其中LoadMoreItemsAsync位于滚动的末尾,c#,xaml,listview,windows-store-apps,windows-phone-8.1,C#,Xaml,Listview,Windows Store Apps,Windows Phone 8.1,我的Windows Phone 8.1应用程序中有一个ListView,我可以得到1000个或更多的结果,因此每次滚动触底时,我都需要实现加载更多功能,或者使用其他逻辑和自然方式触发向列表中添加更多项目。 我发现ListView支持ISupportIncrementalLoading,并发现以下实现: 这是我找到的更好的解决方案,因为它没有指定类型,即它是泛型的。 这个解决方案的问题是,当加载ListView时,LoadMoreItemsAsync会运行所有需要的时间,直到获得所有结果,这意味着
ListView
,我可以得到1000个或更多的结果,因此每次滚动触底时,我都需要实现加载更多功能,或者使用其他逻辑和自然方式触发向列表中添加更多项目。我发现
ListView
支持ISupportIncrementalLoading
,并发现以下实现:
这是我找到的更好的解决方案,因为它没有指定类型,即它是泛型的。这个解决方案的问题是,当加载ListView时,
LoadMoreItemsAsync
会运行所有需要的时间,直到获得所有结果,这意味着用户不会触发LoadMore。我不确定是什么导致了LoadMoreItemsAsync
触发,但有点不对劲,因为它假设当我打开页面并当场加载所有项目时会发生这种情况,而我不做任何操作或滚动。以下是实现:IncrementalLoadingCollection.cs
public interface IIncrementalSource<T> {
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
void SetType(int type);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading where T : IIncrementalSource<I>, new() {
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int type, int itemsPerPage = 10) {
this.source = new T();
this.source.SetType(type);
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public bool HasMoreItems {
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) {
var dispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(
async () => {
uint resultCount = 0;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0) {
hasMoreItems = false;
}
else {
resultCount = (uint)result.Count();
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() => {
foreach(I item in result)
this.Add(item);
});
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
public class DatabaseNotificationModelSource : IIncrementalSource<DatabaseNotificationModel> {
private ObservableCollection<DatabaseNotificationModel> notifications;
private int _type = "";
public DatabaseNotificationModelSource() {
//
}
public void SetType(int type) {
_type = type;
}
public async Task<IEnumerable<DatabaseNotificationModel>> GetPagedItems(int pageIndex, int pageSize) {
if(notifications == null) {
notifications = new ObservableCollection<DatabaseNotificationModel>();
notifications = await DatabaseService.GetNotifications(_type);
}
return await Task.Run<IEnumerable<DatabaseNotificationModel>>(() => {
var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize);
return result;
});
}
}
IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type);
我对它做了一点修改,因为对我的数据库的调用是异步的,这是我找到的唯一一种确保在填充集合之前可以等待查询的方法。
在my
DatabaseNotificationViewModel.cs中
public interface IIncrementalSource<T> {
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
void SetType(int type);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading where T : IIncrementalSource<I>, new() {
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int type, int itemsPerPage = 10) {
this.source = new T();
this.source.SetType(type);
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public bool HasMoreItems {
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) {
var dispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(
async () => {
uint resultCount = 0;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0) {
hasMoreItems = false;
}
else {
resultCount = (uint)result.Count();
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() => {
foreach(I item in result)
this.Add(item);
});
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
public class DatabaseNotificationModelSource : IIncrementalSource<DatabaseNotificationModel> {
private ObservableCollection<DatabaseNotificationModel> notifications;
private int _type = "";
public DatabaseNotificationModelSource() {
//
}
public void SetType(int type) {
_type = type;
}
public async Task<IEnumerable<DatabaseNotificationModel>> GetPagedItems(int pageIndex, int pageSize) {
if(notifications == null) {
notifications = new ObservableCollection<DatabaseNotificationModel>();
notifications = await DatabaseService.GetNotifications(_type);
}
return await Task.Run<IEnumerable<DatabaseNotificationModel>>(() => {
var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize);
return result;
});
}
}
IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type);
IncrementalNotificationsList=新的IncrementalLoadingCollection(类型);
除了不太正常的“加载更多”之外,一切正常。我的代码出了什么问题?我创建了一个关于这个问题的非常简化的示例,并在MSDN论坛上提出了这个问题。老实说,我不知道为什么会发生这种奇怪的行为
我观察到的
- ListView将首先调用
LoadMoreItemsAsync
,计数为1。我假设这是为了确定单个项目的大小,以便计算出下一次调用需要请求的项目数
- 如果ListView运行良好,则对
LoadMoreItemsAsync
的第二次调用应在第一次调用后立即发生,但项数正确(计数>1),然后除非向下滚动,否则不会再调用LoadMoreItemsAsync
。但是,在您的示例中,它可能会再次错误地调用计数为1的LoadMoreItemsAsync
- 在最坏的情况下(实际上在您的示例中经常发生),ListView将继续调用
LoadMoreItemsAsync
,依次重复计数1,直到HasMoreItems
变为false,在这种情况下,它一次加载一个所有项目。发生这种情况时,ListView加载项目时会出现明显的UI延迟。不过,UI线程没有被阻止。ListView只是通过对LoadMoreItemsAsync
的顺序调用来占用UI线程
- 不过,ListView并不总是会耗尽所有项。有时它会加载100、200或500个项目。在每种情况下,模式都是:多次调用
LoadMoreItemsAsync(1)
,然后调用LoadMoreItemsAsync(>1)
,如果之前的调用没有加载所有项目
- 这似乎只发生在页面加载时
- 这个问题在Windows Phone 8.1和Windows 8.1上一直存在
问题的原因是什么
- 在将项目添加到列表之前,
LoadMoreItemsAsync
方法中的问题似乎是等待时间很短的任务(将项目添加到列表后等待任务是可以的)
- 如果删除
LoadMoreItemsAsync
中的所有wait
s,从而强制同步执行,则不会发生此问题。具体地说,如果删除dispatcher.RunAsync
包装器和wait source.GetPagedItems
(只需模拟这些项),则ListView将运行良好
- 删除所有的
wait
s后,即使添加的都是看似无害的wait任务,问题也会再次出现。运行(()=>{})
。真奇怪李>
如何解决问题
如果在LoadMoreItemsAsync
调用中花费的大部分时间都在等待下一页项目的HTTP请求,正如我预期的那样,那么问题就不会发生。因此,我们可以通过等待任务来延长在方法中花费的时间。延迟(10)
,如下所示:
wait Task.WhenAll(Task.Delay(10),dispatcher.RunAsync(corespatcherpriority.Normal,()=>
{
foreach(结果中的I项)
本条增加(项目);
}).AsTask());
我只是为您的示例提供了一个(黑客)解决方法,但没有解释原因如果有人知道发生这种情况的原因,请告诉我。这不是唯一可能导致此问题的原因。如果您的ListView位于ScrollViewer中,它将继续加载所有项目,并且不会正确虚拟化,从而对性能产生负面影响。解决方案是为ListView指定一个特定的高度。我复制了您的代码,无法复制错误。这对我来说是正确的。如果你想查看我在一个新的空白应用程序项目中使用的代码。你是否尝试添加300/400项,并检查我提到的行为?不管我有多少项,我都尝试了1000项,但当我向下滚动到列表末尾时,它仍然只加载更多项。代码中有一些多余的东西,但我认为这不会影响增量加载行为?你能提供一份完整的复印件吗?如果ListView的高度是无限的,那么它需要立即加载所有内容。有趣的是,我得到的解决方法与您的完全相同,即延迟。虽然不是完美的解决方案,但它是一个功能解决方案,从我的角度来看,您清楚地确定了