简介 
 创建可排序的 DataGrid 
 创建可分页的 DataGrid 
 创建可排序、可分页的 DataGrid  
 小结 

简介
Microsoft? ASP.NET 最大的用处之一就是能够在 Web页中方便地显示数据。ASP.NET 包含三个数据 Web 控件(DataGrid、DataList 和 Repeater),每个控件都是为丰富数据而设计的。 在这三个数据 Web 控件中,最常用的是 DataGrid,这主要是由于它有方便的内置功能集。具体来说,只要设置几个属性并创建两个事件处理程序, DataGrid 就可以提供排序、分页或编辑支持。有关这三个数据 Web 控件之间的区别,以及何时选择哪个控件的详细信息, 请务必阅读 Deciding When to Use the DataGrid, DataList or Repeater。

尽管创建可排序或可分页的 DataGrid 非常简单,但是要创建既可分页又可排序的 DataGrid 就比较困难了。在本文中,我们先来看看如何创建可排序和可分页的 DataGrid, 然后再讨论将这两个功能结合到一个 DataGrid 所必需的步骤。前两节内容将探讨如何向 DataGrid Web 控件添加排序功能和分页功能;如果您已经精通这一领域, 请直接跳到最后一节,该节将探讨如何将这两种功能结合到一个 DataGrid 中。

返回页首
创建可排序的 DataGrid
在开始创建可排序的 DataGrid 之前,首先需要一个只显示某些数据的 DataGrid。为了实现本文的目的, 让我们创建一个简单的 DataGrid,让它显示 Northwind 数据库中 Products 表的内容。

注 Northwind 数据库是随 Microsoft 数据库产品(如 Microsoft SQL Server(TM) 和 Microsoft Access)附带的一个标准数据库。

接下来,新建一个名为 SortableDataGrid.aspx 的 ASP.NET Web 页,然后开始添加一个 DataGrid,并将其 ID 属性设置为dgProducts。接着,将 AutoGenerateColumns 属性设置为 False,并对该 DataGrid 进行配置,使其使用三个 BoundColumn 来显示 ProductName、 UnitPrice 和 UnitsInStock 字段。剩下的唯一操作就是查询数据库并将查询结果与该 DataGrid 绑定。下面的代码显示了 Page Load 事件处理程序和一个自定义方法 BindData(),为了使一切运行正常,应当将这些代码添加到代码隐藏类中。 

private void Page_Load(object sender, System.EventArgs e) 

   BindData(); 

private void BindData() 

   // Connect to the Database 
   SqlConnection myConnection = new SqlConnection(
connection string
); 
   // Retrieve the SQL query results and bind it to the Repeater 
   string SQL_QUERY = "SELECT ProductName, UnitPrice, UnitsInStock " + 
                      "FROM Products"; 
   SqlCommand myCommand = new SqlCommand(SQL_QUERY, myConnection); 
   myConnection.Open(); 
   dgProducts.DataSource = myCommand.ExecuteReader(); 
   dgProducts.DataBind(); 
   myConnection.Close(); 


请注意,为了使这些代码能够正常工作,您需要在代码隐藏类中导入 System.Data.SqlClient命名空间,还必须将 SqlConnection 构造函数中的连接字符串更改为您的数据库的连接字符串。输入该代码之后,构建解决方案,然后通过 Web 浏览器访问该 Web 页,对它进行测试。图 1显示了 SortableDataGrid.aspx 的屏幕快照(当然,您可以使用 Microsoft Visual Studio? .NET 中的“自动套用格式”工具,使 DataGrid 的外观更吸引人。)


按此在新窗口打开图片
图 1. 通过浏览器查看的 SortableDataGrid.aspx 


要使 SortableDataGrid.aspx 中的 DataGrid 可排序,首先将 DataGrid 的 AllowSorting 属性设置为 True。将此属性设置为 True 时,DataGrid 会以 LinkButton 来呈现每一列的标题,其效果是将每一列的标题呈现为超级链接。最终用户只需单击相应列标题的超级链接,即可指定要按哪一列对 DataGrid 结果进行排序。

单击列标题的超级链接时,将会回发 ASP.NET Web 页,并且激发 DataGrid 的 SortCommand 事件。作为该 ASP.NET Web 页的开发人员,我们负责为 DataGrid 的 SortCommand 事件创建一个事件处理程序,并将 DataGrid 的 SortCommand 事件与这个创建的事件处理程序进行紧密联系。该事件处理程序负责确定最终用户要按哪一列对数据进行排序,然后对基础数据重新排序,并将它与 DataGrid 重新绑定。

