富数据控件 GridView(行选择、排序、分页)
GridView 行选择
GridView 内建支持选择。只需加入 ShowSelectButton 属性为 true 的 CommandField 列即可。CommandField 可以呈现为超链接、按钮或固定的图片。使用ButtonType 属性选择类型后,就可以通过 SelectText 属性(默认为 Select )指定文字或者通过 SelectImageUrl 属性指定图片的链接。
<asp:CommandField ShowSelectButton="true" ButtonType="Image" SelectImageUrl="~/images/Print.ico"/>
<asp:CommandField ShowSelectButton="true" ButtonType="Button" />
单击选择按钮时,页面回传并展开一系列步骤:
- 首先,GridView.SelectedIndexChanging 事件发生,可以响应它取消操作。
- 接着,调整 GridView.SelectedIndex 属性指向当前选中的行。
- 最后,GridView.SelectedIndexChanged 事件发生,可以在该事件中手动更新其他控件以反映用户的新选择。
- 页面呈现后,SelectedRowStyle 作用于改行。
使用选择来创建 主-从表单
你可以在页面中加入两个 GridView 控件,并用从第一个 GridView 中获得的信息在第二个中执行查询。对于 GridView 而言,需要绑定的属性是 SelectedIndex,不过这里有一个问题,SelectedIndex 返回的是一个基于零的数字,它反映在网格中的位置。这并非获得相关记录的查询所需的信息。相反,你需要当前行的关键字段。幸好,GridView 通过SelectedDataKey 属性可以轻易获得这些信息。要使用该特性,你必须用逗号分隔的一个或多个关键字段的列表来设置 GridView.DataKeyNames 属性。你指定的每个名字必须和绑定对象的属性或绑定记录的字段相匹配!(一般情况下只有一个关键字段。)
<asp:GridView ID="GridView1" runat="server" DataSourceID="sourceEmployees" DataKeyNames="EmployeeID" ....../>
现在可以把第二个数据源绑定到这个字段上进行查询。这个示例在联合查询中使用 EmployeeID 查找 Territories 表中匹配的记录,得到指定雇员管理的所有区域:
<asp:SqlDataSource ID="sourceRegions" runat="server" ConnectionString="<%$ ConnectionStrings:Northwind %>"
ProviderName="System.Data.SqlClient" SelectCommand="select Employees.EmployeeID,Territories.TerritoryID,Territories.TerritoryDescription from Employees
inner join EmployeeTerritories on Employees.EmployeeID =EmployeeTerritories.EmployeeID inner join Territories on
EmployeeTerritories.TerritoryID = Territories.TerritoryID where (Employees.EmployeeID = @EmployeeID)">
<SelectParameters>
<asp:ControlParameter ControlID="GridView1" Name="EmployeeID" PropertyName="SelectedDataKey.Values["EmployeeID"]" />
</SelectParameters>
</asp:SqlDataSource>
<asp:GridView ID="GridView2" runat="server" DataSourceID="sourceRegions">
</asp:GridView>
本例中的查询参数从上一个 GridView 中的 SelectedDataKey.Values 集合中获取,也可以使用索引(本例中是0,因为DataKeyNames 列表中只有一个字段)。使用名字查找时唯一要注意的小技巧是必须使用相应的 HTML 字符实体(")代替双引号。
SelectedIndexChanged 事件
很多情况下,还是需要响应 SelectedIndexChanged 事件。可能需要重定向用户到某个页面(在查询字符串中使用选定的值)或者需要调整同一页面的其他控件。
protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
{
int index = GridView1.SelectedIndex;
// 可以获取选中行的数据键值
int ID = (int)GridView1.SelectedDataKey.Values["EmployeeID"];
// 可以从单元格集合中获取值,前提是要知道索引
string firstName = GridView1.SelectedRow.Cells[3].Text;
string lastName = GridView1.SelectedRow.Cells[4].Text;
Label1.Text = firstName + " " + lastName + " ID:" + ID;
}
将数据字段用作选择按钮
很多时候并不需要创建一个新列去支持行选择,而是可以把一个现有的数据列变为链接,该技术广泛应用于帮助用户通过唯一标识符选定表中的行。单击后页面会触发 GridView.RowCommand 事件,可以处理这个事件通过编程来设置 SelectedIndex 属性,不过更为简单的办法是指定 CommandName 属性的内容为 "Select" ,就可以自动配置响应 GridView.SelectedIndexChanged 事件了。
<asp:ButtonField ButtonType="Button" DataTextField="EmployeeID" CommandName="Select"/>
对 GridView 排序
为了启用排序,必须设置 GridView.AllowSorting 属性为 true,为每个可排序的列定义排序表达式。排序表达式一般使用 SQL 查询中 ORDER BY 子句的形式。
<asp:BoundField DataField="FirstName" HeaderText="First Name" SortExpression="FirstName" />
一旦设置了排序表达式并已经把 AllowSorting 设为 true 后,GridView 会把标题呈现为可单击的链接。
实现真实的排序逻辑是数据源控件的责任,排序如何实现取决于你使用的数据源,并非所有的数据源都支持排序,但 SqlDataSource 和 ObjectDataSource 执持。
使用 SqlDataSource 排序
使用 SqlDataSource 排序时,排序由 DataView 类内置的排序功能实现。用户单击链接时,DataView.Sort 属性被设置为列的排序表达式。
使用 ObjectDataSource 排序
ObjectDataSource 提供了两种排序方法:
- 如果选择方法返回的是 DataSet 或者 DataTable,ObjectDataSource 可以使用和 SqlDataSource 一样的自动排序。
- 如果选择方法返回的是自定义集合,那么需要提供一个接收排序表达式并执行排序的方法。这样的方式为你构建解决方案提供了足够的灵活性,但是它未必足够理想。例如,创建一个带有 Sort()方法的自定义的 EmployeeDetails 集合更有意义,而不是创建一个可以执行排序的 GetEmployee()方法。遗憾的是,ObjectDataSource 不支持这种模式。
你需要创建一个接收字符串作为参数的方法来使用排序参数。你还必须让 ObjectDataSource .SortParameterName 属性使用和这个参数一模一样的名字:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployees" SortParameterName="sortExpression"></asp:ObjectDataSource>
设置了 SortParameterName 后,ObjectDataSource 总是调用接收排序表达式的那个版本的方法(网格第一次打开时,ObjectDataSource 传递一个空字符串作为排序表达式)。
实现可排序的 GetEmployees()方法最简单的做法是,填充一个非连接的 DataSet,使用 DataView 的排序功能:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployees" SortParameterName="sortExpression"></asp:ObjectDataSource>
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1" AllowSorting="true">
</asp:GridView>
public EmployeeDetails[] GetEmployees(string sortExpression)
{
SqlConnection conn = new SqlConnection(conStr);
SqlCommand cmd = new SqlCommand("GetAllEmployees", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter sda = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
conn.Open();
sda.Fill(ds, "Employees");
}
catch (Exception err)
{
throw new ApplicationException("Data Error!");
}
finally
{
conn.Close();
}
DataView view = ds.Tables[0].DefaultView;
// 这里将传入的 表达式 赋值上去
view.Sort = sortExpression;
ArrayList employees = new ArrayList();
foreach (DataRowView row in view)
{
EmployeeDetails emp = new EmployeeDetails((int)row["EmployeeID"],
row["FirstName"].ToString(), row["LastName"].ToString(),
row["TitleOfCourtesy"].ToString());
employees.Add(emp);
}
// 这里需要类型转换
return (EmployeeDetails[])employees.ToArray(typeof(EmployeeDetails));
}
排序和选择
如果同时使用了选择和排序,很快就会发现另一个问题,选定一行然后用任意一列进行排序,你会发现选择行仍然保留,但是它已经变为和原来选择具有相同序号的其它项目了。
过去为了解决这个问题,办法是在每次单击标题连接后通过代码改变选择项。但 ASP.NET 给 GridView 增加了一个 EnablePersistedSelection 属性,把它设为 true,ASP.NET 就会确保选中项由数据键值来决定。
高级排序
增强 GridView 排序功能的第一个办法就是处理 GridView.Sorting 事件,它在排序前发生。
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
// 前者获取的是将要进行排序的表达式
// 后者获取的是排序前的 GridView 排序表达式(即前一次的)
if (e.SortExpression == "FirstName" && GridView1.SortExpression=="LastName")
{
e.SortExpression = "LastName,FirstName";
}
}
再进一步,还可以实行任意列集合的级联排序,你可以在视图状态里记住用户过去所做的排序操作,然后用它们创建一个大的排序表达式。不过这样做也许会导致用户直觉的混淆,会有很多别的问题。这里只是说明了这种操作的可能性。
还可以获取下拉列表的选择的值,用代码来调用 GridView.Sort()方法实现排序:
protected void ddlSorts_SelectedIndexChanged(object sender, EventArgs e)
{
GridView1.Sort(ddlSorts.SelectedValue, SortDirection.Descending);
}
GridView 分页
GridView 控件对分页提供内建的支持。你可以和 SqlDataSource 或 ObjectDataSource 一起实现简单的分页。后者还可以使用跟高效、更具灵活性的方式来实现自定义分页。
自动分页
设置一些属性并处理一个事件后,就可以让 GridView 控件代为管理分页。GridView 提供了几个专为支持分页而设计的属性:
AllowPaging | 启用或禁用分页(默认为禁用) |
PageSize | 获取或设置网格中每页显示的记录数(默认值 10) |
PageIndex | 分页启用时,获取或设置基于零的当前页码 |
PagerSettings | 提供一个为分页控件封装了各种格式化选项的 PagerSettings 对象。这些选项决定分页控件出现的位置、包含的图片或文本。可以设置这些属性来精化分页控件的外观。 |
PagerStyle | 提供一个样式对象,使用它可以配置分页控件的字体、颜色以及文字对齐方式。 |
PageIndexChanging 事件 | 导航之前发生 |
PageIndexChanged 事件 | 导航之后发生 |
下面示例声明一个简单的分页:
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
AllowPaging="true" PageSize="3">
</asp:GridView>
自动分页可以和任意实现了 ICollection 接口的数据源一起使用。也就是说,只要使用 DataSet 模式,SqlDataSource 就支持自动分页。此外,如果自定义的数据访问类返回实现了 ICollection 接口的对象,数组、强类型集合、非连接的 DataSet 都是有效的选项,ObjectDataSource 也支持分页。
自动分页并没有减少数据库查询的数据量。相反,每次用户改变了当前页码时都需要获取和绑定所有数据。使用数据源控件的自动缓存可以大幅度提升自动分页的性能,但巨大的查询结果并不适合在内存中缓存,因此,最好的解决方案是自定义分页。
分页和选择
默认情况下,分页和选择不能一起很好的工作。如果同时启用二者,就会发现在页面跳转时之前选择的行号一直保持选中状态。为了修复这个缺陷,可以设置 GridView 的 EnablePersistedSelection 属性为 true。这样,在页面跳转时选中项会自动被移除(SelectedIndex 被设为 -1),但如果返回到原始选中行的所在页,选中状态将重新恢复。
使用 ObjectDataSource 的自定义分页
自定义分页需要你负责为 GridView 析取和绑定当前页的记录。GridView 不再自动选择要显示的行。不过,GridView 还将提供带有自动生成的链接的分页栏。这些链接允许用户在页间导航。
尽管自定义分页远比自动分页复杂,但它允许你最小化带宽需求并避免在服务器端内存中保存大量的数据。但另一方面,所有的自定义分页策略都需要在每次回发时访问数据库,它也意味着可能给数据库带来更多的工作。
ObjectDataSource 是唯一支持自定义分页的数据源。
自行管理分页的步骤:
- 设置 ObjectDataSource.EnablePaging 为 true
- 通过 3 个属性的设置来实现分页:
- StartRowIndexParameterName
- MaxmumRowsParameterName
- SelectCountMethod
1. 记录计数
为了让 GridView 能够正确创建页码的链接,它必须知道记录的总数以及每页显示的记录数。自定义分页时,必须通过专门的方法显式计算记录总数。
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" EnablePaging="true" TypeName="Model.EmployeeDB"
SelectCountMethod="CountEmployees" ......></asp:ObjectDataSource>
2. 使用获得分页记录的存储过程
GetEmployees()方法不再获取所有员工信息了,而只是获取当前页面显示的记录,因此新建一个存储过程来完成这个有条件的查询:
create proc GetEmployeePage
@Start int,@Count int
as
Create table #TempEmployees --创建临时表
(
ID int identity primary key,
EmployeeID int,
LastName nvarchar(20),
FirstName nvarchar(10),
TitleOfCourtesy nvarchar(25),
)
-- 填充记录到零时表
insert into #TempEmployees
(
EmployeeID,LastName,FirstName,TitleOfCourtesy
)
select
EmployeeID,LastName,FirstName,TitleOfCourtesy
from
Employees order by EmployeeID asc
declare @FromID int
declare @ToID int
set @FromID = @Start
set @ToID = @Start + @Count - 1
select * from #TempEmployees where ID >= @FromID and ID <= @ToID
3. 分页的选择方法
最后是创建一个重载的 GetEmployees()方法来执行分页。这个方法接受2个参数:分页开始的行号(从0开始)和 每页的大小(最大行数)。通过 ObjectDataSource 的 StartRowIndexParameterName 和 MaxmumRowsParameterName 属性指定要使用的参数名字(不指定的话,默认为 startRowIndex 和 maximumRows)。
public EmployeeDetails[] GetEmployees(int startRowIndex, int maximumRows)
{
SqlConnection conn = new SqlConnection(conStr);
SqlCommand cmd = new SqlCommand("GetAllEmployees", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@Start", SqlDbType.Int, 4));
cmd.Parameters["@Start"].Value = startRowIndex + 1;
cmd.Parameters.Add(new SqlParameter("@Count", SqlDbType.Int, 4));
cmd.Parameters["@Count"].Value = maximumRows;
ArrayList employees = new ArrayList();
try
{
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
EmployeeDetails emp = new EmployeeDetails((int)reader["EmployeeID"],
reader["FirstName"].ToString(), reader["LastName"].ToString(),
reader["TitleOfCourtesy"].ToString());
employees.Add(emp);
}
reader.Close();
return (EmployeeDetails[])employees.ToArray(typeof(EmployeeDetails));
}
catch (Exception err)
{
throw new ApplicationException("Data Error!");
}
finally
{
conn.Close();
}
}
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" EnablePaging="true" TypeName="Model.EmployeeDB"
SelectCountMethod="CountEmployees" SelectMethod="GetEmployees"></asp:ObjectDataSource>
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1" AllowPaging="true" PageSize="3">
</asp:GridView>
定制分页栏
GridView 分页控件非常灵活。你可以通过 PagerStyle(可设置背景色、前景色、字体、颜色、大小)和 PagerSettings 属性彻底改变它的外观。
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1" AllowPaging="true" PageSize="3">
<PagerSettings Mode="NextPrevious" PreviousPageText="< Back" NextPageText="Forward >" />
<PagerStyle BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center" />
</asp:GridView>
最重要的细节在于 PageSettings.Mode 属性,它根据下表的几个模式决定分页链接呈现的风格:
Numeric | 网格将呈现 PagerSettings.PageButtonCount 属性定义的页数的链接。 |
NextPrevious | 网格只呈现两个链接,用于跳转到前一页和后一页。还可以设置显示的文字。 |
NumericFirstLast | 和Numeric相同,但同时显示第一页和最后一页的链接。 |
NextPreviousFirstLast | 和NextPrevious相同,同时显示第一页和最后一页的链接。 |
如果不喜欢默认的分页栏,还可以使用模版功能创建一个 PagerTemplate 来实现自己的分页栏。
排序和分页回调
用网格排序和分页的一个缺点是,每次对网格重新排序或移动到另一页时,浏览器需要触发一个回送并呈现一个全新的 HTML 页。这意味着页面将闪动并回滚到开始,这使得整个用户体验出现了一点不和谐。
GridView 中的一个特性可以改善这种情形,即 EnableSortingAndPagingCallbacks 属性。如果将该属性设为 true,那么 GridView 将使用另外一种技术来刷新页面。当单击列标题或页面链接时,浏览器向服务器发送一个异步请求,而不是强制执行一个回送来获得新信息。当浏览器收到该信息时,使用 HTML DOM 技术来修改当前页面。这一技术创造了一种更加无缝的、无闪动的浏览体验。最重要的是,如果浏览器不支持这一特性,GridView 会优雅的降到标准的回送模式。唯一的限制是,你不能在使用模版的网格上使用排序和分页回调。
EnableSortingAndPagingCallbacks 属性使用的是 ASP.NET 的回调架构。实际上是用 JavaScript 和 XMLHttpRequest 对象来执行回调的。