1.2.3 GridView和DetailsView控件
DataGrid是ASP.NET中最受欢迎的控件之一,但在某些方面,它也成为自己成功的牺牲品:如此丰富的功能,以至于让ASP.NET开发人员不满足于此,而是希望它能提供更多功能。DataGrid控件在ASP.NET 2.0中并没有发生太大变化,只是添加了两个分别名为GridView和DetailsView的新控件,它们提供了通常要求DataGrid控件所具有的功能,并且还加入了一些属于它们自己的新功能。
GridView呈现出HTML表的方式与DataGrid一样,但与DataGrid不同的是,GridView可以完全依靠自己来分页和排序。GridView还支持比DataGrid种类更为丰富的列类型(在GridView用语中称为字段类型),并且它们具有更为智能的默认呈现行为,能够自动呈现Boolean值(例如,通过复选框)。GridView也可以容易地与DetailsView搭配使用,以创建主-从视图。GridView控件的主要缺陷是:像DataGrid一样,它通过将信息传回到服务器来完成它该做的大部分工作。
具体来说GridView可以完成以下一些操作:
· 通过数据源控件自动绑定和显示数据。
· 通过数据源控件对数据进行选择、排序、分页、编辑和删除。
· 通过自定义GridView控件来改变外观和行为:指定自定义列和样式。
· 利用模板创建自定义用户界面(UI)元素。
· 通过事件将自己的代码添加到控件的功能中去。
1.通过数据源控件自动绑定和显示数据
GridView控件提供了两种方式用于绑定到数据:要么使用DataSourceID属性进行数据绑定,此方法能够将GridView控件绑定到数据源控件,同时这种方式比较常见也建议使用,因为它允许GridView控件利用数据源控件的功能并提供了内置的排序、分页和更新功能;要么使用DataSource属性进行数据绑定,通过这种方式能够绑定到包括ADO.NET数据集和数据读取器在内的各种对象。此方法需要为所有附加功能(如排序、分页和更新)编写代码。当使用DataSourceID属性绑定到数据源时,GridView控件支持双向数据绑定。除可以使该控件显示返回的数据之外,还可以使它自动支持对绑定数据的更新和删除操作。
2.通过数据源控件对数据进行选择、排序、分页、编辑和删除
GridView控件有一个内置分页功能,可支持基本的分页功能。读者可以使用默认分页功能或创建自定义的分页效果。GridView控件支持对其数据源中的项进行分页。将AllowPaging属性设置为true就可以启用分页。GridView控件可以通过多种方式支持分页:如果将GridView控件绑定到在界面级别支持分页功能的数据源控件,则GridView控件将直接利用这一功能。在界面级别分页意味着GridView控件仅从数据源请求呈现当前页所需的记录数。请求的记录数可能会根据其他因素变化,例如数据源是否支持获取总记录数,由PageSize属性指定的每页的记录数,以及在将PageButtonCount属性设置为Numeric时要显示的页导航按钮的数目。而实际上在.NET Framework所包含的数据源控件中,只有ObjectDataSource控件在界面级别支持分页;如果将GridView控件绑定到不直接支持分页功能的数据源控件,或者如果通过DataSource属性利用代码将GridView控件绑定到一个数据结构,则GridView控件将按照先从源获取所有数据记录来进行分页,且仅显示当前页的记录,然后丢弃剩余的记录。但是只有在GridView控件的数据源返回一个实现ICollection接口的集合(包括数据集)时,才支持这种分页方式。
需要注意的是,如果数据源不直接支持分页且未能实现ICollection接口,则GridView控件将无法进行分页。例如读者正在使用SqlDataSource控件,并将其DataSourceMode属性设置为DataReader,则GridView控件就无法实现分页。
对于自定义分页设置和用户界面,读者可以通过多种方式自定义GridView控件的分页用户界面。可以通过使用PageSize属性来设置页的大小(即每次显示的项数)。还可以通过设置PageIndex属性来设置GridView控件的当前页。可以使用PagerSettings属性或通过提供页导航模板来指定更多的自定义行为。分页模式将AllowPaging属性设置为true时,PagerSettings属性则允许自定义由GridView控件自动生成的分页用户界面的外观。GridView控件可显示允许向前和向后导航的方向控件,以及允许用户移动到特定页的数字控件。
对于分页事件,当GridView控件移动到新的数据页时,该控件会引发两个事件:PageIndexChanging事件在GridView控件执行分页操作之前发生;PageIndexChanged事件在新的数据页返回到GridView控件之后发生。如果需要可以使用PageIndexChanging事件取消分页操作或在GridView控件请求新的数据页之前执行某项任务,也可以使用PageIndexChanged事件在用户移动到另一个数据页之后执行某项任务。在默认情况下,GridView控件在只读模式下显示数据。但是该控件还支持一种编辑模式,在该模式下控件显示一个包含可编辑控件(如TextBox或CheckBox控件)的行。还可以对GridView控件进行配置以显示一个Delete按钮,用户可单击该按钮来删除数据源中相应的记录。同时GridView控件支持在不需要任何编程的情况下通过单个列排序。通过使用排序事件以及提供排序表达式就可以进一步自定义GridView控件的排序功能。
3.实现GridView批量删除、自定义分页、定位页码
下面以一个完整的自定义分页程序演示前面的部分理论,其代码如下:
<table height="20" border="1" align="center" cellpadding="0" cellspacing="0" style="width: 97%"> <tr> <td align="center" style="width: 24%"> <asp:CheckBox ID="cbAll" runat="server" AutoPostBack="True" OnChecked Changed="cbAll_CheckedChanged" Text="Select All /UnSelect All" /> </td> <td align="center" style="width: 16%">ProductName</td> <td width="39%" align="center">UnitPrice</td> <td width="21%" align="center">IsDiscontinued</td> </tr> </table> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" BackColor="White" BorderColor="#E7E7FF" BorderWidth="1px" CellPadding="3" DataKeyNames="ProductID" HorizontalAlign="Center" Width="97%" BorderStyle="None" ShowHeader="False" AllowPaging="True" OnDataBound="GridView1_DataBound" GridLines="Horizontal"> <FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" /> <Columns> <asp:TemplateField > <ItemStyle HorizontalAlign="Center" /> <ItemTemplate> <asp:CheckBox ID="checkAll" runat=server /> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="ProductName" HeaderText="ProductName"> <ItemStyle Width="16%" /> </asp:BoundField> <asp:HyperLinkField DataNavigateUrlFields="ProductID" DataNavigateUrl FormatString="Test.aspx?id={0}" DataTextField="UnitPrice" HeaderText="UnitPrice" > <ItemStyle Width="39%" /> </asp:HyperLinkField> <asp:BoundField DataField="Discontinued" HeaderText="Discontinued"> <ItemStyle Width="21%" /> </asp:BoundField> </Columns> <PagerTemplate> </PagerTemplate> <SelectedRowStyle BackColor="#738A9C" ForeColor="#F7F7F7" Font-Bold= "True" /> <PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign= "Right" /> <HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" /> <RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" /> <AlternatingRowStyle BackColor="#F7F7F7" /> </asp:GridView> <table height="20" border="1" cellpadding="0" cellspacing="0" bordercolor light="#FFFFFF" bordercolordark="#E6E6E6" bgcolor="#FFFFFF" style="width: 99%"> <tr><td> <asp:Button ID="Button1" runat="server" Text="全选" OnClick="Button1_ Click" /> <asp:Button ID="Button2" runat="server" Text="删除" OnClick="Button2_ Click" /></td> <td align=right> <asp:LinkButton ID="lnkbtnFrist" runat="server" OnClick="lnkbtnFrist_ Click">首页</asp:LinkButton> <asp:LinkButton ID="lnkbtnPre" runat="server" OnClick="lnkbtnPre_ Click">上一页</asp:LinkButton> <asp:Label ID="lblCurrentPage" runat="server"></asp:Label> <asp:LinkButton ID="lnkbtnNext" runat="server" OnClick="lnkbtnNext_ Click">下一页</asp:LinkButton> <asp:LinkButton ID="lnkbtnLast" runat="server" OnClick="lnkbtnLast_ Click">尾页</asp:LinkButton> 跳转到第<asp:DropDownList ID="ddlCurrentPage" runat="server" AutoPostBack ="True" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged"> </asp:DropDownList>页</td> </tr></table>
最终的显示效果如图1-12所示。
图1-12
后台代码如下部分:
protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { DataBinds(); } } //数据绑定 void DataBinds() { this.GridView1.DataSource = GetDataFromDataBase().Tables[0].Default View; this.GridView1.DataBind(); this.ddlCurrentPage.Items.Clear(); for (int i = 1; i <= this.GridView1.PageCount; i++) { this.ddlCurrentPage.Items.Add(i.ToString()); } this.ddlCurrentPage.SelectedIndex = this.GridView1.PageIndex; } //全选操作 protected void Button1_Click(object sender, EventArgs e)
{
foreach (GridViewRow row in GridView1.Rows) { ((CheckBox)row.Cells[0].FindControl("checkAll")).Checked = true; } } //删除所选 protected void Button2_Click(object sender, EventArgs e) { for (int rowindex = 0; rowindex < this.GridView1.Rows.Count; rowindex++) { if (((CheckBox)this.GridView1.Rows[rowindex].Cells[0].FindControl("checkAll")).Checked == true) { int id = Convert.ToInt32(this.GridView1.DataKeys[rowindex]. Value); //DeleteProduct(id); } } DataBinds(); } //索引事件 protected void GridView1_PageIndexChanging(object sender, GridView PageEvent Args e) { this.GridView1.PageIndex = e.NewPageIndex; DataBinds(); } //改变CheckBox的状态 protected void cbAll_CheckedChanged(object sender, EventArgs e) { if (this.cbAll.Checked == true) { foreach (GridViewRow row in GridView1.Rows) { ((CheckBox)row.Cells[0].FindControl("checkAll")).Checked = true; } } else { foreach (GridViewRow row in GridView1.Rows) { ((CheckBox)row.Cells[0].FindControl("checkAll")).Checked = false; } } } //自定义导航到具体的页 protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { this.GridView1.PageIndex = this.ddlCurrentPage.SelectedIndex; DataBinds(); } //首页 protected void lnkbtnFrist_Click(object sender, EventArgs e) { this.GridView1.PageIndex = 0; DataBinds(); } //前一页 protected void lnkbtnPre_Click(object sender, EventArgs e) { if (this.GridView1.PageIndex > 0) { this.GridView1.PageIndex = this.GridView1.PageIndex - 1; DataBinds(); } } //下一页 protected void lnkbtnNext_Click(object sender, EventArgs e) { if (this.GridView1.PageIndex < this.GridView1.PageCount) { this.GridView1.PageIndex = this.GridView1.PageIndex + 1; DataBinds(); } } //最后一页 protected void lnkbtnLast_Click(object sender, EventArgs e) { this.GridView1.PageIndex = this.GridView1.PageCount; DataBinds(); } protected void GridView1_DataBound(object sender, EventArgs e) { this.lblCurrentPage.Text = string.Format("当前第{0}页/总共{1}页", this. GridView1.PageIndex + 1, this.GridView1.PageCount); } //数据源 public DataSet GetDataFromDataBase() { string getConnetionString = string.Empty; getConnetionString Configuration.ConfigurationManager.AppSettings ["ConnectionString"].ToString(); string sql = "select ProductID,ProductName,UnitPrice, Discontinued from dbo.Products"; SqlConnection conn = new SqlConnection(); conn.ConnectionString = getConnetionString; SqlDataAdapter adp = new SqlDataAdapter(sql, conn); DataSet ds = new DataSet(); adp.Fill(ds); return ds; }
数据库配置文件如下:
<appSettings> <add key="ConnectionString" value="Server=; user id=sa; pwd=123456; database= Northwind"/> </appSettings>
最后整个程序显示效果如图1-13所示。
图1-13
对于DetailsView控件,它的作用在于在表格中显示数据源的单个记录,此表格中每个数据行表示记录中的一个字段,可以从它的关联数据源中一次显示、编辑、插入或删除一条记录。默认情况下,会将记录的每个字段显示在它自己的一行内。DetailsView控件通常用于更新和插入新记录,并且通常在主/详细方案中使用,在这些方案中,主控件(最常见的是与GridView控件一起使用)的选中记录决定要在DetailsView控件中显示的记录。即使DetailsView控件的数据源公开了多条记录,该控件一次也仅显示一条数据记录。此控件依赖于数据源控件的功能执行诸如更新、插入和删除记录等任务。但是此控件不支持排序。可以自动对其关联数据源中的数据进行分页,但前提是数据由支持ICollection接口的对象表示或基础数据源支持分页。DetailsView控件提供用于在数据记录之间导航的用户界面。若要启用分页行为,则需要将AllowPaging属性设置为true。从关联的数据源选择特定的记录时,可以通过分页到该记录进行选择。由该控件显示的记录是当前选择的记录。
和GridView控件一样,DetailsView控件也有两种数据绑定方式:使用DataSourceID属性进行数据绑定;使用DataSource属性进行数据绑定。但要注意的是,当使用DataSourceID属性绑定到数据源时,DetailsView控件支持双向数据绑定。除可以使该控件显示数据之外,还可以使它自动支持对绑定数据的插入、更新和删除操作。此时若要使DetailsView控件支持编辑操作,绑定数据源必须支持对数据的更新操作,需要同时满足才能达到此目的。
1.如何在DetailsView控件中进行分页
如果DetailsView控件被绑定到某个数据源控件或任何实现了ICollection接口的数据结构(包括数据集),则此控件将从数据源获取所有记录,显示当前页的记录,并丢弃其余的记录。当用户移到另一页时,控件会重复此过程,显示另一条记录。对于有些数据源(如ObjectDataSource控件)提供更高级的分页功能。DetailsView控件在分页时可以利用这些数据源的更加高级的功能,从而获得更好的性能和更大的灵活性。同样如果数据源未实现ICollection接口,DetailsView控件将无法分页。例如如果使用SqlDataSource控件,并将其DataSourceMode属性设置为DataReader,则DetailsView控件是无法实现分页功能的。
下面演示一个配置为提供分页的DetailsView控件:
<h3>DetailsView Example Demo</h3> <table cellspacing="10"> <tr> <td valign="top"> <asp:DetailsView ID="EmployeesDetailsView" DataSourceID="EmployeesSqlDataSource" AutoGenerateRows="False" AllowPaging="True" DataKeyNames="EmployeeID" runat="server"> <HeaderStyle forecolor="White" backcolor="Blue" /> <Fields> <asp:BoundField Datafield="EmployeeID" HeaderText="Employee ID" ReadOnly="True"/> <asp:BoundField Datafield="Title" HeaderText="Title"/> <asp:BoundField Datafield="BirthDate" HeaderText="BirthDay"/> </Fields> <PagerSettings Mode="NextPreviousFirstLast" FirstPageText="<<" LastPageText=">>" PageButtonCount="1" Position="Top"/> </asp:DetailsView> </td> </tr> </table> <asp:SqlDataSource ID="EmployeesSqlDataSource" SelectCommand="SELECT [EmployeeID], [Title], [BirthDate] FROM [Employees]" connectionstring="<%$ ConnectionStrings:NorthwindConnectionString %>" RunAt="server"/>
2.使用DetailsView控件修改数据
通过将AutoGenerateEditButton、AutoGenerateInsertButton和AutoGenerateDeleteButton属性中的一个或多个设置为true,就可以启用DetailsView控件的内置编辑功能。DetailsView控件将自动添加此功能,使用户能够编辑或删除当前绑定的记录以及插入新记录,但前提是DetailsView控件的数据源支持编辑。DetailsView控件需要提供一个用户界面,使用户能够修改绑定记录的内容。通常在一个可编辑视图中会显示一个附加行,其中包含“编辑”、“插入”和“删除”命令按钮。默认情况下,这一行会添加到DetailsView控件的底部。当用户单击某个命令按钮时,DetailsView控件会重新显示该行,并在其中显示可让用户修改该行内容的控件。编辑按钮将被替换为可让用户保存更改或取消编辑行的按钮。
DetailsView控件使用文本框显示BoundField中的数据,以及那些在将AutoGenerateRows属性设置为true时自动显示的数据。布尔型数据使用复选框显示。通过使用TemplateField,当然也可以自定义在编辑模式中显示的输入控件。当DetailsView控件执行插入操作时,将使用Values字典集合传递要插入到数据源中的值。对于更新或删除操作,DetailsView控件使用这三个字典集合将值传递到数据源:Keys字典、NewValues字典和OldValues字典。读者可以使用传递到插入、更新或删除事件的事件参数访问各个字典,这些事件由DetailsView控件引发。
Keys字典中包含一些字段的名称和值,这些字段唯一地标识了包含要更新或删除的记录,此外该字典总是包含编辑记录之前键字段的原始值。若要指定哪些字段放置在Keys字典中,可将DataKeyNames属性设置为表示数据的主键的字段名称列表,各字段名称之间用逗号分隔。Keys集合中会自动填充与在DataKeyNames属性中指定的字段关联的值。Values和NewValues字典分别包含所插入或编辑的记录中的输入控件的当前值。OldValues字典包含除键字段以外的任何字段的原始值,键字段包含在Keys字典中。键字段的新值包含在NewValues字典中。
下面演示一个使用GridView控件和DetailsView控件显示数据,并将DetailsView控件配置为允许修改数据:
<h3>Northwind Employees</h3> <table cellspacing="10"> <tr> <td valign="top"> <asp:DropDownList ID="EmployeesDropDownList" DataSourceID="EmployeesSqlDataSource" DataValueField="EmployeeID" DataTextField="FullName" AutoPostBack="True" Size="10" OnSelectedIndexChanged="EmployeesDropDownList_OnSelectedIndex Changed" RunAt="Server" /> </td> <td valign="top"> <asp:DetailsView ID="EmployeeDetailsView" DataSourceID="EmployeeDetailsSqlDataSource" AutoGenerateRows="False" AutoGenerateInsertbutton="True" AutoGenerateEditbutton="True" AutoGenerateDeletebutton="True" DataKeyNames="EmployeeID" OnItemUpdated="EmployeeDetailsView_ItemUpdated" OnItemDeleted="EmployeeDetailsView_ItemDeleted" RunAt="server"> <HeaderStyle backcolor="Navy" forecolor="White"/> <RowStyle backcolor="White"/> <AlternatingRowStyle backcolor="LightGray"/> <EditRowStyle backcolor="LightCyan"/> <Fields> <asp:BoundField DataField="EmployeeID" HeaderText="Employee ID" InsertVisible="False" ReadOnly="True"/> <asp:BoundField DataField="FirstName" HeaderText="First Name"/> <asp:BoundField DataField="LastName" HeaderText="Last Name"/> <asp:BoundField DataField="Address" HeaderText="Address"/> <asp:BoundField DataField="City" HeaderText="City"/> <asp:BoundField DataField="Region" HeaderText="Region"/> <asp:BoundField DataField="PostalCode" HeaderText="Postal Code"/> </Fields> </asp:DetailsView> </td> </tr> </table> <asp:SqlDataSource ID="EmployeesSqlDataSource" SelectCommand="SELECT EmployeeID, LastName + ', ' + FirstName AS FullName FROM Employees" Connectionstring="<%$ ConnectionStrings:NorthwindConnectionString %>" RunAt="server"> </asp:SqlDataSource> <asp:SqlDataSource ID="EmployeeDetailsSqlDataSource" SelectCommand="SELECT EmployeeID, LastName, FirstName, Address, City, Region, PostalCode FROM Employees WHERE EmployeeID = @EmpID" InsertCommand="INSERT INTO Employees(LastName, FirstName, Address, City, Region, PostalCode) VALUES (@LastName, @FirstName, @Address, @City, @Region, @Postal Code); SELECT @EmpID = SCOPE_IDENTITY()" UpdateCommand="UPDATE Employees SET LastName=@LastName, FirstName= @FirstName, Address=@Address,City=@City, Region=@Region, PostalCode=@PostalCode WHERE EmployeeID=@EmployeeID" DeleteCommand="DELETE Employees WHERE EmployeeID=@EmployeeID" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" OnInserted="EmployeeDetailsSqlDataSource_OnInserted" RunAt="server"> <SelectParameters> <asp:ControlParameter ControlID="EmployeesDropDownList" Property Name="SelectedValue" Name="EmpID" Type="Int32" DefaultValue="0" /> </SelectParameters> <InsertParameters> <asp:Parameter Name="LastName" Type="String" /> <asp:Parameter Name="FirstName" Type="String" /> <asp:Parameter Name="Address" Type="String" /> <asp:Parameter Name="City" Type="String" /> <asp:Parameter Name="Region" Type="String" /> <asp:Parameter Name="PostalCode" Type="String" /> <asp:Parameter Name="EmpID" /> </InsertParameters> <UpdateParameters> <asp:Parameter Name="LastName" Type="String" /> <asp:Parameter Name="FirstName" Type="String" /> <asp:Parameter Name="Address" Type="String" /> <asp:Parameter Name="City" Type="String" /> <asp:Parameter Name="Region" Type="String" /> <asp:Parameter Name="PostalCode" Type="String" /> <asp:Parameter Name="EmployeeID" Type="Int32" DefaultValue="0" /> </UpdateParameters> <DeleteParameters> <asp:Parameter Name="EmployeeID" Type="Int32" DefaultValue="0" /> </DeleteParameters> </asp:SqlDataSource>
后台代码如下:
public void EmployeesDropDownList_OnSelectedIndexChanged(Object sender, EventArgs e) { EmployeeDetailsView.DataBind(); } public void EmployeeDetailsView_ItemUpdated(Object sender, DetailsView UpdatedEventArgs e) { EmployeesDropDownList.DataBind(); EmployeesDropDownList.SelectedValue = e.Keys["EmployeeID"].ToString(); EmployeeDetailsView.DataBind(); } public void EmployeeDetailsView_ItemDeleted(Object sender, DetailsView DeletedEventArgs e) { EmployeesDropDownList.DataBind(); } public void EmployeeDetailsSqlDataSource_OnInserted(Object sender, SqlData SourceStatusEventArgs e) { System.Data.Common.DbCommand command = e.Command; EmployeesDropDownList.DataBind(); EmployeesDropDownList.SelectedValue = command.Parameters["@EmpID"].Value.ToString(); EmployeeDetailsView.DataBind(); }
运行效果如图1-14所示。
图1-14
要注意GridView和DetailsView控件中用于定义字段类型的元素。这些元素实际上等效于DataGrid控件中的元素。表1-4列出了受支持的字段类型。特别重要的是ImageField和DropDownListField,它们都可以有效地削减目前开发人员为在DataGrid中包含图像和数据绑定下拉列表而编写的大部分代码。
表1-4 GridView和DetailsView字段类型