Как стать автором
Обновить

Реализация реверсивного NumericPagerField в ASP.NET

Время на прочтение17 мин
Количество просмотров505
Последняя на данный момент версия .NET Framework — 3.5 принесла нам множество усовершенствований и новых возможностей, одними из которых являются «сладкая парочка» ListView и DataPager.

В один прекрасный момент мне захотелось реализовать с помощью DataPager навигацию между страницами, в которой цифры бы шли от большего к меньшему, как это сделано, например, на Хабре или на Лепре. Такой порядок наиболее подходит для отображения списка отсортированного, например, по дате — сначала новое. Нормальный отсчет элементарно реализуется с помощью поля NumericPagerField, но этот контрол не содержит методов для изменения направления отсчета.

Гугление не дало готового ответа на поставленный вопрос, поэтому передо мной стал выбор – или использовать TemplatePagerField и совершенно в нем запутаться или попытаться как-то изменить готовый NumericPagerField. Второй путь понравился мне больше, по нему я и отправился.


Сначала я хотел переписать CreateDataPager метод NumericPagerField’а, однако оказалось, что это невозможно, поэтому пришлось наследовать базовый класс NumericPagerField’а – DataPagerField и создавать свой собственный пейджер. При этом я воспользовался исходными кодами .NET Framework 3.5, которые имеются в свободном доступе.

Итак, создаем реверсивный пейджер. Для этого создадим новый класс ReversedNumericPagerField, унаследовав DataPagerField:

public class ReversedNumericPagerField: DataPagerField
{
  private int _startRowIndex;
  private int _maximumRows;
  private int _totalRowCount;

  public ReversedNumericPagerField()
  {
  }
}
* This source code was highlighted with Source Code Highlighter.


Для того, чтобы наш новый пейджер хоть как-то работал, необходимо реализовать в нем следующие методы: CreateField, CreateDataPagers и HandleEvent. Так же нам понадобятся некоторые глобальные переменные: _startRowIndex – индекс элемента списка, который будет отображаться первым, _maximumRows – количество элементов списка, которые будут отображаться на одной странице и _totalRowCount – количество элементов в списке.

Метод CreateField.
С этим все просто — возврашает экземпляр класса ReversedNumericPagerField.

protected override DataPagerField CreateField()
{
  return new ReversedNumericPagerField();
}
* This source code was highlighted with Source Code Highlighter.


Метод CreateDataPagers.
Этот метод нам и нужен – он определяет, что и когда отображать на странице и создает нужные элементы управления.

public override void CreateDataPagers(DataPagerFieldItem container, int startRowIndex, int maximumRows, int totalRowCount, int fieldIndex)
{
  _startRowIndex = startRowIndex;
  _maximumRows = maximumRows;
  _totalRowCount = totalRowCount;

  CreateDataPagersForCommand(container, fieldIndex);
}
* This source code was highlighted with Source Code Highlighter.


Стоит отметить, что оригинальный NumericPagerField может генерировать как нормальные ссылки на страницы, с использованием на страницы, с использованием QueryStrings, так и кнопки типа LinkButton. Мне необходимо было только второе, но если вам необходимы оба случая, то лучше всего их реализовать в разных функциях и выбирать нужную, в зависимости от свойства данного элемента управления (контролла). Функциональность для LinkButton в данном случае реализует функция CreateDataPagersForCommand, которая путем «несложных» математических преобразований определяет какую же все-таки цифру рисовать на кнопке и создает эту кнопку.

private void CreateDataPagersForCommand(DataPagerFieldItem container, int fieldIndex)
{
  int currentPageIndex = _startRowIndex / _maximumRows;
  int firstButtonIndex = (_startRowIndex / (ButtonCount * _maximumRows)) * ButtonCount;
  int lastButtonIndex = firstButtonIndex + ButtonCount — 1;
  int lastRecordIndex = ((lastButtonIndex + 1) * _maximumRows) — 1;
  int totalPages = (int)Math.Ceiling((double)_totalRowCount / _maximumRows);

  for (int i = 0; i < ButtonCount && _totalRowCount > ((i + firstButtonIndex) * _maximumRows); i++)
  {
    if (i + firstButtonIndex == currentPageIndex)
    {
      Label pageNumber = new Label();
      pageNumber.Text = (totalPages — i — firstButtonIndex).ToString();
      container.Controls.Add(pageNumber);
    }
    else
    {
      container.Controls.Add(CreateNumericButton((totalPages — i — firstButtonIndex).ToString(), fieldIndex.ToString(), (i + firstButtonIndex).ToString()));
    }
    container.Controls.Add(new LiteralControl(" "));
  }
}
* This source code was highlighted with Source Code Highlighter.


