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

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

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

Редактирование данных в табличных элементах управления. Часть 2

В своей предыдущей статье Редактирование данных в DataGrid я как-то обошел стороной вопрос связи между строками в DataGrid/DataList и данными в источнике данных, и у читателей могло сложиться впечатление, что такоую связь им нужно реализовывать самим. Но это не так - на самом деле такая связь существует, и создается она с помощью свойств DataKeyField/DataKeys элементов управления DataGrid и DataList. Все, что нам нужно для того, чтобы привязать к строкам, связываемым в DataGrid, с информацию о ключах этих строк из источника данных, это перед связыванием данных присвоить имя необходимого нам ключевого поля в свойство DataKeyField:
private void bindData()
{
  SqlConnection myConn = new SqlConnection(ConnStr);
  SqlCommand myCmd = new SqlCommand("select * from titles", myConn);
  myConn.Open();
  IDataReader rdr = myCmd.ExecuteReader();
  DataGrid1.DataSource = rdr;
  DataGrid1.DataKeyField = "title_id";
  DataGrid1.DataBind();
  rdr.Close();
  myConn.Close();
}

Теперь мы можем получить значение ключа для указанной строки из массива объектов DataKeys. Обратите внимание, что это свойство имеет тип object[] и при получении значения ключа из этого массива необходимо привести его к правильному типу данных.

Рассмотрим работу со свойством DataKeys на примере элемента управления DataGrid, выводящего список книг из базы Pubs и предоставляющего возможность удалять книги из базы. Метод для связывания данных у нас уже есть, осталось посмотреть на описание самого DataGrid:

<asp:DataGrid id=DataGrid1 runat="server" AutoGenerateColumns="False">
  <Columns>
    <asp:BoundColumn DataField="title" HeaderText="Title"></asp:BoundColumn>
    <asp:BoundColumn DataField="price" HeaderText="Price"></asp:BoundColumn>
    <asp:BoundColumn DataField="pubdate" HeaderText="Pub Date"></asp:BoundColumn>
    <asp:ButtonColumn Text="Delete" CommandName="Delete"></asp:ButtonColumn>
  </Columns>
</asp:DataGrid>

и обработчика события DeleteCommand для него

private void DataGrid1_DeleteCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) 
{
  SqlConnection myConn = new SqlConnection(ConnStr); 
  SqlCommand myCmd = new SqlCommand("delete from titles where title_id = @title_id", myConn); 
  myCmd.Parameters.Add("@title_id", DataGrid1.DataKeys[e.Item.ItemIndex]);
//   myConn.Open();
//  myCmd.ExecuteNonQuery();
//  myConn.Close();
  lblDeleted.Text  = "delete title with title_id = 'э" + (string) DataGrid1.DataKeys[e.Item.ItemIndex] + "'";
  bindData();
}

Я не делал реального удаления записи из БД, но если вам это нужно - просто откомментируйте закомменченый участок.

Ну и чтобы 2 раза не вставать я покажу в этом же примере как к кнопке удаления прицепить клиентский алерт, запрашивающий подтверждение пользователя на удаление строки. Делается это в обработчике события ItemDataBound с помощью следующего (верного для данного случая) кода:

private void DataGrid1_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
  if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    ((LinkButton) e.Item.Cells[3].Controls[0]).Attributes.Add("onclick", "return confirm('Are you sure?');");
}

Для того, чтобы это код работал, необходимо определить индекс столбца Delete, и подставить его в e.Item.Cells[нужный_номер_столбца].

Еще один интересный момент редактирования данных, который мне хотелось бы рассмотреть в этой статье, это редактирование данных в табличных элементах управления, вложенных в другие табличные элементы управления. В этом случае возникают очень интересные проблемы, не позволяющие использовать стандартный подход. Ведь при связывании данных с внешним табличным элементом управления все строки этого элемента управления пересоздаются заново и, соответственно, заново создаются и все вложенные табличные элементы управления с данными. 

