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?