Здесь для создания кнопки вызывается следующая функция, которая, собственно, возврашает экземпляр LinkButton с заданными свойствами.

private Control CreateNumericButton(string buttonText, string commandArgument, string commandName)
{
  LinkButton button = new LinkButton();
  button.Text = buttonText;
  button.CommandName = commandName;
  button.CommandArgument = commandArgument;
  return button as Control;
}
* This source code was highlighted with Source Code Highlighter.


Метод HandleEvent.
Этот метод обрабатывает события в нашем пейджере и практически без изменений был взят из наследуемого класса.

public override void HandleEvent(CommandEventArgs e)
{
    int newStartRowIndex = -1;
    int currentPageIndex = _startRowIndex / DataPager.PageSize;
    int firstButtonIndex = (_startRowIndex / (ButtonCount * DataPager.PageSize)) * ButtonCount;
    int lastButtonIndex = firstButtonIndex + ButtonCount — 1;
    int lastRecordIndex = ((lastButtonIndex + 1) * DataPager.PageSize) — 1;

    if (String.Equals(e.CommandName, DataControlCommands.PreviousPageCommandArgument))
    {
      newStartRowIndex = (firstButtonIndex — 1) * DataPager.PageSize;
      if (newStartRowIndex < 0)
      {
        newStartRowIndex = 0;
      }
    }
    else if (String.Equals(e.CommandName, DataControlCommands.NextPageCommandArgument))
    {
      newStartRowIndex = lastRecordIndex + 1;
      if (newStartRowIndex > _totalRowCount)
      {
        newStartRowIndex = _totalRowCount — DataPager.PageSize;
      }
    }
    else
    {
      int pageIndex = Convert.ToInt32(e.CommandName, CultureInfo.InvariantCulture);
      newStartRowIndex = pageIndex * DataPager.PageSize;
    }

    if (newStartRowIndex != -1)
    {
      DataPager.SetPageProperties(newStartRowIndex, DataPager.PageSize, true);
    }
}
* This source code was highlighted with Source Code Highlighter.


Это в принципе все, полученный пейджер будет отображать цифры в нужной последовательности, но будет делать это очень некрасиво, так он фактически является голым – в нем нет свойств, задающих CSS классы, он не создает ссылки для предыдущей и следующей страниц и т.п. Зато, надеюсь, так понятней, как это работает. Следующий код представляет собой наш пейджер полностью, со всеми вспомогательными функциями и свойствами.

public class ReversedNumericPagerField: DataPagerField
{
  private int _startRowIndex;
  private int _maximumRows;
  private int _totalRowCount;

  public ReversedNumericPagerField()
  {
  }

  public int ButtonCount
  {
    get
    {
      object o = ViewState[«ButtonCount»];
      if (o != null)
      {
        return (int)o;
      }
      return 5;
    }
    set
    {
      if (value < 1)
      {
        throw new ArgumentOutOfRangeException(«value»);
      }
      if (value != ButtonCount)
      {
        ViewState[«ButtonCount»] = value;
        OnFieldChanged();
      }
    }
  }

  public ButtonType ButtonType
  {
    get
    {
      object o = ViewState[«ButtonType»];
      if (o != null)
        return (ButtonType)o;
      return ButtonType.Link;
    }
    set
    {
      if (value < ButtonType.Button || value > ButtonType.Link)
      {
        throw new ArgumentOutOfRangeException(«value»);
      }
      if (value != ButtonType)
      {
        ViewState[«ButtonType»] = value;
        OnFieldChanged();
      }
    }
  }

  public string CurrentPageLabelCssClass
  {
    get
    {
      object o = ViewState[«CurrentPageLabelCssClass»];
      if (o != null)
      {
        return (string)o;
      }
      return String.Empty;
    }
    set
    {
      if (value != CurrentPageLabelCssClass)
      {
        ViewState[«CurrentPageLabelCssClass»] = value;
        OnFieldChanged();
      }
    }
  }

  public string NextPageImageUrl
  {
    get
    {
      object o = ViewState[«NextPageImageUrl»];
      if (o != null)
      {
        return (string)o;
      }
      return String.Empty;
    }
    set
    {
      if (value != NextPageImageUrl)
      {
        ViewState[«NextPageImageUrl»] = value;
        OnFieldChanged();
      }
    }
  }