Соответственно для реализации редактирования данных в этом случае необходимо дополнительно сохранять 2 значения - номер строки внешнего табличного элемента управления, в которой содержится табличный элемент управления с редактируемой строкой, и номер самой редактируемой строки во внутреннем табличном элементе управления. Попробуем реализовать это.

Итак требуется создать страницу, выводящую список работников по издательствам с возможностью редактирования данных работников. Внешний список (издательств) формируется с помощью элемента управления Repeater, внутренний же список (работников) - с помощью элемента управления DataGrid. Для элемента управления DataGrid определены также обработчики событий EditCommand, UpdateCommand и CancelCommand. Полный код веб формы представлен ниже:

<form id="IncludedDataGridEdit" method="post" runat="server">
  <P><asp:Label id=lblSQLCmd runat="server" EnableViewState="False"></asp:Label></P>
  <asp:Repeater id=rptrPubs runat="server">
    <ItemTemplate>
      <b><%# DataBinder.Eval(Container.DataItem, "Pub_Name")%></b> employees:<br>
      <asp:DataGrid ID="dgEmployees" Runat="server" AutoGenerateColumns="False" OnEditCommand="dgEmployees_EditCommand" 
      OnCancelCommand="dgEmployees_CancelCommand" OnUpdateCommand="dgEmployees_UpdateCommand">
        <Columns>
          <asp:BoundColumn DataField="fname" HeaderText="First Name"></asp:BoundColumn>
          <asp:BoundColumn DataField="lname" HeaderText="Last Name"></asp:BoundColumn>
          <asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Update" 
          CancelText="Cancel" EditText="Edit"></asp:EditCommandColumn>
        </Columns>
      </asp:DataGrid>
    </ItemTemplate>
    <SeparatorTemplate>
      <hr width="50%">
    </SeparatorTemplate>
  </asp:Repeater>
</form>

Как же будет работать эта форма? Изначально данные выводятся в табличных элементах управления в режиме просмотра, но во вложенных DataGridах в каждой строке присутствует кнопка Edit. При нажатии на эту кнопку в обработчике события DataGrid.EditCommand в ViewState сохраняются 2 переменные - индекс строки элемента управления DataGrid, в котором произошло данное событие:

  ViewState["selectedDataGridIndex"] = e.Item.ItemIndex; 

а также индекс строки элемента управления Repeater, в которой лежит данный DataGrid:

  ViewState["selectedRepeaterIndex"] = ((RepeaterItem) ((DataGrid) 
sender).Parent).ItemIndex; 

после чего вызывается метод связывания элемента управления Repeater с источником данных 

Как же используются данные индексы для отображения строки в режиме редактирования? Как я уже говорил ранее процедура связывания данных пересоздает заново все элементы управления, вложенные в табличный элемент управления, для которого был вызван метод DataBind. Но так как нужные индексы строк уже есть - все, что необходимо сделать, это в обработчике события ItemCreated(ItemDataBound) внешнего элемента управления Repeater отловить по сохраненному ранее индексу нужную строку и установить для вложенного в эту строку элемента управления DataGrid значение свойства EditItemIndex в ранее сохраненное:

private void rptrPubs_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e)
{
  if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
  {
    if((int) ViewState["selectedRepeaterIndex"] == e.Item.ItemIndex)
      ((DataGrid) e.Item.FindControl("dgEmployees")).EditItemIndex = (int) ViewState["selectedDataGridIndex"];
    DataView dv = new DataView(ds.Tables[1]);
    dv.RowFilter = "pub_id = " + ((DataRowView) e.Item.DataItem)["pub_id"].ToString();
    ((DataGrid) e.Item.FindControl("dgEmployees")).DataKeyField = "emp_id";
    ((DataGrid) e.Item.FindControl("dgEmployees")).DataSource = dv;
    ((DataGrid) e.Item.FindControl("dgEmployees")).DataBind();
  }
}

Теперь осталось реализовать обработчики для событий отмены редактирования и сохранения данных и дело сделано.