在编写 SortCommand 事件处理程序的代码之前,我们必须首先添加该事件处理程序。使用诸如 Visual Studio .NET 之类的工具会使该步骤大为简化。只需在“设计器”中单击 dgProducts DataGrid 即可编辑它的属性。接着,单击“属性”窗格顶部的“闪电”图标,以便查看该 DataGrid 的事件。滚动浏览至SortCommand 事件,然后输入要与该事件相关联的事件处理程序的名称,Visual Studio .NET 将完成其余任务!图 2 显示“属性”窗格中的事件列表。


按此在新窗口打开图片
图 2. 为 DataGrid 的 SortCommand 事件创建事件处理程序


如图 2 所示,我已经决定将此事件处理程序命名为dgProducts_Sort,只要使用任何合法的函数名就足够了。一旦在适当的事件文本框中输入某个事件处理程序的名称,Visual Studio .NET 就会自动在代码隐藏类中创建该事件处理程序的函数外壳,并将该事件与事件处理程序相联系。

如果您使用的不是 Visual Studio .NET,则将需要手动执行以下两个步骤。首先创建该事件处理程序的外壳,如下所示:

private void dgProducts _Sort(object source, 
                              DataGridSortCommandEventArgs e) 

  // we'll add the code here in a bit! 


接着,将 DataGrid 的 SortCommand 事件与该事件处理程序进行紧密联系,这可通过以下两种方法之一来完成: 

• 在 DataGrid 的声明中添加 OnSortCommand="dgProducts_Sort"(即,将它添加到 HTML 部分中的 <asp:DataGrid> 开始标记内部) 
 
• 在代码隐藏类中的 InitializeComponent() 方法中,以编程方式将 DataGrid 的事件分配给该事件处理程序。在 C# 中,这可通过以下语法来完成: dgProducts.SortCommand += new DataGridSortCommandEventHandler(dgProducts_Sort); 
 

如果使用的是 Microsoft Visual Studio? .NET,则语法如下所示:

AddHandler dgProducts.SortCommand, AddressOf dgProducts_Sort 

在创建该事件处理程序并将其与 DataGrid 的 SortCommand 事件相联系之后,就可以开始添加该事件处理程序的代码了。实质上,我们需要确定最终用户要按哪一列对结果进行排序,并重新查询数据库, 以便按照所需的顺序重新检索结果。通过检查传递到 dgProducts_Sort 事件处理程序中的DataGridSortCommandEventArgs 参数的 SortExpression 属性,可以确定在最终用户单击之后导致回发的列。

请注意,每个 DataGrid 列都有一个与之相关的 SortExpression 值。当 DataGrid 的 AutoGenerateColumns 属性设置为 True 时,会为每一列自动分配一个 SortExpression 值, 此值等于该列所显示的 DataSource 字段的名称。当 AutoGenerateColumns 设置为 False 时,必须显式指定 SortExpression 属性的值。如果没有为某个特定列设置 SortExpression 属性,则该列的标题将不显示为超级链接;因此,最终用户将无法按这一特定的列对 DataGrid 进行排序。为了证明这一点,请仅设置前两个 BoundColumn 列的 SortExpression 属性。尽管可以将SortExpression 属性设置为任何值,但是请将前两个 BoundColumn 的SortExpression 均设置为与 DataField 属性相同的值。这样一来,DataGrid 声明应当如下所示:

<asp:DataGrid id="dgProducts" runat="server" AllowSorting="True" 
                  AutoGenerateColumns="False" ...> 
   <Columns> 
      <asp:BoundColumn DataField="ProductName"  
                HeaderText="Product Name"   
SortExpression="ProductName"
></asp:BoundColumn> 
      <asp:BoundColumn DataField="UnitPrice" HeaderText="Unit Price"  
                DataFormatString="{0:c}"  
SortExpression="UnitPrice"
></asp:BoundColumn> 
      <asp:BoundColumn DataField="UnitsInStock"  
                HeaderText="Units In Stock" DataFormatString="{0:d}"> 
      </asp:BoundColumn> 
   </Columns> 
</asp:DataGrid> 

