Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - ASP.NET »

Обсудить на форуме Обсудить на форуме

Использование событий ItemCreated/ItemDataBound в табличных элементах управления
Вспомним прошлые дни и банальную задачу вывода данных из таблицы базы данных на asp страницу в виде таблицы... В мире ASP.NET все, что нужно для выполнения данной задачи, это заполнить DataSet результатами запроса и связать его с DataGrid и DataList. Ничего сложного, ведь так? Сложности начинаются потом :).

Новые возможности связывания данных в ASP.NET

Вспомним прошлые дни и банальную задачу вывода данных из таблицы базы данных на asp страницу в виде таблицы. Для этого необходимо было открывать рекордсет и затем проходя по нему выводить данные с одновременным форматированием. Например вот так:

<%
set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;
Initial Catalog=Northwind;Data Source=(local);"
set rst = conn.Execute("SELECT ProductName, UnitPrice, 
UnitsInStock, Discontinued, CategoryID FROM Products" +
    " order by ProductName")
%>
<table ID="Table1">
<tr>
<td>Product</td><td>Price</td><td>
In Stock</td><td>Discontinued</td>
</tr>
<%
while not rst.eof
%>
<tr>
<td><%=
rst("ProductName")%></td><td><%=rst("UnitPrice")%>
</td><td><%=rst("UnitsInStock")%></td>
<td><%=rst("Discontinued")%></td>
</tr>
<%
rst.MoveNext
wend
%>

В мире ASP.NET, как вы наверное уже знаете, такие извращения ни к чему. Все, что нужно для выполнения данной задачи, это заполнить DataSet результатами запроса и связать его с DataGrid и DataList. Ничего сложного, ведь так? Сложности начинаются потом :).

Использования события ItemDataBound для управления отображением данных

Одна из таких сложностей – форматирование выводимых данных в зависимости от их значений. Например ваш начальник любит красивые таблички с цифрами, разрисованные разным цветом в зависимости от того, что там написано. И в приведенном выше примере начальник требует чтобы продукты первых четырех категорий имели различный цвет фона. Раньше решение данной задачи было тривиальным - в цикле вывода данные предварительно анализировались и в зависимости от различных параметров форматировались по разному (строились монструозные теги <font> или устанавливались соответствующие стили. На первый взгляд ничего такого в ASP.NET сделать нельзя, ведь объект DataGrid - единое целое и выводить с помощью него данные в цикле не получится. Но не все так плохо - на самом деле все можно сделать :)

Объект DataGrid (как впрочем и объекты DataList и Repeater) имеет событие ItemDataBound. Данное событие срабатывает для каждой строки из источника данных после того, как данные связаны в DataGrid, но до того, как эти данные выведены. В обработчике данного события вы и можете как раз повлиять на то, как эти данные будут представлены пользователю. Второй параметр обрабатчика этого события представляет собой объект типа DataGridItemEventArgs, с помощью которого можно произвести множество различных манипуляций с выводимыми данными.

В данной конкретной задаче нас интересуют следующие вопросы:

  1. Значения данных в текущей строке (нам же нужно форматировать выводимую информацию на основании каких-то цифр :).
  2. Доступ к выводимым результатам (действительно - их то и нужно же отформатировать). Все это (и много другое) можно найти в свойствах объекта DataGridItemEventArgs.

Поищем для начала исходные данные. Ссылка на объект с ними может быть найдена в свойстве DataGridItemEventArgs.Item.DataItem. В типизированном мире .NET это свойство имеет тип object, соответственно его нужно привести к какому-нибудь более приемлемому типу. Если ваши данные, которые вы связываете с DataGrid, находятся в объекте DataView, то данный объект можно смело приводить к DataRowView - не ошибетесь :). Итак теперь можно писать следующую конструкцию для получения категрии продукта:

switch((int)((DataRowView) e.Item.DataItem)["CategoryID"])

е в данном случае - объект типа DataGridItemEventArgs.

