Silverlight: a generic Pagination Control

Print Content | More

The default silverlight controls like DataGrid and ItemsControl come without a default paging mechanism like we have in ASP.NET, so this article will be forcused in developing a generic Pager Control in Silverlight. This is what we want to obtain:

SilverlightPagination1

I was lurking around looking for some informations on ASP.NET MVC, and I found two interesting articles on how to do pagination in ASP.NET MVC (http://blog.wekeroad.com/2007/12/10/aspnet-mvc-pagedlistt/ and http://code-inside.de/blog-in/2008/04/08/aspnet-mvc-pagination-view-user-control/), the tecnique seemed inetersting and I decided to use it in my pager control.

The control will be divided in 2 major areas, the pager itself a series of helper classes that will give us different views of the data (a single 'page' of data can be tought as a view) and the selection control (which will use the pager) that will display a series of buttons to choose which page of data to display.

Let's start with the pager, since the control do not have to know the exact type of the data for which he will provide the paging features we need to define two interfaces: one that represent the view of the data we want to display (the actual page displayed) and another that will be the functions exposed by the pager itself.

Here we have some snippets of code taken from the two interfaces:

/// <summary> /// Generic functions exposed by the view, the actual implementation /// of this interface will be based on a list class to hold also the /// data of the view. /// </summary> public interface IPagedList : System.Collections.IList { /// <summary> /// total number of records /// </summary> int TotalCount { get; } /// <summary> /// index of the page represented by this view /// </summary> int PageIndex { get; } /// <summary> /// Size of the page /// </summary> int PageSize { get; } ...

/// <summary> /// do a previous page exists? /// </summary> bool IsPreviousPage { get; } /// <summary> /// do a next page exist? /// </summary> bool IsNextPage { get; } ... } /// <summary> /// functions exposed by the pager, they will build a view over the data /// </summary> public interface IPager { IPagedList ToPageList(int index); IPagedList ToPageList(int index, int pagesize); }

 

We need this level of abstraction cause we provide a strongly type implementation of the classes using Generics. All the paging job is done in the constructors of the view, here we take advantage of the current implementation of List<T> and IQuerable<T>:

/// <summary> /// The physical implementatio of the view, it's based on a List<> to also hold the data /// the source can be a List of anything or the result of a Linq query /// </summary> /// <typeparam name="T"></typeparam> public class PagedList<T> : List<T>, IPagedList { public PagedList(IQueryable<T> source, int index, int pageSize) { this._TotalCount = source.Count(); this._PageSize = pageSize; this._PageIndex = index; this.AddRange(source.Skip(index * pageSize).Take(pageSize).ToList()); } public PagedList(List<T> source, int index, int pageSize) { this._TotalCount = source.Count(); this._PageSize = pageSize; this._PageIndex = index; this.AddRange(source.Skip(index * pageSize).Take(pageSize).ToList()); } ...

public int TotalCount { get { return _TotalCount; } } private int _TotalCount;

 

and :

/// <summary> /// extremely simple implementation of the pager, /// given a list of data or a Linq query /// this class will build a series of views over it /// </summary> /// <typeparam name="T"></typeparam> public class Pager<T> : IPager { public Pager(List<T> source) { lSource = source; } private List<T> lSource; public Pager(IQueryable<T> source) { qSource = source; } private IQueryable<T> qSource; private const int DefaultPageSize = 10; public IPagedList ToPageList(int index) { if (lSource != null) return new PagedList<T>(lSource, index, DefaultPageSize); return new PagedList<T>(qSource, index, DefaultPageSize); } public IPagedList ToPageList(int index, int pagesize) { if (pagesize <= 0) pagesize = DefaultPageSize; if (lSource != null) return new PagedList<T>(lSource, index, pagesize); return new PagedList<T>(qSource, index, pagesize); } }

 

That's all for our set of helpers classes, on to the Control itself now; This first implementation will be extremely simple, The basic idea is to use a StackPanel as the main container and to dynamically add buttons to it, we will have 1 button for each page, plus button for previous, next, first and last page selection.

This control will act as a DataSource (or ItemsSource) for other controls able to render lists of data, so we need a way to specify to which control it will be linked, for the purpose we define a 'LinkedControl' property which will be setted to the name of the control we want to use to display the paginated data. The control has to support the 'ItemsSource' property so we need to do a check:

... /// <summary> /// Gets or sets the LinkedControl possible Value of the Control object. /// </summary> public Control LinkedControl { get { return (Control)GetValue(LinkedControlProperty); } set { //check if the control supports the items source property _itemsSource = value.GetType().GetProperty("ItemsSource"); if (_itemsSource == null) throw new ArgumentException("ItemsSource not supported"); SetValue(LinkedControlProperty, value); } } /// <summary> /// Identifies the LinkedControl dependency property. /// </summary> public static readonly DependencyProperty LinkedControlProperty = DependencyProperty.Register( "LinkedControl", typeof(Control), typeof(SilverlightPager), new PropertyMetadata(null)); private PropertyInfo _itemsSource; ...

 

The control will also be able to support the use of templates to define the graphical representations of the buttons, so we'll have two dependancy properties (PagerButtonStyle and PagerSelectedButtonStyle) to set the staticresources to use for the buttons.

... /// <summary> /// Gets or sets the PagerButtonStyle possible Value of the Style object. /// </summary> public Style PagerButtonStyle { get { return (Style)GetValue(PagerButtonStyleProperty); } set { SetValue(PagerButtonStyleProperty, value); } } // Using a DependencyProperty as the backing store for PagerButtonStyle. This enables animation, styling, binding, etc... public static readonly DependencyProperty PagerButtonStyleProperty = DependencyProperty.Register("PagerButtonStyle", typeof(Style), typeof(SilverlightPager), new PropertyMetadata(null)); /// <summary> /// Gets or sets the PagerSelectedButtonStyle possible Value of the Style object. /// </summary> public Style PagerSelectedButtonStyle { get { return (Style)GetValue(PagerSelectedButtonStyleProperty); } set { SetValue(PagerSelectedButtonStyleProperty, value); } } ...

 

The main core function actually gets the data for the view to visualize and recreate the whole sets of buttons everytime you select a new page:

/// <summary> /// buildup the pager aligning the buttons /// </summary> private void GetDataAndBuildPager() { if (_Source == null) throw new ArgumentException("No pager setted"); //Get the page data IPagedList page = _Source.ToPageList(_CurrentPageIndex, PageSize); //Build the layout LayoutRoot.Children.Clear(); //First button Button b = new Button(); b.Tag = 0; b.IsEnabled = !page.IsFirst; b.Content = "<<"; b.Click += new RoutedEventHandler(b_Click); SetButtonStyle(b, PagerButtonStyle); LayoutRoot.Children.Add(b); //Prev button b = new Button(); b.Tag = _CurrentPageIndex - 1; b.IsEnabled = page.IsPreviousPage; b.Content = "<"; b.Click += new RoutedEventHandler(b_Click); SetButtonStyle(b, PagerButtonStyle); LayoutRoot.Children.Add(b); for (int i = Math.Max(0, _CurrentPageIndex - PageInterval); i <= Math.Min(page.TotalPages, _CurrentPageIndex + PageInterval); i++) { b = new Button(); b.Tag = i; b.Content = i.ToString(); b.IsEnabled = true; LayoutRoot.Children.Add(b); if ((i == page.PageIndex) && (PagerSelectedButtonStyle != null)) SetButtonStyle(b, PagerSelectedButtonStyle); else { b.Click += new RoutedEventHandler(b_Click); SetButtonStyle(b, PagerButtonStyle); } } //Next Button b = new Button(); b.Tag = _CurrentPageIndex + 1; b.IsEnabled = page.IsNextPage; b.Content = ">"; b.Click += new RoutedEventHandler(b_Click); SetButtonStyle(b, PagerButtonStyle); LayoutRoot.Children.Add(b); //Last Button b = new Button(); b.Tag = page.TotalPages; b.IsEnabled = !page.IsLast; b.Content = ">>"; b.Click += new RoutedEventHandler(b_Click); SetButtonStyle(b, PagerButtonStyle); LayoutRoot.Children.Add(b); //update the linked control SetItemsSource(page); } void b_Click(object sender, RoutedEventArgs e) { //set the new current page _CurrentPageIndex = (int)(((Button)sender).Tag); GetDataAndBuildPager(); }

 

This is only a first release and the control itself is far from beeing optimized, it can be a starting point for deriving your own Pager control, The test solution accluded shows an example of paginating a Datagrid and an ItemsControl.

Example Solution:



Control, Silverlight, Window, Pagination, Datagrid, Itemscontrol

8 comments

Related Post

  1. #1 da Guardian - Saturday March 2010 alle 01:15

    Another bug fixed, the count of total pages to display in the pager was wrong. The example solution is updated with the right code.

  2. #2 da Guardian - Saturday March 2010 alle 01:15

    Fixed a couple of small bugs and updated the example solution

  3. #3 da Silverlight Pagination Control bug fixed | Guardian's Home - Saturday March 2010 alle 01:15

    [...] There were a couple of bugs in how the number of pages were computed, the problem is now solved and the solution is updated, see the article at: Silverlight: a generic Pagination Control [...]

  4. #4 da Sam - Saturday March 2010 alle 01:15

    Hello, is this working in Silverlight 2? I'm just seeing a blank page when I run the solution at the moment... Thanks though, it's a great example and mechanism..

  5. #5 da Guardian - Saturday March 2010 alle 01:15

    mmm probably this sample is stuck with SL2B2 code I'll update it as soon as I can.

  6. #6 da herzmeister der welten - Saturday March 2010 alle 01:15

    IMHO, paging has always been a disaster in usability and should now be a relict of the past. Microsoft should rather make the Silverlight grid performant enough, and the underlying collection should implement functionality like the winforms gridview virtual mode. That means dynamic buffering/reloading of content scrolled into view, similiar to this implementation for the ExtJs javascript framework at: http://www.siteartwork.de/livegrid_demo/ ). Of course, additionally there can be buttons or links where the user can jump to certain positions in the content list, but those should then say context specific things like "AC/DC - Crowbar", "Crumbsuckers - Edge Of Sanity" instead of "1-20, 21-40".

  7. #7 da Brian - Saturday March 2010 alle 01:15

    Just implemented the control. There is an issue with the nav button styling (initially leading to the above mentioned blank screen). Simply comment out the 'SetButtonStyle(b, PagerButtonStyle);' line in 'GetDataAndBuildPager()' to get the project working. Otherwise, nice design and just what I needed to get started. Thanks!!!

  8. #8 da WPF: a generic server-side pagination data provider at Guardians Home - Saturday March 2010 alle 01:15

    [...] in WPF, paginating a collection is quite easy and you can refer to one of my previous posts too (http://www.nablasoft.com/guardian/index.php/2008/09/08/silverlight-a-generic-pagination-control/ and [...]

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)