Обработчик события отмены редактирования (DataGrid.CancelCommand) всего лишь сбразывает переменные, в которых хранятся индексы редактируемых строк:

  ViewState["selectedDataGridIndex"] = -1;
  ViewState["selectedRepeaterIndex"] = -1;

после чего вызывается метод связывания данных.

Обработчик события сохранения редактируемых данных (DataGrid.UpdateCommand) ненамного сложнее - его работа заключается в сохранении сделанных изменений в базу. А так как в метод - обработчик этого события передается полная информация о строке, в которой возникло это событие, этот процесс ничем не отличается от стандартного. В рассматриваемом примере в результате выполнения этого обработчика выводится SQL запрос, который мог быть использован для изменения данных в БД. И после изменения данных вызывается метод-обработчик отмены редактирования.

  lblSQLCmd.Text = String.Format("update employee set fname = '{0}', lname = '{1}' where emp_id = {2}", 
    ((TextBox) e.Item.Cells[0].Controls[0]).Text, ((TextBox) e.Item.Cells[1].Controls[0]).Text, 
    ((DataGrid) sender).DataKeys[e.Item.ItemIndex]);
  this.dgEmployees_CancelCommand(sender, e);

Вот и все, веб форма, позволяющая редактировать данные во вложенных табличных элементах управления, готова.

Теперь перейдем к рассмотрению события ItemCommand. Рассмотрим следующий пример - есть элемент управления DataGrid с 2-мя Templated столбцами, содержащими элементы управления LinkButton. Для одного элемента управления LinkButton определен обработчик события OnClick, для второго же указан CommandName. Также для самого DataGrid определен обработчик события ItemCommand:

<asp:DataGrid id=DataGrid1 runat="server" AutoGenerateColumns="False">
  <Columns>
    <asp:TemplateColumn>
      <ItemTemplate>
        <asp:LinkButton Runat="server" OnClick="btn_click">Button <%# (int) Container.DataItem%></asp:LinkButton>
      </ItemTemplate>
    </asp:TemplateColumn>
    <asp:TemplateColumn>
      <ItemTemplate>
        <asp:LinkButton Runat="server" CommandName="<%# Container.DataItem%>">Item <%# (int) Container.DataItem%>
        </asp:LinkButton>
      </ItemTemplate>
    </asp:TemplateColumn>
  </Columns>
</asp:DataGrid>
protected void btn_click(object sender, EventArgs e)
{
  Response.Write("Button clicked<br>");
}

private void DataGrid1_ItemCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  Response.Write("Item fired<br>");
}

Обработчики событий Button.Click и DataGrid.ItemCommand просто выводят сообщение о том, что они вызывались. Протестируем теперь даннуй страницу - свяжем DataGrid с массивом целых и попробуем понажимать на линки и посмотреть результаты.

При нажатии на линки из второго столбца срабатывает событие DataGrid.ItemCommand и на страницу выводится надпись Item fired. Почему это происходит и как это работае я расскажу несколько позже. Более интересные результаты получаются если нажать на ссылку в первом столбце, для которой у нас есть обработчик события OnClick. В этом случае выводится надпись Button clicked (как в принципе и ожидалось :)), но кроме нее также выводится и надпись Item fired, что означает что и в этом случае обработчик события OnItemCommand отработал! И это важно знать - обработчик события OnItemCommand срабатывает всегда, когда нажимается кнопка, находящаяся внутри табличного элемента управления.

Как же это все работает? А с помощью всплывающих событий, т.е. событий, передающихся вверх по иерархии элементов управления до тех пор, пока какой-нибудь элемент управления не прекратит это всплывание. Работу всплывающих событий поддерживают методы OnBubbleEvent и RaiseBubbleEvent, определенные в классе System.Web.UI.Control - корневом классе иерархии элементов управления ASP.NET. Детально рассматривать их в этой статье я не буду, замечу лишь, что если вы не хотите, чтобы ваш элемент управления не передавал свои события родителю (не поддерживал всплывающие события) - переопределите метод OnBubbleEvent чтобы он возвращал false.

