在滚动结束时使用LoadMoreItemsAsync创建ListView

我在Windows Phone 8.1应用程序中有一个ListView ,我可以有1000个或更多的结果,所以每次滚动到底时我需要实现一个Load Morefunction,或者一些其他逻辑和自然方式触发添加更多列表中的项目。
我发现ListView支持ISupportIncrementalLoading ,并发现了这个实现: https : //marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/这是我找到的更好的解决方案,因为它没有指定类型,即它是通用的。

我对此解决方案的问题是,当ListView被加载时, LoadMoreItemsAsync运行所需的所有时间,直到获得所有结果,这意味着用户不会触发加载更多。 我不确定是什么使得LoadMoreItemsAsync触发器,但是有些东西是不对的,因为它假设当我打开页面并在现场加载所有项目时,没有我做任何事情或任何滚动。 这是实施:
IncrementalLoadingCollection.cs

 public interface IIncrementalSource { Task<IEnumerable> GetPagedItems(int pageIndex, int pageSize); void SetType(int type); } public class IncrementalLoadingCollection : ObservableCollection, ISupportIncrementalLoading where T : IIncrementalSource, 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 LoadMoreItemsAsync(uint count) { var dispatcher = Window.Current.Dispatcher; return Task.Run( 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(); } } 

这是PersonModelSource.cs

 public class DatabaseNotificationModelSource : IIncrementalSource { private ObservableCollection notifications; private int _type = ""; public DatabaseNotificationModelSource() { // } public void SetType(int type) { _type = type; } public async Task<IEnumerable> GetPagedItems(int pageIndex, int pageSize) { if(notifications == null) { notifications = new ObservableCollection(); notifications = await DatabaseService.GetNotifications(_type); } return await Task.Run<IEnumerable>(() => { var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize); return result; }); } } 

我改变了一下,因为对我的数据库的调用是异步的,这是我发现确保在填充集合之前可以等待查询的唯一方法。

在我的DatabaseNotificationViewModel.cs

 IncrementalNotificationsList = new IncrementalLoadingCollection(type); 

一切都很好,除了不那么正常的“加载更多”。 我的代码有什么问题?

我在这里创建了一个非常简单的问题示例,并在MSDN论坛上提出了这个问题。 老实说,我不知道为什么会发生这种奇怪的行为。

我观察到了什么

  • ListView将首先调用LoadMoreItemsAsync ,计数为1.我假设这是为了确定单个项目的大小,以便它可以计算出请求下次调用的项目数。
  • 如果ListView表现良好,第二次调用LoadMoreItemsAsync应该在第一次调用后立即发生,但具有正确的项目数(count> 1),然后除非向下滚动,否则不会再调用LoadMoreItemsAsync 。 但是,在您的示例中,它可能会再次错误地调用LoadMoreItemsAsync并将计数值设置为1。
  • 在最坏的情况下,实际上在您的示例中经常发生的是,ListView将继续按LoadMoreItemsAsync调用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删除所有await则不会发生此问题,从而强制它同步执行。 具体来说,如果删除dispatcher.RunAsync包装并await source.GetPagedItems (只是模拟项目),那么ListView将表现得很好。
  • 删除所有await s后,即使您添加的所有内容都看似无害, await Task.Run(() => {}) ,问题也会重新出现。 多奇怪啊!

如何解决问题

如果在LoadMoreItemsAsync调用中花费的大部分时间都在等待下一页项目的HTTP请求,正如我期望的大多数应用程序那样,那么问题就不会发生。 因此,我们可以通过等待Task.Delay(10)来延长在方法中花费的时间,就像这样:

 await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { foreach (I item in result) this.Add(item); }).AsTask()); 

我只为你的例子提供了(hacky)解决方法,但没有解释原因。 如果有人知道为什么会这样,请告诉我。

这不是导致此问题的唯一因素。 如果您的ListView位于ScrollViewer中,它将继续加载所有项目,并且不会正确虚拟化,从而对性能产生负面影响。 解决方案是为ListView提供特定的高度。