Sooner or later everyone face the problem of having to deal with paginated data in WPF, paginating a collection is quite easy and you can refer to one of my previous posts too (http://www.primordialcode.com/index.php/2008/09/08/silverlight-a-generic-pagination-control/ and http://www.primordialcode.com/index.php/2008/09/19/silverlight-pagination-control-bug-fixed/).

This time I needed a way to interact with some WCF services that I have to use in order to get the paged data from the server. What I wanted to create was a component that encapsulates the logic of handling the requests for the data in order to minimize the amount of code I have to write for similar operations in different forms of the application, some key features:

  • It has to handle a reference to the current page index
  • You have to specify the page size
  • You have to specify the types of object that will be hold inside the returned collection
  • You can specify an object that represent a set of filter you can do on the data (you can change them and ask for a refresh)
  • You have to specify the function that will be used to retrieve the paged data, it has however to conform to a specific signature.
  • You can use the data structures returned by this object (CurrentPage, TotalPages and Data, which contains the actual array of data objects) with the binding infrastructure of WPF.
  • This control does not have a graphical representation for the command buttons (First, Last, Next, etc.), it is only a support class.

Here’s the actual implementation:

/// <summary>
/// Starting point for a WPF paged data source control, next step: convert it to a real data source control
/// </summary>
/// <typeparam name="T">Types of object that will be returned</typeparam>
/// <typeparam name="TParams">Type of the class that holds optional parameters data</typeparam>
public class PagedObjectDataProvider<T, TParams> : INotifyPropertyChanged
{
   /// <summary>
   /// defines the signature of the function that will be used to retrieve the data
   /// </summary>
   /// <param name="pageindex">index of the page requested</param>
   /// <param name="pagesize">size of the page</param>
   /// <param name="param">optional parameters to be passed to the function</param>
   /// <param name="numrows">on exit it will contains the total number of records available to be retrieve from the source</param>
   /// <returns></returns>
   public delegate IList<T> GetPagedDataDelegate(int pageindex, int pagesize, TParams param, out int numrows);
 
   /// <summary>
   /// Current page index
   /// </summary>
   public int CurrentPage
   {
      get { return _CurrentPage + 1; }
      set { _CurrentPage = value - 1; }
   }
   private int _CurrentPage;
   
   /// <summary>
   /// Size of the page
   /// </summary>
   public int PageSize { get; set; }
   
   /// <summary>
   /// Total items available on the server to retrieve
   /// </summary>
   public int TotalRowsCount
   {
      get { return _TotalRowCount; }
      set { _TotalRowCount = value; }
   }
   private int _TotalRowCount;
   
   /// <summary>
   /// Total number of pages
   /// </summary>
   public int TotalPagesCount
   {
      get
      {
         int totalpages = _TotalRowCount / PageSize;
         totalpages += ((_TotalRowCount % PageSize) > 0) ? 1 : 0;
         return totalpages;
      }
   }
 
   /// <summary>
   /// Parameters that will be used as a 'filter' by the function that requests data
   /// </summary>
   public TParams Parameters { get; set; }
 
   private GetPagedDataDelegate GetDataFunction;
 
   /// <summary>
   /// This member property actually holds the data and you can bind to it
   /// </summary>
   public ObservableCollection<T> Data { get; internal set; }
 
   public PagedObjectDataProvider(int pageSize, TParams parameters, GetPagedDataDelegate getDataFunction)
   {
      PageSize = pageSize;
      Parameters = parameters;
      GetDataFunction = getDataFunction;
      Data = new ObservableCollection<T>();
      _CurrentPage = -1;
   }
 
   public void GetData()
   {
      // the first time we always get the first page if not initialized in any way
      // execute this function in a background thread to not block the interface
      if (_CurrentPage < 0)
         _CurrentPage = 0;
      
      Data.Clear();
      // execute this in a background thread to emulate the IsAsyncronous behavior of the objectdatasource
      ThreadPool.QueueUserWorkItem(new WaitCallback(InternalGetData), null);
   }
 
   private void InternalGetData(object addictionaldata)
   {
      GetDataFunction(_CurrentPage, PageSize, Parameters, out _TotalRowCount).ForEach(o => UiThread.Run(() => Data.Add(o)));
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs("CurrentPage"));
         PropertyChanged(this, new PropertyChangedEventArgs("TotalRowsCount"));
         PropertyChanged(this, new PropertyChangedEventArgs("TotalPagesCount"));
         PropertyChanged(this, new PropertyChangedEventArgs("Data"));
      }
   }
 
   /// <summary>
   /// Function used to request the next page of data
   /// </summary>
   /// <param name="page"></param>
   /// <returns></returns>
   public ICollection<T> MoveTo(PageMode page)
   {
      if (_CurrentPage >= 0)
         switch (page)
         {
            case PageMode.First:
               _CurrentPage = 0;
               break;
            case PageMode.Previous:
               _CurrentPage = Math.Max(_CurrentPage - 1, 0);
               break;
            case PageMode.Next:
               _CurrentPage = Math.Min(_CurrentPage + 1, TotalPagesCount - 1);
               break;
            case PageMode.Last:
               _CurrentPage = TotalPagesCount - 1;
               break;
         }
 
      GetData();
 
      return Data;
   }
 
   #region INotifyPropertyChanged Members
 
   public event PropertyChangedEventHandler PropertyChanged;
 
   #endregion
}
 
public enum PageMode
{
   First,
   Previous,
   Next,
   Last
}

To use it you need to create it in code, with something like:

// define a property at the class level
public PagedObjectDataProvider<LinkResultDto, object> RilevazioniPaged { get; internal set; }
 
...
 
// define a function that will be used to retrieve the data
private IList<LinkResultDto> GetPagedDataDelegate(int pageindex, int pagesize, object param, out int numrows)
        {
            return RilevazioniService.GetRilevazioniForBrowser(11, null, 0, 0, null, pageindex, pagesize, out numrows);
        }
 
...
 
// create the object in the constructor
RilevazioniPaged = new PagedObjectDataProvider<LinkResultDto, object>(20, null, GetPagedDataDelegate);
 
...
 
// Call the MoveTo function to retrieve the data from the service
RilevazioniPaged.MoveTo(mode);

To display the results you can use a Xaml like this:

<!-- data section -->
<ListView Grid.Row="0" Grid.Column="0" 
          Name="lvRilevazioni" ItemsSource="{Binding RilevazioniPaged.Data}" SelectionMode="Single"
          IsSynchronizedWithCurrentItem="True" HorizontalContentAlignment="Stretch" 
          VerticalAlignment="Stretch"  >
</ListView>
...
<!-- Pager section -->
<StackPanel Orientation="Horizontal">
    <Button Content="First" Click="First_Click" />
    <Button Content="Previous" Click="Previous_Click" />
    <Label>Page:</Label>
    <Label Content="{Binding RilevazioniPaged.CurrentPage}" />
    <Label>/</Label>
    <Label Content="{Binding RilevazioniPaged.TotalPagesCount}" />
    <Button Content="Next" Click="Next_Click" />
    <Button Content="Last" Click="Last_Click" />
</StackPanel>

This is a ‘first-shot’ solution and it doesn’t totally satisfies me, I’ll work to modify it in the future and have something more similar to a ‘true’ WPF data provider that you can use declaratively in the designer too.

Related Content