Какую же пользу можно извлечь из этого всего? Представим себе следующую задачу - необходимо вывести список издательств из базы данных pubs и дать возможность на той же странице смотреть список выпущенных выбранным издательством книг или список работников для этого издательства. Для вывода списка издательств будет использоваться элемент управления DataList. Разместим в шаблоне ItemTemplate этого элемента управления информацию об издательстве (название издательства и информацию о его местонахождении), а также 2 элемента управления LinkButton с установленными атрибутами CommandName:

<ItemTemplate>
  <%# DataBinder.Eval(Container.DataItem, "pub_name")%><br>
  <%# DataBinder.Eval(Container.DataItem, "city")%>, 
  <%# DataBinder.Eval(Container.DataItem, "state")%> 
  <%# DataBinder.Eval(Container.DataItem, "country")%><br>
  <asp:LinkButton CommandName="ViewTitles" Runat="server">View Titles</asp:LinkButton>  
  <asp:LinkButton CommandName="ViewEmployees" Runat="server">View Employees</asp:LinkButton>
</ItemTemplate>

Для вывода информации о работниках или книгах издательства будет использоваться шаблон SelectedItemTemplate, который содержит кроме элементов управления из шаблона ItemTemplate также LinkButton для скрытия развернутой информации о работниках или книгах, а также элемент управления DataGrid для вывода этой информации. Для простоты я использовал минимально функциональный DataGrid с атрибутом AutoGenerateColumns установленном в true:

<SelectedItemTemplate>
  <%# DataBinder.Eval(Container.DataItem, "pub_name")%><br>
  <%# DataBinder.Eval(Container.DataItem, "city")%>, 
  <%# DataBinder.Eval(Container.DataItem, "state")%> 
  <%# DataBinder.Eval(Container.DataItem, "country")%><br>
  <asp:LinkButton CommandName="ViewTitles" Runat="server">View Titles</asp:LinkButton>  
  <asp:LinkButton CommandName="ViewEmployees" Runat="server">View Employees</asp:LinkButton>  
  <asp:LinkButton CommandName="Hide" Runat="server">Hide</asp:LinkButton><br>
  <asp:DataGrid ID="dgDetails" Runat="server" />
</SelectedItemTemplate>