Теперь неплохо было бы соответствующим образом отформатировать выводимые данные, т.е. раскрасить фон в зависимости от значения CatogoryID. Доступ к такой вохможности также нужно искать в свойствах DataGridItemEventArgs.Item. И найти его несложно - свойство DataGridItemEventArgs.Item.BackColor позволяет манипулировать цветом фона выводимой строки. Как раз то, что нам нужно :).

case 1:
  e.Item.BackColor = Color.Aqua;
  break;

И, наконец, затронем еще один вопрос. Событие ItemDataBound срабатывает для каждой выводимой строки, не только для строки с данными. А что будет, если мы попробуем получить объект с данными для строки заголовков или разделителя? А исключение будет :). Боссу такого показывать нельзя, ибо босс, любящий исключения, сродни Windows 98, работающей без ошибок :). Значит перед всеми нашими манипуляциями необходимо сначала проверить какую строку сейчас мы собираемся обрабатывать. Опять таки в данной ситуации помощет свойство DataGridItemEventArgs.Item.ItemType типа ListItemType. Наш код должен выполняться только для типов Item и AlternatingItem.

if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)

Вот и все. Полный код asp.net страницы, рисующей разноцветные строки, представляю:

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Drawing" %>
<HTML>
   <script language="C#" runat="server">
   void Page_Load(object sender, System.EventArgs e)
   {
      SqlConnection myConn = 
new SqlConnection("server=localhost;database=Northwind;uid=sa;pwd=");
      SqlDataAdapter myData = 
new SqlDataAdapter("SELECT ProductName, UnitPrice, UnitsInStock, " +
"Discontinued, CategoryID FROM Products order by ProductName", myConn);
      DataSet ds = new DataSet();
      myData.Fill(ds);
      DataGrid1.DataSource = ds.Tables[0].DefaultView;
      DataGrid1.DataBind();
   }

   void DataGrid1_ItemDataBound(object sender, 
System.Web.UI.WebControls.DataGridItemEventArgs e)
   {
      if(e.Item.ItemType == ListItemType.Item || 
e.Item.ItemType == ListItemType.AlternatingItem)
      {
         switch((int)((DataRowView) e.Item.DataItem)["CategoryID"])
         {
            case 1:
               e.Item.BackColor = Color.Aqua;
               break;
            case 2:
               e.Item.BackColor = Color.Yellow;
               break;
            case 3:
               e.Item.BackColor = Color.FloralWhite;
               break;
            case 4:
               e.Item.BackColor = Color.Gold;
               break;
            default:
               e.Item.BackColor = Color.White;
               break;
         }
      }
   }

   </script>
   <HEAD>
      <title>DataGridExample1</title>
   </HEAD>
   <body MS_POSITIONING="FlowLayout">
      <form method="post" runat="server" ID="Form1">
         <asp:DataGrid id="DataGrid1" runat="server" 
OnItemDataBound="DataGrid1_ItemDataBound">
                        </asp:DataGrid>
      </form>
   </body>
</HTML>

Теперь неплохо бы определиться с некоторыми важными моментами, на которые я вскользь обратил внимание в прошлом примере. Первый момент – тип объекта, содержащего данные. Как вы помните свойство DataGridItemEventArgs.Item.DataItem имеет тип object и перед использованием данное свойство необходимо привести к правильному типу. Рассмотрим вкратце какие типы будут считаться «правильными» в конкретных случаях.

 

Тип объекта – источника данных Тип объекта DataGridItemEventArgs.Item.DataItem
DataView, DataTable System.Data.DataRowView
IDataReader (SqlDataReader, OleDbDataReader) System.Data.Common.DbDataRecord
Типизированная коллекция/массив объектов Тип объекта в коллекции (например для StringCollection – string, для DataTableCollection – DataTable)

Эти типы нужны вам для получения доступа к данным.

