How can I paginate a WPF DataGrid?

The code project article above is quite good for getting this done with ADO tables. While for most applications, it is likely to work great, and is easy to understand, there is a more “WPF-zen-like” way to do it as well, and that would be using CollectionViews. The advantage of using a CollectionView compared to the example above is that it is a bit more general in terms of what data you’re putting in your grid (not that you can’t make that example more general), and it fits in well with the general WPF databinding model. It gives you a place to support common operations like sorting, grouping, etc, if you need those.

I put together a very short example of a barely working PagingCollectionView bound to the .NET 4.0 DataGrid control. While the example itself is pretty trivial, it shows you at least how to get started, as you have a proxy around the actual collection of data on which you can execute simple operations like MoveToNextPage and MoveToPreviousPage.

Here’s the C# for both the Window event handling and the PagingCollectionView:

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace GridPagingExample
{
    public partial class MainWindow : Window
    {
        private readonly PagingCollectionView _cview;

        public MainWindow()
        {
            InitializeComponent();
            this._cview = new PagingCollectionView(
                new List<object>
                {
                    new { Animal = "Lion", Eats = "Tiger" },
                    new { Animal = "Tiger", Eats =  "Bear" },
                    new { Animal = "Bear", Eats = "Oh my" },
                    new { Animal = "Wait", Eats = "Oh my isn't an animal" },
                    new { Animal = "Oh well", Eats = "Who is counting anyway" },
                    new { Animal = "Need better content", Eats = "For posting on stackoverflow" }
                },
                2
            );
            this.DataContext = this._cview;
        }

        private void OnNextClicked(object sender, RoutedEventArgs e)
        {
            this._cview.MoveToNextPage();
        }

        private void OnPreviousClicked(object sender, RoutedEventArgs e)
        {
            this._cview.MoveToPreviousPage();
        }
    }

    public class PagingCollectionView : CollectionView
    {
        private readonly IList _innerList;
        private readonly int _itemsPerPage;

        private int _currentPage = 1;

        public PagingCollectionView(IList innerList, int itemsPerPage)
            : base(innerList)
        {
            this._innerList = innerList;
            this._itemsPerPage = itemsPerPage;
        }

        public override int Count
        {
            get 
            { 
                if (this._innerList.Count == 0) return 0;
                if (this._currentPage < this.PageCount) // page 1..n-1
                {
                    return this._itemsPerPage;
                }
                else // page n
                {
                    var itemsLeft = this._innerList.Count % this._itemsPerPage;
                    if (0 == itemsLeft)
                    {
                        return this._itemsPerPage; // exactly itemsPerPage left
                    }
                    else
                    {
                        // return the remaining items
                        return itemsLeft;
                    }
                }
            }
        }

        public int CurrentPage
        {
            get { return this._currentPage; }
            set
            {
                this._currentPage = value;
                this.OnPropertyChanged(new PropertyChangedEventArgs("CurrentPage"));
            }
        }

        public int ItemsPerPage { get { return this._itemsPerPage; } }

        public int PageCount
        {
            get 
            { 
                return (this._innerList.Count + this._itemsPerPage - 1) 
                    / this._itemsPerPage; 
            }
        }

        private int EndIndex
        {
            get
            {
                var end = this._currentPage * this._itemsPerPage - 1;
                return (end > this._innerList.Count) ? this._innerList.Count : end;
            }
        }

        private int StartIndex
        {
            get { return (this._currentPage - 1) * this._itemsPerPage; }
        }

        public override object GetItemAt(int index)
        {
            var offset = index % (this._itemsPerPage); 
            return this._innerList[this.StartIndex + offset];
        }

        public void MoveToNextPage()
        {
            if (this._currentPage < this.PageCount)
            {
                this.CurrentPage += 1;
            }
            this.Refresh();
        }

        public void MoveToPreviousPage()
        {
            if (this._currentPage > 1)
            {
                this.CurrentPage -= 1;
            }
            this.Refresh();
        }
    }
}

Here’s the XAML for the window:

<Window x:Class="GridPagingExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <Label Grid.Row="0" Margin="2">
                <Label.Content>
                    <Binding Path="CurrentPage">
                        <Binding.StringFormat>Current Page: {0}</Binding.StringFormat>
                    </Binding>
                </Label.Content>
            </Label>
            <Button Content="Next" Click="OnNextClicked" Margin="2"/>
            <Button Content="Previous" Click="OnPreviousClicked" Margin="2"/>
        </StackPanel>
        <DataGrid ItemsSource="{Binding}" Grid.Row="1">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Animal" Width="*" Binding="{Binding Animal}"/>
                <DataGridTextColumn Header="Eats" Width="*" Binding="{Binding Eats}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

You could build out this CollectionView to support more functionality, some trivial, like MoveToLastPage and MoveToFirstPage, and some that will take some more thought about how you want it to behave, such as sorting. Hope it is helpful.

Leave a Comment