  public string NextPageText
  {
    get
    {
      object o = ViewState[«NextPageText»];
      if (o != null)
      {
        return (string)o;
      }
      return "→";
    }
    set
    {
      if (value != NextPageText)
      {
        ViewState[«NextPageText»] = value;
        OnFieldChanged();
      }
    }
  }

  public string NextPreviousButtonCssClass
  {
    get
    {
      object o = ViewState[«NextPreviousButtonCssClass»];
      if (o != null)
      {
        return (string)o;
      }
      return String.Empty;
    }
    set
    {
      if (value != NextPreviousButtonCssClass)
      {
        ViewState[«NextPreviousButtonCssClass»] = value;
        OnFieldChanged();
      }
    }
  }

  public string NumericButtonCssClass
  {
    get
    {
      object o = ViewState[«NumericButtonCssClass»];
      if (o != null)
      {
        return (string)o;
      }
      return String.Empty;
    }
    set
    {
      if (value != NumericButtonCssClass)
      {
        ViewState[«NumericButtonCssClass»] = value;
        OnFieldChanged();
      }
    }
  }

  public string PreviousPageImageUrl
  {
    get
    {
      object o = ViewState[«PreviousPageImageUrl»];
      if (o != null)
      {
        return (string)o;
      }
      return String.Empty;
    }
    set
    {
      if (value != PreviousPageImageUrl)
      {
        ViewState[«PreviousPageImageUrl»] = value;
        OnFieldChanged();
      }
    }
  }

  public string PreviousPageText
  {
    get
    {
      object o = ViewState[«PreviousPageText»];
      if (o != null)
      {
        return (string)o;
      }
      return "←";
    }
    set
    {
      if (value != PreviousPageText)
      {
        ViewState[«PreviousPageText»] = value;
        OnFieldChanged();
      }
    }
  }

  public bool RenderNonBreakingSpacesBetweenControls
  {
    get
    {
      object o = ViewState[«RenderNonBreakingSpacesBetweenControls»];
      if (o != null)
      {
        return (bool)o;
      }
      return true;
    }
    set
    {
      if (value != RenderNonBreakingSpacesBetweenControls)
      {
        ViewState[«RenderNonBreakingSpacesBetweenControls»] = value;
        OnFieldChanged();
      }
    }
  }

  private void AddNonBreakingSpace(DataPagerFieldItem container)
  {
    if (RenderNonBreakingSpacesBetweenControls)
    {
      container.Controls.Add(new LiteralControl(" "));
    }
  }

  protected override DataPagerField CreateField()
  {
    return new ReversedNumericPagerField();
  }

  public override void HandleEvent(CommandEventArgs e)
  {
    int newStartRowIndex = -1;
    int currentPageIndex = _startRowIndex / DataPager.PageSize;
    int firstButtonIndex = (_startRowIndex / (ButtonCount * DataPager.PageSize)) * ButtonCount;
    int lastButtonIndex = firstButtonIndex + ButtonCount — 1;
    int lastRecordIndex = ((lastButtonIndex + 1) * DataPager.PageSize) — 1;

    if (String.Equals(e.CommandName, DataControlCommands.PreviousPageCommandArgument))
    {
      newStartRowIndex = (firstButtonIndex — 1) * DataPager.PageSize;
      if (newStartRowIndex < 0)
      {
        newStartRowIndex = 0;
      }
    }
    else if (String.Equals(e.CommandName, DataControlCommands.NextPageCommandArgument))
    {
      newStartRowIndex = lastRecordIndex + 1;
      if (newStartRowIndex > _totalRowCount)
      {
        newStartRowIndex = _totalRowCount — DataPager.PageSize;
      }
    }
    else
    {
      int pageIndex = Convert.ToInt32(e.CommandName, CultureInfo.InvariantCulture);
      newStartRowIndex = pageIndex * DataPager.PageSize;
    }

    if (newStartRowIndex != -1)
    {
      DataPager.SetPageProperties(newStartRowIndex, DataPager.PageSize, true);
    }
  }

