GridView — это отличный сильно кастомизируемый контрол ASP.NET. Сегодня я хочу показать, как создать наследник этого контрола, позволяющий добавлять пейджинг в стиле Digg в ваше приложение.
Для начала, отнаследуемся и добавим свойство UseCustomPager, которое будет определять, использовать или нет пейджинг в стиле Digg:
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 содержит виртуальный метод InitializePager, который можно перегрузить для создания собственного пейджера:
{
if (UseCustomPager)
CreateCustomPager(row, columnSpan, pagedDataSource);
else
base.InitializePager(row, columnSpan, 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;
}
Несколько настроек пейджера используются в этом коде:
- PagerSettings.PreviousPageText — текст, который будет отображаться на кнопке “Previous”.
- PagerSettings.NextPageText — текст, который будет отображаться на кнопке “Next”.
- PagerSettings.PageButtonCount — сколько страниц показывать до и после текущей.
Вы могли обратить внимание, что в методе BuildLinkButton используется контрол PagerLinkButton. Это всего лишь наследник LinkButton, который упрощает использование внутри GridViewWithPager:
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
}
}
Наш контрол почти закончен. Осталось определить метод ParentBuildCallbackArgument. Как можно увидеть из исходников GridView, этот метод используется для сериализации индекса страницы, порядка сортировки и выражения сортировки, но, по каким-то причинам, он был объявлен как внутренний (internal). Я не люблю хаки, но в этом случае считаю, что можно немного схитрить:
{
MethodInfo m =
typeof (GridView).GetMethod("BuildCallbackArgument", BindingFlags.NonPublic | BindingFlags.Instance, null,
new Type[] {typeof (int)}, null);
return (string) m.Invoke(this, new object[] {pageIndex});
}
Кстати, как можно заметить, я не добавлял комментарии к методам. Это сделано специально, потому что я не хочу создавать библиотеку контролов, а просто делюсь своим опытом
И теперь пример использования:
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>
И скриншот:
Исходный код можно загрузить отсюда.
Русский
English
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.