Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Paging WPF DataGrid

4.95/5 (12 votes)
26 Aug 2018CPOL10 min read 49.7K  
Simple and easy paging of a WPF DataGrid with DataTable and LINQ queries

Introduction

I needed a way to Page information displayed to a DataGrid from a DataTable. I searched around and found the following articles here on CodeProject:

These worked decently, however, when I built them, they were a little error prone and did not always respond in the expected way. They were designed in a fashion similar to WinForms and the code base is rather complex and was confusing at first to try and figure out what was going on. I needed a method compatible with our current build that would run reliably in all cases.

If you're relatively new to C#, I recommend you try the above examples and code them out by hand. They make excellent use of switch and case statements that you don't see too often.

You can clone the git repository of my (horribly)beautiful example here:

Image 1Image 2

Background

I am working on an enterprise application and I have an extremely large IList object that gets loaded into memory and displayed into a DataGrid. We wanted the DataGrid to show maybe a few hundred list items versus the hundreds of thousands it was putting out now.

The examples I found above had strange ways of handling lists and sometimes didn't work at all when the list was not in multiples of ten. Also the output needed to display the number of Items and be exactly accurate. I solved this problem by abstracting the core concepts from these examples and building out my own class to handle the entire process.

Building the Example

This example is extremely simple and easy to build. The Git Repository is heavily commented.

Let's Get Building!

To get started, open up a new Visual Studios WPF Blank Application.

In Solution Explorer, right click on your project and click add new Item.

Choose the Class file and name it StudentModel.cs.

In the StudentModel.cs file, add the following code to make a simple generic student class:

C#
public class Student
{
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

Then in the same file, we add a way to populate the list with some random data:

C#
public IList<Student> GetData()
{
    List<Student> genericList = new List<Student>();
    Student studentObj;
    Random randomObj = new Random();
    for (int i = 0; i < 12345; i++) //You can make this number anything
                                    //you can think of (and your processor can handle).
    {
        studentObj = new Student
        {
            FirstName = "first " + i,
            MiddleName = "Middle " + i,
            LastName = "Last " + i,
            Age = (int)randomObj.Next(1, 100)
        };

        genericList.Add(studentObj);

    }
    return genericList;
}

The Result should look like this:

C#
using System;
using System.Collections.Generic;

namespace YourProjectNameSpace
{
    class StudentModel
    {
        public class Student
        {
            public string FirstName { get; set; }
            public string MiddleName { get; set; }
            public string LastName { get; set; }
            public int Age { get; set; }
        }


        public IList<Student> GetData()
        {
            List<Student> genericList = new List<Student>();
            Student studentObj;
            Random randomObj = new Random();
            for (int i = 0; i < 12345; i++) //You can make this number anything 
                                            //you can think of (and your processor can handle).
            {
                studentObj = new Student
                {
                    FirstName = "first " + i,
                    MiddleName = "Middle " + i,
                    LastName = "Last " + i,
                    Age = (int)randomObj.Next(1, 100)
                };

                genericList.Add(studentObj);

            }
            return genericList;
        }
    }
}

The Class and list method were borrowed from this article.


That's it for our Student Class model. Let's Start building the Paging System.

Right click on your project again in Visual Studio and click on Add > New Item.

Select the class file and name it Paging.cs.

In the Paging.cs file, make sure you have the following using statements at the top:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

Your file should now look something like this:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace CodeProjectDemonstration
{
    class Paging
    {

    }
}

Inside the Paging Class, we are going to do a few specific processes. We are going to set an int PageIndex, prepare a DataTable and Query Our List.

Let's begin with the Page Index and the DataTable. Put them in the Curly Braces for class Paging:

C#
 class Paging
{
    public int PageIndex { get; set; }

