GridView is a great highly customizable ASP.NET control. Today I want to show, how to create derived control, which allows to add Digg-style pagination to your application.
First, we will create derived control and add property UseCustomPager, which will define whether or not to use Digg-style pagination:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using System; using System.Globalization; using System.Reflection; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; namespace App_Code { public class GridViewWithPager : GridView { public bool UseCustomPager { get { return (bool?) ViewState["UseCustomPager"] ?? false; } set { ViewState["UseCustomPager"] = value; } } } } |
GridView has virtual method InitializePager which could be overridden to create our custom pager:
1 2 3 4 5 6 7 | protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource) { if (UseCustomPager) CreateCustomPager(row, columnSpan, pagedDataSource); else base.InitializePager(row, columnSpan, pagedDataSource); } |
Now let’s create our custom pager:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | protected virtual void CreateCustomPager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource) { int pageCount = pagedDataSource.PageCount; int pageIndex = pagedDataSource.CurrentPageIndex + 1; int pageButtonCount = PagerSettings.PageButtonCount; TableCell cell = new TableCell(); row.Cells.Add(cell); if (columnSpan > 1) cell.ColumnSpan = columnSpan; if (pageCount > 1) { HtmlGenericControl pager = new HtmlGenericControl("div"); pager.Attributes["class"] = "pagination"; cell.Controls.Add(pager); int min = pageIndex - pageButtonCount; int max = pageIndex + pageButtonCount; if (max > pageCount) min -= max - pageCount; else if (min < 1) max += 1 - min; // Create "previous" button Control page = pageIndex > 1 ? BuildLinkButton(pageIndex - 2, PagerSettings.PreviousPageText, "Page", "Prev") : BuildSpan(PagerSettings.PreviousPageText, "disabled"); pager.Controls.Add(page); // Create page buttons bool needDiv = false; for (int i = 1; i <= pageCount; i++) { if (i <= 2 || i > pageCount - 2 || (min <= i && i <= max)) { string text = i.ToString(NumberFormatInfo.InvariantInfo); page = i == pageIndex ? BuildSpan(text, "current") : BuildLinkButton(i - 1, text, "Page", text); pager.Controls.Add(page); needDiv = true; } else if (needDiv) { page = BuildSpan("…", null); pager.Controls.Add(page); needDiv = false; } } // Create "next" button page = pageIndex < pageCount ? BuildLinkButton(pageIndex, PagerSettings.NextPageText, "Page", "Next") : BuildSpan(PagerSettings.NextPageText, "disabled"); pager.Controls.Add(page); } } private Control BuildLinkButton(int pageIndex, string text, string commandName, string commandArgument) { PagerLinkButton link = new PagerLinkButton(this); link.Text = text; link.EnableCallback(ParentBuildCallbackArgument(pageIndex)); link.CommandName = commandName; link.CommandArgument = commandArgument; return link; } private Control BuildSpan(string text, string cssClass) { HtmlGenericControl span = new HtmlGenericControl("span"); if (!String.IsNullOrEmpty(cssClass)) span.Attributes["class"] = cssClass; span.InnerHtml = text; return span; } |
Several pager settings used in this code:
- PagerSettings.PreviousPageText — text to be shown on the “Previous” button.
- PagerSettings.NextPageText — text to be shown on the “Next” button.
- PagerSettings.PageButtonCount — how many pages to show before and after the current page.
You could see, that in BuildLinkButton method I have used custom control PagerLinkButton. This is just descendant of the LinkButton control which simplifies usage inside our GridViewWithPager:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | using System; using System.Web.UI; using System.Web.UI.WebControls; namespace App_Code { public class PagerLinkButton : LinkButton { public PagerLinkButton(IPostBackContainer container) { _container = container; } public void EnableCallback(string argument) { _enableCallback = true; _callbackArgument = argument; } public override bool CausesValidation { get { return false; } set { throw new ApplicationException("Cannot set validation on pager buttons"); } } protected override void Render(HtmlTextWriter writer) { SetCallbackProperties(); base.Render(writer); } private void SetCallbackProperties() { if (_enableCallback) { ICallbackContainer container = _container as ICallbackContainer; if (container != null) { string callbackScript = container.GetCallbackScript(this, _callbackArgument); if (!string.IsNullOrEmpty(callbackScript)) OnClientClick = callbackScript; } } } #region Private fields private readonly IPostBackContainer _container; private bool _enableCallback; private string _callbackArgument; #endregion } } |
Our control is almost done. All we need is to define method ParentBuildCallbackArgument. As you could see from GridView sources, this method is used for serializing page index, sort direction and sort expression, but for some reason, it has been defined as internal. I don’t like hacks, but in this case I think that I can cheat a little:
1 2 3 4 5 6 7 | private string ParentBuildCallbackArgument(int pageIndex) { MethodInfo m = typeof (GridView).GetMethod("BuildCallbackArgument", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] {typeof (int)}, null); return (string) m.Invoke(this, new object[] {pageIndex}); } |
BTW, as you could see, I have not added any comments to methods. It is done specially, because I don’t want to create custom controls library, just sharing my experience :-)
And now I’ll show you example of usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <asp:XmlDataSource runat="server" ID="xdsCountries" DataFile="~/App_Data/CountryCodeList.xml" /> <ac:GridViewWithPager runat="server" UseCustomPager="true" AllowPaging="true" DataSourceID="xdsCountries" PageSize="10" AutoGenerateColumns="false"> <PagerSettings PreviousPageText="« previous" NextPageText="next »" PageButtonCount="3" /> <Columns> <asp:TemplateField HeaderText="Code"> <ItemTemplate><%# XPath("CountryCoded") %></ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Name"> <ItemTemplate><%# XPath("CountryName") %></ItemTemplate> </asp:TemplateField> </Columns> </ac:GridViewWithPager> |
And a screenshot:
Full source code could be downloaded here.
GridView — это отличный сильно кастомизируемый контрол ASP.NET. – Ты серьезно так считаешь?
Да, и не думаю, что я сильно ошибаюсь :-) Все, что нужно – легко получить. Включая DIV’ную разметку вместо табличной, которую он генерит по умолчанию, всевозможные сортировки и пейджинги, управление блоками данных (колонками в частности). Да, это действительно отлично кастомизируемый контрол. Если есть пример того, что сложно сделать — велкам.
Вполне допускаю что я его криво использую.
1. Была задача реализовать фильтры, встроенные в каждую колонку. Реализовал, но криво, джаваскриптом создаю строку пустую и туда засовываю фильтры.
2. Проблема в датабиндинге. Связал с обжект датасоурсом. При любом действии (то же нажатие на кнопку пейджинга) вызывается много раз DataBind, что ведет к ненужным запросом данных…
Мысль в том что не вижу смысла бороться с этой всей кастомизацией, делая элементарные действия. Не хочу думать про весь этот цикл событий жизни, вспоминая что исполнится первыми и где тут еще поставить очередной DataBind, чтоб все отрисовалось правильно…
Standard GridView is sux with such paging style or another.
2 Total Beaver: Any arguments?
Very nice one I implemented it in one of my projects and looks + works perfectly fine.
Thanks a bunch
great code but does someone has the VB.net translation of this, im not into C#
thx
Wouter
All the functionalities works except when I tries to get the First and Last page index, it always return 0. Any idea why?
Hi!
Thanks for a great control. I am having a problem capturing the postback on the page click event for some reason.
I am running this with VS 2008 and ASP.NET 3.5. Could that be the problem?
Any help would be greatly appreciated.
Cheers
Hey Wouter, you can use the following website to convert the code to vb.net. That’s What i did and it works perfectly
http://labs.developerfusion.co.uk/convert/csharp-to-vb.aspx
All the functionalities works except when I tries to get the First and Last page index, it always return 0. Any idea why?
Thanks for sharing your work. This saves me a whole lot of time to get something that looks really good.
Great article.. I tried with Asp.Net AJAX update panel it is working good but only after 1 postback.. I just added to an update panel and tried, when I click page 2 its causing full post back then its working perfectly with partial postbacks,, can you please check and rectify this issue?