GridView с пейджингом в стиле Digg

(ASP.NET) · English (36,186 views)

GridView — это отличный сильно кастомизируемый контрол ASP.NET. Сегодня я хочу показать, как создать наследник этого контрола, позволяющий добавлять пейджинг в стиле Digg в ваше приложение.

Для начала, отнаследуемся и добавим свойство UseCustomPager, которое будет определять, использовать или нет пейджинг в стиле Digg:

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 содержит виртуальный метод InitializePager, который можно перегрузить для создания собственного пейджера:

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);
}

Теперь создадим собственный пейджер:

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("&hellip;", 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:

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
    }
}

Наш контрол почти закончен. Осталось определить метод ParentBuildCallbackArgument. Как можно увидеть из исходников GridView, этот метод используется для сериализации индекса страницы, порядка сортировки и выражения сортировки, но, по каким-то причинам, он был объявлен как внутренний (internal). Я не люблю хаки, но в этом случае считаю, что можно немного схитрить:

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});
}

Кстати, как можно заметить, я не добавлял комментарии к методам. Это сделано специально, потому что я не хочу создавать библиотеку контролов, а просто делюсь своим опытом :-)

И теперь пример использования:

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="&laquo; previous"
        NextPageText="next &raquo;" 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>

И скриншот:

Пейджинг в стиле Digg

Исходный код можно загрузить отсюда.

13 Responses to this entry

Subscribe to comments with RSS

Kigorw
said on 29.08.2007 at 11.57 · Permalink

GridView — это отличный сильно кастомизируемый контрол ASP.NET. – Ты серьезно так считаешь?

said on 29.08.2007 at 12.27 · Permalink

Да, и не думаю, что я сильно ошибаюсь :-) Все, что нужно – легко получить. Включая DIV’ную разметку вместо табличной, которую он генерит по умолчанию, всевозможные сортировки и пейджинги, управление блоками данных (колонками в частности). Да, это действительно отлично кастомизируемый контрол. Если есть пример того, что сложно сделать — велкам.

Kigorw
said on 29.08.2007 at 13.40 · Permalink

Вполне допускаю что я его криво использую.

1. Была задача реализовать фильтры, встроенные в каждую колонку. Реализовал, но криво, джаваскриптом создаю строку пустую и туда засовываю фильтры.

2. Проблема в датабиндинге. Связал с обжект датасоурсом. При любом действии (то же нажатие на кнопку пейджинга) вызывается много раз DataBind, что ведет к ненужным запросом данных…

Мысль в том что не вижу смысла бороться с этой всей кастомизацией, делая элементарные действия. Не хочу думать про весь этот цикл событий жизни, вспоминая что исполнится первыми и где тут еще поставить очередной DataBind, чтоб все отрисовалось правильно…

Total Beaver
said on 19.09.2007 at 22.46 · Permalink

Standard GridView is sux with such paging style or another.

said on 26.09.2007 at 16.52 · Permalink

Very nice one I implemented it in one of my projects and looks + works perfectly fine.

Thanks a bunch

said on 26.09.2007 at 23.31 · Permalink

great code but does someone has the VB.net translation of this, im not into C#

thx
Wouter

Mel User
said on 29.04.2008 at 9.48 · Permalink

All the functionalities works except when I tries to get the First and Last page index, it always return 0. Any idea why?

Andrew
said on 11.08.2008 at 20.32 · Permalink

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

krishna
said on 04.09.2008 at 12.36 · Permalink

All the functionalities works except when I tries to get the First and Last page index, it always return 0. Any idea why?

steve martin
said on 29.09.2008 at 17.11 · Permalink

Thanks for sharing your work. This saves me a whole lot of time to get something that looks really good.

Anz @
said on 01.11.2008 at 15.24 · Permalink

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?

Comments are closed

Comments for this entry are closed for a while. If you have anything to say – use a contact form. Thank you for your patience.