CraigWardman.com

[ Virtual Paging with Silverlight/WCF Services ]

/Blog/Post

Virtual Paging with Silverlight/WCF Services

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:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections;
 
/// <summary>
/// A class which can be used in Silverlight to enable server side (virtual) paging code to store it's view state
/// </summary>
public class 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)); 
        }
    }
 
    private int _VirtualItemCount;
    /// <summary>
    /// Get or set this value, which is the total number of records in the database
    /// </summary>
    public int VirtualItemCount
    {
        get { return _VirtualItemCount; }
        set { 
            _VirtualItemCount = value; 
            OnPropertyChanged("VirtualItemCount"); 
            OnPropertyChanged("ItemCount"); 
            OnPropertyChanged("TotalItemCount"); 
        }
    }
 
    public int VirtualPageCount
    {
        get { return (int)Math.Ceiling((double)VirtualItemCount / (double)PageSize); }
    }
 
    private int _pageIndex = 0;
    private int _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"
    public bool CanChangePage
    {
        get { return !_isPageChanging; }
    }
 
    private bool _isPageChanging = false;
    /// <summary>
    /// Return true between states of page changing and page changed, otherwise false
    /// </summary>
    public bool 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>
    public int ItemCount
    {
        get { return VirtualItemCount; }
    }
 
    public bool MoveToFirstPage()
    {
        return MoveToPage(0);
    }
 
    public bool MoveToLastPage()
    {
        return MoveToPage(VirtualPageCount - 1);
    }
 
    public bool MoveToNextPage()
    {
        return MoveToPage(PageIndex + 1);
    }
 
    public bool 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 change
                return false;
            }
 
        }
        else
        {
            //page index out of bounds, or was busy changing pages
            return false;
        }
    }
 
    public bool MoveToPreviousPage()
    {
        return MoveToPage(PageIndex - 1);
    }
 
    public event EventHandler<EventArgs> PageChanged;
    protected void OnPageChanged()
    {
        if (PageChanged != null)
            PageChanged(this, EventArgs.Empty);
    }
 
    public event EventHandler<PageChangingEventArgs> PageChanging;
    protected void 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>
    public int PageIndex
    {
        get { return _pageIndex; }
    }
 
    /// <summary>
    /// Get/set the page size which will can then be used by the external paging calls
    /// </summary>
    public int PageSize
    {
        get
        {
            return _pageSize;
        }
        set
        {
            _pageSize = value;
        }
    }
 
    public int TotalItemCount
    {
        //this will always be the same as virtual item count
        get { return VirtualItemCount; }
    }
    #endregion
 
    #region "INotifyPropertyChanged"
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
 
    #region"INotifyCollectionChanged"
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    protected void 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.

E.g.

private PagedVirtualCollectionView _SearchResults = new PagedVirtualCollectionView(new List<SearchResult>());
public PagedVirtualCollectionView SearchResults
{
    get { return _SearchResults; }
}
 
public MyViewModel()
{
    _SearchResults.PageChanging += new EventHandler<PageChangingEventArgs>(SearchResultsPageChangingHandler);
}
 
protected void SearchResultsPageChangingHandler(object sender, PageChangingEventArgs e)
{
    UpdateSearchResults(e.NewPageIndex);
}
 
public void UpdateSearchResults(int pageIndex)
{
    //CALL YOUR SERVICE PASSING pageIndex and SearchResults.PageSize
}
 
void UpdateSearchResultsCompleted(object sender, SearchCompletedEventArgs e)
{          
    if (e.Error == null)
    {
        SearchResults.SourceCollection = e.Result;
        SearchResults.VirtualItemCount = e.totalRecords; //totalRecords is defined as an 'out' parameter to the WCF service
    }
}

Share This!

1 Comment to Virtual Paging with Silverlight/WCF Services

  1. Bal√°zs's Gravatar Bal√°zs
    March 8, 2012 at 3:14 pm | Permalink

    Very useful post. It helped a lot.

1 Trackback to Virtual Paging with Silverlight/WCF Services

  1. By DotNetKicks.com on August 24, 2011 at 10:20 am

Leave a Reply

CraigWardman.com
Car Leasing | Lease Cars