Также крайне важно знать какого типа строка в DataGrid/DataList сейчас связывается. Как я уже упоминал, некоторые типы строк не имеют объекта с данными (что часто бывает очень критично). Типы строк задаются перечислением ListItemType и могут принимать следующие значения:

AlternatingItem Четная строка с данными DataGrid, DataList, Repeater
EditItem Строка с редактируемыми данными DataGrid, DataList
Footer Дно элемента управления DataGrid, DataList, Repeater
Header Заголовок элемента управления DataGrid, DataList, Repeater
Item Строка с данными DataGrid, DataList, Repeater
Pager Строка, отображающая элементы управления для постраничного просмотра данных DataGrid
SelectedItem Выбранная строка с данными DataGrid, DataList
Separator Разделитель между строками с данными DataList, Repeater

Теперь вооружившись этими знаниями рассмотрим следующий пример: в элементе управления DataGrid отобразим информацию из таблицы продуктов базы Northwind с помощью SqlDataReader. Дополнительно также выведем стоимость товара на складе для каждого товара и итоговую сумму. Для неактивных товаров в соответствующей ячейке поставим символ X, иначе пробел. Также пронумеруем выводимые в DataGrid строки (удивительно как любит это начальство :)). Ну и чтобы проверить работу на максимальном количестве типов строк установим выбранную и редактируемую строки в значение, отличное от -1. А также заодно посмотрим на работу с элементами управления в ячейках (для столбца «On Order» программно заменим поле ввода, выводящееся по умолчанию для редактирования данных, на выпадающий список).

В классе формы описаны 2 переменные, используемые для нумерации строк (protected int num = 1;) и для суммы товаров на складе (private decimal total = 0;). Пришла пора рассмотреть код примера.

Код элемента управления DataGrid веб формы:

<asp:DataGrid id="DataGrid1" runat="server" 
AutoGenerateColumns="False" ShowFooter="True">
   <Columns>
      <asp:TemplateColumn HeaderText="#">
         <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
         <ItemStyle HorizontalAlign="Center"></ItemStyle>
         <ItemTemplate>
            <%# num%>
         </ItemTemplate>
      </asp:TemplateColumn>
      <asp:BoundColumn DataField="ProductName" 
HeaderText="Product"></asp:BoundColumn>
      <asp:BoundColumn DataField="QuantityPerUnit" 
HeaderText="Quantity/Unit"></asp:BoundColumn>
      <asp:BoundColumn DataField="UnitPrice" HeaderText="Price">
         <ItemStyle HorizontalAlign="Right"></ItemStyle>
      </asp:BoundColumn>
      <asp:BoundColumn DataField="UnitsInStock" HeaderText="In Stock">
         <ItemStyle HorizontalAlign="Right"></ItemStyle>
      </asp:BoundColumn>
      <asp:BoundColumn DataField="UnitsOnOrder" HeaderText="On Order">
         <ItemStyle HorizontalAlign="Right"></ItemStyle>
      </asp:BoundColumn>
      <asp:TemplateColumn HeaderText="Total In Stock">
         <ItemStyle HorizontalAlign="Right"></ItemStyle>
         <ItemTemplate>
            <asp:Label ID="lblTotal" Runat="server" />
         </ItemTemplate>
         <FooterStyle HorizontalAlign="Right"></FooterStyle>
      </asp:TemplateColumn>
      <asp:TemplateColumn HeaderText="Discontinued">
         <ItemStyle HorizontalAlign="Center"></ItemStyle>
      </asp:TemplateColumn>
   </Columns>
</asp:DataGrid>

Код обработчика события ItemDataBound веб формы:

private void DataGrid1_ItemDataBound(object sender, 
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
   System.Data.Common.DbDataRecord rec = null;
   if(e.Item.ItemType ==ListItemType.Item || 
e.Item.ItemType == ListItemType.AlternatingItem || 
           e.Item.ItemType == ListItemType.SelectedItem || 
e.Item.ItemType == ListItemType.EditItem)
      rec = (System.Data.Common.DbDataRecord) e.Item.DataItem;
   switch(e.Item.ItemType)
   {
      case ListItemType.Item:
      case ListItemType.AlternatingItem:
      case ListItemType.SelectedItem:
         num++;
         e.Item.Cells[7].Text = (bool) rec["Discontinued"] ? "X" : " ";
         ((Label) e.Item.FindControl("lblTotal")).Text = 
	((decimal) rec["UnitPrice"] * (short) rec["UnitsInStock"]).ToString();
         total += (decimal) rec["UnitPrice"] * (short) rec["UnitsInStock"];
         break;
      case ListItemType.EditItem:
         num++;
         e.Item.Cells[7].Text = (bool) rec["Discontinued"] ? "X" : " ";
         ((Label) e.Item.FindControl("lblTotal")).Text = 
	((decimal) rec["UnitPrice"] * (short) rec["UnitsInStock"]).ToString();
         e.Item.Cells[5].Controls.Clear();
         DropDownList ddlOrder = new DropDownList();
         ddlOrder.Items.Add(new ListItem("Select", ""));
         ddlOrder.Items.Add(new ListItem("0", "0"));
         ddlOrder.Items.Add(new ListItem("10", "10"));
         ddlOrder.Items.Add(new ListItem("20", "20"));
         ddlOrder.Items.Add(new ListItem("30", "30"));
         ddlOrder.Items.Add(new ListItem("40", "40"));
         ddlOrder.Items.Add(new ListItem("50", "50"));
         ddlOrder.Items.Add(new ListItem("60", "60"));
         ddlOrder.Items.Add(new ListItem("70", "70"));
         ddlOrder.Items.Add(new ListItem("80", "80"));
         ddlOrder.Items.Add(new ListItem("90", "90"));
         ddlOrder.Items.Add(new ListItem("100", "100"));
         if(ddlOrder.Items.FindByValue(rec["UnitsOnOrder"].ToString()) != null)
            ddlOrder.Items.FindByValue(rec["UnitsOnOrder"].ToString()).Selected = true;
         e.Item.Cells[5].Controls.Add(ddlOrder);
         total += (decimal) rec["UnitPrice"] * (short) rec["UnitsInStock"];
         break;
      case ListItemType.Footer:
         e.Item.Cells[5].Text = "<b>Total:</b>";
         e.Item.Cells[5].HorizontalAlign = HorizontalAlign.Right;
         e.Item.Cells[6].Text = String.Format("{0:c}", total);
         e.Item.Cells[6].HorizontalAlign = HorizontalAlign.Right;
         break;
   }
}

Рассморим приведенный код подробнее. Для начала нам необходимо получить объект с данными текущей строки. Но так как эти данные существуют далеко не всегда – необходимо предварительно проверить какого типа строка сейчас обрабатывается.

Затем в операторе switch происходит реальная работа с данными. Я специально использовал именно конструктор switch для того, чтобы вы могли закомментировав ту или иную часть кода увидеть изменения в выполнении страницы.

Код для строк типа Item, AlternatingItem и SelectedItem достаточно прост и не представляет особого интереса. Более интересные действия происходят при обработке строки редактирования, а конкретно код для вывода выпадающего списка вместо поля ввода для столбца UnitsOnOrder. Для начала в соответствующей ячейке очищается список элементов управления (и удаляется уже находящийся там элемент управления TextBox). Затем создается и заполняется значениями элемент управления DropDownList, а также выполняется код для выбора текущего значения поля UnitsOnOrder. И наконец созданный элемент управления добавляется в список элементов управления ячейки.

Последние 6 строк оператора switch добавляют данные о сумме товаров на складе в дно элемента управления DataGrid, а также производят минимальное форматирование выводимых данных.

Использование ItemCreated

