Click here to Skip to main content
15,867,750 members
Articles / Desktop Programming / Windows Forms
Article

DataView Paging in WPF

Rate me:
Please Sign up or sign in to vote.
4.00/5 (10 votes)
23 Sep 2007CPOL2 min read 161.4K   4K   62   13
Custom paging for Dataview in WPF

Introduction

In WPF (Windows Presentation Foundation), the GridView control does not have built-in paging support unlike Visual Studio 2005 (I think. May be I am missing something). When I was doing some test coding in WPF using GridView and wanted paging support, I Googled as usual but it was of no use. After lots of searching, I decided to implement custom paging for the purpose. After completing, I decided to share it with others because there aren't many such articles about WPF.

Image 1

In this article, I will explain how to create a DataView, how to bind it to a dynamic datasource and I will also explain about implementing paging. The paging logic is very simple and can be used in any other similar situation.

Here I will use Northwind as the database and two tables Products for data.

First create a GridView to display the data. To create a gridview, use this syntax:

XML
<ListView Name="lstProducts">
   <ListView.View>
    <GridView></GridView>
   </ListView.View>
</ListView>

To create columns, use this syntax:

XML
<GridViewColumn Header="ProductID"></GridViewColumn>

For binding a datasource to the gridview, set the ItemsSource property to "{Binding}" and for binding a database field to the gridviewcolumn, set the DisplayMemberBinding property to "{Binding Path=[FieldName]}".

So, here is the XAML for bindable gridview:

XML
<ListView Margin="15,115,15,48" Name="lstProducts" ItemsSource="{Binding}">
   <ListView.View>
      <GridView>
        <GridViewColumn Header="ProductID"
             DisplayMemberBinding="{Binding Path=ProductID}"></GridViewColumn>
          <GridViewColumn Header="Product Name"
               DisplayMemberBinding="{Binding Path=ProductName}"></GridViewColumn>
          <GridViewColumn Header="SupplierID"
               DisplayMemberBinding="{Binding Path=SupplierID}"></GridViewColumn>
          <GridViewColumn Header="CategoryID"
               DisplayMemberBinding="{Binding Path=CategoryID}"></GridViewColumn>
          <GridViewColumn Header="Qty. Per Unit"
               DisplayMemberBinding="{Binding Path=QuantityPerUnit}"></GridViewColumn>
          <GridViewColumn Header="Unit Price"
               DisplayMemberBinding="{Binding Path=UnitPrice}"></GridViewColumn>
          <GridViewColumn Header="In Stock"
               DisplayMemberBinding="{Binding Path=UnitInStock}"></GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

In the code behind, write the code to bind the data to the gridview. Here is the function to bind the data:

C#
private void ListProducts()
{
     sqlCon = new SqlConnection();
     sqlCon.ConnectionString = \\ConnectionString.

     cmd = new SqlCommand();
     cmd.Connection = sqlCon;
     cmd.CommandType = CommandType.Text;
     cmd.CommandText = "SELECT * FROM Products";

     sqlDa = new SqlDataAdapter();
     sqlDa.SelectCommand = cmd;

     ds = new DataSet();
     try
     {
         sqlDa.Fill(ds,"Products");

         if (ds.Tables["Products"].Rows.Count > 0)
         {
             lstProducts.DataContext = ds.Tables["Products"].DefaultView;
         }
         else
         {
              MessageBox.Show("Message");
         }
      }
      catch (Exception ex)
      {
          MessageBox.Show("Error Message");
      }
      finally
      {
          sqlDa.Dispose();
          cmd.Dispose();
          sqlCon.Dispose();
      }
 }

To call this function in the window Loaded event (Load event in Visual Studio 2005), update the XAML like this:

XML
<Window
.......
Loaded="OnLoad"
>

Write the code for OnLoad handler in the *.cs file.

C#
private void OnLoad(object sender, System.EventArgs e)
{
    ListProducts();
}