  private Control CreateNumericButton(string buttonText, string commandArgument, string commandName)
  {
    IButtonControl button;

    switch (ButtonType)
    {
      case ButtonType.Button:
        button = new Button();
        break;
      case ButtonType.Link:
      default:
        button = new LinkButton();
        break;
    }

    button.Text = buttonText;
    button.CommandName = commandName;
    button.CommandArgument = commandArgument;

    WebControl webControl = button as WebControl;
    if (webControl != null && !String.IsNullOrEmpty(NumericButtonCssClass))
    {
      webControl.CssClass = NumericButtonCssClass;
    }

    return button as Control;
  }

  private Control CreateNextPrevButton(string buttonText, string commandName, string commandArgument, string imageUrl)
  {
    IButtonControl button;

    switch (ButtonType)
    {
      case ButtonType.Link:
        button = new LinkButton();
        break;
      case ButtonType.Button:
        button = new Button();
        break;
      case ButtonType.Image:
      default:
        button = new ImageButton();
        ((ImageButton)button).ImageUrl = imageUrl;
        ((ImageButton)button).AlternateText = HttpUtility.HtmlDecode(buttonText);
        break;
    }
    button.Text = buttonText;
    button.CommandName = commandName;
    button.CommandArgument = commandArgument;

    WebControl webControl = button as WebControl;
    if (webControl != null && !String.IsNullOrEmpty(NextPreviousButtonCssClass))
    {
      webControl.CssClass = NextPreviousButtonCssClass;
    }

    return button as Control;
  }

  public override void CreateDataPagers(DataPagerFieldItem container, int startRowIndex, int maximumRows, int totalRowCount, int fieldIndex)
  {
    _startRowIndex = startRowIndex;
    _maximumRows = maximumRows;
    _totalRowCount = totalRowCount;

    CreateDataPagersForCommand(container, fieldIndex);
  }

  private void CreateDataPagersForCommand(DataPagerFieldItem container, int fieldIndex)
  {
    int currentPageIndex = _startRowIndex / _maximumRows;
    int firstButtonIndex = (_startRowIndex / (ButtonCount * _maximumRows)) * ButtonCount;
    int lastButtonIndex = firstButtonIndex + ButtonCount — 1;
    int lastRecordIndex = ((lastButtonIndex + 1) * _maximumRows) — 1;
    int totalPages = (int)Math.Ceiling((double)_totalRowCount / _maximumRows);

    if (firstButtonIndex != 0)
    {
      container.Controls.Add(CreateNextPrevButton(PreviousPageText, DataControlCommands.PreviousPageCommandArgument, fieldIndex.ToString(CultureInfo.InvariantCulture), PreviousPageImageUrl));
      AddNonBreakingSpace(container);
    }

    for (int i = 0; i < ButtonCount && _totalRowCount > ((i + firstButtonIndex) * _maximumRows); i++)
    {
      if (i + firstButtonIndex == currentPageIndex)
      {
        Label pageNumber = new Label();
        pageNumber.Text = (totalPages — i — firstButtonIndex).ToString(CultureInfo.InvariantCulture);
        if (!String.IsNullOrEmpty(CurrentPageLabelCssClass))
        {
          pageNumber.CssClass = CurrentPageLabelCssClass;
        }
        container.Controls.Add(pageNumber);
      }
      else
      {
        container.Controls.Add(CreateNumericButton((totalPages — i — firstButtonIndex).ToString(CultureInfo.InvariantCulture), fieldIndex.ToString(CultureInfo.InvariantCulture), (i + firstButtonIndex).ToString(CultureInfo.InvariantCulture)));
      }
      AddNonBreakingSpace(container);
    }

    if (lastRecordIndex < _totalRowCount — 1)
    {
      AddNonBreakingSpace(container);
      container.Controls.Add(CreateNextPrevButton(NextPageText, DataControlCommands.NextPageCommandArgument, fieldIndex.ToString(CultureInfo.InvariantCulture), NextPageImageUrl));
      AddNonBreakingSpace(container);
    }
  }
}
* This source code was highlighted with Source Code Highlighter.


Данный класс выполняет положенную на него функцию и отображает цифры в обратном порядке. Однако он был создан на скорую руку и возможно математика в функции CreateDataPagersForCommand не совсем оптимальна. В любом случае, я надеюсь что эта статья поможет кому-нибудь.

И еще. Так как это моя первая статья о программировании и соответственно первая статья на Хабре – мне было непросто подобрать нужную русскую программистскую терминологию, так как в повседневной практике я использую английскую. Так что пишите замечания, исправлю. И посоветуйте англо-русский словарик программиста :)
Теги:
Хабы:
+7
Комментарии7

Публикации