RadDataPager, RadGridView and IPagedCollection - Approach to implementing IPagedCollection

11 posts, 1 answers
  1. Tim
    Tim avatar
    169 posts
    Member since:
    Apr 2008

    Posted 17 Sep 2010 Link to this post

    First of all, I have read the blog posts (http://blogs.telerik.com/rossenhristov/posts/10-03-10/q1_2010_new_feature_paging_with_radgridview_for_silverlight_and_wpf.aspx) and I am not using RIA services.

    Second, I have a working paged implementation using QueryableCollectionView to wrap my ObservableCollection.  The problem is that I know how big the total dataset is, but I'm not allowed to set TotalItemCount.  I want to return 500 objects at a time from a WCF service, so as the user pages, I would like to fill in 500 new objects - BUT I want the pager to say I am on "Page 1 of 200" or "Items 1-100 of 1000" and when I get to the end of the 500 objects, go get the next 500 (I.e., the IPagedCollectionView interface.)  If I update the collection in the Paging event handlers, then the "of x" part goes up, as expected, but unless you page "off the end" it gives the illusion there are only 500 objects.

    From searching, the solution seems to be that I must implement the IPagedCollectionView interface myself.

    Does anyone have a recommended approach or complete example?  Should I extend ObservableCollection and implement IPagedCollectionView there, for example?

    I studied the section from the post above

    IV. Paging collections implementing IPagedCollectionView


    And it would seem that if both my RadDataPager.Source and RadGridView.ItemsSource are bound to a QueryableCollectionView, it should work, but it doesn't - I need to point the RadDataPager's source at the ElementName=RadGridView, Path=Items for it to work.

    In short, think of it as a lazy-load, x objects at a time out of a known total of y objects, where the IPagedCollectionView members notice the holes and fetch the data on-demand.

    Thanks,
    Tim
  2. Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 22 Sep 2010 Link to this post

    Hello Tim,

    I have good news for you. Due to similar requests we have developed a new feature of RadDataPager called Unbound Mode. Basically, you do not set any Source for the pager, i.e.e you do not need to implement the IPagedCollectionView interface. Instead you can set the ItemCount property manually. Once you set the ItemCount and PageSize the pager does the math and know the number of pages.

    You can think of the Unbound Mode as of using the pager solely for its UI. Nothing more. When the user goes to a page, the PageIndexChanged event will be raised. By attaching to this event, in the event handler you will get the page index that user has requested. Knowing this page index, you can call any kind of service and return the appropriate data to the client.

    This approach is way more easier than implementing the whole IPagedCollectionView interface.

    You will need to upgrade to the Service Pack version coming out tomorrow in order to have this feature.

    I hope this helps.

    Best wishes,
    Ross
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  3. DevCraft banner
  4. Tim
    Tim avatar
    169 posts
    Member since:
    Apr 2008

    Posted 22 Sep 2010 Link to this post

    That's good news, thanks.  I can't upgrade just yet unfortunately.  I modified your EndlessPagedCollectionView example to suit my needs for now.  I've included it for reference.  I called it BoundedPagedCollectionView since it knows the upper bound in advance, but pages in chunks on-demand.  The WCF service call is simulated with a DispatchTimer and completed asynchronously when the timer fires.

    Tim

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Windows.Threading;
    using Telerik.Windows.Controls;
    using System.ComponentModel;
    using System.Collections.Specialized;
     
    namespace SilverlightApplication1
    {
        /// <summary>
        ///
        /// </summary>
        public class BoundedPagedCollectionView : IEnumerable
            , IPagedCollectionView
            , INotifyPropertyChanged
            , INotifyCollectionChanged
        {
            public event EventHandler<EventArgs> PageChanged;
            public event EventHandler<PageChangingEventArgs> PageChanging;
            public event PropertyChangedEventHandler PropertyChanged;
            public event NotifyCollectionChangedEventHandler CollectionChanged;
      
            private int pageIndex = -1;
            private bool isPageChanging;
            private int pageSize;
            private int MaxOrders;
            private RadGridView GridView;
      
            private readonly List<Order> Orders = new List<Order>();
     
            public BoundedPagedCollectionView(int maxOrders, RadGridView gridView)
            {
                MaxOrders = maxOrders;
                GridView = gridView;
                PageChanging += BoundedPagedCollection_PageChanging;
            }
     
            bool IPagedCollectionView.CanChangePage
            {
                get { return true; }
            }
      
            bool IPagedCollectionView.IsPageChanging
            {
                get { return this.isPageChanging; }
            }
      
            private void SetIsPageChanging(bool value)
            {
                if (this.isPageChanging != value)
                {
                    this.isPageChanging = value;
                    this.OnPropertyChanged("IsPageChanging");
                }
            }
      
            int IPagedCollectionView.ItemCount
            {
                get
                {
                    return MaxOrders;
                }
            }
      
            bool IPagedCollectionView.MoveToFirstPage()
            {
                return ((IPagedCollectionView)this).MoveToPage(0);
            }
      
            bool IPagedCollectionView.MoveToLastPage()
            {
                return ((IPagedCollectionView)this).MoveToPage(((IPagedCollectionView)this).TotalItemCount / ((IPagedCollectionView)this).PageSize);
            }
      
            bool IPagedCollectionView.MoveToNextPage()
            {
                return ((IPagedCollectionView)this).MoveToPage(((IPagedCollectionView)this).PageIndex + 1);
            }
     
            private int __pageIndex;
            bool IPagedCollectionView.MoveToPage(int pageIndex)
            {
                if (this.OnPageChanging(pageIndex) && pageIndex != -1)
                {
                    return false;
                }
     
                __pageIndex = pageIndex;
                if (Orders.Count < (__pageIndex + 1) * ((IPagedCollectionView)this).PageSize)
                {
     
                    //
                    // This simulates a WCF callback in that the update comes in on a different thread
                    // at some later point - 500 milliseconds.
                    //
     
                    DispatcherTimer pageTimer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 0, 0, 500)};
                    pageTimer.Tick += CompletionCallback;
                    pageTimer.Start();
                    GridView.IsBusy = true;
                }
                else
                {
                    this.SetIsPageChanging(true);
                    this.pageIndex = __pageIndex;
                    this.SetIsPageChanging(false);
     
                    this.OnPropertyChanged("PageIndex");
                    this.OnPropertyChanged("ItemCount");
                    this.OnPageChanged();
     
                    // Call this to inform all listeners that data has changed.
                    this.OnCollectionChanged();
                }
                return true;
            }
      
            private void CompletionCallback(object sender, EventArgs e)
            {
                (sender as DispatcherTimer).Stop();
     
                if (Orders.Count < (__pageIndex + 1) * ((IPagedCollectionView)this).PageSize)
                {
                    for (int i = Orders.Count; i < (__pageIndex + 1) * ((IPagedCollectionView)this).PageSize && i < MaxOrders; i++)
                    {
                        Orders.Add(new Order(i));
                    }
                }
     
                GridView.IsBusy = false;
                this.SetIsPageChanging(true);
                this.pageIndex = __pageIndex;
                this.SetIsPageChanging(false);
     
                this.OnPropertyChanged("PageIndex");
                this.OnPropertyChanged("ItemCount");
                this.OnPageChanged();
     
                // Call this to inform all listeners that data has changed.
                this.OnCollectionChanged();
            }
             
            bool IPagedCollectionView.MoveToPreviousPage()
            {
                return ((IPagedCollectionView)this).MoveToPage(((IPagedCollectionView)this).PageIndex - 1);
            }
      
            int IPagedCollectionView.PageIndex
            {
                get { return this.pageIndex; }
            }
      
            int IPagedCollectionView.PageSize
            {
                get
                {
                    return this.pageSize;
                }
                set
                {
                    if (value < 1)
                    {
                        throw new ArgumentOutOfRangeException("value", "The PageSize of an endless collection should be positive.");
                    }
      
                    if (this.pageSize != value)
                    {
                        this.pageSize = value;
                        this.OnPropertyChanged("PageSize");
                        this.OnPropertyChanged("ItemCount");
      
                        // Behave like good collections do.
                        ((IPagedCollectionView)this).MoveToFirstPage();
                    }
                }
            }
      
            int IPagedCollectionView.TotalItemCount
            {
                get
                {
                    return MaxOrders;
                }
            }
      
            public System.Collections.IEnumerator GetEnumerator()
            {
                // No orders, return empty Enumerator
                if (Orders.Count == 0)
                {
                    return Orders.GetRange(0, 0).GetEnumerator();
                }
     
                // Not a full last page?  Return partial Enumerator
                if (((IPagedCollectionView)this).PageIndex * ((IPagedCollectionView)this).PageSize + pageSize > Orders.Count)
                {
                    return
                        Orders.GetRange(((IPagedCollectionView) this).PageIndex*((IPagedCollectionView) this).PageSize,
                                        Orders.Count -
                                        ((IPagedCollectionView) this).PageIndex*((IPagedCollectionView) this).PageSize).
                            GetEnumerator();
                }
     
                // Return a full page
                return
                    Orders.GetRange(((IPagedCollectionView) this).PageIndex*((IPagedCollectionView) this).PageSize,
                                    ((IPagedCollectionView) this).PageSize).GetEnumerator();
            }
      
            private void OnPropertyChanged(string propertyName)
            {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, e);
                }
            }
      
            private bool OnPageChanging(int newPageIndex)
            {
                PageChangingEventArgs e = new PageChangingEventArgs(newPageIndex);
                if (this.PageChanging != null)
                {
                    this.PageChanging(this, e);
                }
      
                return e.Cancel;
            }
      
            private void OnPageChanged()
            {
                EventArgs e = EventArgs.Empty;
                if (this.PageChanged != null)
                {
                    this.PageChanged(this, e);
                }
            }
      
            private void OnCollectionChanged()
            {
                NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                if (this.CollectionChanged != null)
                {
                    this.CollectionChanged(this, e);
                }
            }
     
            private void BoundedPagedCollection_PageChanging(object sender, PageChangingEventArgs e)
            {
                if (e.NewPageIndex > MaxOrders / pageSize)
                {
                    e.Cancel = true;
                }
            }
      
            /// <summary>
            /// Sample Order
            /// </summary>
            public class Order
            {
                private int id;
                private DateTime date;
                private double amount;
                private bool confirmed;
                private string code;
                private string country;
      
                /// <summary>
                /// Gets the ID.
                /// </summary>
                /// <value>The ID.</value>
                public int ID
                {
                    get { return this.id; }
                }
      
                /// <summary>
                /// Gets or sets the date.
                /// </summary>
                /// <value>The date.</value>
                public DateTime Date
                {
                    get
                    {
                        return this.date;
                    }
                    private set
                    {
                        this.date = value;
                    }
                }
      
                /// <summary>
                /// Gets or sets the amount.
                /// </summary>
                /// <value>The amount.</value>
                public double Amount
                {
                    get
                    {
                        return this.amount;
                    }
                    private set
                    {
                        this.amount = value;
                    }
                }
      
                /// <summary>
                /// Gets or sets a value indicating whether this <see cref="Order"/> is confirmed.
                /// </summary>
                /// <value><c>true</c> if confirmed; otherwise, <c>false</c>.</value>
                public bool Confirmed
                {
                    get
                    {
                        return this.confirmed;
                    }
                    private set
                    {
                        this.confirmed = value;
                    }
                }
      
                /// <summary>
                /// Gets or sets the code.
                /// </summary>
                /// <value>The code.</value>
                public string Code
                {
                    get
                    {
                        return this.code;
                    }
                    private set
                    {
                        this.code = value;
                    }
                }
      
                /// <summary>
                /// Gets or sets the country.
                /// </summary>
                /// <value>The country.</value>
                public string Country
                {
                    get
                    {
                        return this.country;
                    }
                    private set
                    {
                        this.country = value;
                    }
                }
      
                /// <summary>
                /// Initializes a new instance of the <see cref="Order"/> class.
                /// </summary>
                /// <param name="id">The id.</param>
                public Order(int id)
                {
                    this.id = id;
                    int random = new Random(id).Next(-100, 100);
                    this.Date = DateTime.Now.AddDays(random);
                    this.Amount = Math.Abs(random);
                    this.Confirmed = random % 2 == 0;
      
                    int someRandom = Math.Abs(this.id.GetHashCode()) / 10
                        + Math.Abs(this.Date.GetHashCode()) / 10
                        + Math.Abs(this.Amount.GetHashCode()) / 10
                        + Math.Abs(this.Confirmed.GetHashCode()) / 10;
                    this.Code = someRandom.ToString() + someRandom.ToString();
      
                    switch (random % 5)
                    {
                        case 0:
                            this.Country = "USA";
                            break;
                        case 1:
                            this.Country = "UK";
                            break;
                        case 2:
                            this.Country = "Germany";
                            break;
                        case 3:
                            this.Country = "Spain";
                            break;
                        case 4:
                            this.Country = "France";
                            break;
                        default:
                            this.Country = "Other";
                            break;
                    }
                }
            }
        }
    }
  5. Veselin Vasilev
    Admin
    Veselin Vasilev avatar
    2992 posts

    Posted 22 Sep 2010 Link to this post

    Hello Tim,

    This is fantastic!

    Thank you for sharing the code with the community.

    You can contribute even more if you create a sample runnig project which demonstrates your accomplishment and upload it to our code-libraries

    As a small benefit  you can get up to 10 000 Telerik Points.

    Thank you in advance!

    All the best,
    Veselin Vasilev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  6. Tim
    Tim avatar
    169 posts
    Member since:
    Apr 2008

    Posted 22 Sep 2010 Link to this post

    Done.  I presume it must be approved before it appears because it does not seem to be there at the moment.

    Tim
  7. Answer
    Veselin Vasilev
    Admin
    Veselin Vasilev avatar
    2992 posts

    Posted 24 Sep 2010 Link to this post

    Hi Tim,

    I have modified it a bit (adding Trial Telerik assemblies and updating the references) and it is already live at
    http://www.telerik.com/community/code-library/silverlight/gridview/a-paged-on-demand-ipagedcollectionview-example-where-chunks-of-a-known-total-are-loaded-as-requested.aspx

    I have updated your Telerik points.

    Thank you once again for your efforts.

    Kind regards,
    Veselin Vasilev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  8. Tim
    Tim avatar
    169 posts
    Member since:
    Apr 2008

    Posted 24 Sep 2010 Link to this post

    Cool, I am glad to help.  You have a fine product suite and an excellent relationship with the community.

    Tim
  9. Tim
    Tim avatar
    169 posts
    Member since:
    Apr 2008

    Posted 28 Sep 2010 Link to this post

    Suppose I wanted to augment the RadDataPager (see attached image) so that included the # of items loaded out of a known total, a page size control, and a button which would export to CSV.  Is there a sample out there on modifying or extending the control template?  I'd like to include the extra bits INSIDE the RadDataPager itself.

    Thanks,
    Tim
  10. Vlad
    Admin
    Vlad avatar
    11100 posts

    Posted 29 Sep 2010 Link to this post

    Hello,

     Yes, you can restyle the pager completely with Blend since the control is completely lookless. You can check this blog post for more info. 

    Kind regards,
    Vlad
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  11. Joao Paulo Grassi
    Joao Paulo Grassi avatar
    98 posts
    Member since:
    Feb 2011

    Posted 14 Dec 2011 Link to this post

    Hello!

    Sorry to bring up this old topic again, but i have the same issue.

    I implement the indicated approach supplied by Ross using the DataPager in it's unbound mode. Everything is working as i expect but now i lost the Filter abilities. Example: If I'm in the Page 1, only people with "A" name appears, and if the user try to use the RadGridView Filter and choose to show people with Name with "B" nothing will show of course because the GridView does not have the collection yet.

    Is there a way to workaround this? I mean implement a way when the user use the filter i call me service and return a new collection to the GridView?

    Thanks a lot!
  12. Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 14 Dec 2011 Link to this post

    Hi Joao Paulo Grassi,

    You can bind RadGridView and RadDataPager to one of our data source controls. In this way you will have both filtering and paging on the server.

    We have:

    - RadDomainDataSource for WCF RIA Services.
    - RadDataServiceDataSource for WCF Data Services.

    Please, explore their online demos. They show the integration between the three controls.

    Kind regards,
    Ross
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

Back to Top
DevCraft banner