LazyPagedCollectionView

2 posts, 0 answers
  1. Espen
    Espen avatar
    16 posts
    Member since:
    Sep 2011

    Posted 19 Jan 2011 Link to this post

    After a little research and trial, I figured out that the basics of RadDataPager really just loads everything from a source and page it. Given I use WCF Data Services, I want to lazy load additional pages when needed. As far as I understand, I need to implement IPagedCollectionView to get the functionality I want, as Unbound Mode doesn't cut it. I want to show how many pages are available to the user, and I want to cache already retrived items. Now, I found an example around here which I have adapted, but it still doesn't work properly and as expected. My implementation is as follows:
    public class LazyPagedCollectionWrapper<T> : IPagedCollectionView, INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable<T> where T:new()
    {
        public event EventHandler<EventArgs> PageChanged;
        public event EventHandler<PageChangingEventArgs> PageChanging;
        public event PropertyChangedEventHandler PropertyChanged;
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        private readonly IRepository<T> repository;
        private readonly Expression<Func<T, object>>[] eagerProperties;
        private readonly DataServiceCollection<T> dataServiceCollection;
        private int nextNewPageIndex;
     
        public LazyPagedCollectionWrapper(int pageSize, IRepository<T> repository, params Expression<Func<T, object>>[] eagerProperties)
        {
            PageSize = pageSize;
            this.repository = repository;
            this.eagerProperties = eagerProperties;
            this.dataServiceCollection = repository.Result as DataServiceCollection<T>;
            this.dataServiceCollection.CollectionChanged += OnDataServiceCollectionChanged;
            this.dataServiceCollection.LoadCompleted += OnDataServiceLoadCompleted;
        }
     
        public bool MoveToFirstPage()
        {
            return MoveToPage(0);
        }
     
        public bool MoveToLastPage()
        {
            var lastIndex = TotalItemCount / PageSize;
            return MoveToPage(TotalItemCount % PageSize == 0 ? lastIndex - 1 : lastIndex);
        }
     
        public bool MoveToNextPage()
        {
            return MoveToPage(PageIndex + 1);
        }
     
        public bool MoveToPreviousPage()
        {
            return MoveToPage(PageIndex - 1);
        }
     
        public bool MoveToPage(int newPageIndex)
        {
            if (OnPageChanging(newPageIndex) && newPageIndex > -1 && newPageIndex < TotalItemCount / PageSize)
                return false;
            nextNewPageIndex = newPageIndex;
            if (ItemCount < (newPageIndex + 1) * PageSize)
                repository.LoadPaged(newPageIndex, PageSize, eagerProperties);
            else
                SwitchPage(newPageIndex);
            return true;
        }
     
        public bool CanChangePage
        {
            get { return true; }
        }
     
        private bool isPageChanging;
        public bool IsPageChanging
        {
            get { return isPageChanging; }
            set
            {
                isPageChanging = value;
                OnPropertyChanged("IsPageChanging");
            }
        }
     
        public int ItemCount
        {
            get { return dataServiceCollection.Count; }
        }
     
        private int pageIndex;
        public int PageIndex
        {
            get { return pageIndex; }
            set
            {
                pageIndex = value;
                OnPropertyChanged("PageIndex");
            }
        }
     
        private int pageSize;
        public int PageSize
        {
            get { return pageSize; }
            set
            {
                pageSize = value;
                OnPropertyChanged("PageSize");
            }
        }
     
        private int totalItemCount;
        public int TotalItemCount
        {
            get { return totalItemCount; }
            set
            {
                totalItemCount = value;
                OnPropertyChanged("TotalItemCount");
            }
        }
     
        protected virtual void OnPageChanged()
        {
            if (PageChanged != null)
                PageChanged(this, EventArgs.Empty);
        }
     
        protected virtual bool OnPageChanging(int newPageIndex)
        {
            var e = new PageChangingEventArgs(newPageIndex);
            if (PageChanging != null)
                PageChanging(this, e);
            return e.Cancel;
        }
     
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
     
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
                CollectionChanged(this, e);
        }
     
        private void SwitchPage(int newPageIndex)
        {
            IsPageChanging = true;
            PageIndex = newPageIndex;
            IsPageChanging = false;
            OnPageChanged();
        }
     
        private void OnDataServiceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            OnCollectionChanged(e);
            switch(e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Reset:
                    OnPropertyChanged("ItemCount");
                    break;
            }
        }
     
        private void OnDataServiceLoadCompleted(object sender, LoadCompletedEventArgs e)
        {
            TotalItemCount = (int)e.QueryOperationResponse.TotalCount;
            SwitchPage(nextNewPageIndex);
        }
     
        public IEnumerator<T> GetEnumerator()
        {
            if (ItemCount == 0)
                return dataServiceCollection.Take(0).GetEnumerator();
            if ((PageIndex + 1) * PageSize > TotalItemCount)
                return dataServiceCollection.
                    Skip(PageIndex * PageSize).
                    Take(((PageIndex + 1) * PageSize) - TotalItemCount).
                    GetEnumerator();
            return dataServiceCollection.Skip(PageIndex * PageSize).Take(PageSize).GetEnumerator();
        }
     
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    Note that I have a RadGridView and a RadDataPager. They both correctly bound to this "collection" in Xaml. Basically like this where PagedCollection equals a LazyPagedCollectionView:
    <telerik:RadGridView ItemsSource="{Binding PagedCollection}" />
    <telerik:RadDataPager Source="{Binding PagedCollection}" />

    There's a few things that don't really work here.
    1. Max amount of pages only shows as the amount of pages you have lazily loaded. TotalItemCount are correctly updated and notified via PropertyChanged event, but it doesn't seem like RadDataPager is really listening to this change. I would assume that the mathematics behind the scene that displays this number would be tied to this property, but I may be wrong? If I'm required to know the total amount of items beforehand, it would be in my opinion a serious flaw.

    2.When next is clicked in the RadDataPager, the next page of data is correctly loaded into the wrapped collection, and CollectionChanged is called and PropertyChanged is called for "ItemCount" if appropriate, followed by PageChanged event. RadDataGrid however doesn't change the page(!). To get that to work, I have to call CollectionChanged event with NotifyCollectionChangedAction.Reset instead of just forwarding the correct change event of the wrapped collection. Doesn't seems like RadDataGrid has any concept of the PageChanged event. Binding RadGridView to the RadDataPager's PagedSource doesn't help either.

    Those are the 2 major issues I can't really wrap my head around right...it just seems illogical to me, but would be nice if someone could either point out errors or confirm any flaws in the control.
  2. Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 19 Jan 2011 Link to this post

    Hi Espen Berglund,

    RadDataPager does not load any data. It is totally unaware of what data it pages. In other words, data does not "flow" through RadDataPager. It simply issues commands to the IPagedCollectionView implementation that was set as its Source.

    Having established that, can you try to replace RadDataPager with the MS DataPager and RadGridView with the MS DataGrid. Are the results the same of different?

    I assume that most of the problems arise from Microsoft very vague definition of ItemCount vs. TotamItemCount. Here is a discussion about this issue.

    1. Max amount of pages only shows as the amount of pages you have lazily loaded. TotalItemCount are correctly updated and notified via PropertyChanged event, but it doesn't seem like RadDataPager is really listening to this change. I would assume that the mathematics behind the scene that displays this number would be tied to this property, but I may be wrong? If I'm required to know the total amount of items beforehand, it would be in my opinion a serious flaw.

    RadDataPager does listen for a change in the TotalItemCount property. But it is the ItemCount property that affects the page count calculation. The PageCount is updated when ItemCount changes. Here is from our source code:

    public int PageCount
    {
        get
        {
            if (this.PagedSource != null)
            {
                if (this.PageSize > 0)
                {
                    return Math.Max(1, (int)Math.Ceiling((double)this.ItemCount / this.PageSize));
                }
             
                return 1;
            }
     
            return 0;
        }
    }

    And here is what the MS DataPager does when we open it with Reflector:

    private void UpdatePageCount()
    {
        if (this.PagedSource.PageSize > 0)
        {
            this.PageCount = Math.Max(1, (int) Math.Ceiling(((double) this.PagedSource.ItemCount) / ((double) this.PagedSource.PageSize)));
        }
        else
        {
            this.PageCount = 1;
        }
    }

    2.When next is clicked in the RadDataPager, the next page of data is correctly loaded into the wrapped collection, and CollectionChanged is called and PropertyChanged is called for "ItemCount" if appropriate, followed by PageChanged event. RadDataGrid however doesn't change the page(!). To get that to work, I have to call CollectionChanged event with NotifyCollectionChangedAction.Reset instead of just forwarding the correct change event of the wrapped collection. Doesn't seems like RadDataGrid has any concept of the PageChanged event. Binding RadGridView to the RadDataPager's PagedSource doesn't help either.

    Of course you have to call CollectionChanged with Reset. DataGrids (both RadGridView and any other grid, as a matter of fact any ItemsControl like ListBox even) do not have the notion of paging. In fact, they even don't know that someone is paging the collection that they are bound to. What they know is that they have some collection set as their ItemsSource. They show the contents of this collection. If this collection has 10 items, the grids will show these 10 items. They don't know that these 10 items might be a single page from a data source that has 100 items. The one responsible for paging should make sure that when the page is changed, the next 10 items are loaded in the collection and the world is notified via a CollectionChanged Reset event. DataGrids respond only to collection changes in their source collection. They don't know anything about paging.

    I hope this makes sense.

    P.S. A good reference implementation of an IPagedCollectionView would be Microsoft's PagedCollectionView class. You can examine it with Reflector and create something similar. This will ensure that pagers like RadDataPager and the MS DataPager will behave correctly.

    Best wishes,
    Ross
    the Telerik team
    Let us know about your Windows Phone 7 application built with RadControls and we will help you promote it. Learn more>>
  3. DevCraft banner
Back to Top