Теперь осталось добавить код для отображения необходимых данных при нажатии на соответствующие LinkButton. Для этого необходимо создать обработчик события ItemCommand в котором в зависимости от значения CommandName произвести следующие действия:

  1. Установить значение SelectedIndex элемента управления DataList в значение номера строки, в которой произошло событие. Получить это значение можно из свойства Item.ItemIndex класса DataListCommandEventArgs, передаваемого в обработчик события (либо же установить его в -1 если был нажат LinkButton c CommandName="Hide"
  2. Сохранить значение CommandName для последующей обработки его в обработчике события DataList.ItemDataBound (именно там будет происходить реальный вывод данных о работниках или книгах издательства).
  3. Создать обработчик события DataList.ItemDataBound в котором в шаблоне SelectedItemTemplate связать необходимые данные с элементом управления DataGrid, расположенном в нем.

Код класса страницы, выполяющий это, представлен ниже:

public class DataGridItemCmd : System.Web.UI.Page
{
  protected System.Web.UI.WebControls.DataList dlPublishers;
  private string ConnStr = "server=localhost;database=pubs;uid=sa;pwd=";
  private string CmdName = "";
  
  private void Page_Load(object sender, System.EventArgs e)
  {
    if(!IsPostBack)
    {
      bindData();
    }
  }

  override protected void OnInit(EventArgs e)
  {
    InitializeComponent();
    base.OnInit(e);
  }
    
  private void InitializeComponent()
  {    
    this.dlPublishers.ItemCommand += new System.Web.UI.WebControls.DataListCommandEventHandler(this.dlPublishers_ItemCommand);
    this.dlPublishers.ItemDataBound += new System.Web.UI.WebControls.DataListItemEventHandler(this.dlPublishers_ItemDataBound);
    this.Load += new System.EventHandler(this.Page_Load);
  }
  private void bindData()
  {
    SqlConnection myConn = new SqlConnection(ConnStr);
    SqlCommand myCmd = new SqlCommand("select * from publishers", myConn);
    myConn.Open();
    IDataReader dr = myCmd.ExecuteReader(CommandBehavior.CloseConnection);
    dlPublishers.DataSource = dr;
    dlPublishers.DataBind();
    dr.Close();
  }

  private void dlPublishers_ItemCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e)
  {
    switch(e.CommandName)
    {
      case "ViewTitles":
      case "ViewEmployees":
        dlPublishers.SelectedIndex = e.Item.ItemIndex;
        CmdName = e.CommandName;
        break;
      case "Hide":
        dlPublishers.SelectedIndex = -1;
        break;
    }
    bindData();
  }
  private void dlPublishers_ItemDataBound(object sender, System.Web.UI.WebControls.DataListItemEventArgs e)
  {
    if(e.Item.ItemType == ListItemType.SelectedItem)
    {
      SqlDataAdapter myData;
      if(CmdName == "ViewTitles")
        myData = new SqlDataAdapter("select * from titles where pub_id = " + ((IDataRecord) e.Item.DataItem)["pub_id"].ToString(), 
          ConnStr);
      else
        myData = new SqlDataAdapter("select * from employee where pub_id = " + ((IDataRecord) e.Item.DataItem)["pub_id"].ToString(), 
          ConnStr);
      DataSet ds = new DataSet();
      myData.Fill(ds);
      ((DataGrid) e.Item.FindControl("dgDetails")).DataSource = ds.Tables[0].DefaultView;
      ((DataGrid) e.Item.FindControl("dgDetails")).DataBind();
    }
  }
}

Как видите событие ItemCommand позволяет с легкостью обрабатывать события, возникающие при нажатии на элементы управления Button/LinkButton, добавляемые в табличные элементы управления. При этом в обработчик события ItemCommand кроме информации о том, какое событие было сгенерировано (свойство CommandName класса DataListCommandEventArgs) также передается ссылка на саму строку, в которой произошло это событие и программист имеет всю необходимую ему информацию.

С помощью события ItemCommand можно даже сделать редактируем элементу управления Repeater, пример чего я и покажу напоследок реализовав элементе управления repeater редактирование данных таблицы titles базы данных pubs. 

Сначала необходимо решить как будут отображаться и редактироваться данные в нем. Для этого в шаблоне ItemTemplate создается один регион, предназначенный для вывода данных в режиме просмотра, и второй для вывода данных для редактирования. Оба этих региона имеют атрибут runat="server" и их свойства Visible меняются в зависимости от того, редактируется ли данная запись или просто выводится в режиме просмотра. Также в этих регионах размещены элементы управления Button c атрибутами CommandName, установленными в "edit" (для перехода в режим редактирования), "update" (для сохранения редактируемой строки) и "cancel" (для отмены редактирования). Полный код данного элемента управления Repeater представлен ниже:

<asp:Repeater id=rptrMain runat="server">
  <HeaderTemplate>
    <table cellpadding="2" cellspacing="2" align="center">
      <tr>
        <td>Title</td>
        <td>Price</td>
        <td>Pub Date</td>
      </tr>
  </HeaderTemplate>
  <FooterTemplate>
    </table>
  </FooterTemplate>
  <ItemTemplate>
    <tr id="view" runat="server">
      <td><%# DataBinder.Eval(Container.DataItem, "title") %></td>
      <td><%# DataBinder.Eval(Container.DataItem, "price") %></td>
      <td><%# DataBinder.Eval(Container.DataItem, "pubdate") %></td>
      <td><asp:Button Runat="server" Text="Edit" CommandName="edit" /></td>
    </tr>
    <tr id="edit" runat="server" visible="false">
    <td><asp:TextBox id="txtTitleID" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "title_id") %>' Visible="False" />
      <asp:TextBox id="txtTitle" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "title") %>' /></td>
    <td><asp:TextBox id="txtPrice" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "price") %>'/></td>
    <td><asp:TextBox id="txtPubDate" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "pubdate") %>'/></td>
    <td><asp:Button Runat="server" Text="Update" CommandName="update" /> 
    <asp:Button Runat="server" Text="Cancel" CommandName="cancel"/></td></tr>
  </ItemTemplate>