Now we have the grid with all the products listed. Let's start implementing paging. First we need to create the buttons for paging. Buttons are created using <Button></Button> tag. And for click event, use Click="EventName".

So here is the XAML for buttons with events:

XML
<Button Height="23" HorizontalAlignment="Left" Margin="18,0,0,22"
    Name="btnFirst" VerticalAlignment="Bottom" Width="40"
    Content="&lt;&lt;" Click="btnFirst_Click" Opacity="0.75">
</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,474,22"
    Name="btnNext" VerticalAlignment="Bottom" Width="40"
    Content="&gt;" Click="btnNext_Click" Opacity="0.75">
</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,429,22"
    VerticalAlignment="Bottom" Width="40" Name="btnLast"
    Click="btnLast_Click" Opacity="0.75" Content="&gt;&gt;">
</Button>
<Button Height="23" Margin="62,0,551,22" VerticalAlignment="Bottom"
    Name="btnPrev" Click="btnPrev_Click" Opacity="0.75" Content="&lt;">
</Button>

In the code behind, write the code to paging. For the paging, I have created four private members:

C#
//For holding the data globally.
DataTable dt_Products = new DataTable("Products");

//For storing the current page number.
private int paging_PageIndex = 1;

//For storing the Paging Size. Here it is static but you can use a property
//to expose and update value.
private int paging_NoOfRecPerPage = 20;

//To check the paging direction according to use selection.
private enum PagingMode {First = 1,Next = 2,Previous = 3,Last = 4};

We are using dt_Products for storing the data that is retrieved from the database. For that, update the ListProducts() function a little bit. After retrieving data, we have to display the first set of records according to the value of paging_NoOfRecPerPage (here it is 20). For that, I have created a temporary table with the same schema as dt_Products using the clone method.

