Thursday, April 23, 2009

How to controll the ASP.NET ListView's page size using a DropDownList with available page sizes

Download the source code for this article
Issue

It's fairly easy to get a paged ASP.NET ListView. All you have to do is to add an asp:DataPager into the list's template and you are done.

However, the page size is set statically inside the DataPager template:

   1: <asp:DataPager ID="DataPager1" runat="server" PagedControlID="ListView1" PageSize="30">
   2:      <Fields>
   3:          <asp:NumericPagerField ButtonCount="8" ButtonType="Link" />
   4:      </Fields>
   5:  </asp:DataPager>

This HOWTO explains how to controll the page size dynamically, for example using an external DropDownList with available page sizes. Functional requirements are as follows:



  • the DropDownList should be initially bound to a value corresponding to the initial page size of the ListView (for example: we will have 5 available sizes on the list, 10, 20, 30, 50, 100 but the initial page size is 30. The DropDown should be then initially bound to the value 30)
  • as the DropDownList's selection changes, the ListView should automatically set the new page size

Solution

First question: how are we supposed to get the actual page size of the ListView?


First answer: we have to search the List's template to get the DataPager and ask the pager for the list's size.


Second question: How do we set a new page size of the ListView?


Second answer: After we are able to find the current DataPager, we can use it's SetPageProperties method to set a new page size.


Third question: How do we set the initial value of the drop down list?


Third answer: We can bind the selection to the DataPager's PageSize property.


It seems then that the most important issue here to solve is to be able to retrieve the ListView's current DataPager. This is how it's done:



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: using System.Web.UI.WebControls;
   7: using System.Web.UI;
   8:  
   9: namespace WebApplication24
  10: {
  11:     public static class ListViewHelper
  12:     {       
  13:         /// <summary>
  14:         /// Retrieve current DataPager of the ListView
  15:         /// </summary>
  16:         public static DataPager GetActivePager( this ListView ListView )
  17:         {
  18:             DataPager Pager =
  19:                     ControlHelper.Find( ListView,
  20:                        control => control is DataPager &&
  21:                        ( (DataPager)control ).PagedControlID == ListView.ID ) as DataPager;
  22:  
  23:             if ( Pager != null )
  24:                 return Pager;
  25:             else
  26:                 return new DataPager();
  27:         }
  28:     }
  29: }

What is ControlHelper.Find( )? Well, it seems that the Control's FindControl method does not always work as expected. I got plenty of cases where FindControl does not find anything, while my custom Find finds what I expect to be found. Here it is:



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5: using System.Web.UI;
   6:  
   7: namespace WebApplication24
   8: {
   9:     public class ControlHelper
  10:     {
  11:         /// <summary>
  12:         /// Find a control by its ID 
  13:         /// </summary>
  14:         /// <param name="control"></param>
  15:         /// <param name="IdToFind"></param>
  16:         /// <returns></returns>
  17:         public static Control Find( Control control, string IdToFind )
  18:         {
  19:             if ( !string.IsNullOrEmpty( IdToFind ) )
  20:             {
  21:                 if ( control.ID == IdToFind )
  22:                 {
  23:                     return control;
  24:                 }
  25:                 foreach ( Control child in control.Controls )
  26:                 {
  27:                     Control result = Find( child, IdToFind );
  28:                     if ( result != null )
  29:                     {
  30:                         return result;
  31:                     }
  32:                 }
  33:             }
  34:  
  35:             return null;
  36:         }
  37:  
  38:         /// <summary>
  39:         /// Find a control with a predicate
  40:         /// </summary>
  41:         public static Control Find( Control control, Predicate<Control> MatchPredicate )
  42:         {
  43:             if ( MatchPredicate( control ) )
  44:             {
  45:                 return control;
  46:             }
  47:             foreach ( Control child in control.Controls )
  48:             {
  49:                 Control result = Find( child, MatchPredicate );
  50:                 if ( result != null )
  51:                 {
  52:                     return result;
  53:                 }
  54:             }
  55:             return null;
  56:         }
  57:  
  58:         /// <summary>
  59:         /// Find all controls with a predicate
  60:         /// </summary>
  61:         public static Control[] FindAll( Control Parent, Predicate<Control> MatchPredicate )
  62:         {
  63:             List<Control> ret = new List<Control>();
  64:  
  65:             FindAllHelper( Parent, MatchPredicate, ret );
  66:  
  67:             return ret.ToArray();
  68:         }
  69:  
  70:         private static void FindAllHelper( 
  71:             Control Parent, 
  72:             Predicate<Control> MatchPredicate, 
  73:             List<Control> results )
  74:         {
  75:             if ( MatchPredicate( Parent ) )
  76:                 results.Add( Parent );
  77:             // rekursja
  78:             foreach ( Control child in Parent.Controls )
  79:             {
  80:                 FindAllHelper( child, MatchPredicate, results );
  81:             }
  82:         }
  83:     }
  84: }

After we've prepared the basics, the rest is rather simple. The DropDownList will have the AutoPostBack set to true. After the DropDownList postbacks to the server, we'll set a new page size of the ListView:



   1: protected void SetPageSize_SelectedChanged( object sender, EventArgs e )
   2:  {
   3:      DropDownList pageSize = (DropDownList)sender;
   4:  
   5:      int NewSize = Convert.ToInt32( pageSize.Text );
   6:  
   7:      ListView1.GetActivePager().SetPageProperties( 0, NewSize, true );
   8:  }

Additionally, as a nice bonus, we'll fill a description of the ListView saying how many total rows are there and which rows are currently visible:



   1: protected void ListView1_DataBound( object sender, EventArgs e )
   2:  {
   3:      InitializeCountDescription();        
   4:  }
   5:  
   6:  private void InitializeCountDescription()
   7:  {
   8:      int _count = DataModel.Instance.Persons.Count;
   9:  
  10:      int start = ListView1.GetActivePager().StartRowIndex;
  11:      int end = Math.Min( _count, start + ListView1.GetActivePager().PageSize );
  12:  
  13:      Label lblItemCount = ControlHelper.Find( this, "lblListCount" ) as Label;
  14:      if ( lblItemCount != null )
  15:          lblItemCount.Text = string.Format( "{0}-{1} (from {2})", start + 1, end, _count );
  16:  }

Below is a screenshot from the example application.You can download the source code using the link provided at the top of the entry.