Как я уже упоминал событие ItemDataBound срабатывает после связывания данных, но до их вывода. Но бывают случаи, когда необходимо произвести некоторые действия до связывания данных. И в этом случае на помощь приходит событие ItemCreated, срабатывающее после создания строки, но до связывания с данными.

Рассмотрим следующий пример: необходимо вывести информацию о клиентах (таблица Customers) вместе с общей информацией о заказах клиентов (таблица Orders и сумма товаров в заказе из таблицы Order Details базы Northwind). Естесственно пронумеровать строки (как клиентов, так и заказов клиентов), по каждому клиенту посчитать и вывести сумму всех заказов, ну и чтобы не скучно было еще и выделить заказы на сумму больше, чем 2500 :).

На первый взгляд все просто - выводим Repeater с информацией о клиенте и там же вставляем DataGrid со списком заказов. Но возникают некоторые «но»... Например добавление обработчика ItemDataBound к элементу управления DataGrid, находящемуся в шаблоне Repeaterа, определение источника данных для DataGrid и обнуление счетчика строк в том же DataGrid с информацией о заказах.

Использовать для этого обработчик события ItemDataBound элемента управления Repeater несколько поздновато – в этот момент все данные уже связаны. Так что ItemCreated – именно то событие, которое нам нужно (конечно можно ItemDataBound и источник данных для DataGrid можно задать и по другому, но мы то сейчас рассматриваем работу с событием ItemCreated :)).

Итак взглянем вначале на код веб формы:

<asp:Repeater id="Repeater1" runat="server">
   <HeaderTemplate>
      <table ID="Table4">
   </HeaderTemplate>
   <FooterTemplate>
      </table>
   </FooterTemplate>
   <ItemTemplate>
   <tr>
      <td rowspan="2" valign="center"><%# num1 %></td>
      <td>Company:
         <%# DataBinder.Eval(Container.DataItem, "CompanyName")%>
      </td>
      <td>Contact:
         <%# DataBinder.Eval(Container.DataItem, "ContactName")%>
      </td>
      <td>Address:
         <%# DataBinder.Eval(Container.DataItem, "City")%>
         ,
         <%# DataBinder.Eval(Container.DataItem, "PostalCode")%>
         <%# DataBinder.Eval(Container.DataItem, "Country")%>
      </td>
   </tr>
   <tr>
      <td colspan="3" align="right">
      <asp:DataGrid ID="DataGrid1" Runat="server" 
AutoGenerateColumns="False" Width="80%" ShowFooter="True">
         <Columns>
            <asp:TemplateColumn HeaderText="#" ItemStyle-HorizontalAlign="Center">
            <ItemTemplate>
               <%# num2 %>
            </ItemTemplate>
         </asp:TemplateColumn>
         <asp:BoundColumn HeaderText="Order ID" DataField="OrderID" />
         <asp:BoundColumn HeaderText="Order date" DataField="OrderDate" />
         <asp:BoundColumn HeaderText="Freight" DataField="Freight" />
         <asp:BoundColumn HeaderText="Shipped Date" DataField="ShippedDate" />
         <asp:BoundColumn HeaderText="Total" DataField="summ" />
         </Columns>
      </asp:DataGrid>
      </td>
      </tr>
   </ItemTemplate>
</asp:Repeater>

Ничего принципиально сложного в этом коде нет, так что смело можно его не расшифровывать. Более интересен код класса веб формы и я, на всякий случай, приведу его весь:

public class ItemDataBoundExample3 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Repeater Repeater1;
protected int num1 = 0;
protected int num2 = 0;
private decimal total = 0;
DataSet ds = null;
private void Page_Load(object sender, System.EventArgs e)
{
SqlConnection myConn = new SqlConnection("server=localhost;database=Northwind;uid=sa;pwd=");
SqlDataAdapter myData = new SqlDataAdapter("SELECT 
top 20 CustomerID, CompanyName, ContactName, " +
"City, PostalCode, Country FROM Customers order by CustomerID", myConn);
ds = new DataSet();
myData.Fill(ds, "Customers");
myData.SelectCommand.CommandText = 
"SELECT Orders.CustomerID, Orders.OrderID, Orders.OrderDate, " +
"Orders.Freight, Orders.ShippedDate, 
SUM([Order Details].UnitPrice * [Order Details].Quantity) " +
"AS summ FROM Orders INNER JOIN [Order Details] 
ON Orders.OrderID = [Order Details].OrderID " +
"where CustomerID in (SELECT top 20 CustomerID 
from Customers order by CustomerID) " +
"GROUP BY Orders.OrderID, dbo.Orders.OrderDate, 
dbo.Orders.Freight, dbo.Orders.ShippedDate, " +
"dbo.Orders.CustomerID";
myData.Fill(ds, "Orders");
Repeater1.DataSource = ds.Tables["Customers"].DefaultView;
Repeater1.DataBind();
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{    
this.Repeater1.ItemCreated += new 
System.Web.UI.WebControls.RepeaterItemEventHandler(this.Repeater1_ItemCreated);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion
private void Repeater1_ItemCreated(object sender, 
System.Web.UI.WebControls.RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
num1++;
num2 = 1;
total = 0;
DataGrid dg = (DataGrid) e.Item.FindControl("DataGrid1");
dg.ItemDataBound += 
new System.Web.UI.WebControls.DataGridItemEventHandler(this.DataGrid1_ItemDataBound);
ds.Tables["Orders"].DefaultView.RowFilter = 
String.Format("CustomerID = '{0}'", ((DataRowView) e.Item.DataItem)["CustomerID"]);
dg.DataSource = ds.Tables["Orders"].DefaultView;
}
}
private void DataGrid1_ItemDataBound(object sender, 
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item 
e.Item.ItemType == ListItemType.AlternatingItem)
{
num2++;
total += (decimal) ((DataRowView) e.Item.DataItem)["summ"];
if((decimal) ((DataRowView) e.Item.DataItem)["summ"] > 2500)
e.Item.BackColor = Color.LightCoral;
}
if(e.Item.ItemType == ListItemType.Footer)
{
e.Item.Cells.Clear();
TableCell cell = new TableCell();
cell.ColumnSpan = 6;
cell.HorizontalAlign = HorizontalAlign.Center;
cell.Text = String.Format("Total: <b>{0}</b> usd", total);
e.Item.Cells.Add(cell);
}
}
}

В классе описаны 4 переменные. Num1 и num2 используются для нумерации клиентов и заказов клиентов соответственно, переменная total предназначена для получения общей суммы заказов клиента, ну а в ds хранятся данные из таблиц Customers и Orders (в данном случае лучше получить эти данные сразу используя 2 запроса и не мучать базу :)).

В Page_Load переменная ds заполняется данными из источника данных и данные из таблицы Customers связываются с элементом Repeater. После чего начинается самое интересное :). В обработчике события ItemCreated элемента Repeater инициализируются переменные num2 и total для нумерации строк в элементе DataGrid и подсчета суммы заказов клиента и для элемента DataGrid, находящегося в текущей строке, программно добавляется обработчик события ItemDataBound и источник данных. Обратите внимание на то, что я не вызываю метод DataGrid.DataBind() для связывания данных, так как это метод буде автоматически вызван при связывании строки элемента Repeater.

Код в последнем обработчике, ItemDataBound для элемента DataGrid, нашего примера не представляет особого интереса. Единственное, на что я бы хотел обратить внимание – манипуляция с содержимым дна элемента DataGrid, которая производится в условии if(e.Item.ItemType == ListItemType.Footer). Это код показывает как можно добавлять и удалять ячейки в строке данных элемента DataGrid.

Использование событий ItemCreated/ItemDataBound дает программистам мощные средства для управления содержанием и внешним видом выводимых табличных данных.


Может пригодится:


Автор: Manowar
Прочитано: 4227
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру