扩展GridView控件以包含一个与排序相关的箭头标记

本文英文原版及代码下载:
http://aspnet.4guysfromrolla.com/articles/012308-1.aspx

扩展GridView控件以包含一个与排序相关的箭头标记

导言:

在ASP.NET 2.0发表之前,我针对DataGrid控件写了一本书以及许多的文章,虽然DataGrid依然存在于ASP.NET 2.0里,但相较而言GridView控件提供了更多的功能及特性.它可以绑定到象SqlDataSource和ObjectDataSource这样的数据源控件, 不用手写代码我们就可以执行排序、分页、编辑、删除等功能.

虽然DataGrid (以及GridView)支持内置的排序功能,但我们无法从感官上判断是按哪一列来进行排序的.在系列文章的Part 18部分《An Extensive Examination of the DataGrid Web Control》,我们看到了如何对一个支持排序的DataGrid的表头列进行update,当依据某个列进行升序或降序排序操作时,在该列上显示一个向上或向下的箭头标记.实现方法是这样的,遍历DataGrid控件的Columns collection,当该列的SortExpression值与DataGrid控件的SortExpression相匹配时,就对该列添加相应的箭头标记.


图1

我最近需要在一个GridView控件上实现该功能。与DataGrid示例不同,我不打算在ASP.NET页面里添加代码,而是重新创建一个自定义Web server控件,来对GridView进行扩充,并添加必要的functionality.在本文,我们将探讨具体的操作步骤,以及如何在ASP.NET页面里使用该控件.相关示例代码在本文结尾处可下载.

GridView Sorting简述

GridView控件实现排序很简单,只要将AllowSorting属性设置为True即可.这样,它的表头列就呈现为一个LinkButton.当点击后,回传并触发GridView的Sorting event事件.如果GridView绑定的数据源控件支持排序,那么GridView将在内部进行排序以及重新绑定数据.如果是通过编程的方式来将数据绑定到GridView的,那么我们就要为Sorting event事件创建事件处理器,自己写代码实现排序和重新绑定数据.当触发Sorting event事件并重新绑定数据后,接下来GridView就触发Sorted event事件,结束排序流程.

GridView的每列都有一个SortExpression属性,当点击某个列上的 LinkButton时,就指示数据按该列进行排序,同时将GridView的SortExpression属性赋值为该列的SortExpression,再加SortDirection属性,GridView就有了内置的、双向的排序功能.

下面的图解释了排序的流程,包括对SortExpression 和 SortDirection属性赋值.记住,正个流程是从用户点击某个列的LinkButton开始的,当按要求对数据排完序后就结束了.所以,从终端用户的角度来看,当他点击某个表头列的文字后,数据就按照该列进行排序了.


图2

创建一个对GridView进行了扩展的Custom Server Control

在关于DataGrid控件系列文件的Part 18部分,我们将与箭头标记相关的代码放在页面的后台代码类里.具体来说,代码思路是遍历DataGrid的Columns collection,对每列而言,先将出现在HeaderText里的任何<img>元素清除掉,这样就把所有列里的箭头图片都抹去了.然后,代码检查当前列的SortExpression是否与DataGrid控件的SortExpression值相匹配,如果是的话,就把相应的向上或向下的箭头标记添加到该列的HeaderText的尾部.

类似的,GridView也有一个Columns collection;每列也有一个SortExpression;也有SortExpression 和 SortDirection属性.换句话说,要对GridView添加箭头标记的话,我们可以象操作DataGrid那样来进行。不过在此,我打算创建一个对GridView进行了扩展的custom server control,添加必要的代码来对必要的方法进行重写.

为此,我们要创建一个继承自Web control的public class来进行扩展.我在名为skmControls的Class Library project里创建了一个名为GridView的类.(在文章《Creating a TextBox Word / Character Counter Control》里我们首先创建并探讨了该skmControls2 Class Library project)

public class GridView : System.Web.UI.WebControls.GridView
{
    ... Override necessary GridView methods here ...
}

GridView有一个虚方法,virtual InitializeRow method.每次向GridView添加一row时都会调用该方法,包括header row.我最先想到的是重写该方法,对header row而言,遍历所有的列(field),并每列的HeaderText适当更新 (也就是先移除所有的image标记,再为那个作为排序依据的列添加恰当的向上或向下的箭头标记).然而,在databinding阶段,如果你尝试更新GridView的field的属性的话,该field将向GridView报告其状态发生了改动,并要求重新绑定数据.这样一来我们就陷入了一个死循环:

1.Databinding开始
2.对header row执行InitializeRow method方法
3.我再对header row里每个单元(cells)的HeaderText进行更新
4.对HeaderText的更新将导致通知GridView重新绑定数据,重新返回到第1步!

因此我们需要在databinding处理过程之前或之后来修改HeaderText属性.几经考虑后,我意识到我只需要在完成了对数据的排序后再修改HeaderText属性.GridView控件的OnSorted method方法将触发Sorted event事件,当然在数据完成了排序之后才会发生该事件.因此,我决定重写该方法,并在该方法里更新HeaderText属性.

public class GridView : System.Web.UI.WebControls.GridView
{
   protected override void OnSorted(EventArgs e)
   {
      string imgArrowUp = ...;
      string imgArrowDown = ...;
     
      foreach (DataControlField field in this.Columns)
      {
         // strip off the old ascending/descending icon
         int iconPosition = field.HeaderText.IndexOf(@" <img border=""0"" src=""");
         if (iconPosition > 0)
            field.HeaderText = field.HeaderText.Substring(0, iconPosition);

         // See where to add the sort ascending/descending icon
         if (field.SortExpression == this.SortExpression)
         {
            if (this.SortDirection == SortDirection.Ascending)
               field.HeaderText += imgArrowUp;
            else
               field.HeaderText += imgArrowDown;
         }
      }

      base.OnSorted(e);
   }
}

经过重写的该OnSorted method方法首先为向上和向下的箭头图标指定URL,我们将在后面详细探讨.接下来,对GridView的Columns collection遍历,将每列的<img>元素删除.再接下来,检查当前列的SortExpression是否与GridView的SortExpression匹配,如果匹配的话,根据GridView的 SortDirection属性的值,HTML将把向上或向下的箭头标记显示在该列的HeaderText里.

一个有关HTML Encoding的问题

我在测试的时候发现一个问题,在默认的情况下,BoundField HTML会对其HeaderText的内容进行encode处理.换句话说,在处理BoundField输出时,OnSorted method方法把<img>标签添加到BoundField,而HTML又对其进行encode处理,分别把 < 和 > 替换为&lt 和 &gt;也就是说,它将一个HeaderText值

Price <img border="0" src="up.gif" />
转换为
Price &lt;img border="0" src="up.gif" /&gt

因此浏览器呈现的是:Price <img border="0" src="up.gif" />,而不是我们期望的一个图标.

BoundField有一个HtmlEncode属性,默认为True,这样HTML就把BoundField包含的整个内容进行编码处理.因此我们想到的是将HtmlEncode属性设置为False,但如果我们只是不希望对表头进行HTML encode处理,而对data row依然进行HTML encode,哪又怎么办呢?

为此,我在skmControls2 project里创建了另一个自定义类,该类继承自BoundField class,重写了InitializeCell method方法,而HTML encode就是发生在这个方法里的.只有当BoundField的HtmlEncode 和 SupportsHtmlEncode属性都为True时,才会对其内容进行HTML encode处理.另外,SupportsHtmlEncode属性是只读的,对BoundField而言,总是返回 True.因此我重写了SupportsHtmlEncode属性,它根据一个私有成员变量返回一个值.这样一来,当初始化一个表头单元(header cell)时,在重写的这个InitializeCell method方法里,我将该私有成员变量设置为false.

public class BoundField : System.Web.UI.WebControls.BoundField
{
   bool allowHtmlEncode = true;

   protected override bool SupportsHtmlEncode
   {
      get
      {
         return allowHtmlEncode;
      }
   }

   public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState,

int rowIndex)
   {
      if (this.HtmlEncode && cellType == DataControlCellType.Header)
      {
         allowHtmlEncode = false;
         base.InitializeCell(cell, cellType, rowState, rowIndex);
         allowHtmlEncode = true;
      }
      else
         base.InitializeCell(cell, cellType, rowState, rowIndex);
   }

