C# uwp增量加载保持无限调用GetPagedItemAsync

C# uwp增量加载保持无限调用GetPagedItemAsync,c#,xaml,listview,uwp,windows-community-toolkit,C#,Xaml,Listview,Uwp,Windows Community Toolkit,我有一个uwp应用程序,我正在使用Windows社区工具包来实现列表中的增量加载功能 复制发行的回购样本: 如何重现问题 <Grid x:DefaultBindMode="OneWay"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowD

我有一个uwp应用程序,我正在使用Windows社区工具包来实现列表中的增量加载功能

复制发行的回购样本:

如何重现问题

 <Grid x:DefaultBindMode="OneWay">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition />
    </Grid.RowDefinitions>

    <GridView
        x:Name="TopListView"
        ItemsSource="{x:Bind ViewModel.AppVM.EvidencesConfigurationList}"
        SelectedItem="{x:Bind ViewModel.AppVM.TopListViewItem, Mode=TwoWay}">
        <GridView.ItemTemplate>
            <DataTemplate x:DataType="data:ModulesConfigurationDto">
                <Grid >
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <TextBlock Text="{x:Bind QueryCount}" />
                    <TextBlock
                        Grid.Row="1"
                        Text="{x:Bind DisplayName}" />
                </Grid>
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>

    <ListView
        x:Name="AllEvidencesListView"
        Grid.Row="1"
        Header="{x:Bind ViewModel.AppVM.TopListViewItem.DisplayName}"
        ItemsSource="{x:Bind ViewModel.AppVM.TopListViewItem.Evidences}" />
