Virtual Paging with Silverlight/WCF Services

August 18, 2011

When you want to page a collection in Silverlight all the online documentation points you to the ‘PagedCollectionView’ class, which offers a paging wrapper around an IEnumerable.

This needs the full list of IEnumerable data to function in the first place, which is fine for small datasets, but for the most part you want to page a database resultset from many thousands of rows, down to maybe 10 at a time.

Your Silverlight application probably gets its data from a WCF service, either directly or by using RIA Services link, so you want to pass your paging request from Silverlight to the service layer, where it can be transformed through your architecture to more than likely a SQL query, so that one page of data is transported at a time.

There are hints on Google to implement an IPagedCollectionView in order to achieve this. Below shows my version of an IPagedCollectionView which can then be used for ‘virtual’ paging of data in Silverlight:

usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.ComponentModel;usingSystem.Collections.Specialized;usingSystem.Collections;/// <summary>/// A class which can be used in Silverlight to enable server side (virtual) paging code to store it's view state/// </summary>publicclass PagedVirtualCollectionView : IPagedCollectionView, IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged
{/// <summary>/// Constructor takes initial page of data as its source/// </summary>/// <param name="source">A page of source data</param>public PagedVirtualCollectionView(IEnumerable source){
_sourceCollection = source;}private IEnumerable _sourceCollection;/// <summary>/// The underlying page of source data/// </summary>public IEnumerable SourceCollection
{
get {return _sourceCollection;}
set {
_sourceCollection = value;
OnPropertyChanged("SourceCollection");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));}}privateint _VirtualItemCount;/// <summary>/// Get or set this value, which is the total number of records in the database/// </summary>publicint VirtualItemCount
{
get {return _VirtualItemCount;}
set {
_VirtualItemCount = value;
OnPropertyChanged("VirtualItemCount");
OnPropertyChanged("ItemCount");
OnPropertyChanged("TotalItemCount");}}publicint VirtualPageCount
{
get {return(int)Math.Ceiling((double)VirtualItemCount /(double)PageSize);}}privateint _pageIndex =0;privateint _pageSize;#region "IEnumerable"/// <summary>/// For IEnumerable interface, passes the call to the underlying SourceCollection/// </summary>/// <returns></returns>public IEnumerator GetEnumerator(){return _sourceCollection.GetEnumerator();}#endregion#region "IPagedCollectionView"publicbool CanChangePage
{
get {return!_isPageChanging;}}privatebool _isPageChanging = false;/// <summary>/// Return true between states of page changing and page changed, otherwise false/// </summary>publicbool IsPageChanging
{
get {return _isPageChanging;}
set { _isPageChanging = value; OnPropertyChanged("IsPageChanging");}}/// <summary>/// This will be the same as the virtual item count, for use in data paging controls/// </summary>publicint ItemCount
{
get {return VirtualItemCount;}}publicbool MoveToFirstPage(){return MoveToPage(0);}publicbool MoveToLastPage(){return MoveToPage(VirtualPageCount -1);}publicbool MoveToNextPage(){return MoveToPage(PageIndex +1);}publicbool MoveToPage(int pageIndex){if(pageIndex >=0&& pageIndex <=(VirtualPageCount -1)){//fire the page changing event so the call can be made to load the next page of data
PageChangingEventArgs pcea =new PageChangingEventArgs(pageIndex);
OnPageChanging(pcea);if(!pcea.Cancel){//let outside world know we are changing pages
IsPageChanging = true;//event handlers have run, we should now be on the new page
_pageIndex = pageIndex;//let the outside world know we are no longer changing pages
IsPageChanging = false;
OnPropertyChanged("PageIndex");//raise an event to signal the completed change of page
OnPageChanged();return true;}else{//event handler cancelled the page changereturn false;}}else{//page index out of bounds, or was busy changing pagesreturn false;}}publicbool MoveToPreviousPage(){return MoveToPage(PageIndex -1);}publicevent EventHandler<EventArgs> PageChanged;protectedvoid OnPageChanged(){if(PageChanged !=null)
PageChanged(this, EventArgs.Empty);}publicevent EventHandler<PageChangingEventArgs> PageChanging;protectedvoid OnPageChanging(PageChangingEventArgs e){if(PageChanging !=null)
PageChanging(this, e);}/// <summary>/// Read-only version of page index, use the helper functions to move through the pages/// </summary>publicint PageIndex
{
get {return _pageIndex;}}/// <summary>/// Get/set the page size which will can then be used by the external paging calls/// </summary>publicint PageSize
{
get
{return _pageSize;}
set
{
_pageSize = value;}}publicint TotalItemCount
{//this will always be the same as virtual item count
get {return VirtualItemCount;}}#endregion#region "INotifyPropertyChanged"publicevent PropertyChangedEventHandler PropertyChanged;protectedvoid OnPropertyChanged(string propertyName){if(PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));}#endregion#region"INotifyCollectionChanged"publicevent NotifyCollectionChangedEventHandler CollectionChanged;protectedvoid OnCollectionChanged(NotifyCollectionChangedEventArgs e){if(CollectionChanged !=null)
CollectionChanged(this, e);}#endregion}

Basically it works by allowing you to specify a ‘VirtualItemCount’ aswel as your ‘SourceCollection’ so that you can dictate how many items there are in total (before paging has been applied).

Whether you are using MVVM or traditional DataContext, you should bind your grid and pager to the instance of the PagedVirtualCollectionView and handle the ‘PageChanging’ event. You can then replace the ‘SourceCollection’ with the current page of data and set the ‘VirtualItemCount’ to the total count of records.