</asp:Repeater>

Как видите для того, чтобы строку перевести в режим редактирования нужно всего 2 строчки кода:

  view.Visible = false;
  edit.Visible = true;

Но где и когда это делать? А делать это придется в обработчике события Repeater.ItemDataBound (или Repeater.ItemCreated, тут уж на любителя), предварительно сохранив индекс строки, которая в данный момент находится в режиме редактирования. Забегая несколько вперед скажу, что индекс редактируемой строки будет храниться в ViewState["EditItem"], и исходя из этого обработчик события Repeater.ItemDataBound будет иметь следующий код:

  if((int) ViewState["EditItem"] != -1 && e.Item.ItemIndex == (int) ViewState["EditItem"])
  {
    e.Item.FindControl("view").Visible = false;
    e.Item.FindControl("edit").Visible = true;
  }

Теперь осталось дело за малым - реализовать обработчик события Repeater.ItemCommand, в котором будут обрабатываться события, посылаемые кнопками, вставленными в шаблоны элемента управления Repeater. Напомню, что необходимо обработать 3 типа событий - "edit", "cancel" и "update". При возникновении события "edit" необходимо только сохранить индекс строки, в которой произожло это событие, для ее перевода в режим редактирования. Аналогично для "cancel" необходимо установить индекс редактируемой строки в -1 (нет редактируемых строк). Больше работы придется выполнить при событии "update" - в этом случае необходимо получить измененные данные из текстовых элементов управления и обновить их в источнике данных. После чего опять таки сбросить индекс редактируемой строки. Ну и напоследок необходимо вызвать процедуру связывания данных для отображения произошедших изменений.

Небольшое примечание - к сожалению элемент управления Repeater не имеет свойств DataKeyField и DataKeys и поэтому придется вручную сохранфть значение ключа даписи в строке с данными. Это делается путем добавления невидимого элемента управления TextBox, содержащего значение поля title_id.

private void rptrMain_ItemCommand(object source, System.Web.UI.WebControls.RepeaterCommandEventArgs e)
{
  switch(e.CommandName)
  {
    case "edit":
      ViewState["EditItem"] = e.Item.ItemIndex;
      break;
    case "update":
      try
      {
        SqlConnection myConn = new SqlConnection(ConnStr);
        SqlCommand myCmd = new SqlCommand("update titles set title = @title, price = @price, pubdate = @pubdate where " +
          "title_id = @title_id", myConn);
        myCmd.Parameters.Add("@title", ((TextBox) e.Item.FindControl("txtTitle")).Text);
        myCmd.Parameters.Add("@price", Decimal.Parse(((TextBox) e.Item.FindControl("txtPrice")).Text));
        myCmd.Parameters.Add("@pubdate", DateTime.Parse(((TextBox) e.Item.FindControl("txtPubDate")).Text));
        myCmd.Parameters.Add("@title_id", ((TextBox) e.Item.FindControl("txtTitleID")).Text);
        myConn.Open();
        myCmd.ExecuteNonQuery();
        myConn.Close();
      }
      catch {}
      ViewState["EditItem"] = -1;
      break;
    case "cancel":
      ViewState["EditItem"] = -1;
      break;
  }
  bindData();
}

Вот и все. Осталось добавить метод bindData(), реализующий связывание данных и не забыть инициализировать значение в ViewState["EditItem"]. После чего элемент управления Repeater с возможностью редактирования данных готов.


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


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


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

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

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

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