C#
private void ListProducts()
{
...........

paging_PageIndex = 1;\\For default
sqlDa.Fill(dt_Products);

if (dt_Products.Rows.Count > 0)
{
    DataTable tmpTable = new DataTable();

    //Copying the schema to the temporary table.
    tmpTable = dt_Products.Clone();

    //If total record count is greater than page size then import records
    //from 0 to pagesize (here 20)
    //Else import reports from 0 to total record count.
    if (dt_Products.Rows.Count >= paging_NoOfRecPerPage)
    {
       for (int i = 0; i < paging_NoOfRecPerPage; i++)
       {
            tmpTable.ImportRow(dt_Products.Rows[i]);
       }
    }
    else
    {
       for (int i = 0; i < dt_Products.Rows.Count; i++)
       {
           tmpTable.ImportRow(dt_Products.Rows[i]);
       }
    }

    //Bind the table to the gridview.
    lstProducts.DataContext = tmpTable.DefaultView;

    //Dispose the temporary table.
    tmpTable.Dispose();
 }

Now only the first 20 (page size) records will be displayed. Now we have to write the code for navigation. Let's create a function for that:

C#
private void CustomPaging(int mode)
{
    //There is no need for these variables but i created them just for readability
    int totalRecords = dt_Products.Rows.Count;
    int pageSize = paging_NoOfRecPerPage;

    //If total record count is less than  the page size then return.
    if (totalRecords  <= pageSize)
    {
       return;
    }

    switch (mode)
    {
        case (int)PagingMode.Next:
           if (totalRecords  > (paging_PageIndex  * pageSize))
           {
               DataTable tmpTable = new DataTable();
               tmpTable = dt_Products.Clone();

               if (totalRecords  >= ((paging_PageIndex  * pageSize) + pageSize))
               {
                   for (int i = paging_PageIndex * pageSize; i <
                       ((paging_PageIndex   * pageSize) + pageSize); i++)
                   {
                       tmpTable.ImportRow(dt_Products.Rows[i]);
                   }
               }
               else
               {
                   for (int i = paging_PageIndex * pageSize; i < totalRecords; i++)
                   {
                       tmpTable.ImportRow(dt_Products.Rows[i]);
                   }
               }

               paging_PageIndex += 1;

               lstProducts.DataContext = tmpTable.DefaultView;
               tmpTable.Dispose();
           }
           break;
       case (int)PagingMode.Previous:
           if (paging_PageIndex > 1)
           {
               DataTable tmpTable = new DataTable();
               tmpTable = dt_Products.Clone();

               paging_PageIndex -= 1;

               for (int i = ((paging_PageIndex * pageSize) - pageSize);
                   i < (paging_PageIndex * pageSize); i++)
               {
                  tmpTable.ImportRow(dt_Products.Rows[i]);
               }

               lstProducts.DataContext = tmpTable.DefaultView;
               tmpTable.Dispose();
           }
           break;
       case (int)PagingMode.First:
             paging_PageIndex = 2;
             CustomPaging((int)PagingMode.Previous);
             break;
       case (int)PagingMode.Last:
             paging_PageIndex = (totalRecords/pageSize);
             CustomPaging((int)PagingMode.Next);
             break;
     }
 }

Paging function is completed. We just need to call on button click and pass the correct mode.

C#
private void btnFirst_Click(object sender, System.EventArgs e)
{
    CustomPaging((int)PagingMode.First);
}

private void btnNext_Click(object sender, System.EventArgs e)
{
   CustomPaging((int)PagingMode.Next);
}

private void btnPrev_Click(object sender, System.EventArgs e)
{
   CustomPaging((int)PagingMode.Previous);
}

private void btnLast_Click(object sender, System.EventArgs e)
{
   CustomPaging((int)PagingMode.Last);
}

That's all for the paging.

If you want to display paging information and page number along with this, then create two labels:

XML
<Label Height="23.277" HorizontalAlignment="Left" Margin="14.37,89.723,0,0"
    Name="lblPagingInfo" VerticalAlignment="Top" Width="282.63"/>
<Label Height="23.277" HorizontalAlignment="Left" Margin="108.37,0,0,23"
    Name="lblPageNumber" VerticalAlignment="Bottom" Width="26.63" Content="1"/>

To display the paging information, let's write a simple function. Add this function call in the CustomPaging() function outside the switch block. DONE. Compile and RUN.

Here is the complete code:

XAML

XML
<Window
.......
Loaded="OnLoad"
>

<Grid>

<ListView Margin="15,115,15,48" Name="lstProducts" ItemsSource="{Binding}">
   <ListView.View>
      <GridView>
        <GridViewColumn Header="ProductID"
            DisplayMemberBinding="{Binding Path=ProductID}"></GridViewColumn>
          <GridViewColumn Header="Product Name"
            DisplayMemberBinding="{Binding Path=ProductName}"></GridViewColumn>
          <GridViewColumn Header="SupplierID"
            DisplayMemberBinding="{Binding Path=SupplierID}"></GridViewColumn>
          <GridViewColumn Header="CategoryID"
            DisplayMemberBinding="{Binding Path=CategoryID}"></GridViewColumn>
          <GridViewColumn Header="Qty. Per Unit"
            DisplayMemberBinding="{Binding Path=QuantityPerUnit}"></GridViewColumn>
          <GridViewColumn Header="Unit Price"
            DisplayMemberBinding="{Binding Path=UnitPrice}"></GridViewColumn>
          <GridViewColumn Header="In Stock"
            DisplayMemberBinding="{Binding Path=UnitInStock}"></GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

<Button Height="23" HorizontalAlignment="Left" Margin="18,0,0,22"
    Name="btnFirst" VerticalAlignment="Bottom" Width="40" Content="&lt;&lt;"
    Click="btnFirst_Click" Opacity="0.75">
</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,474,22"
    Name="btnNext" VerticalAlignment="Bottom" Width="40" Content="&gt;"
    Click="btnNext_Click" Opacity="0.75">
</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,429,22"
    VerticalAlignment="Bottom" Width="40" Name="btnLast"
    Click="btnLast_Click" Opacity="0.75" Content="&gt;&gt;">
</Button>
<Button Height="23" Margin="62,0,551,22" VerticalAlignment="Bottom"
    Name="btnPrev" Click="btnPrev_Click" Opacity="0.75" Content="&lt;">
</Button>

<Label Height="23.277" HorizontalAlignment="Left" Margin="14.37,89.723,0,0"
    Name="lblPagingInfo" VerticalAlignment="Top" Width="282.63"/>
<Label Height="23.277" HorizontalAlignment="Left" Margin="108.37,0,0,23"
    Name="lblPageNumber" VerticalAlignment="Bottom" Width="26.63" Content="1"/>

</Grid>
</Window>

C#

C#
//For holding the data globally.
DataTable dt_Products = new DataTable("Products");

//For storing the current page number.
private int paging_PageIndex = 1;

//For storing the Paging Size. Here it is static but you can use a property
//to expose and update value.
private int paging_NoOfRecPerPage = 20;

//To check the paging direction according to use selection.
private enum PagingMode {First = 1,Next = 2,Previous = 3,Last = 4};

private void OnLoad(object sender, System.EventArgs e)
{
    ListProducts();
}

private void btnFirst_Click(object sender, System.EventArgs e)
{
    CustomPaging((int)PagingMode.First);
}

private void btnNext_Click(object sender, System.EventArgs e)
{
   CustomPaging((int)PagingMode.Next);
}

private void btnPrev_Click(object sender, System.EventArgs e)
{
   CustomPaging((int)PagingMode.Previous);
}

private void btnLast_Click(object sender, System.EventArgs e)
{
   CustomPaging((int)PagingMode.Last);
}

private void ListProducts()
{
     sqlCon = new SqlConnection();
     sqlCon.ConnectionString = \\ConnectionString.

     cmd = new SqlCommand();
     cmd.Connection = sqlCon;
     cmd.CommandType = CommandType.Text;
     cmd.CommandText = "SELECT * FROM Products";

     sqlDa = new SqlDataAdapter();
     sqlDa.SelectCommand = cmd;

     try
     {

         paging_PageIndex = 1;\\For default
     sqlDa.Fill(dt_Products);

    if (dt_Products.Rows.Count > 0)
    {
             DataTable tmpTable = new DataTable();

            //Copying the schema to the temporary table.
            tmpTable = dt_Products.Clone();

           //If total record count is greater than page size then
           //import records from 0 to pagesize (here 20)
           //Else import reports from 0 to total record count.
           if (dt_Products.Rows.Count >= paging_NoOfRecPerPage)
           {
               for (int i = 0; i < paging_NoOfRecPerPage; i++)
               {
                    tmpTable.ImportRow(dt_Products.Rows[i]);
               }
           }
           else
           {
               for (int i = 0; i < dt_Products.Rows.Count; i++)
               {
                   tmpTable.ImportRow(dt_Products.Rows[i]);
               }
           }

          //Bind the table to the gridview.
          lstProducts.DataContext = tmpTable.DefaultView;

          //Dispose the temporary table.
          tmpTable.Dispose();
       }
       else
       {
          MessageBox.Show("Message");
       }
    }
    catch (Exception ex)
    {
       MessageBox.Show("Error Message");
    }
    finally
    {
       sqlDa.Dispose();
       cmd.Dispose();
       sqlCon.Dispose();
    }
 }

private void CustomPaging(int mode)
{
    //There is no need for these variables but i created them just for readability
    int totalRecords = dt_Products.Rows.Count;
    int pageSize = paging_NoOfRecPerPage;

    //If total record count is less than  the page size then return.
    if (totalRecords  <= pageSize)
    {
       return;
    }

    switch (mode)
    {
        case (int)PagingMode.Next:
           if (totalRecords  > (paging_PageIndex  * pageSize))
           {
               DataTable tmpTable = new DataTable();
               tmpTable = dt_Products.Clone();

               if (totalRecords  >= ((paging_PageIndex  * pageSize) + pageSize))
               {
                   for (int i = paging_PageIndex * pageSize;
                        i < ((paging_PageIndex   * pageSize) + pageSize); i++)
                   {
                       tmpTable.ImportRow(dt_Products.Rows[i]);
                   }
               }
               else
               {
                   for (int i = paging_PageIndex * pageSize; i < totalRecords; i++)
                   {
                       tmpTable.ImportRow(dt_Products.Rows[i]);
                   }
               }

               paging_PageIndex += 1;

               lstProducts.DataContext = tmpTable.DefaultView;
               tmpTable.Dispose();
           }
           break;
       case (int)PagingMode.Previous:
           if (paging_PageIndex > 1)
           {
               DataTable tmpTable = new DataTable();
               tmpTable = dt_Products.Clone();

               paging_PageIndex -= 1;

               for (int i = ((paging_PageIndex * pageSize) - pageSize);
                   i < (paging_PageIndex * pageSize); i++)
               {
                  tmpTable.ImportRow(dt_Products.Rows[i]);
               }

               lstProducts.DataContext = tmpTable.DefaultView;
               tmpTable.Dispose();
           }
           break;
       case (int)PagingMode.First:
             paging_PageIndex = 2;
             CustomPaging((int)PagingMode.Previous);
             break;
       case (int)PagingMode.Last:
             paging_PageIndex = (totalRecords/pageSize);
             CustomPaging((int)PagingMode.Next);
             break;
     }

     DisplayPagingInfo();
 }

private void DisplayPagingInfo()
{
    //There is no need for these variables but i created them just for readability
    int totalRecords = dt_Products.Rows.Count;
    int pageSize = paging_NoOfRecPerPage;

    string pagingInfo = "Displaying " + (((paging_PageIndex-1)*pageSize)+1) +
        to " + paging_PageIndex*pageSize ;

    if (dt_Products.Rows.Count < (paging_PageIndex * pageSize))
    {
        pagingInfo = "Displaying " + (((paging_PageIndex - 1) * pageSize) + 1) +
        to " + totalRecords;
    }
    lblPagingInfo.Content = pagingInfo;
    lblPageNumber.Content = paging_PageIndex;
} 

License

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


Written By
Software Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 3 Pin
15640218625-Aug-10 22:43
15640218625-Aug-10 22:43 
GeneralFiltering Pin
sukip24-Aug-10 5:07
sukip24-Aug-10 5:07 
GeneralVery Good! But there is an error... Pin
Lancuto16-Jun-09 3:36
Lancuto16-Jun-09 3:36 
AnswerRe: Very Good! But there is an error... Pin
Vladimir Trifonov26-Jul-12 1:46
Vladimir Trifonov26-Jul-12 1:46 
GeneralRe: Very Good! But there is an error... Pin
Member 1075584510-Feb-15 21:51
Member 1075584510-Feb-15 21:51 
QuestionWorks fine, but what about sorting? Pin
Allan Wenham3-Mar-09 4:56
Allan Wenham3-Mar-09 4:56 
GeneralPaging Pin
andershundborg30-Dec-08 10:50
andershundborg30-Dec-08 10:50 
GeneralIt is wonderful Pin
xiaoxiaoma27-Nov-07 16:24
xiaoxiaoma27-Nov-07 16:24 
GeneralA Complete Download would be helpful Pin
altDuck19-Sep-07 6:56
altDuck19-Sep-07 6:56 
I appreciate the article. Thanks. However, for those of us just getting exposed the WPF, there are some holes left by your examples. Seeing the complete picture would be helpful.
GeneralRe: A Complete Download would be helpful Pin
Sreejith Thathanattu19-Sep-07 17:44
Sreejith Thathanattu19-Sep-07 17:44 
GeneralHi its nice Pin
Malli.b19-Sep-07 3:27
Malli.b19-Sep-07 3:27 
GeneralRe: Hi its nice Pin
Sreejith Thathanattu19-Sep-07 17:45
Sreejith Thathanattu19-Sep-07 17:45 
GeneralArticle Formatting Pin
jackmos18-Sep-07 4:04
professionaljackmos18-Sep-07 4:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.