Советы и приемы при работе с ListView - Множественные шаблоны элементов
ОГЛАВЛЕНИЕ
Множественные шаблоны элементов
Как и прочие основанные на шаблонах и привязанные к данным элементы управления, ListView повторяет один и тот же шаблон элемента для каждого привязанного элемента данных. Что если его нужно изменить для определенного подмножества элементов? Честно говоря, должен признаться, что мне ни разу не требовалось использовать более чем один шаблон элемента за годы программирования в ASP.NET. Несколько раз я индивидуализировал внешний вид небольшой группы элементов в элементах управления DataGrid и GridView, основываясь на условиях среды выполнения, однако это всегда влекло за собой применение иного набора атрибутов стиля.
Лишь в очень немногих случаях я программно добавлял новые элементы управления (в основном элементы Label («Метка») или клетки таблицы) к существующему шаблону. В привязанных к данным элементах управления, запускающих события привязки к данным, это не является особо сложной задачей, по крайней мере, если разработчик хорошо знает внутреннюю структуру элементов управления, с которыми он работает.
Хотя программное внедрение элементов управления – это решение, которое отлично работает на практике, у меня к нему никогда не лежала душа. Так что я решил попробовать иной подход, когда клиент попросил меня изменить основанное на ListView меню веб-страницы. В меню, подобном изображенному на рис. 1, мне требовалось сменить визуализацию элементов одного их подменю с вертикальной на горизонтальную.
Элемент управления ListView создает свою разметку, просматривая источник данных и применяя следующий алгоритм. В первую очередь проверяется, требуется ли разделитель элементов. Если да, то создается экземпляр шаблона и объект элемента данных. Объект элемента данных является контейнером шаблона элемента и несет информацию о индексе элемента в представлении и привязанном источнике данных. Когда создается экземпляр шаблона элемента, запускается событие ItemCreated. Следующий этап – это привязка данных. После его завершения запускается событие ItemDataBound.
Как можно заметить, общедоступное событие, которое можно было бы обработать и которое позволяло бы программно изменять шаблон для каждого элемента, отсутствует. Шаблон можно изменить в событиях страницы Init или Load, но он изменится для всех привязанных элементов. Если обработать ItemCreated и установить там свойство ItemTemplate, изменение повлияет на следующий элемент, но не на обрабатываемый в настоящий момент. Здесь необходимо событие ItemCreating, но ListView не запускает такого события. Следовательно, решение заключается в создании собственного элемента управления ListView, как показано на рис. 6.
Рис. 6 Запуск события ItemCreating
namespace Samples.Controls
{
public class ListViewItemCreatingEventArgs : EventArgs
{
private int _dataItemIndex;
private int _displayIndex;
public ListViewItemCreatingEventArgs(int dataItemIndex,
int displayIndex) {
_dataItemIndex = dataItemIndex;
_displayIndex = displayIndex;
}
public int DisplayIndex {
get { return _displayIndex; }
set { _displayIndex = value; }
}
public int DataItemIndex {
get { return _dataItemIndex; }
set { _dataItemIndex = value; }
}
}
public class ListView : System.Web.UI.WebControls.ListView
{
public event EventHandler<ListViewItemCreatingEventArgs>
ItemCreating;
protected override ListViewDataItem CreateDataItem(int
dataItemIndex, int displayIndex) {
// Fire a NEW event: ItemCreating
if (ItemCreating != null)
ItemCreating(this, new ListViewItemCreatingEventArgs
(dataItemIndex, displayIndex));
// Call the base method
return base.CreateDataItem(_dataItemIndex, displayIndex);
}
}
}
Переопределение метода CreateDataItem дает шанс выполнить код непосредственно перед созданием шаблона элемента. Метод CreateDataItem объявляется защищенным и виртуальным в классе ListView. Как можно увидеть на рис. 6, переопределить метод довольно просто. Сперва запускается индивидуализированное событие ItemCreating, а затем вызывается базовый метод.
Событие ItemCreating передает несколько целых чисел обратно коду пользователя – абсолютный индекс элемента в источнике данных и индекс для определенной страницы. Например, для страницы размером 10, когда ListView работает над визуализацией первого элемента второй страницы, dataItemIndex содержит 11 элементов, а displayIndex – 1 элемент. Чтобы использовать новое событие ItemCreating, просто объявите метод и обработчик в индивидуализированном элементе управления ListView, как показано в следующем коде:
<x:ListView runat="server" ID="ListView1"
ItemPlaceholderID="itemPlaceholder"
DataSourceID="ObjectDataSource1"
OnItemCreating="ListView1_ItemCreating">
<LayoutTemplate>
<div>
<asp:PlaceHolder runat="server" ID="itemPlaceholder" />
</div>
</LayoutTemplate>
</x:ListView>
В своем коде событие можно обработать следующим образом:
void ListView1_ItemCreating(
object sender, ListViewItemCreatingEventArgs e)
{
string url = "standard.ascx";
if (e.DisplayIndex % DataPager1.PageSize == 0)
url = "firstItem.ascx";
ListView1.ItemTemplate = Page.LoadTemplate(url);
}
Здесь для визуализации элементов данных используются два пользовательских элемента управления. Конкретный пользовательский элемент управления определяется индексом отображения. Все элементы, кроме первого, используют тот же шаблон. На рис. 7 показана страница в действии.
Рис. 7 Множественные шаблоны элементов
При мысли о сложности реальных страниц это решение кажется слишком простым. Чаще всего приходится использовать различные шаблоны, основываясь на отображаемом содержимом. Для изменения шаблона данных в процессе привязки данных необходимо дополнительно усовершенствовать элемент управления ListView. Взгляните на код, показанный на рис. 8.
Рис. 8 Выбор шаблона на основе содержимого
namespace Samples.Controls
{
public class ListViewItemCreatingEventArgs : EventArgs
{
private int _dataItemIndex;
private int _displayIndex;
public ListViewItemCreatingEventArgs(int dataItemIndex,
int displayIndex) {
_dataItemIndex = dataItemIndex;
_displayIndex = displayIndex;
}
public int DisplayIndex {
get { return _displayIndex; }
set { _displayIndex = value; }
}
public int DataItemIndex {
get { return _dataItemIndex; }
set { _dataItemIndex = value; }
}
}
public class ListView : System.Web.UI.WebControls.ListView
{
public event EventHandler<ListViewItemCreatingEventArgs>
ItemCreating;
private int _displayIndex;
private bool _shouldInstantiate = false;
protected override void InstantiateItemTemplate(Control container,
int displayIndex) {
if (_shouldInstantiate) {
base.InstantiateItemTemplate(container, displayIndex);
_shouldInstantiate = false;
}
}
protected override ListViewDataItem CreateDataItem(int
dataItemIndex, int displayIndex) {
// Fire a NEW event: ItemCreating
if (ItemCreating != null)
ItemCreating(this, new
ListViewItemCreatingEventArgs(dataItemIndex,
displayIndex));
// Cache for later
_displayIndex = displayIndex;
// Call the base method
return base.CreateDataItem(_dataItemIndex, displayIndex);
}
protected override void OnItemCreated(ListViewItemEventArgs e) {
base.OnItemCreated(e);
// You can proceed with template instantiation now
_shouldInstantiate = true;
InstantiateItemTemplate(e.Item, _displayIndex);
}
}
}
Метод CreateDataItem запускает событие ItemCreating и кэширует индекс отображения для последующего использования. Вдобавок, метод InstantiateItemTemplate переопределяется, чтобы задержать собственно создание экземпляра шаблона. Для этой цели используется закрытый логический флаг. Как уже упоминалось, элемент управления ListView запускает процесс привязки данных после создания экземпляра шаблона.
Однако в реализации, показанной в коде на рис. 8, реально не создается никаких экземпляров шаблона элемента до запуска события ItemCreated. После возникновения события ItemCreated объект элемента данных привязывается к контейнеру элемента ListView через свойство DataItem. Путем обработки события ItemCreated в коде можно решить, какой шаблон использовать, на основе привязанного элемента данных, как показано ниже:
protected override void OnItemCreated(ListViewItemEventArgs e)
{
base.OnItemCreated(e);
_shouldInstantiate = true;
InstantiateItemTemplate(e.Item, _displayIndex);
}
В данном случае базовый метод запускает событие ItemCreated для страницы. После этого индивидуализированный элемент управления ListView выполняет сброс логического флага и вызывает метод для создания экземпляра шаблона элемента. В итоге экземпляр шаблона элемента создается чуть позже, чем во встроенном элементе управления ListView, но свойство ItemTemplate можно программно установить для каждого элемента в обработчике событий ItemCreated после просмотра содержимого привязанного элемента данных (см. рис. 9). На рис. 10 показан пример страницы, где синий шаблон используется для мужчин, а розовый – для женщин.
Рис. 9 Установка шаблона элемента
void ListView1_ItemCreated(object sender, ListViewItemEventArgs e)
{
// Grab a reference to the data item
ListViewDataItem currentItem = (e.Item as ListViewDataItem);
Employee emp = (Employee) currentItem.DataItem;
if (emp == null)
return;
// Apply your logic here
string titleOfCourtesy = emp.TitleOfCourtesy.ToLower();
string url = "forgentlemen.ascx";
if (titleOfCourtesy == "ms." || titleOfCourtesy == "mrs.")
url = "forladies.ascx";
// Set the item template to use
Samples.ListView ctl = (sender as Samples.Controls.ListView);
ctl.ItemTemplate = Page.LoadTemplate(url);
}
Рис. 10 Стандартное меню
Заключение
В конечном итоге новый элемент управления ListView в ASP.NET 3.5 является переработанной версией элемента управления DataList, существовавшего со времен ASP.NET 1.0. ListView допускает более жесткий контроль над создаваемой разметкой и полностью поддерживает объекты источников данных.
В этой статье было показано, как использовать вложенные ListView для создания разбитых на страницы, многоуровневых представлений данных и как изменять стандартный процесс визуализации ListView, создавая индивидуализированный производный элемент управления и переопределяя несколько методов. Конечным результатом является элемент управления, поддерживающий множественные шаблоны элементов. Это единственный привязанный к данным элемент управления ASP.NET, предоставляющий подобный уровень гибкости.
Дино Эспозито (Dino Esposito) работает архитектором в компании IDesign и является автором книги Programming ASP.NET 3.5 Core Reference («Справочник по основам программирования в среде ASP.NET 3.5»). Проживая в Италии, Дино часто выступает на различных профессиональных мероприятиях по всему миру.