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:
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:
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:
public IList<Student> GetData()
{
List<Student> genericList = new List<Student>();
Student studentObj;
Random randomObj = new Random();
for (int i = 0; i < 12345; i++)
{
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:
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++)
{
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:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
Your file should now look something like this:
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
:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
<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):
<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:
<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):
<StackPanel Orientation='Horizontal'
HorizontalAlignment='Right'>
<Label x:Name='PageInfo' />
<ComboBox x:Name='NumberOfRecords' />
</StackPanel>
For a Page Grand Total of:
<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:
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;
Your file should look like so:
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;
namespace CodeProjectDemonstration
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Before the public MainWindow()
function, add the following:
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:
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;
}
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:
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;
namespace CodeProjectDemonstration
{
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.
public string PageNumberDisplay()
{
int PagedNumber = numberOfRecPerPage * (PagedTable.PageIndex + 1);
if (PagedNumber > myList.Count)
{
PagedNumber = myList.Count;
}
return "Showing " + PagedNumber + " of " + myList.Count;
}
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:
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:
<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:
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;
namespace CodeProjectDemonstration
{
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;
}
public string PageNumberDisplay()
{
int PagedNumber = numberOfRecPerPage * (PagedTable.PageIndex + 1);
if (PagedNumber > myList.Count)
{
PagedNumber = myList.Count;
}
return "Showing " + PagedNumber + " of " + myList.Count;
}
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.
The full code set for your perusal.
StudentModel.cs
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++)
{
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
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;
}
}
}
MainWindow.XAML
<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
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using System.Windows.Controls;
namespace CodeProjectDemonstration
{
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;
}
public string PageNumberDisplay()
{
int PagedNumber = numberOfRecPerPage * (PagedTable.PageIndex + 1);
if (PagedNumber > myList.Count)
{
PagedNumber = myList.Count;
}
return "Showing " + PagedNumber + " of " + myList.Count;
}
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:
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 DataTable
s, 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