Последняя на данный момент версия .NET Framework — 3.5 принесла нам множество усовершенствований и новых возможностей, одними из которых являются «сладкая парочка» ListView и DataPager.
В один прекрасный момент мне захотелось реализовать с помощью DataPager навигацию между страницами, в которой цифры бы шли от большего к меньшему, как это сделано, например, на Хабре или на Лепре. Такой порядок наиболее подходит для отображения списка отсортированного, например, по дате — сначала новое. Нормальный отсчет элементарно реализуется с помощью поля NumericPagerField, но этот контрол не содержит методов для изменения направления отсчета.
Гугление не дало готового ответа на поставленный вопрос, поэтому передо мной стал выбор – или использовать TemplatePagerField и совершенно в нем запутаться или попытаться как-то изменить готовый NumericPagerField. Второй путь понравился мне больше, по нему я и отправился.
Сначала я хотел переписать CreateDataPager метод NumericPagerField’а, однако оказалось, что это невозможно, поэтому пришлось наследовать базовый класс NumericPagerField’а – DataPagerField и создавать свой собственный пейджер. При этом я воспользовался исходными кодами .NET Framework 3.5, которые имеются в свободном доступе.
Итак, создаем реверсивный пейджер. Для этого создадим новый класс ReversedNumericPagerField, унаследовав DataPagerField:
Для того, чтобы наш новый пейджер хоть как-то работал, необходимо реализовать в нем следующие методы: CreateField, CreateDataPagers и HandleEvent. Так же нам понадобятся некоторые глобальные переменные: _startRowIndex – индекс элемента списка, который будет отображаться первым, _maximumRows – количество элементов списка, которые будут отображаться на одной странице и _totalRowCount – количество элементов в списке.
Метод CreateField.
С этим все просто — возврашает экземпляр класса ReversedNumericPagerField.
Метод CreateDataPagers.
Этот метод нам и нужен – он определяет, что и когда отображать на странице и создает нужные элементы управления.
Стоит отметить, что оригинальный NumericPagerField может генерировать как нормальные ссылки на страницы, с использованием на страницы, с использованием QueryStrings, так и кнопки типа LinkButton. Мне необходимо было только второе, но если вам необходимы оба случая, то лучше всего их реализовать в разных функциях и выбирать нужную, в зависимости от свойства данного элемента управления (контролла). Функциональность для LinkButton в данном случае реализует функция CreateDataPagersForCommand, которая путем «несложных» математических преобразований определяет какую же все-таки цифру рисовать на кнопке и создает эту кнопку.
Здесь для создания кнопки вызывается следующая функция, которая, собственно, возврашает экземпляр LinkButton с заданными свойствами.
Метод HandleEvent.
Этот метод обрабатывает события в нашем пейджере и практически без изменений был взят из наследуемого класса.
Это в принципе все, полученный пейджер будет отображать цифры в нужной последовательности, но будет делать это очень некрасиво, так он фактически является голым – в нем нет свойств, задающих CSS классы, он не создает ссылки для предыдущей и следующей страниц и т.п. Зато, надеюсь, так понятней, как это работает. Следующий код представляет собой наш пейджер полностью, со всеми вспомогательными функциями и свойствами.
Данный класс выполняет положенную на него функцию и отображает цифры в обратном порядке. Однако он был создан на скорую руку и возможно математика в функции CreateDataPagersForCommand не совсем оптимальна. В любом случае, я надеюсь что эта статья поможет кому-нибудь.
И еще. Так как это моя первая статья о программировании и соответственно первая статья на Хабре – мне было непросто подобрать нужную русскую программистскую терминологию, так как в повседневной практике я использую английскую. Так что пишите замечания, исправлю. И посоветуйте англо-русский словарик программиста :)
В один прекрасный момент мне захотелось реализовать с помощью 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 не совсем оптимальна. В любом случае, я надеюсь что эта статья поможет кому-нибудь.
И еще. Так как это моя первая статья о программировании и соответственно первая статья на Хабре – мне было непросто подобрать нужную русскую программистскую терминологию, так как в повседневной практике я использую английскую. Так что пишите замечания, исправлю. И посоветуйте англо-русский словарик программиста :)