    DataTable PagedList = new DataTable();
}

Now the hard part of the whole operation. We need to take in an IList<T> and query it down to only the page records we want and convert that result to a DataTable.

To begin, inside the Curly Braces underneath the DataTable you created earlier, create a public DataTable named SetPaging and provide the parameters as below:

C#
public DataTable SetPaging(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{

}

Here, we are setting the Paramaters IList<StudentModel.Student> called ListToPage and an int called RecordsPerPage that we will get later. The reason we are passing the fully qualified class name from the Student class we made earlier will become apparent as we move on.

Now, we'll set up two internal variables that the SetPaging function will need:

C#
int PageGroup = PageIndex * RecordsPerPage;

IList<StudentModel.Student> PagedList = new List<StudentModel.Student>();

An int called PageGroup that we determine by getting the current PageIndex we made earlier, times the RecordsPerPage that we want and we also create an instance of a new list.

Result:

C#
public DataTable SetPaging(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{
    int PageGroup = PageIndex * RecordsPerPage;
    IList<StudentModel.Student> PagedList = new List<StudentModel.Student>();
}

Now we need to set up the LINQ Query, Do some work on the Query and finally return our DataTable:

The code for the last pieces look like this:

C#
PagedList = ListToPage.Skip(PageGroup).Take(RecordsPerPage).ToList();
DataTable FinalPaging = PagedTable(PagedList);
return FinalPaging;

Here, we take the ListToPage and perform a LINQ Query upon it to only get the number of rows from the specified index. Skip(PageGroup) will skip the number of Items in the list passed to it. Take(RecordsPerPage) will only give us the number of records that we asked for and ToList() just puts it all neatly into our PageList.

The result:

C#
public DataTable SetPaging(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{
    int PageGroup = PageIndex * RecordsPerPage;
    IList<StudentModel.Student> PagedList = new List<StudentModel.Student>();
    PagedList = ListToPage.Skip(PageGroup).Take(RecordsPerPage).ToList();
    DataTable FinalPaging = PagedTable(PagedList);
    return FinalPaging;
}

Now to resolve the squiggle. To get the DataTable, we have to convert PagedList into a DataTable with the help of the next function.

Below the last SetPaging curly brace, add this:

C#
private DataTable PagedTable<T>(IList<T> SourceList)
{

}

Here, our only parameter is the List that was passed from the previous function as PagedList.

To convert this, we need to do some very specific work. First, we have to define the columns of our DataTable. We can do this by reading the Type of the list that is passed in. We also need to create a new DataTable to store our new information:

C#
private DataTable PagedTable<T>(IList<T> SourceList)
{
    Type columnType = typeof(T);
    DataTable TableToReturn = new DataTable();
}

To get the Columns, we can loop through the columnType Property that we set up before:

C#
foreach (var Column in columnType.GetProperties())
{
    TableToReturn.Columns.Add(Column.Name, Column.PropertyType);
}

Here, we loop through the properties type and set our column names via the Column.Name property and get the type of column by using the Column.PropertyType.

Now we need to get the actual rows that were returned by our LINQ Query. We can do this with another loop:

C#
foreach (object item in SourceList)
{
    DataRow ReturnTableRow = TableToReturn.NewRow();
    foreach (var Column in columnType.GetProperties())
    {
        ReturnTableRow[Column.Name] = Column.GetValue(item);
    }
    TableToReturn.Rows.Add(ReturnTableRow);
}

We use the [Column.Name] property Tag to set where each value goes in the columns.

Finally, we return the TableToReturn DataTable to our calling function. The whole block looks like this:

C#
private DataTable PagedTable<T>(IList<T> SourceList)
{
    Type columnType = typeof(T);
    DataTable TableToReturn = new DataTable();

    foreach (var Column in columnType.GetProperties())
    {
        TableToReturn.Columns.Add(Column.Name, Column.PropertyType);
    }

    foreach (object item in SourceList)
    {
        DataRow ReturnTableRow = TableToReturn.NewRow();
        foreach (var Column in columnType.GetProperties())
        {
            ReturnTableRow[Column.Name] = Column.GetValue(item);
        }
        TableToReturn.Rows.Add(ReturnTableRow);
    }
    return TableToReturn;
}

Our code file should now look something like this:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace CodeProjectDemonstration
{
    class Paging
    {
        public int PageIndex { get; set; }

        DataTable PagedList = new DataTable();

        public DataTable SetPaging(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            int PageGroup = PageIndex * RecordsPerPage;

            IList<StudentModel.Student> PagedList = new List<StudentModel.Student>();

            PagedList = ListToPage.Skip(PageGroup).Take(RecordsPerPage).ToList();

            DataTable FinalPaging = PagedTable(PagedList);

            return FinalPaging;
        }

        private DataTable PagedTable<T>(IList<T> SourceList)
        {
            Type columnType = typeof(T);
            DataTable TableToReturn = new DataTable();

            foreach (var Column in columnType.GetProperties())
            {
                TableToReturn.Columns.Add(Column.Name, Column.PropertyType);
            }

            foreach (object item in SourceList)
            {
                DataRow ReturnTableRow = TableToReturn.NewRow();
                foreach (var Column in columnType.GetProperties())
                {
                    ReturnTableRow[Column.Name] = Column.GetValue(item);
                }
                TableToReturn.Rows.Add(ReturnTableRow);
            }
            return TableToReturn;
        }
    }
}

If we set up our DataGrid now and called our SetPaging function, we would get a returned DataTable with whatever the default PageIndex and RecordsPerPage are, but we want to Page this data first.

So to begin paging the Data Paging we will simply set up four separate functions; Next, Previous, First, and Last.

The Next Function looks like this:

C#
public DataTable Next(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{
    PageIndex++;
    if (PageIndex >= ListToPage.Count / RecordsPerPage)
    {
        PageIndex = ListToPage.Count / RecordsPerPage;
    }
    PagedList = SetPaging(ListToPage, RecordsPerPage);
    return PagedList;
}

We are passing in the same parameters that we need for the SetPaging function, incrementing the PageIndex by one, checking to ensure it never becomes higher than our ListCount divided by our RecordsPerPage, and calling the SetPaging Function and passing through the parameters.

The Previous function is similar:

C#
public DataTable Previous(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{
    PageIndex--;
    if(PageIndex <= 0)
    {
        PageIndex = 0;
    }
    PagedList = SetPaging(ListToPage, RecordsPerPage);
    return PagedList;
}

Here, we decrement the PageIndex by one, ensure it never gets below zero, and call the SetPaging Function, passing through the parameters.

The first and last functions do the same thing, just in different directions:

C#
public DataTable First(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{
    PageIndex = 0;
    PagedList = SetPaging(ListToPage, RecordsPerPage);
    return PagedList;
}

public DataTable Last(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
{
    PageIndex = ListToPage.Count / RecordsPerPage;
    PagedList = SetPaging(ListToPage, RecordsPerPage);
    return PagedList;
}

So the complete file should now look like this:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace CodeProjectDemonstration
{
    class Paging
    {
        public int PageIndex { get; set; }

        DataTable PagedList = new DataTable();

        public DataTable SetPaging(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            int PageGroup = PageIndex * RecordsPerPage;

            IList<StudentModel.Student> PagedList = new List<StudentModel.Student>();

            PagedList = ListToPage.Skip(PageGroup).Take(RecordsPerPage).ToList();
            DataTable FinalPaging = PagedTable(PagedList);

            return FinalPaging;
        }

        private DataTable PagedTable<T>(IList<T> SourceList)
        {
            Type columnType = typeof(T);
            DataTable TableToReturn = new DataTable();

            foreach (var Column in columnType.GetProperties())
            {
                TableToReturn.Columns.Add(Column.Name, Column.PropertyType);
            }

            foreach (object item in SourceList)
            {
                DataRow ReturnTableRow = TableToReturn.NewRow();
                foreach (var Column in columnType.GetProperties())
                {
                    ReturnTableRow[Column.Name] = Column.GetValue(item);
                }
                TableToReturn.Rows.Add(ReturnTableRow);
            }
            return TableToReturn;
        }

        public DataTable Next(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex++;
            if (PageIndex >= ListToPage.Count / RecordsPerPage)
            {
                PageIndex = ListToPage.Count / RecordsPerPage;
            }
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }

        public DataTable Previous(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex--;
            if (PageIndex <= 0)
            {
                PageIndex = 0;
            }
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }

        public DataTable First(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex = 0;
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }

        public DataTable Last(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex = ListToPage.Count / RecordsPerPage;
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }
    }
}

That's everything we need to make the Paging work. Now, we'll set up our MainWindow and for the purposes of this example, we'll do some Initialization in the code behind.


Building the MainWindow

If you're a regular to WPF, these concepts should be simple and you can skip to the initialization if you want. Otherwise, we'll begin building our XAML file first.

I'm kinda lazy, so I'll break my page into two grids, one with five rows and five columns:

XML
<Window x:Class="CodeProjectDemonstration.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CodeProjectDemonstration"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>

        </Grid>
    </Grid>
</Window>

This should give us a nice and easy way to layout our demonstration page.

Begin by adding in the DataGrid and setting its x:Name = 'dataGrid' (like I said, lazy):

XML
<DataGrid x:Name='dataGrid'
          Grid.Column='1'
          Grid.Row='1'
          Grid.RowSpan='3'
          Grid.ColumnSpan='3' Margin='8'/>

Now, we'll get even lazier and add in a StackPanel for all our buttons and PageNumber Output:

The Buttons:

XML
<StackPanel Grid.Row='1'
            Grid.RowSpan='3'>
    <Button Content='Next'
            x:Name='NextButton'
            Margin='6' />
    <Button Content='Previous'
            x:Name='PreviousButton'
            Margin='6' />
    <Button Content='First'
            x:Name='FirstButton'
            Margin='6' />
    <Button Content='Last'
            x:Name='LastButton'
            Margin='6' />
</StackPanel>

The Label and ComboBox (I made them horizontal so I don't look like a complete slouch):

XML
<StackPanel Orientation='Horizontal'
             HorizontalAlignment='Right'>
     <Label x:Name='PageInfo' />
     <ComboBox x:Name='NumberOfRecords' />
 </StackPanel>

For a Page Grand Total of:

XML
<Window x:Class="CodeProjectDemonstration.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CodeProjectDemonstration"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <DataGrid x:Name='dataGrid'
                      Grid.Column='1'
                      Grid.Row='1'
                      Grid.RowSpan='3'
                      Grid.ColumnSpan='3'
                      Margin='8' />
            <StackPanel Grid.Row='1'
                        Grid.RowSpan='3'>
                <Button Content='Next'
                        x:Name='NextButton'
                        Margin='6' />
                <Button Content='Previous'
                        x:Name='PreviousButton'
                        Margin='6' />
                <Button Content='First'
                        x:Name='FirstButton'
                        Margin='6' />
                <Button Content='Last'
                        x:Name='LastButton'
                        Margin='6' />
                <StackPanel Orientation='Horizontal'
                            HorizontalAlignment='Right'>
                    <Label x:Name='PageInfo' />
                    <ComboBox x:Name='NumberOfRecords' />
                </StackPanel>
            </StackPanel>
        </Grid>
    </Grid>
</Window>

At this point, Databinding would be the best route to simplify code and work within the separations of concerns. We will not be doing that in this example. If you don't know what Databinding is and would like to start learning, follow this link.


Initialization!

So we will begin by intializing all of our necessary variables and call our functions. All from the code behind file (I know, revolutionary).

Ensure you have at least these using statements:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;

Your file should look like so:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;

namespace CodeProjectDemonstration
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Before the public MainWindow() function, add the following:

C#
private int numberOfRecPerPage;
static Paging PagedTable = new Paging();
static StudentModel StudentList = new StudentModel();
IList<StudentModel.Student> myList = StudentList.GetData();

This will neatly Initialize our list to use at will and gives us access to the Paging Class we made earlier.

Now in MainWindow(), add the following:

C#
public MainWindow()
 {
     InitializeComponent();

     PagedTable.PageIndex = 1; //Sets the Initial Index to a default value

     int[] RecordsToShow = { 10, 20, 30, 50, 100 }; //This Array can be any number of groups

     foreach (int RecordGroup in RecordsToShow)
     {
         NumberOfRecords.Items.Add(RecordGroup); //Fill the ComboBox with the Array
     }

     NumberOfRecords.SelectedItem = 10; //Initialize the ComboBox

     numberOfRecPerPage = Convert.ToInt32
             (NumberOfRecords.SelectedItem); //Convert the Combox Output to type int

     DataTable firstTable = PagedTable.SetPaging(myList, numberOfRecPerPage); //Fill a
                         //DataTable with the First set based on the numberOfRecPerPage

     dataGrid.ItemsSource = firstTable.DefaultView; //Fill the dataGrid
                                                    //with the DataTable created previously
 }

The important part here is the first initialization of the DataGrid. As you see, we call the SetPaging Function directly and pass in the default values. We then assign the returned DataTable to the DataGrid. Another thing to note is that we initialize the PageIndex to 1 in the Paging Class.

Your whole file should look like this now:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;

namespace CodeProjectDemonstration
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int numberOfRecPerPage; 

        static Paging PagedTable = new Paging();

        static StudentModel StudentList = new StudentModel();

        IList<StudentModel.Student> myList = StudentList.GetData();

        public MainWindow()
        {
            InitializeComponent();

            PagedTable.PageIndex = 1; 

            int[] RecordsToShow = { 10, 20, 30, 50, 100 }; 

            foreach (int RecordGroup in RecordsToShow)
            {
                NumberOfRecords.Items.Add(RecordGroup); 
            }

            NumberOfRecords.SelectedItem = 10; 

            numberOfRecPerPage = Convert.ToInt32(NumberOfRecords.SelectedItem); 

            DataTable firstTable = PagedTable.SetPaging(myList, numberOfRecPerPage);

            dataGrid.ItemsSource = firstTable.DefaultView; 
        }
    }
}

Just gorgeous, I know.

Now directly under the last curly brace of the MainWindow(), we will add in a little piece of code that will display our Number of Records shown and the Total number of Records.

C#
public string PageNumberDisplay()
{
    int PagedNumber = numberOfRecPerPage * (PagedTable.PageIndex + 1);
    if (PagedNumber > myList.Count)
    {
        PagedNumber = myList.Count;
    }
    return "Showing " + PagedNumber + " of " + myList.Count; //This dramatically
                 //reduced the number of times I had to write this string statement
}

Here, we calculate where we are in the List based on the number of records and the page index. If the number is higher that our list, we just return the list number. This makes this counter always accurate, no matter where we are in the list.

Now for the part we've all been waiting for... the Buttons!

Go back to your MainWindow.XAML and double click each button and the ComboBox to wire them up. Then they should match what you see below:

C#
private void NextButton_Click(object sender, RoutedEventArgs e)
{
    dataGrid.ItemsSource = PagedTable.Next(myList, numberOfRecPerPage).DefaultView;
    PageInfo.Content = PageNumberDisplay();
}

private void PreviousButton_Click(object sender, RoutedEventArgs e)
{
    dataGrid.ItemsSource = PagedTable.Previous(myList, numberOfRecPerPage).DefaultView;
    PageInfo.Content = PageNumberDisplay();
}

private void FirstButton_Click(object sender, RoutedEventArgs e)
{
    dataGrid.ItemsSource = PagedTable.First(myList, numberOfRecPerPage).DefaultView;
    PageInfo.Content = PageNumberDisplay();
}

private void LastButton_Click(object sender, RoutedEventArgs e)
{
    dataGrid.ItemsSource = PagedTable.Last(myList, numberOfRecPerPage).DefaultView;
    PageInfo.Content = PageNumberDisplay();
}

private void NumberOfRecords_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    numberOfRecPerPage = Convert.ToInt32(NumberOfRecords.SelectedItem);
    dataGrid.ItemsSource = PagedTable.First(myList, numberOfRecPerPage).DefaultView;
    PageInfo.Content = PageNumberDisplay();
}

So here, we have the four Buttons and the ComboBox. For each Button, we call the desired method and pass in the list and number of records to display. For the ComboBox, we call the SelectionChanged method to update the numberOfRecPerPage then we re-page the table. I had to use the first method because the table would jump from say 30 to 300 if I changed it to 100 records per page. So to keep the UI clean and the User interaction consistent, I basically reset the view when they change the number of records per page. If you figure out how to repage without that jump, post it in the comments!

As a result, your MainWindow.XAML file will look like:

XML
<Window x:Class="CodeProjectDemonstration.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CodeProjectDemonstration"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <DataGrid x:Name='dataGrid'
                      Grid.Column='1'
                      Grid.Row='1'
                      Grid.RowSpan='3'
                      Grid.ColumnSpan='3'
                      Margin='8' />
            <StackPanel Grid.Row='1'
                        Grid.RowSpan='3'>
                <Button Content='Next'
                        x:Name='NextButton'
                        Margin='6'
                        Click='NextButton_Click' />
                <Button Content='Previous'
                        x:Name='PreviousButton'
                        Margin='6'
                        Click='PreviousButton_Click' />
                <Button Content='First'
                        x:Name='FirstButton'
                        Margin='6'
                        Click='FirstButton_Click' />
                <Button Content='Last'
                        x:Name='LastButton'
                        Margin='6'
                        Click='LastButton_Click' />
                <StackPanel Orientation='Horizontal'
                            HorizontalAlignment='Right'>
                    <Label x:Name='PageInfo' />
                    <ComboBox x:Name='NumberOfRecords'
                              SelectionChanged='NumberOfRecords_SelectionChanged' />
                </StackPanel>
            </StackPanel>
        </Grid>
    </Grid>
</Window>

The code behind file:

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;

namespace CodeProjectDemonstration
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int numberOfRecPerPage; //Initialize our Variable, Classes and the List

        static Paging PagedTable = new Paging();

        static StudentModel StudentList = new StudentModel();

        IList<StudentModel.Student> myList = StudentList.GetData();

        public MainWindow()
        {
            InitializeComponent();

            PagedTable.PageIndex = 1; //Sets the Initial Index to a default value

            int[] RecordsToShow = { 10, 20, 30, 50, 100 }; //This Array can be any number groups

            foreach (int RecordGroup in RecordsToShow)
            {
                NumberOfRecords.Items.Add(RecordGroup); //Fill the ComboBox with the Array
            }

            NumberOfRecords.SelectedItem = 10; //Initialize the ComboBox

            numberOfRecPerPage = Convert.ToInt32(NumberOfRecords.SelectedItem); //Convert the 
                                                                     //Combobox Output to type int

            DataTable firstTable = PagedTable.SetPaging(myList, numberOfRecPerPage); //Fill a 
                                    //DataTable with the First set based on the numberOfRecPerPage

            dataGrid.ItemsSource = firstTable.DefaultView; //Fill the dataGrid with the 
                                                           //DataTable created previously
        }

        public string PageNumberDisplay()
        {
            int PagedNumber = numberOfRecPerPage * (PagedTable.PageIndex + 1);
            if (PagedNumber > myList.Count)
            {
                PagedNumber = myList.Count;
            }
            return "Showing " + PagedNumber + " of " + myList.Count; //This dramatically 
                          //reduced the number of times I had to write this string statement
        }

        private void NextButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.Next(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void PreviousButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.Previous(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void FirstButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.First(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void LastButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.Last(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void NumberOfRecords_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            numberOfRecPerPage = Convert.ToInt32(NumberOfRecords.SelectedItem);
            dataGrid.ItemsSource = PagedTable.First(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }
    }
}

You're all set! Build and run the Paging system.

It should look oh soooo beautiful.

Image 3

The full code set for your perusal.

StudentModel.cs

C#
using System;
using System.Collections.Generic;

namespace CodeProjectDemonstration
{
    class StudentModel
    {
        public class Student
        {
            public string FirstName { get; set; }
            public string MiddleName { get; set; }
            public string LastName { get; set; }
            public int Age { get; set; }
        }


        public IList<Student> GetData()
        {
            List<Student> genericList = new List<Student>();
            Student studentObj;
            Random randomObj = new Random();
            for (int i = 0; i < 12345; i++) //You can make this number anything 
                                            //you can think of (and your processor can handle).
            {
                studentObj = new Student
                {
                    FirstName = "first " + i,
                    MiddleName = "Middle " + i,
                    LastName = "Last " + i,
                    Age = (int)randomObj.Next(1, 100)
                };

                genericList.Add(studentObj);

            }
            return genericList;
        }
    }
}

Paging.cs

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace CodeProjectDemonstration
{
    class Paging
    {
        public int PageIndex { get; set; }

        DataTable PagedList = new DataTable();

        public DataTable SetPaging(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            int PageGroup = PageIndex * RecordsPerPage;

            IList<StudentModel.Student> PagedList = new List<StudentModel.Student>();

            PagedList = ListToPage.Skip(PageGroup).Take(RecordsPerPage).ToList(); //This is 
                      //where the Magic Happens. If you have a Specific sort or want to return 
                      //ONLY a specific set of columns, add it to this LINQ Query.

            DataTable FinalPaging = PagedTable(PagedList);

            return FinalPaging;
        }

        private DataTable PagedTable<T>(IList<T> SourceList)
        {
            Type columnType = typeof(T);
            DataTable TableToReturn = new DataTable();

            foreach (var Column in columnType.GetProperties())
            {
                TableToReturn.Columns.Add(Column.Name, Column.PropertyType);
            }

            foreach (object item in SourceList)
            {
                DataRow ReturnTableRow = TableToReturn.NewRow();
                foreach (var Column in columnType.GetProperties())
                {
                    ReturnTableRow[Column.Name] = Column.GetValue(item);
                }
                TableToReturn.Rows.Add(ReturnTableRow);
            }
            return TableToReturn;
        }

        public DataTable Next(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex++;
            if (PageIndex >= ListToPage.Count / RecordsPerPage)
            {
                PageIndex = ListToPage.Count / RecordsPerPage;
            }
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }

        public DataTable Previous(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex--;
            if (PageIndex <= 0)
            {
                PageIndex = 0;
            }
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }

        public DataTable First(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex = 0;
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }

        public DataTable Last(IList<StudentModel.Student> ListToPage, int RecordsPerPage)
        {
            PageIndex = ListToPage.Count / RecordsPerPage;
            PagedList = SetPaging(ListToPage, RecordsPerPage);
            return PagedList;
        }
    }
}

MainWindow.XAML

XML
<Window x:Class="CodeProjectDemonstration.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CodeProjectDemonstration"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <DataGrid x:Name='dataGrid'
                      Grid.Column='1'
                      Grid.Row='1'
                      Grid.RowSpan='3'
                      Grid.ColumnSpan='3'
                      Margin='8' />
            <StackPanel Grid.Row='1'
                        Grid.RowSpan='3'>
                <Button Content='Next'
                        x:Name='NextButton'
                        Margin='6'
                        Click='NextButton_Click' />
                <Button Content='Previous'
                        x:Name='PreviousButton'
                        Margin='6'
                        Click='PreviousButton_Click' />
                <Button Content='First'
                        x:Name='FirstButton'
                        Margin='6'
                        Click='FirstButton_Click' />
                <Button Content='Last'
                        x:Name='LastButton'
                        Margin='6'
                        Click='LastButton_Click' />
                <StackPanel Orientation='Horizontal'
                            HorizontalAlignment='Right'>
                    <Label x:Name='PageInfo' />
                    <ComboBox x:Name='NumberOfRecords'
                              SelectionChanged='NumberOfRecords_SelectionChanged' />
                </StackPanel>
            </StackPanel>
        </Grid>
    </Grid>
</Window>

MainWindow.xaml.cs

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;

namespace CodeProjectDemonstration
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int numberOfRecPerPage; //Initialize our Variable, Classes and the List

        static Paging PagedTable = new Paging();

        static StudentModel StudentList = new StudentModel();

        IList<StudentModel.Student> myList = StudentList.GetData();

        public MainWindow()
        {
            InitializeComponent();

            PagedTable.PageIndex = 1; //Sets the Initial Index to a default value

            int[] RecordsToShow = { 10, 20, 30, 50, 100 }; //This Array can be any number groups

            foreach (int RecordGroup in RecordsToShow)
            {
                NumberOfRecords.Items.Add(RecordGroup); //Fill the ComboBox with the Array
            }

            NumberOfRecords.SelectedItem = 10; //Initialize the ComboBox

            numberOfRecPerPage = Convert.ToInt32(NumberOfRecords.SelectedItem); //Convert the 
                                                                     //Combox Output to type int

            DataTable firstTable = PagedTable.SetPaging(myList, numberOfRecPerPage); //Fill a 
                                     //DataTable with the First set based on the numberOfRecPerPage

            dataGrid.ItemsSource = firstTable.DefaultView; //Fill the dataGrid with the 
                                                           //DataTable created previously
        }

        public string PageNumberDisplay()
        {
            int PagedNumber = numberOfRecPerPage * (PagedTable.PageIndex + 1);
            if (PagedNumber > myList.Count)
            {
                PagedNumber = myList.Count;
            }
            return "Showing " + PagedNumber + " of " + myList.Count; //This dramatically 
                         //reduced the number of times I had to write this string statement
        }

        private void NextButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.Next(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void PreviousButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.Previous(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void FirstButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.First(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void LastButton_Click(object sender, RoutedEventArgs e)
        {
            dataGrid.ItemsSource = PagedTable.Last(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }

        private void NumberOfRecords_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            numberOfRecPerPage = Convert.ToInt32(NumberOfRecords.SelectedItem);
            dataGrid.ItemsSource = PagedTable.First(myList, numberOfRecPerPage).DefaultView;
            PageInfo.Content = PageNumberDisplay();
        }
    }
}

Points of Interest

So things that were important that I found lacking in the other examples were the durability of the Control and minor user issues. If I spammed click next even past the last record, the Index would continue to go up. In order to go back, I had to spam the previous button the same +1 number of times. I also found to Page forward, I had to click the forward button twice to move to the next set of records. I still can't figure out why.The code didn't seem durable nor abstractable. I needed to run this against hundreds of thousands of returned items and potentially across many many different object classes. I was never able to truly make this system completely generic but I know someone out there can!

I found it quite interesting to think that, all that code above does one thing and it's all powered by a single method:

C#
PagedList = ListToPage.Skip(PageGroup).Take(RecordsPerPage).ToList();

This single LINQ query does everything that we need for Paging and if you were using your Lists as is without DataTables, you can make my code much smaller. That little LINQ query is so powerful though it has to be wrapped up in layers of code to make it user friendly and functionable. It's like a Ferari Engine, nearly useless until you put it in a Chasis with all the pretty bits and suddenly it's something so desirable.

History

  • 2018-08-26: Initial posting

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)