在所有的GridView field里,只对BoundField的数据进行HTML encode处理,所以上述的处理是专门针对BoundField而言的.对其它的TemplateField, CheckBoxField, ButtonField等而言,我们不用进行额外的处理就可以准确无误的添加箭头标记.为向上或向下的箭头指定Image URLs

那么如何为箭头图标指定URL呢?我为此创建了2个属性,ArrowUpImageUrl 和 ArrowDownImageUrl;当然如果开发人员嫌麻烦的话,也可以将这2个箭头图片镶嵌在skmControls2装配件里.当开发人员没有为ArrowUpImageUrl 和 ArrowDownImageUrl指定值时,这是很有用的.

关于如何将资源(resources)镶入装配件,以及在一个ASP.NET页面里检索这些资源,请参阅文章《Accessing Embedded Resources through a URL Using WebResource.axd》

在ASP.NET页面里使用该自定义GridView控件

在文章结尾处可下载到本文所用代码,要使用该skmControls2控件,将DLL拷贝到 website的/Bin目录,再在要使用该控件的.aspx页面添加如下的@Register声明:

<%@ Register Assembly="skmControls2" Namespace="skmControls2" TagPrefix="skm" %>


另外,你还可以在Web.config文件里添加@Register声明,这样你就不用在页面上添加了,具体方法见《Tip/Trick: How to Register User Controls and Custom Controls in Web.config》

象使用普通GridView控件那样来使用,只不过将<asp:GridView>标签里的"asp"替换成"skm", 象这样<skm:GridView>,就这么简单!同样的,对BoundFields,你替换为 <skm:BoundField>代码里有个页面从Northwind数据库的Products数据库表里将ProductID, ProductName, CategoryName, UnitPrice,以及Discontinued列显示在一个支持排序的GridView控件里,用的了一个TemplateField,一个CheckBoxField,以及多个BoundFields(也就是skmControls2 BoundFields).如下:

<skm:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataSourceID="NorthwindDataSource"

AllowSorting="True">
   <Columns>
      <skm:BoundField DataField="ProductID" HeaderText="ID" InsertVisible="False"
         SortExpression="ProductID" />
      <asp:TemplateField HeaderText="Name" SortExpression="ProductName">
         <ItemTemplate>
            <asp:Label runat="server" Text='<%# Bind("ProductName") %>' id="Label1" Font-Bold="True"></asp:Label>
         </ItemTemplate>
      </asp:TemplateField>
      <skm:BoundField DataField="CategoryName" HeaderText="Category Name" SortExpression="CategoryName" />
      <skm:BoundField HtmlEncode="False" DataFormatString="{0:c}" DataField="UnitPrice" HeaderText="Price"

SortExpression="UnitPrice" />
      <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
   </Columns>
</skm:GridView>

注意到我们没有在声明代码里设置ArrowUpImageUrl 或 ArrowDownImageUrl属性的值,因此,方格里使用的是默认提供的向上或向下箭头标记(见下面的第一个截屏).如果你想使用自己提供的箭头图标,那么就要显式的指定URL值,或通过编程来指定.可以是绝对URL(比如:http://www.example.com/images/UpArrow.gif) 或相对路径,比如: ~/Images/UpArrow.png. 在示例页面里包含了一个checkbox,允许你使用默认的或自己提供的箭头图标.

下面的2个截屏是代码演示情况,第一个显示的是按价格的降序排序,当然使用的是内置默认的那个向下箭头图标.


图3

第二个截屏显示的是按产品名称来排序的,不过使用的是自己提供的箭头标记.


图4

                              当使用Fields Dialog Box时自定义的BoundField Markup会丢失

如果你使用skmControls2 library里的自定义BoundField control的话,比如:<skm:BoundField ... />, 有一点你应该知道,当我们使用Fields dialog box来修改GridView的列时,Visual Studio会自动的把我们自定义的BoundField markup替换成默认的<asp:BoundField ... />.当我们在GridView的智能标签里点击"Edit Columns"时就会弹出Fields dialog box对话框.

结语:

本文我们探讨了如何构建一个自定义GridView server control(以及一个自定义BoundField control),以便展示一个箭头标记.该箭头标记使我们可以清楚的看到是按哪列的升序还是降序来排序的.最好将该功能封装在一个自定义server control里,这样一来我们就不用在ASP.NET页面里写代码就可以实现该功能了.

祝编程快乐!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值