</Grid>
  • 关闭提供的项目并在visual studio中打开它
  • 运行应用程序
  • 在左上角,您将看到两个gridview项目,单击其中的第一个,称为“证据”
  • 请注意,在下面的Listview中,证据项将继续添加到循环中,因为GetPagedItemAsync被重复调用,而没有任何滚动
  • 解释

    因此,在我的主页上,我在顶部有一个GridView,它绑定到一组模块配置DTO,在选择它的项目时,我想在GridView下面显示证据列表,因为每个项目都有自己的证据列表,所选项目应该在下面显示它的列表

    因此,我使用社区工具包进行增量加载,在选择一个项目时,它应该加载第一组项目,然后仅在滚动时调用另一组项目,以此类推

    现在我遇到的问题是,即使我在这个方法中返回一个包含单个项的列表,它仍会重复调用GetPagedItemsAsync,如果我在这个方法中返回一个空列表,那么它就没有这个问题,UI也会保持空状态

    密码 主页

     <Grid x:DefaultBindMode="OneWay">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
    
        <GridView
            x:Name="TopListView"
            ItemsSource="{x:Bind ViewModel.AppVM.EvidencesConfigurationList}"
            SelectedItem="{x:Bind ViewModel.AppVM.TopListViewItem, Mode=TwoWay}">
            <GridView.ItemTemplate>
                <DataTemplate x:DataType="data:ModulesConfigurationDto">
                    <Grid >
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <TextBlock Text="{x:Bind QueryCount}" />
                        <TextBlock
                            Grid.Row="1"
                            Text="{x:Bind DisplayName}" />
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    
        <ListView
            x:Name="AllEvidencesListView"
            Grid.Row="1"
            Header="{x:Bind ViewModel.AppVM.TopListViewItem.DisplayName}"
            ItemsSource="{x:Bind ViewModel.AppVM.TopListViewItem.Evidences}" />
    </Grid>
    
    AppViewModel

     public class AppViewModel : Observable
    {
        private ModulesConfigurationDto _topListViewItem;
        private ObservableCollection<ModulesConfigurationDto> _evidencesConfigurationList;
    
        public ModulesConfigurationDto TopListViewItem { get => _topListViewItem; set => Set(ref _topListViewItem, value); }
    
        public ObservableCollection<ModulesConfigurationDto> EvidencesConfigurationList { get { if (_evidencesConfigurationList is null) { _evidencesConfigurationList = new ObservableCollection<ModulesConfigurationDto>(); } return _evidencesConfigurationList; } set => Set(ref _evidencesConfigurationList, value); }
    
    
        public async Task LoadDataAsync()
        {
            await Task.Delay(200);//fake web api call
    
            List<ModulesConfigurationDto> result = new List<ModulesConfigurationDto> {
                new ModulesConfigurationDto { DisplayName = "RecentEvidences", ModuleCode = "TM", QueryCount = 27 },
                new ModulesConfigurationDto { DisplayName = "Evidences", ModuleCode = "TM", QueryCount = 27 }};
    
            if (result?.Count > 0)
            {
                EvidencesConfigurationList.Clear();
    
                IEnumerable<ModulesConfigurationDto> evidencesConfigurationList = result.Where(a => a.ModuleCode == "TM");
    
                foreach (ModulesConfigurationDto item in evidencesConfigurationList)
                {
                    item.Evidences = new IncrementalLoadingCollection<EvidencesSource, EvidenceDTO>();
                }
                EvidencesConfigurationList.AddRange(evidencesConfigurationList);
            }
        }
    }
    
    公共类AppViewModel:可观察
    {
    私有模块配置到_topListViewItem;
    私有可观察收集(U证据配置列表);
    TopListViewItem的公共模块配置{get=>\u TopListViewItem;set=>set(ref _TopListViewItem,value);}
    公共ObserviceCollection证据配置列表{get{if(_-EvidenceConfigurationList为null){u-EvidenceConfigurationList=new-ObserviceCollection();}返回_-EvidenceConfigurationList;}集=>set(ref _-EvidenceConfigurationList,value);}
    公共异步任务LoadDataAsync()
    {
    等待任务。延迟(200);//伪web api调用
    列表结果=新列表{
    新模块配置为{DisplayName=“RecentEvidences”,ModuleCode=“TM”,QueryCount=27},
    新的模块配置为{DisplayName=“证据”,ModuleCode=“TM”,QueryCount=27};
    如果(结果?.Count>0)
    {
    证据配置列表。清除();
    IEnumerable证据配置列表=结果,其中(a=>a.ModuleCode==“TM”);
    foreach(模块配置到证据配置列表中的项)
    {
    item.Evidences=new IncrementalLoadingCollection();
    }
    证据配置列表.AddRange(证据配置列表);
    }
    }
    }
    
    证据来源(用于增量加载)

    公共类证据源:IIncrementalSource
    {
    公共AppViewModel AppVM=>App.AppVM;
    公共异步任务GetPagedItemsAsync(int-pageIndex、int-pageSize、CancellationToken CancellationToken=default)
    {
    var证据=新列表();
    等待任务。延迟(500);//伪web api调用
    证据。添加(新的证据到());
    返还证据;
    }
    }
    
    更新1

    根据“Faywang-MSFT”的回答,我尝试了以下方法

    private readonly List<EvidenceDTO> evidences;
    
        public EvidencesSource()
        {
            evidences = new List<EvidenceDTO>();
            for (int i = 0; i < 40; i++)
            {
                evidences.Add(new EvidenceDTO());
            }
        }
    
        public async Task<IEnumerable<EvidenceDTO>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default)
        {            
            await Task.Delay(500); // fake web api call
            var result = (from dto in evidences
                          select dto).Skip(pageIndex * pageSize).Take(pageSize);
            return result;
        }
    
    私有只读列表证据;
    公共证据源()
    {
    证据=新列表();
    对于(int i=0;i<40;i++)
    {
    证据。添加(新的证据到());
    }
    }
    公共异步任务GetPagedItemsAsync(int-pageIndex、int-pageSize、CancellationToken CancellationToken=default)
    {            
    等待任务。延迟(500);//伪web api调用
    var结果=(从证据中的dto)
    选择dto).Skip(页面索引*页面大小).Take(页面大小);
    返回结果;
    }
    
    但它并没有解决这个问题,因为默认的pageSize值是20,所以GetPagesItemsAsync第一次运行以获取前20个项目,然后再次调用自身以获取剩余的20个项目,然后第三次再次调用自身,但这一次因为结果返回空,所以它不会再次调用,我查看UI,所有40个项目都已加载,因此,该方法一直在调用自己,直到返回一个空结果

    更新2

    现在,我尝试了Windows社区工具包示例应用程序中的确切示例,并在页面的下半部分放置了一个PeopleListView,它也显示了相同的错误。我也用这些代码更新了示例应用程序,你可以在页面的下半部分看到,所有40个人都被加载了两次,执行这个方法时我没有滚动它,当我滚动它时,它已经有了所有的项目,所以这次它返回0个项目

    更新3

    如果我将证据或人员的数量增加到200,那么它第一次运行了两次GetPagedItemAsync,因此得到了40个项目,然后它停止执行,除非我滚动,所以它工作正常。唯一关心的是,为什么它第一次得到两个页面,而它应该只得到一个页面?这对我来说是个问题,因为我从webapi获取数据,所以不能进行太多无用的调用

    更新4

    如果我将页面大小更改为2

    var collection = new IncrementalLoadingCollection<PeopleSource, Person>(itemsPerPage:2);
    
    var collection=newincrementalloadingcollection(itemsPerPage:2);
    
    然后,该方法似乎会一直调用自己,直到加载了大约40项,这大约是UI可以显示的两倍大小,即:大约18,但有趣的是,如果我将pageSize更改为40

     var collection = new IncrementalLoadingCollection<PeopleSource, Person>(itemsPerPage:40); 
    
    var collection=newincrementalloadingcollection(itemsPerPage:40);
    
    那么
     var collection = new IncrementalLoadingCollection<PeopleSource, Person>(itemsPerPage:40); 
    
    public async Task<IEnumerable<EvidenceDTO>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken))
    {
        var evidences = new List<EvidenceDTO>();
        evidences.Add(new EvidenceDTO());
        var result = (from dto in evidences
                              select dto).Skip(pageIndex * pageSize).Take(pageSize);
        await Task.Delay(500);
    
        return result;
    }
    
    using System;
    using System.Collections.ObjectModel;
    using System.Runtime.InteropServices.WindowsRuntime;
    using System.Threading;
    using System.Threading.Tasks;
    using Windows.Foundation;
    using Windows.UI.Xaml.Data;
    
    namespace UwpHelpers.Controls.Common
    {
        public class IncrementalLoadingCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
        {
            private readonly Func<CancellationToken, uint, Task<ObservableCollection<T>>> func;
            private uint maxItems;
            private bool isInfinite;
            private CancellationToken token;
    
            /// <summary>
            /// Infinite, incremental scrolling list supported by ListView and GridView
            /// </summary>
            /// <param name="func"></param>
            public IncrementalLoadingCollection(Func<CancellationToken, uint, Task<ObservableCollection<T>>> func)
                : this(func, 0)
            {
            }
    
            /// <summary>
            /// Incremental scrolling list supported by ListView and GridView
            /// </summary>
            /// <param name="func">Task that retrieves the items</param>
            /// <param name="maxItems">Set to the maximum number of items to expect</param>
            public IncrementalLoadingCollection(Func<CancellationToken, uint, Task<ObservableCollection<T>>> func, uint maxItems)
            {
                this.func = func;
                if (maxItems == 0) //Infinite
                {
                    isInfinite = true;
                }
                else
                {
                    this.maxItems = maxItems;
                    isInfinite = false;
                }
            }
    
            public bool HasMoreItems
            {
                get
                {
                    if (this.token.IsCancellationRequested)
                        return false;
    
                    if (isInfinite)
                        return true;
    
                    return this.Count < maxItems;
                }
            }
    
            public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
            {
                return AsyncInfo.Run(tkn => InternalLoadMoreItemsAsync(tkn, count));
            }
    
            private async Task<LoadMoreItemsResult> InternalLoadMoreItemsAsync(CancellationToken passedToken, uint count)
            {
                ObservableCollection<T> tempList = null;
                this.token = passedToken;
                var baseIndex = this.Count;
                uint numberOfItemsToGet = 0;
    
                if (!isInfinite)
                {
                    if (baseIndex + count < maxItems)
                    {
                        numberOfItemsToGet = count;
                    }
                    else
                    {
                        numberOfItemsToGet = maxItems - (uint) (baseIndex);
                    }
                }
                else
                {
                    numberOfItemsToGet = count;
                }
    
                tempList = await func(passedToken, numberOfItemsToGet);
    
                if (tempList.Count == 0) //no more items, stop the incremental loading 
                {
                    maxItems = (uint) this.Count;
                    isInfinite = false;
                }
                else
                {
                    foreach (var item in tempList)
                    {
                        this.Add(item);
                    }
                }
    
                return new LoadMoreItemsResult { Count = (uint) tempList.Count };
            }
        }
    }