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:

Related Content