使用CheckBox选中多个项(Hotmail模式)
    这个系列是在开发过程中对遇到的技术问题的一个总结,当时对这个控件的了解不是很多,但是根据需要实现的功能,不得不查阅N多资料,在MSDN文档中只有英文的说明,我整理并翻译了出来,在此和大家一起分享。转载请加此连接[url]http://august.blog.51cto.com[/url],谢谢!
     在类似于Microsoft Hotmail的应用程序中,用户可以通过勾选CheckBox选中多项,然后对所有选中的项进行操作,如删除或复制它们。
为了添加这样的功能,要在DataGrid中添加一个模板列,并在该模板列中加一个CheckBox。当页面运行的时候,用户就可以对他们需要的项进行选择。
    实际执行用户的动作时,可以遍历DataGrid的Items集合检查适当的列(单元格)来看CheckBox是否被选中,下面的例子显示了如何根据用户的选择来删除DataSet中的行。假设数据集.dsBook中包含了一个Books表。
private void btnDelete_Click(object sender, System.EventArgs e)
{
   int i = 0;
   CheckBox cb;
   int bookid;
   dsBooks.BooksRow dr;
   foreach(DataGridItem dgi in this.DataGrid1.Items)
   {
      cb = (CheckBox) dgi.Cells[0].Controls[1];
      if(cb.Checked)
      {
         // Determine the key of the selected record ...
         bookid = (int) DataGrid1.DataKeys;
         // ... get a pointer to the corresponding dataset record ...
         dr = this.dsBooks1.Books.FindBybookid(bookid);
         // ... and delete it.
         dr.Delete();
      }
      i++;
   }
   this.sqlDataAdapter1.Update(this.dsBooks1);
   this.sqlDataAdapter1.Fill(this.dsBooks1);
   DataGrid1.DataBind();
}
需要注意的几点:
     你可以通过下面的标准方法来检查CheckBox是否被选中:为从模板列中得到控件值,首先要从单元格的控件集合中得到一个对象,并且进行适当的类型转换。如果你得到了一个CheckBox控件,要记住它通常是第二个(索引为1)控件,因为在它前面有一个文字控件(即使它为空)。
     如果你要删除一个记录,必须通过DataSet中的关键字来实现,而不是通过offset。DataGrid控件中项的索引可能并不与表中同样记录的索引匹配,即使开始时匹配在删除第一条记录后就不再匹配了,这里代码通过DataGrid的DataKey集合得到记录的关键字,然后通过在DataSet表中使用FindByKey()方法来实现要删除的记录。
在DataSet中的记录被删除后(技术上,它们只是被做了删除标记),你要调用DataAdapter的Updata方法来从数据库中实际删除他们,然后代码从数据库刷新DataSet并重绑定回DataGrid.
   一次编辑多行
   DataGrid控件中编辑行的标准方法是通过添加一个“编辑、更新、取消”按钮到DataGrid的列中,只允许用户一次编辑一行,如果用户要编辑多行,他们必须单击“编辑”按钮,然后修改,再点击“更新”按钮,每一行都要这么做。
在某些情况下,一个有效的变通方法就是配置DataGrid使它默认就是编辑模式,这种情形DataGrid总是通过TextBox或者其它控件来显示可编辑的数据。用户不需要再将其转为编辑模式,典型情况下,用户作出任何想要的更改,然后单击一个按钮(并不是DataGrid中的按钮)来一次提交所有的更改。
    你可以在任何数据模型下使用这种编辑方式,无论是通过DataSet或者直接通过数据命令使用数据源。为将DataGrid设定为多行可编辑模式,添加列并将所有可编辑列转化为模板列,在DataGrid生成器中的列标签中,选中一列,选择窗口底部的“将此列转化为模板列”,可以通过右键→编辑模板来对模板进行编辑。
    添加编辑控件项模板,注意你不是像通常那样将它们添加到编辑项模板,因为在编辑模式下,行不会被显示,也就是说,在ItemTemplate中才包含可编辑控件。
像一般情况下那样设定数据绑定,你需要单独为每个可编辑控件进行绑定,一个典型的数据绑定表达式可能是这样:DataBinder.Eval(Container,”DataItem.title”)
加载DataGrid与一般情况无异,但是更新时稍有不同,当用户点击更新按钮时,你需要遍历整个DataGrid,一项一项地从可编辑控件中提取值,并将其指定给命令的参数,然后为每一项执行更新操作‘
private void btnUpdate_Click(object sender, System.EventArgs e)
{
   int i;
   DataGridItem dgi;
   int bookid;
   TextBox TextBoxTitle;
   CheckBox CheckBoxInStock;
   TextBox TextBoxPrice;
 
   for(i = 0; i <= DataGrid1.Items.Count -1 ; i++)
   {
      dgi = DataGrid1.Items;
      Label LabelBookId = (Label) dgi.Cells[0].Controls[1];
      bookid = int.Parse(LabelBookId.Text);
      TextBoxTitle = (TextBox) dgi.FindControl("TextBoxTitle");
      CheckBoxInStock = (CheckBox) dgi.FindControl("CheckBoxInStock");
      TextBoxPrice = (TextBox) dgi.FindControl("TextBoxPrice");
      this.dcmdUpdateBooks.Parameters["@bookid"].Value = bookid;
      this.dcmdUpdateBooks.Parameters["@Title"].Value = TextBoxTitle.Text;
      this.dcmdUpdateBooks.Parameters["@instock"].Value =
CheckBoxInStock.Checked;
      this.dcmdUpdateBooks.Parameters["@Price"].Value =
float.Parse(TextBoxPrice.Text);
      this.sqlConnection1.Open();
      this.dcmdUpdateBooks.ExecuteNonQuery();
      this.sqlConnection1.Close();
   }
}
检查出变化的项
    上面展示的更新策略的一个不足之处在于:如果只有很少的行发生变更,那么为每一行(无论变化与否)都向DataSet或者数据库发出更新命令,效率是比较低的,如果你使用的是一个DataSet,那么可以添加逻辑来检查DataGrid的控件与DataSet行中的对应列之间是否有变化,如果像上例那样,没有使用DataSet,就不能很容易地做出比较,因为那将涉及一次数据库的访问与回传。
    对于以上两种数据源都适应的一个策略是,确定一个能在你做出更新之前就能检查出该行是否为“脏数据”的方法,实现它的一个确定的方法是处理行中控件的Changed事件,相似地,对于CheckBox控件,可以使用其CheckedChanged事件。
在这些事件的处理中你可以保持一个将要更新的行的列表。通常,最好的方法是保持手影响的行的主键。比如:可以声明一个ArrayList对象来存储将要更新的行的主键。
假如要为上面的例子采用这样的策略,创建一个ArrayList对象的实例作为Page类的一个成员。
Protected  ArrayList bookidlist = new ArrayList();
然后创建一个事件处理程序,在控件发生变化时,向ArrayList对象中添加书的ID。下面的代码是一个当TextBox产生TextChanged事件成一个CheckBox产生CheckedChanged事件时触发的一个。事件处理程序:
protected void RowChanged( object sender, System.EventArgs e)
{
   DataGridItem dgi = (DataGridItem)(((Control)sender).NamingContainer);
   Label bookidlabel = (Label) dgi.Cells[0].Controls[1];
   int bookid = int.Parse(bookidlabel.Text);
   if (!bookidlist.Contains(bookid))
   {
      bookidlist.Add(bookid);
   }
}
注意:方法不能声明为private,否则后面就不能对其绑定。
     默认情况下,change事件并不将page回传到服务器,理解这点是十分有益的,经常是页面通过其它某种方式提交的时候才触发该事件(通过单击事件)。在页面处理过程中,页面和其上的所有控件被初始化,然后所有的change事件被触发。只有当change事件处理程序完成,控件的click事件发生,才引起页面的回传。
     在上面的RowChanged方法中,代码需要从当前项获得书的ID,但它并不把该项传给你(就象许多DataGrid事件一样),因此你要解决这个问题,由事件的Sender参数获得它的NamingContainer属性,也就是DataGrid项,由DataGrid项向下通过Cell.controls就可以得到用于显示书的ID的Label控件的值。
    在加入前需要检查书的ID尚未存在于数组中,行中的每个控件都单独产生一个事件,所以如果行中多于一个控件发生了change事件,就可以防止多项向数组中添加该书的ID号。控件的change事件总是在click事件之前被触发和处理。因此,你可以在change事件中创建ArrayList,并要确保它在用于提交表单的按钮的click事件处理程序(本例中是btnUpdate_click)执行时是可用的。
     既然你已经有了ArrayList,现在可以对管理更新的事件处理程序进行一个小的修改,在btnUpdate_click事件中,对DataGrid项循环时,添加一个判断,来检查当前的书的ID号是否是已经在ArrayList中,如果在,则执行更新:
private void btnUpdate_Click(object sender, System.EventArgs e)
{
   int i;
   DataGridItem dgi;
   int bookid;
   //Rest of declarations here
 
   for(i = 0; i <= DataGrid1.Items.Count -1 ; i++)
   {
      dgi = DataGrid1.Items;
      TableCell tc = dgi.Cells[0];
      string s = dgi.Cells[0].Text;
      Label LabelBookId = (Label) dgi.Cells[0].Controls[1];
      bookid = int.Parse(LabelBookId.Text);
      if (bookidlist.Contains(bookid))
      {
         // Update code here
      }
   }
}
还有一个任务:绑定处理程序列控件的事件,在VS中,只有通过HTML视图来完成,控件在后台代码(code-behind)文件中并没有默认地实例化,因此,代码工具并不支持它们,将.aspx文件切换到HTML视图,在每个控件的声明元素中添加下面的语法:
<asp:TemplateColumn HeaderText="title">
   <ItemTemplate>
      <asp:TextBox OnTextChanged="RowChanged"
         id=TextBoxTitle runat="server"
         Text='<%# DataBinder.Eval(Container, "DataItem.title") %>'>
      </asp:TextBox>
   </ItemTemplate>
</asp:TemplateColumn>
 
<asp:TemplateColumn HeaderText="instock">
   <ItemTemplate>
      <asp:CheckBox id=cbInStock OnCheckedChanged="RowChanged"
        runat="server"
        Checked='<%# DataBinder.Eval(Container, "DataItem.instock") %>'>
      </asp:CheckBox>
   </ItemTemplate>
</asp:TemplateColumn>
      TextBox和CheckBox均可以在各自的change事件触发时调用相同的方法,因为两个事件句柄具有同样的签名(signature),对于一个ListBox的控件或TextBox和CheckBox均可以在各自的change事件触发时调用相同的方法,因为两个事件句柄具有同样的签名。对于一个ListBox控件或Drop-Down List控件也是如此,它们的SelectedIndexChanged事件也传递相同的参数。