现在,在 dgProducts_Sort 事件处理程序中,我们需要确定SortExpression 的值,然后按照正确的排序顺序将数据重新绑定到 DataGrid。要实现此目的有许多方法,最简单的就是重新查询按照 SortExpression指定的字段排序的所有数据。这不一定是最高效的方法,但却是仅有 77 条记录的 Products 表可接受的方法。要进行上述重新查询,可以修改 BindData() 方法以接受字符串参数,即要作为结果排序依据的列名。下面显示 BindData() 的这个更新版本:

private void BindData(
string orderBy


   // Connect to the Database 
   SqlConnection myConnection = new SqlConnection(
connection string
); 
   // Retrieve the SQL query results and bind it to the DataGrid 
   string SQL_QUERY = "SELECT ProductName, UnitPrice, UnitsInStock " + 
                      "FROM Products ORDER BY "
 + orderBy

   SqlCommand myCommand = new SqlCommand(SQL_QUERY, myConnection); 
   myConnection.Open(); 
   dgProducts.DataSource = myCommand.ExecuteReader(); 
   dgProducts.DataBind(); 
   myConnection.Close(); 


With this new version of BindData(), ourdgProducts_Sort event handler requires only one line of code, which can be seen below:

private void dgProducts_Sort(object source, 
          System.Web.UI.WebControls.DataGridSortCommandEventArgs e) 

   BindData(e.SortExpression);


剩下的唯一工作就是更新 Page Load 事件处理程序。首先,当页面没有回发时,只需调用 BindData() 方法即可,这是由于在以后的回发过程中,dgProducts_Sort 事件处理程序将进行 BindData() 调用。 其次,Page Load 事件处理程序使用的是旧版本的 BindData() 方法,它不采用任何输入参数 - 我们需要对其进行更新,以便传入要作为 DataGrid 最初排序依据的字段名。

private void Page_Load(object sender, System.EventArgs e) 

  if (!Page.IsPostBack);
  BindData("ProductName");


在进行了所有这些更改之后,构建解决方案并对它进行测试。图 3 显示了首次访问时的SortableDataGrid.aspx,而图 4 显示了用户单击 “Unit Price”(单价)列标题超级链接之后的 Web 页。请注意,在这两幅图中,DataGrid 无法按“Units In Stock”(库存量)列进行排序,这是由于我们并未提供此 BoundColumn 的 SortExpression 属性。


按此在新窗口打开图片

按此在新窗口打开图片
创建可分页的 DataGrid
与创建可排序的 DataGrid 一样,要创建可分页的 DataGrid,最好先创建一个只显示数据的 DataGrid。由于我们在上一节的第一部分中已经完成了此操作,因此我不想在这里对此主题再作过多讨论,只会提到创建一个名为 PageableDataGrid.aspx 的 ASP.NET Web 页,并从我们开始讨论添加排序功能之前的 SortableDataGrid.aspx Web 页中,复制 HTML 部分中的 DataGrid 声明,以及代码隐藏类中的 Page_Load 和 BindData() 方法的代码。

尽管可以逐字逐句地复制 Page Load 事件处理程序,但是将需要对BindData() 方法进行少许更改。具体来说,请不要将 SqlDataReader 绑定到 DataGrid,您需要使用 DataTable 或DataSet。我们将稍微讨论一下为什么要这样做。

在向 DataGrid 中添加分页支持之前,一定要认识到 DataGrid 有两种形式的分页支持:

• 默认分页 
 
• 自定义分页 
 

这些分页模型各有利弊:默认分页比自定义分页易于实现,但是自定义分页可提供更好的性能。之所以出现上述利弊, 是因为若使用默认分页,当每个用户每次从一页数据导航到另一页时,您总是要将需要分页的整个 DataSource 绑定到 DataGrid。使用默认分页时,DataGrid 本身要负责确定DataSource 中有多少条记录以及到底需要显示 DataSource 中的哪些记录。相反,自定义分页要求只有要显示在当前数据源中的那些记录时,才会位于要绑定到 DataGrid 的DataSource 中。

默认分页之所以易于实现,是因为当用户从一页数据导航到下一页时,您完全不必费心思考。就是说,您只需将 SQL 查询的结果绑定到 DataGrid,让 DataGrid 自行判断要显示哪些记录即可。使用自定义分页时,您必须使用巧妙的 SQL 语句或复杂的存储过程来挑选要显示在特定页上的准确记录集。与默认分页相比,自定义分页提供的性能更好,这是由于它只会访问那些需要显示在特定数据页上的记录。 使用默认分页时,每次用户查看另一页数据时,都会检索所有的记录。另外,默认分页要求您将 DataTable 或 DataSet 对象绑定到 DataGrid;也就是说,您不能使用 DataReader,这是由于 DataGrid 需要能够确定 DataSource 中有多少条记录,这样才能确定总共有多少页数据存在。

在本文中,我们把重点放在使用默认分页上,这是因为它比较容易实现。但是,将重点只放在默认分页上并不会有什么损失, 因为我们会在下一节中讨论用来创建可排序、可分页的 DataGrid 的技术,而此技术对于默认分页和自定义分页的效果一样好。

使用默认分页模型创建可分页 DataGrid 的第一步是将 DataGrid 的 AllowPaging 属性设置为 True。当该属性设置为 True 时,在默认情况下,它将在 DataGrid 的底部显示一个导航界面。该导航界面允许最终用户逐步阅读 DataGrid 中显示的各个数据页。在默认情况下,该导航界面使用 LinkButtons 来呈现上一组和下一组超级链接。当最终用户单击其中的某个超级链接时,将会回发 ASP.NET Web 页,并且激发 DataGrid 的 PageIndexChanged 事件。 我们负责为该事件创建一个事件处理程序,以便更新 DataGrid 的 CurrentPageIndex 属性,并将数据重新绑定到 DataGrid。

DataGrid 的 CurrentPageIndex 属性指出要显示的数据页。除了该属性以外,还有一些其他属性与分页有密切关系: 

• PageSize — 此属性指出要在每页上显示多少条记录,默认值为 10。 
 
• PageCount — 此属性指出数据页的总数。
 

因此, PageIndexChanged 事件处理程序只需将CurrentPageIndex 属性设置为用户选择的页,然后重新绑定 DataGrid(通过调用BindData()),就这么简单!通过引用传递到该事件处理程序的DataGridPageChangedEventArgs 对象的 NewPageIndex 属性,可以确定要查看的页索引。

在 BindData() 中调用 DataGrid 的 DataBind() 方法时,DataGrid 将使用 CurrentPageIndex 属性来确定要显示的数据页。接着,DataGrid 将使用要在每页上显示的记录数量来计算哪条记录应当显示为该页的第一条记录。然后,DataGrid 将导航到该记录,并显示该记录和接下来的 PageSize 记录。

让我们花点时间来执行以下操作:使用上一节中讨论的方法,向 DataGrid 的 PageIndexChanged 事件中添加一个名为 dgProducts Page 的事件处理程序。该事件处理程序的源代码如下所示:

private void dgProducts_Page(object source, 
             System.Web.UI.WebControls.DataGridPageChangedEventArgs e) 

   dgProducts.CurrentPageIndex = e.NewPageIndex;
   BindData();


同样,正如对于 SortableDataGrid.aspx Web 页一样,我们只想让 Page Load 事件处理程序在加载第一页时将数据绑定到 DataGrid,而在以后的回发时不进行绑定。下面的代码显示已更新的 Page Load 事件处理程序和 BindData() 方法。请注意, BindData() 方法将 DataTable 绑定到 DataGrid,而不是SqlDataReader。回想一下,在使用默认分页时,您无法使用 DataReader,而必须使用DataTable 或 DataSet;若使用 DataReader 将导致异常。不能使用 DataReader 的原因在于,DataGrid 必须能够确定 DataSource 总共有多少条记录,才能确定总共有多少页。因为 DataReader 支持只进访问,所以 DataGrid 无法确定它总共包含多少条记录。因此,默认分页必须使用可确定其记录数量的对象,如 DataTable 或 DataSet。

private void Page_Load(object sender, System.EventArgs e) 

   if (!Page.IsPostBack) 
      BindData(); 

private void BindData() 

   // Connect to the Database 
   SqlConnection myConnection = new SqlConnection(
connection string
); 
   // Retrieve the SQL query results and bind it to the DataGrid 
   string SQL_QUERY = "SELECT ProductName, UnitPrice, UnitsInStock " + 
                      "FROM Products"; 
   SqlCommand myCommand = new SqlCommand(SQL_QUERY, myConnection); 
   // Use a DataTable ?€_ required for default paging 
   SqlDataAdapter myAdapter = new SqlDataAdapter(myCommand); 
   DataTable myTable = new DataTable(); 
   myAdapter.Fill(myTable); 
   dgProducts.DataSource = myTable; 
   dgProducts.DataBind(); 
   myConnection.Close(); 


图 5 显示了首次访问时的PageableDataGrid.aspx它显示了 Products 表的前十条记录。图 6 显示了第二页数据,它是用户在图 5中的导航界面上单击 > 超级链接之后显示的。


按此在新窗口打开图片

按此在新窗口打开图片
图 6. 显示第二页数据。

• Specifying Paging Behavior in a DataGrid Web Server Control
 
• Letter/Alphanumeric Based Paging in ASP.NET
 
• Providing DataGrid Pagination
 

返回页首
创建可排序、可分页的 DataGrid 
到目前为止,我们已看过了如何创建可排序的 DataGrid 和可分页的 DataGrid。现在,我们把注意力转到将这两种功能组合到一个可排序、可分页的 DataGrid。将这两个功能合并到一个 DataGrid 的困难在于:每种方法都使用不同形式的 BindData() 方法。可排序的 DataGrid 将字符串参数传入 BindData() 中,以便指出要将 DataGrid 的哪一列作为排序依据。相反,可分页的 DataGrid 却不这样做。

初次尝试使这种差异趋于一致时,请使用接受字符串参数的 BindData() 版本,并让 DataGrid 的 PageIndexChanged 事件处理程序总是传入一些预定义的字符串值。例如,由于 PageIndexChanged 事件处理程序必须调用 BindData() 并传入字符串,对于该事件处理程序可以使用下面的代码:

private void dgProducts_Page(object source,  
             System.Web.UI.WebControls.DataGridPageChangedEventArgs e) 

   dgProducts.CurrentPageIndex = e.NewPageIndex; 
BindData("ProductName");


但是,此方法并不奏效。考虑下面的一系列事件:

• 用户访问该页,这会显示按 ProductName 字段进行排序的第一页数据。
 
• 用户单击“Unit Price”(单价)列标题超级链接,这会显示按“Unit Price”(单价)排序的第一页数据。 
 
• 用户单击 > 导航超级链接,这会请求下一页数据,从而导致在回发时执行 PageIndexChanged 事件处理程序,以便检索按 ProductName 排序的数据。因此,用户现在看到的是按 ProductName 排序的第二页,而不是按 UnitPrice 排序的第二页。
 

在从 PageIndexChanged 事件处理程序调用 BindData() 时,我们需要某种方式来记住排序所依据的字段。在往返于使用 ASP.NET 的服务器时,可通过许多方法来保存状态;但是,因为我们只需要为当前的 Web 页保存这些信息,而不需要为当前用户保存,所以最好的方法是在 Web 页的 ViewState 中存储用户要作为排序依据的列。

深入讨论 ViewState 已经远远超出了本文的范围。但是,我们可以花一点点时间,稍微讨论一下它在创建可排序、可分页的 DataGrid 中的用途。首先,要知道 ASP.NET Web 页中的所有控件都具有一个 ViewState,而且在将这些控件放到 Web 窗体 (<form runat="server">) 中时,它们的 ViewState 会保存到隐藏的 HTML 窗体字段中。我们可以用编程方式向 ViewState 中插入对象。然后,插入的这些对象会与其余的 ViewState 内容一起保存,这就表示这些值在回发时会被记住。如需进一步了解 ViewState,请务必阅读 Susan Warren 的佳作: Taking a Bite Out of ASP.NET ViewState。

下面显示用来在 ViewState 中插入和检索元素的语法:

// C# 
object o = ViewState[
key
];   // retrieval 
ViewState[
key
] = o;      // assignment 
' VB.NET 
Dim o as Object = ViewState(
key
)   ' retrieval 
ViewState(
key
) = o         'assignment 

在这里,key 是一个字符串关键字,它为存储在 ViewState 中的对象提供一个名称。

现在,每当用户单击 DataGrid 列标题中的超级链接时,我们都会做三件事:

• 将 DataGrid 的 CurrentPageIndex 重置为 0 
 
• 将 DataGridSortCommandEventArgs 参数的SortExpression 属性存储到键值为 SortExprValue 的 ViewState 中。 
 
• 调用 BindData(),传入 SortExpression 值。
 

要知道,每当用户单击 DataGrid 列标题中的超级链接时,第 1 步都将导致 DataGrid 返回到第一页数据。这是合理的,原因在于,假设某个用户当前正在查看按 Product Name(产品名)排序的第三页结果,如果该用户决定查看按 Unit Price(单价)排序的结果,则他/她应当看到第一页结果,从而看到最便宜的商品。如果我们不将 SortCommand 事件处理程序中的 CurrentPageIndex 重置为 0,则该用户在决定按 Unit Price(单价)排序时,将看到按 UnitPrice 字段排序的第三页数据,因此会造成混淆。

下面显示了这个新的 SortCommand 事件处理程序:

private void dgProducts_Sort(object source,  
          System.Web.UI.WebControls.DataGridSortCommandEventArgs e) 

dgProducts.CurrentPageIndex = 0;
ViewState["SortExprValue"] = e.SortExpression;
BindData(e.SortExpression);


有了这个 ViewState 变量, PageIndexChanged 事件处理程序就变得很容易编写。我们只需将 DataGrid 的 PageIndexChanged 属性设置为DataGridPageChangedEventArgs 的 NewPageIndex 属性,就像我们以前曾做过的那样,然后调用 BindData(),并传入 ViewState 对象 SortExprValue 的值。

private void dgProducts_Page(object source,  
           System.Web.UI.WebControls.DataGridPageChangedEventArgs e) 

dgProducts.CurrentPageIndex = e.NewPageIndex;
BindData(ViewState["SortExprValue"].ToString());


最后一个操作是更新 Page_Load 事件处理程序,以便 ViewState 对象SortExprValue 被设置为要作为 DataGrid 默认排序依据的列。这个小小的变动会生成如下所示的新Page_Load 事件处理程序:

private void Page_Load(object sender, System.EventArgs e) 

   if (!Page.IsPostBack) 
   { 
ViewState["SortExprValue"] = "ProductName";
BindData(ViewState["SortExprValue"].ToString());
   } 


瞧,一点都不难!本文的其余部分提供了一些屏幕快照,它们显示了这个可排序、可分页的 DataGrid 在操作中的情况。 请注意,您可以下载本文结尾的所有 ASP.NET 演示的完整源代码。 

图 7 显示了通过浏览器首次访问时的可排序、可分页的 DataGrid 的屏幕快照。图 8 显示了按 Product Name(产品名)排序的第二页数据屏幕快照。图 9 显示了用户选择按 Unit Price(单价)对结果进行排序之后立即生成的输出结果,图10 显示了按 Unit Price(单价)排序的第二页数据屏幕快照。

当设计一个安全系统时,应该根据安全第一、性能第二的原则来选择实现技术。例如,不带 SSL 的基本身份验证可以获得较好的性能,但是,不管它的速度有多快,如果不能降低系统受到攻击的威胁,则对于该系统并没有什么用处。

本文没有包含身份验证与数据加密相结合的整体性能影响,而这是实际系统中采用的安全保护方式。根据所采用的各种方案的不同组合, 一个安全系统的性能也会随之变化。


按此在新窗口打开图片
按此在新窗口打开图片

按此在新窗口打开图片

按此在新窗口打开图片
小结
创建可分页或可排序的 DataGrid 相对来说比较简单。但是,要将这两个方面组合到一个 DataGrid 中就需要一点技巧, 因为在对数据进行分页浏览时,我们需要记住 DataGrid 在排序时所依据的列。正如本文所提到的那样,我们可以利用 ViewState 来帮助记住 DataGrid 在排序时所依据的 DataSource 字段。 尽管本文将重点放在使用默认分页上,但是其技术同样适用于自定义分页。

如果您对本文内容、所讨论的技术或者所讨论的代码示例有疑问,欢迎您给我发电子邮件,我的联系地址是 mitchell@4guysfromrolla.com 。

祝您编程愉快!

推荐的链接

• DataGrid Web 控件的进一步探讨:第 15 部分 : Adding Default Paging Support to a DataGrid
 
• DataGrid Web 控件的进一步探讨: 第 4 部分:Creating a Sortable DataGrid
 
• ASP.NET 论坛中的The DataGrid, DataList, and Repeater Forum