c# - 在滚动结束时创建一个带有 LoadMoreItemsAsync 的 ListView

标签 c# xaml listview windows-store-apps windows-phone-8.1

我的 Windows Phone 8.1 应用程序中有一个 ListView,我可以得到大约 1000 个或更多的结果,所以我需要在每次滚动到底部或其他一些时实现加载更多功能触发向列表中添加更多项目的逻辑和自然方式。
我发现 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<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>();
    }
}

这是 PersonModelSource.cs

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;
            });
    }
}

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

在我的 DatabaseNotificationViewModel.cs

IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type);


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

最佳答案

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

我观察到的

  • ListView 将首先调用 LoadMoreItemsAsync,计数为 1。我假设这是为了确定单个项目的大小,以便它可以计算出下一次调用请求的项目数.
  • 如果 ListView 表现良好,第二次调用 LoadMoreItemsAsync 应该在第一次调用后立即发生,但项目数正确(计数 > 1),然后不再调用 LoadMoreItemsAsync 将发生,除非您向下滚动。但是,在您的示例中,它可能会错误地调用 LoadMoreItemsAsync 并且计数为 1 again
  • 在最坏的情况下,实际上在您的示例中经常发生的情况是,ListView 将继续调用 LoadMoreItemsAsync 并按顺序一遍又一遍地调用,直到 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 后,即使您添加的只是看似无害的 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)解决方法,但没有解释原因。 如果有人知道为什么会这样,请告诉我。

关于c# - 在滚动结束时创建一个带有 LoadMoreItemsAsync 的 ListView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26719617/

相关文章:

c# - 未找到方法 : 'System.Threading.Monitor.Enter' with Nhibernate, Mono 和 SQLite

c# - 如何通过 FastMember 自动将枚举转换为 int?

c# - WPF MergedDictionaries - 值不能为 Null - 参数名称 : item

c# - 使用自定义项目模板在 ComboBox 中显示所选项目

c# - 在 xaml 中声明数组的数组

Android - 在数据库中获取_id onItemClick

javascript - WinJS 渲染 listview 时捕获事件

c# - 创建 native C++ OpenGL 3D 编辑器并将其用作 C# 中的 WinForms 或 WPF 控件

android - 为什么我必须有一个 ListView ?

c# - 为什么 TcpListener 会泄漏 ESTABLISHED 连接?