Nikhil Kothari
Microsoft Corporation
2000年8月
摘要: DataGrid 控件是与 ASP+ Page Framework 一起发运的数据绑定的服务器控件。本文使用 DataGrid 来建立包含主/详细资料视图的 Web 页面。还探讨控件所提供功能的各个方面,包括排序、编辑、分页和模板列,而控件就是使用这些功能来建立最终的页面的。
下载 DataGridSamples.exe 文件。(119 KB)
目录
简介
Microsoft® Visual Studio.NET 的下一发行版包括 DataGrid Web 控件 (作为服务器控件的 Active Server Page+ (ASP+) 套件的一部分)。 该控件提供用以根据数据源的内容来表示 HTML 的功能。
DataGrid 控件可以用于若干个只读汇报情形。该控件设计用于对丰富而完全可定制的数据表格布局的输出进行简化。还提供多个机制,用于通过超级链接及其对选择、排序、分页和原地编辑和其它特性的支持,为输出添加交互性。这使得该控件在若干的常见 Web 应用方案中很有用,诸如列表、购物车和查询结果。
DataGrid 还提供一些功能,这些功能具有 ASP+ 架构所特有的所有服务器控件的特点。该控件包含进行与浏览器无关的输出所需的逻辑,同时提供了一个统一的编程模型,从而能够处理回传数据,以及对请求之间的状态进行管理。这样,开发商就可以针对带有属性、方法和事件的对象模型进行编程,而不必处理直接用 HTML 编程所带来的不一致性和复杂性。
使用 ASP+ 列表绑定控件 (英文) 介绍 DataGrid 控件以及相关的 DataList 和 Repeater 控件。还介绍了数据绑定、模板和格式化等等多个关键的概念。该文以此为基础写成,并将重点放在 DataGrid 控件,以揭示如何在您自己的应用程序中利用该控件所提供的功能。
我们要建立什么?
该文举出了一序列的示例页面,彼此结合,从而最终生成一个页面,该页面以示例数据库的 Authors 表和 Titles 表为依据,提供主/详细资料视图(该数据库随 Microsoft SQL Server™2000 一起发运)。序列中的每个页面均介绍 DataGrid 控件的一个新的特性或功能。下列图象是从 pubs 数据库抽取出来的。
主/详细资料视图类似于 Microsoft Access 所介绍的窗体/子窗体概念。也类似于随 Microsoft Visual InterDev®6.0 一起发运的 DataForm Wizard (数据窗体向导)。主/详细资料视图显示一到多的关系结果,其中视图的一个部分显示第一个查询或主查询的结果。然后跟踪一个选择,以筛选所使用的第二个查询的结果,从而在视图的另一部分显示选择内容的详细资料。
图 1. 完成的页面
图 1 将 Author 列表显示在页面的上半部分,并将关于所选作者的详细资料(包括相关书名)显示在下半部分。 Authors 列表和 Titles 均是用 DataGrid 控件加以表示的。 显示作者的 DataGrid 举例说明如何进行选择、排序、和分页。显示书名的 DataGrid 演示如何进行原地编辑、格式化和定制列。
数据访问
为了使示例自成一体,从 SQL Server 抽取数据并将该数据连同其架构信息一同保留为一个 XML 文件 TitlesDB.xml。下面是该文件的一个片断。
<root>
<schema id="DocumentElement" targetNamespace=""
xmlns="http://www.w3.org/1999/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<element name="Author">
<complexType content="elementOnly">
<element name="au_id" type="string" minOccurs="1"
maxOccurs="1"></element>
<element name="au_name" type="string" minOccurs="1"
maxOccurs="1"></element>
<element name="address" type="string" minOccurs="0"
maxOccurs="1"></element>
<element name="city" type="string" minOccurs="0"
maxOccurs="1"></element>
<element name="state" type="string" minOccurs="0"
maxOccurs="1"></element>
<element name="zip" type="string" minOccurs="0"
maxOccurs="1"></element>
<element name="phone" type="string" minOccurs="0"
maxOccurs="1"></element>
</complexType>
<unique name="AuthorConstraint" msdata:PrimaryKey="True">
<selector>.</selector>
<field>au_id</field>
</unique>
</element>
<element name="Title">
<complexType content="elementOnly">
<element name="title_id" type="string" minOccurs="1"
maxOccurs="1"></element>
<element name="au_id" type="string" minOccurs="1"
maxOccurs="1"></element>
<element name="title" type="string" minOccurs="1"
maxOccurs="1"></element>
<element name="price" msdata:DataType="System.Currency"
type="string"
minOccurs="1" maxOccurs="1"></element>
<element name="pubdate" type="timeInstant" minOccurs="1"
maxOccurs="1"></element>
</complexType>
<unique name="TitleConstraint" msdata:PrimaryKey="True">
<selector>.</selector>
<field>title_id</field>
</unique>
<key name="AuthorTitle">
<selector>../Author</selector>
<field>au_id</field>
</key>
<keyref refer="AuthorTitle">
<selector>.</selector>
<field>au_id</field>
</keyref>
</element>
</schema>
<DocumentElement>
<Author>
<au_id>154-00-1300</au_id>
<au_name>John Doe</au_name>
<phone>425 705 1234</phone>
<address>One Microsoft Way</address>
<city>Redmond</city>
<state>CA</state>
<zip>98005</zip>
</Author>
<Title>
<title_id>BU1032</title_id>
<au_id>213-46-8915</au_id>
<title>The Busy Executive's Database Guide</title>
<price>19.99</price>
<pubdate>1991-06-12T07:00:00</pubdate>
</Title>
</DocumentElement>
</root>
这些样例简化了数据访问,从而将重点全部放在 DataGrid 的使用上。上面的 XML 被加载进一个 DataSet。 DataSet 为数据提供高速缓存,从而可以进行筛选、排序和编辑等等各种操作。下面的代码来自 Global.asax,用于加载 DataSet 和将其保存为 Session 状态。
public void Session_OnStart() {
// 将样例中所用的数据载入会话范围的 DataSet.
FileStream fs = null;
DataSet ds = null;
try {
fs = new FileStream(Server.MapPath("Data\\TitlesDB.xml"),
FileMode.Open, FileAccess.Read);
ds = new DataSet();
ds.ReadXml(fs);
} finally {
if (fs != null) {
fs.Close();
fs = null;
}
}
Session["AppData"] = ds;
}
在实际的 Web 应用程序中,通常不是使用处于 Session 或 Application 状态的高速缓存数据,而是通过所存储的过程、中间层业务对象,或通过调用 Web 服务所揭示的方法来访问和修改数据。无论采取怎样的手段来访问数据,您会发现你依旧以同样的方式来编程和与控件的对象模型进行进行交互。 www.IBuySpy.com (英文) 网站就是演示这些概念的一个很好的应用示例。
第 1 步: 一个基本的 DataGrid
序列的第一步展示了一个页面,其中包含单独一个 DataGrid 控件,用于显示来自数据源的一个只读作者列表。结果类似于 使用 ASP+ 列表绑定控件 (英文) 所取得的效果。
图 2. 完成第 1 步后的页面
DataGrid 声明来自:
Step1.aspx: <asp:DataGrid id="authorsGrid" runat="server"
AutoGenerateColumns="false"
BackColor="White"
BorderWidth="1px" BorderStyle="Solid" BorderColor="Tan"
CellPadding="2" CellSpacing="0"
Font-Name="Verdana" Font-Size="8pt">
<property name="Columns">
<asp:BoundColumn HeaderText="ID" DataField="au_id">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Name" DataField="au_name">
<property name="HeaderStyle">
<asp:TableItemStyle Width="150px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="State" DataField="state">
<property name="HeaderStyle">
<asp:TableItemStyle Width="50px"/>
</property>
</asp:BoundColumn>
</property>
<property name="HeaderStyle">
<asp:TableItemStyle BackColor="DarkRed" ForeColor="White"
Font-Bold="true"/>
</property>
<property name="ItemStyle">
<asp:TableItemStyle ForeColor="DarkSlateBlue"/>
</property>
<property name="AlternatingItemstyle">
<asp:TableItemStyle BackColor="Beige"/>
</property>
</asp:DataGrid>
上面的代码展示 DataGrid,该控件的各种属性已经过声明设定。 DataGrid 控件与其它 Web 控件如 Font、BackColor、ForeColor 和 BorderWidth 共享一组公用的样式属性。另外, DataGrid 提供仅适用于表的属性如 CellPadding。最后, DataGrid 提供附加的样式属性,这些样式属性影响其中各项目和列如 HeaderStyle、ItemStyle 和 AlternatingItemStyle 的表示。这些样式属性用于创建丰富多采且极富魅力的数据视觉效果。
DataGrid 支持从其所绑定的数据源自动生成列的功能。但是,在本例中, AutoGenerateColumns 属性已被设定为 false。因此必须借助要展示的列集对 Columns 集合进行初始化。从而可以更多地控制表现效果,诸如列的次序和标头以及与每列对应的样式。这一步中所定义的列均为 BoundColumns,从而可以通过其 DataField 属性,绑定到数据源的单独一个字段。您在以后步骤中可以看到, DataGrid 允许选择各种各样类型的列。
下面的类包含支持本页面的代码。
Step1Page.cs: namespace Samples {
...
public class Step1Page : Page {
protected DataGrid authorsGrid;
// 检索存入会话状态的应用程序数据
private DataSet GetSessionData() {
return (DataSet)Session["AppData"];
}
// 检索 Authors 表
private ICollection GetAuthors() {
DataSet ds = GetSessionData();
DataView dv = ds.Tables["Author"].DefaultView;
dv.RowFilter = String.Empty;
return dv;
}
// 将 Authors 表载入 DataGrid
private void LoadAuthorsGrid() {
ICollection authors = GetAuthors();
authorsGrid.DataSource = authors;
authorsGrid.DataBind();
}
// 超控 OnLoad,以在第一次请求过程中加载数据
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (!IsPostBack) {
LoadAuthorsGrid();
}
}
}
}
该类超越 Page 的 OnLoad 方法 (类似于实施 Page_Load),将 Author 列表作为 DataGrid 数据源进行加载。与其它服务器控件一样, DataGrid 实施显式的数据绑定模型,其中控件只在 DataBind 方法被调用时才列举器数据源中的值。这样设计各个控件,您就可以完全控制在哪个时间点需要数据源,并将无需数据时的数据访问降至最低,例如大多数的往返过程。这一点会在将来的步骤中得到证明。
代码还展示从 Session 状态检索的数据,数据是在会话启动过程中存为该状态的。
第 2 步: 带有多页面的主/详细资料视图
创建主/详细资料视图的一个方法就是使用多页面。在该方法中,主查询中的选定值的详细资料视图被显示在次要页面上。在关于选定内容的信息中,该次要页面作为 URL 请求中的一个参数得到传递。
图 3. 第 2 后的页面,展示详细资料页面的浮动视图
Step2.aspx 包含对 DataGrid 的更改,以使其包含一个名为 DataGrid 声明的列。
Step2.aspx: <asp:DataGrid id="authorsGrid" runat="server"
AutoGenerateColumns="false"
BackColor="White"
BorderWidth="1px" BorderStyle="Solid" BorderColor="Tan"
CellPadding="2" CellSpacing="0"
Font-Name="Verdana" Font-Size="8pt">
<property name="Columns">
<asp:BoundColumn HeaderText="ID" DataField="au_id">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Name" DataField="au_name">
<property name="HeaderStyle">
<asp:TableItemStyle Width="150px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="State" DataField="state">
<property name="HeaderStyle">
<asp:TableItemStyle Width="50px"/>
</property>
</asp:BoundColumn>
<asp:HyperLinkColumn Text="Details" DataNavigateUrlField="au_id"
DataNavigateUrlFormatString="Step2a.aspx?AuthorID={0}"/>
</property>
<property name="HeaderStyle">
<asp:TableItemStyle BackColor="DarkRed" ForeColor="White"
Font-Bold="true"/>
</property>
<property name="ItemStyle">
<asp:TableItemStyle ForeColor="DarkSlateBlue"/>
</property>
<property name="AlternatingItemstyle">
<asp:TableItemStyle BackColor="Beige"/>
</property>
</asp:DataGrid>
DataGrid 声明与第 1 步中的 DataGrid 声明几乎一样。已将单独一个 HyperLinkColumn 添加到 Columns 集合。 HyperLinkColumn 用来在 DataGrid 的每行中创建一个可导航的链接。该类型的列允许其 Text 和 NavigateUrl 属性与数据绑定。这样, Text 属性是静态的,而 NavigateUrl 是数据绑定到作者 ID 的(通过设定 DataNavigateUrl 属性)。另外,指定 DataNavigateUrlFormatString 建立一个以 Author ID 为参数的指向详细资料页面的 URL。 因此,每行均包含一个带有“详细资料”超级链接的附加列,而该链接的 URL 以与该行关联的数据为依据。
HyperLinkColumn 代表着向本来只读的数据显示添加交互功能的第一步。
支持该页面 (Step2Page.cs) 的代码与第 1 步相同。添加这一列并不添加任何代码,也不导致任何代码发生变化,因此此处没有列出那些代码。
Step2a.aspx 实施详细资料页面,以显示某一具体作者的详细资料。
Step2a.aspx: <asp:Panel id="detailsPanel" runat="server">
<table border="0" cellspacing="0" cellpadding="2" width="100%"
style="font-family: verdana; font-size: 8pt">
<tr>
<td width="200"><b>Name:</b></td>
<td width="100%">
<%# DataBinder.Eval(CurrentAuthor, "au_name") %>
</td>
</tr>
<tr>
<td width="200"><b>ID:</b></td>
<td width="100%">
<%# DataBinder.Eval(CurrentAuthor, "au_id") %>
</td>
</tr>
<tr>
<td width="200" valign="top"><b>Address:</b></td>
<td width="100%">
<%# DataBinder.Eval(CurrentAuthor, "address") %><br>
<%# DataBinder.Eval(CurrentAuthor, "city") %>,
<%# DataBinder.Eval(CurrentAuthor, "state") %>
<%# DataBinder.Eval(CurrentAuthor, "zip") %>
</td>
</tr>
<tr>
<td width="200"><b>Phone:<b></td>
<td width="100%">
<%# DataBinder.Eval(CurrentAuthor, "phone") %>
</td>
</tr>
<tr>
<td colspan="2"><b>Title(s) Authored</b></td>
</tr>
<tr>
<td colspan="2">
<asp:DataGrid id="titlesGrid" runat="server"
DataSource='<%# DataBinder.Eval(CurrentAuthor, "AuthorTitle") %>'
AutoGenerateColumns="false"
ShowFooter="true"
BackColor="White"
BorderWidth="1px" BorderStyle="Solid" BorderColor="Tan"
CellPadding="2" CellSpacing="0"
Font-Name="Verdana" Font-Size="8pt"
OnItemCreated="OnItemCreatedTitlesGrid">
<property name="Columns">
<asp:BoundColumn HeaderText="ID" DataField="title_id">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Title" DataField="title">
<property name="HeaderStyle">
<asp:TableItemStyle Width="250px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Published" DataField="pubdate"
DataFormatString="{0:MMM yyyy}">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Price" DataField="price"
DataFormatString="{0:c}">
<property name="HeaderStyle">
<asp:TableItemStyle Width="50px"/>
</property>
<property name="ItemStyle">
<asp:TableItemStyle HorizontalAlign="Right"/>
</property>
</asp:BoundColumn>
</property>
<property name="HeaderStyle">
<asp:TableItemStyle BackColor="DarkRed" ForeColor="White"
Font-Bold="true"/>
</property>
<property name="FooterStyle">
<asp:TableItemStyle BackColor="Tan"/>
</property>
<property name="ItemStyle">
<asp:TableItemStyle ForeColor="DarkSlateBlue"/>
</property>
<property name="AlternatingItemstyle">
<asp:TableItemStyle BackColor="Beige"/>
</property>
</asp:DataGrid>
</td>
</tr>
</table>
</asp:Panel>
本页包含若干使用 DataBinder.Eval 的数据绑定表达式。这些表达式抽取本页代码中所实施的 CurrentAuthor 属性的各个属性。
DataGrid 用于显示选定作者所编著的书名。与第 1 步相同, DataGrid 包含一个针对要显示的列的定义,以及用于为列、行和总体控制提供可视格式化的样式属性设置。
BoundColumns 还允许对数据进行格式化。正如在上面声明中看到的那样, DataFormatString 属性用于对日期和货币值进行格式化。格式化对于表示本地化文字和控制非字符串类型的文字呈现来讲极其有用。
Step2aPage.cs 包含支持详细资料页面的代码。
Step2aPage.cs: namespace Samples {
...
public class Step2aPage : Page {
private object currentAuthor;
// 返回当前选定的 Author
public object CurrentAuthor {
get {
return currentAuthor;
}
}
// 检索存入会话状态的应用程序数据
private DataSet GetSessionData() {
return (DataSet)Session["AppData"];
}
// 处理 ItemCreated 事件,以将标尾定制为
// 显示总结信息
protected void OnItemCreatedTitlesGrid(object sender,
DataGridItemCreatedEventArgs e) {
if (e.Item.ItemType == ListItemType.Footer) {
int cellCount = e.Item.Cells.Count;
for (int i = 0; i < cellCount - 1; i++) {
e.Item.Cells.RemoveAt(0);
}
int itemCount = titlesGrid.Items.Count;
string itemCountString;
if (itemCount == 0) {
itemCountString = "No Titles Found";
}
else {
itemCountString = Int32.ToString(itemCount) +
" title(s)";
}
TableCell summaryCell = e.Item.Cells[0];
summaryCell.Text = itemCountString;
summaryCell.ColumnSpan = cellCount;
summaryCell.HorizontalAlign = HorizontalAlign.Right;
}
}
// 超控 OnLoad,以载入选定作者的详细资料
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
string authorID = Request.QueryString["AuthorID"];
if (authorID != null) {
SelectAuthor(authorID);
detailsPanel.DataBind();
}
}
// 根据给定作者 ID 来设置 CurrentAuthor 对象
private void SelectAuthor(string authorID) {
DataSet ds = GetSessionData();
DataView dv = ds.Tables["Author"].DefaultView;
dv.RowFilter = "au_id = '" + authorID + "'";
currentAuthor = dv[0];
}
}
}
通过访问 Request.QueryString 集合,类就超越页面的 OnLoad 方法来检索作为 URL 请求中的参数传来的 Author ID。然后使用该 Author ID 来查询数据源和设置 CurrentAuthor 属性。最后,调用 DataBind,从而为所有的数据绑定的表达式求值。
另外,该页面为 DataGrid 控件的 ItemCreated 事件实施了一个事件处理器。 ItemCreated 事件是 DataGrid 为其高级用途提供的扩展机制之一。该事件允许从行的控件结构内部添加和删除控件,以及在特殊情况下将其添加到特定行。
DataGrid 每次创建一个项目(或行)时,就引发该事件。这具体有两种情况:
要对控件进行数据绑定,且项目需要从头创建,则在将项目进行数据绑定之前,引发该事件。
当要从往返过程中的保存状态创建项目时,则在将所保存的状态载入项目及其包含的控件之前引发该事件。
结果是,该事件提供一个挂钩,用于对项目的现有控件结构进行更改。
在本例中,处理器修改控件的标尾,以显示所列书名的的一个计数。标尾所包含的列的数目与其它行一样。摘要需要横跨整个 DataGrid。因此,处理器仅保留行中的一个单元格,将其余全部删除,并将仅剩的单元格的 ColumnSpan 重置,以使其横跨整个列集,设定其 HorizontalAlign 属性,以使文字右对齐,最后设定其 Text,指示计数。
处理该事件时,只有一条规则需要遵守:您必须进行同样的结构转换,无论调用该项目的上下文如何,无论是在数据绑定过程中,还是在往返过程中。
第 3 步: 单页面中的主/详细资料视图
创建主/详细资料视图的另一备选且更常用的方法就是用单页面显示这些视图。
图 4. 完成第 3 步后的页面
Authors DataGrid 来自:
Step3.aspx: <asp:DataGrid id="authorsGrid" runat="server"
...
DataKeyField="au_id"
OnSelectedIndexChanged="OnSelIndexChangedAuthorsGrid">
<property name="Columns">
<asp:ButtonColumn Text="Select" Command="Select"/>
...
</property>
...
<property name="SelectedItemStyle">
<asp:TableItemStyle BackColor="PaleGoldenRod" Font-Bold="true"/>
</property>
</asp:DataGrid>
与前一步相比,该声明中有三处变化。
首先,在第 2 步中添加的 HyperLinkColumn 已被删除。这不再需要,因为整个视图是在单页面中实施的。
其次,向列集添加了一个新的列类型 ButtonColumn。该列在每行中生成 LinkButton,用于提交该页,而不是从该页进行浏览。列的 Command 属性设定为 Select,对相应的 LinkButton 属性进行设定。 DataGrid 将 Select 作为一个标准命令,将包含被单击按钮的列选定。
当选定内容发生变化时, DataGrid 就引发 SelectedIndexChanged 事件,该事件在代码中得到处理。 DataKeyField 属性页得到设定,从而导致 DataKeys 集合填置与数据源中的每个项目相对应的值。
该页面还包含针对题为 detailsPanel 的面板内的详细资料节的 UI 和布局。这是从第 2 步原样复制的。
Step3Page.cs 表示这一步中的 .aspx 页面的支持代码。其中包括来自第 2 步中两个有代码支持的文件的代码组合。来自详细资料页面的大部分代码均为原样照搬。下面显示的代码包含我们所作的更改和补充。
Step3Page.cs: namespace Samples {
...
public class Step3Page : Page {
private object currentAuthor;
// 将 Authors 表载入 DataGrid
// 另外还更新选定的作者
private void LoadAuthorsGrid() {
ICollection authors = GetAuthors();
authorsGrid.DataSource = authors;
if (authors.Count != 0) {
authorsGrid.SelectedIndex = 0;
}
else {
authorsGrid.SelectedIndex = -1;
}
authorsGrid.DataBind();
UpdateDetails();
}
// 处理作者网格中的 SelectedIndexChanged 事件,以
// 更新详细资料
protected void OnSelIndexChangedAuthorsGrid(object sender,
EventArgs e) {
UpdateDetails();
}
// 根据当前选定的作者来更新
// 详细资料节
private void UpdateDetails() {
UpdateSelection();
if (currentAuthor != null) {
detailsPanel.Visible = true;
detailsPanel.DataBind();
}
else {
detailsPanel.Visible = false;
}
}
// 更新当前选定的作者
private void UpdateSelection() {
int selectedIndex = authorsGrid.SelectedIndex;
currentAuthor = null;
if (selectedIndex != -1) {
string authorID =
(string)authorsGrid.DataKeys[selectedIndex];
DataSet ds = GetSessionData();
DataView dv = ds.Tables["Author"].DefaultView;
dv.RowFilter = "au_id = '" + authorID + "'";
currentAuthor = dv[0];
}
}
}
}
每次加载 Authors DataGrid 时,就初始化其 SelectedIndex 属性。一旦得到绑定,即其 DataKeys 集合填置完毕,就通过调用 UpdateDetails,更新详细资料节。
针对 SelectedIndexChanged 事件的处理器中的详细资料节也得到更新。注意,此时已借助 DataBind 未被调用以来的保存状态将 DataKeys 集合填置完毕。
UpdateDetails 方法首先调用 UpdateSelection。 UpdateSelection 使用 Authors DataGrid 的 SelectedIndex 和 DataKeys 属性来确定选定作者的 ID 和初始化 CurrentAuthor 属性。然后, UpdateDetails 调用 detailsPanel 控件上的 DataBind,对访问 CurrentAuthor 的数据绑定表达式进行求值。
除了借助 SelectedIndexChanged 事件进行选择跟踪,还可以从本样例实现另一关键概念。注意,改变选择内容并不需要已将 Authors DataGrid 进行过数据绑定。因此,从不需要重新加载 Authors 列表,从而极大地减少了对 Authors 表的访问。这是 ASP+ 中所实施的显式数据绑定模型的一个关键益处。
第 4 步: 排序
DataGrid 支持生成可点击标头的功能,此类标头可以用于让最终用户对控件中所展示的数据进行排序。这一步添加了对 Authors 列表进行排序的功能。
图 5. 完成第 4 步后页面的屏幕快照
Authors DataGrid 来自:
Step4.aspx <asp:DataGrid id="authorsGrid" runat="server"
...
AllowSorting="true"
OnSortCommand="OnSortCommandAuthorsGrid"
OnItemCreated="OnItemCreatedAuthorsGrid">
<property name="Columns">
<asp:ButtonColumn Text="Select" Command="Select"/>
<asp:BoundColumn HeaderText="ID" DataField="au_id">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Name" DataField="au_name"
SortField="au_name">
<property name="HeaderStyle">
<asp:TableItemStyle Width="150px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="State" DataField="state"
SortField="state">
<property name="HeaderStyle">
<asp:TableItemStyle Width="75px"/>
</property>
</asp:BoundColumn>
</property>
...
</asp:DataGrid>
通过将 AllowSorting 设定为 true,启用排序。然后,对不支持排序的每列的 SortField 属性进行设定。没有设定该属性的列不会生成可点击标头。最后,处理 SortCommand 事件,这会在有代码支持的文件的上下文中加以论述。样例还为 ItemCreated 事件添加了一个事件处理器,以在视觉上显示列标头中的当前排序设置。下面的代码展示为向 Authors 列表添加排序功能而在有代码支持的类中进行更改和补充。
Step4Page.cs: namespace Samples {
...
public class Step4Page : Page {
// 返回当前的排序方向,该信息是在
// Page 状态中维持的
protected bool SortAscending {
get {
object o = State["SortAscending"];
if (o != null)
return (bool)o;
return true;
}
set {
State["SortAscending"] = value;
}
}
// 返回当前的排序字段,该信息是在
// Page 状态中维持的
protected string SortField {
get {
object o = State["SortField"];
if (o != null)
return (string)o;
return "au_name";
}
set {
State["SortField"] = value;
}
}
// 检索 Authors 表
private ICollection GetAuthors() {
DataSet ds = GetSessionData();
DataView dv = ds.Tables["Author"].DefaultView;
dv.RowFilter = String.Empty;
string sort = SortField;
if (SortAscending == false) {
sort += " desc";
}
dv.Sort = sort;
return dv;
}
// 处理 ItemCreated 事件,以用排序图符对标头
// 进行定制
protected void OnItemCreatedAuthorsGrid(object sender,
DataGridItemCreatedEventArgs e) {
if (e.Item.ItemType == ListItemType.Header) {
string sortField = SortField;
bool ascending = SortAscending;
Label sortGlyph = new Label();
sortGlyph.Text = ascending ? " 5" : " 6";
sortGlyph.Font.Name = "Webdings";
TableCell cell = null;
if (sortField.Equals("au_name")) {
cell = e.Item.Cells[2];
}
else if (sortField.Equals("state")) {
cell = e.Item.Cells[3];
}
if (cell != null) {
cell.Controls.Add(sortGlyph);
}
}
}
// 处理作者网格中的 SortCommand 事件,以更新
// 排序参数和重新加载新排序的数据
protected void OnSortCommandAuthorsGrid(object sender,
DataGridSortCommandEventArgs e) {
string currentSortField = SortField;
SortField = e.SortField;
if (currentSortField.Equals(e.SortField)) {
SortAscending = !SortAscending;
}
else {
SortAscending = true;
}
LoadAuthorsGrid();
}
}
}
页面将当前字段和排序方向作为名值对,保留在页面的 State 属性中,并将其作为 SortField 和 SortAscending 属性提供出来,此时,页面负责在往返过程之间维持这些属性的值。
这些属性用于 GetAuthors 函数,这里,在返回 Author 列表 之前,会根据属性的当前值对其进行排序。
用于 SortCommand 事件的事件处理器在 DataGridSortCommandEventArgs 的一个实例中得到传递。该对象包含 SortField 属性的值,而该属性与标题被单击的列相关联。您可以选择对该值进行任意处理,只要该处理在您的应用程序中合理。例如,该值可以包含单个字段的名称,例如本样例中的情形,也可以包含排序信息,从而允许进行多列排序。
实施该方法,对 SortField 和 SortAscending 属性的值进行更新。最后,该方法调用 LoadAuthorsGrid,对 Authors DataGrid 进行数据绑定。此时情况是, DataGrid 的列需要重新生成,要求您设定数据源和调用 DataBind。
ItemCreated 的事件处理器类似于第 2 步中用于 Titles DataGrid 的事件处理器。此时,处理器修改标头行的控件结构,具体方法是添加表示上升和下降的图符,指示当前已排序的列和当前的排序方向。
这里有一个问题需要回答,DataGrid 为何不对其数据进行实际的排序。这主要有两个原因。首先, ASP+ 的数据源是一般的 ICollection,以便您在选择数据源时拥有最大的灵活性。数据源并不包含内置的排序语义。其次,也是更重要的一点, 在每次请求过程中,自动排序均会需要数据源的一个活动的实例。这不太适合用于显式数据绑定结构中,因为这需要您在页面的往返过程中访问和加载数据。
第 5 步: 分页
DataGrid 提供显示特殊行的功能,该行包含用于显示页码和“下一步”/“上一步”浏览按钮的 UI。这一步添加了对 Authors 列表进行分页查看的功能。
图 6. 完成第 5 步后页面的屏幕快照
Authors DataGrid 来自:
Step5.aspx: <asp:DataGrid id="authorsGrid" runat="server"
...
AllowPaging="true" PageSize="5"
OnPageIndexChanged="OnPageIndexChangedAuthorsGrid">
...
<property name="PagerStyle">
<asp:DataGridPagerStyle BackColor="Tan" HorizontalAlign="Right"
PageButtonCount="3" Mode="NumericPages"/>
</property>
</asp:DataGrid>
与排序类似,您必须设定几个属性并实施一个事件处理器,从而启用分页功能。启用分页功能的具体步骤是,将 AllowPaging 属性设定为 true,并将 PageSize 属性设定为一个正整数。 PageSize 属性确定了要在单页面中表现的数据源的值。
该样例还展示设定 PagerStyle 属性,从而使您可以对分页 UI 的视觉外观和布局进行定制。样例将分页器一次展示三个页码按钮,方法是将 Mode 设定为 NumericButtons,而将 PageButtonCount 属性设定为 "3",并使其向右对其。除了这些设置,您还可以选择“下一步”/“上一步”样式按钮,分页 UI 的位置,其颜色方案,等等。
要支持分页,就必须对 DataGrid 的 PageIndexChanged 事件进行处理,具体讨论见 Step5Page.cs。
Step5Page.cs: namespace Samples {
...
public class Step5Page : Page {
// 处理作者网格中的 PageIndexChanged 事件,以
// 更新数据
protected void OnPageIndexChangedAuthorsGrid(object sender,
DataGridPageChangedEventArgs e) {
LoadAuthorsGrid();
}
}
}
PageIndexChangedEvent 的事件处理程序只是重新加载 DataGrid,从而自动呈现数据源中所有项目的分页视图。事件处理器是在 DataGridPageChangedEventArgs 的一个实例中传递的,该实例包含关于旧的页面索引和新的页面索引的信息。如果根据应用程序的状态,您确定不应更改页面索引,则可以仅将 DataGrid 的 CurrentPageIndex 属性设定回旧的页面索引,跳过重新加载数据。显式数据绑定模型使这成为可能。这里,直到对 DataBind 进行调用时 DataGrid 的内容才得到刷新。
DataGrid 支持一种备选的分页模式,您可以通过将 AllowCustomPaging 属性设定为 true 而启用该模式。在该模式中,您负责向 DataGrid 告知您的数据源中的值的总数,具体方法是设定 VirtualItemCount 属性。一旦处于该模式,您的数据源就想必只包含当前页面的值。在该模式中,仅需从查询检索最少数目的值,从而进一步优化您的数据访问。
第 6 步: 原地编辑
DataGrid 支持对其行的原地编辑。这一步使用该功能,从而允许在 Titles DataGrid 内部进行编辑。
图 7. 完成第 6 步后的页面
Titles DataGrid 来自:
Step6.aspx: <asp:DataGrid id="titlesGrid" runat="server"
...
OnEditCommand="OnEditCommandTitlesGrid"
OnUpdateCommand="OnUpdateCommandTitlesGrid"
OnCancelCommand="OnCancelCommandTitlesGrid">
<property name="Columns">
<asp:BoundColumn HeaderText="ID" DataField="title_id" ReadOnly="true">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Title" DataField="title" ReadOnly="true">
<property name="HeaderStyle">
<asp:TableItemStyle Width="250px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Published" DataField="pubdate"
DataFormatString="{0:MMM yyyy}" ReadOnly="true">
<property name="HeaderStyle">
<asp:TableItemStyle Width="100px"/>
</property>
</asp:BoundColumn>
<asp:BoundColumn HeaderText="Price" DataField="price"
DataFormatString="{0:c}">
<property name="HeaderStyle">
<asp:TableItemStyle Width="50px"/>
</property>
<property name="ItemStyle">
<asp:TableItemStyle HorizontalAlign="Right"/>
</property>
</asp:BoundColumn>
<asp:EditCommandColumn EditText="Edit" CancelText="Cancel"
UpdateText="OK"/>
</property>
...
</asp:DataGrid>
通过处理控件的 EditCommand、 UpdateCommand 和 CancelCommand 事件来达到在 DataGrid 内进行编辑的目的。
Columns 集合包含一个名为 EditCommandColumn 的新的列。该列自动创建每行右侧的按钮集。为只读模式中的各行创建 Edit 按钮,并为编辑模式中的各行创建 Update 和 Cancel 按钮。列的 EditText、 CancelText 和 UpdateText 属性用来指定按钮的文本。注意,也可以将文本设定为 HTML 标记,从而为这些按钮使用图象。
最后,各种列的 ReadOnly<./font> 属性被设定为 true。这就防止对这些列中的数据进行编辑,即使列处于编辑模式。对于本样例,只有价格字段是可编辑的,因而所有其它 BoundColumns 均被标注为只读。
上面声明的事件处理器在下面展示的有代码支持的类中得到实施。
Step6Page.cs: namespace Samples {
...
public class Step6Page : Page {
// 更新当前选定的作者并重新加载与选定内容对应的书名
// 网格
protected void LoadTitlesGrid() {
UpdateSelection();
titlesGrid.DataBind();
}
// 处理书名网格中的 CancelCommand 事件,以
// 不施用更改就结束编辑
protected void OnCancelCommandTitlesGrid(object sender,
DataGridCommandEv