65.9K
CodeProject 正在变化。 阅读更多。
Home

GridView Redux

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (25投票s)

2006 年 1 月 9 日

11分钟阅读

viewsIcon

253788

downloadIcon

2037

如何以风格添加/编辑 GridView。

引言

ASP.NET 2.0 的 GridView 是一个强大的控件,可以让你创建新的数据库行、原地编辑,甚至提供 DropDownListCheckBox 来辅助你的输入过程。它甚至还可以进行数据验证。

在“开箱即用”的基础上,我添加了以下附加功能:

  • 插入新行。
  • 通过 DropDownList 编辑。
  • 验证。
  • 删除确认。
  • 复选框输入。
  • 当前行高亮。
  • 基于值的选择性单元格高亮。

我将要展示的大部分功能都不是什么高深技术,而且大部分都可以在网上找到(尽管我从未见过“在最前面插入新行”的功能)。这是我整合所有内容到一篇文章中的一个谦虚尝试。

背景

我接手了一个项目,需要将一个单用户 Access 应用程序转换为一个基于 Web 的多用户 SQL Server 应用程序。Access 应用程序有大约 10 个表需要通过 Add/Edit/Delete 界面进行管理。这些表的维护将由一个人完成,因此并发问题不成问题。我决定通过一些调整,GridView 可以完成所需的任务。开箱即用的 GridView 不支持行插入,也不会提供 DropDownList 来编辑实际上是代表另一个表中行的 ID 的数据。本例使用 NorthWind Products 表来添加和更新数据行。这是一个生成实现此任务的网页的分步指南。它很长且艰苦,但这才是必需的。完成此练习后,您应该可以自信地处理自己的表了。您可能需要打印出步骤并随身携带,这样就不必一直切换屏幕(除非您有双显示器 ;-)

使用代码

下载项目,解压到硬盘上的某个位置,然后从现有文件夹创建一个新的 Web 项目。生成项目并试用,以便您了解它的作用。现在,创建一个新的 ASPX 文件,命名为 Default2.aspx,并手动完成所有操作。

手动创建项目的步骤

  • 创建一个空白项目。
  • 创建一个 Images 文件夹。
  • 右键单击 Images
  • 选择“添加现有项”。
  • 导航到您解压下载文件的文件夹,选择所有 .GIF 文件,然后单击“添加”。
  • 在设计视图中,添加一个 DataSource
  • 配置 DataSource 以选择每个单独的字段。
  • 创建一个 GridView 并选择之前创建的 DataSource
  • 单击 GridViewSmartTag 打开其 Tasklist
  • 启用 分页排序编辑删除
  • 选择 编辑列
  • 在右下角的窗格中,最上面的项是 CommandField
  • 单击它,右上角的窗格将显示属性。
  • 将“外观 > 按钮类型”更改为 Image
  • CancelButtonUrl 设置为 Images/Cancel.gif
  • 相应地设置其余的 ButtonUrl
  • 从左下角的窗格中选择 ProductID
  • 按右侧的红色 X 删除它,因为它不是一个可编辑字段。
  • 生成并运行项目。

现在您有了一个可编辑/可删除的表维护程序。

使用 DropDownLists 进行编辑

  • 创建一个新的 SqlDataSouce(位于工具箱的“数据”下)。
  • 将此 DataSource 配置为连接到 Suppliers 表,并在 SELECT 语句中,选择 SupplierIDCompanyName
  • 创建一个新的 SqlDataSouce(您知道它在哪里)。
  • 将此 DataSource 配置为连接到 Categories 表,并在 SELECT 语句中,选择 CategoryIDCategoryName
  • 单击 GridView 的 SmartTag 以打开 Tasklist
  • 单击“编辑列...”。
  • 在左下角的窗格中,高亮显示 SupplierID,然后单击右侧的“将此字段转换为模板”短语。
  • CategoryID 也执行相同的操作。
  • 单击 OK
  • 现在您应该会看到 GridView 的 Tasklist。
  • 单击底部的 编辑模板
  • 单击向下箭头,您将看到一个模板列表,选择(在 列 [2] - SupplierID 下)“EditItemTemplate”。
  • 不要选择“ItemTemplate”。
  • 选择文本框并删除它。
  • 从工具箱拖动一个 DropDownList 控件并将其放置在文本框原来的位置。
  • 单击其 SmartTag 并单击“选择数据源”。
  • 选择您之前设置的供应商 DataSource
  • 将显示字段绑定到 CompanyName,将值字段绑定到 SupplierID
  • 单击 GridView 的 SmartTag。
  • 现在,选择“EditItemTemplate”,但这次是在 列 [3] - CategoryID 下。
  • 重复您刚才所做的操作来连接 CategoryID
  • 现在,生成并运行应用程序。
  • 您将看到 SupplierIDCategoryID 列中显示的是供应商/类别 ID(我们很快就会解决这个问题),但当您单击 编辑 时,它们会神奇地变成下拉列表(嗯,这并非真正的魔法,但确实令人印象深刻)。
  • 单击 取消(因为我们还没有准备好进行更新)。
  • 关闭应用程序。
  • 现在,我们将把那些烦人的供应商/类别 ID 变成更用户友好的名称。
  • 您需要修改您创建的第一个 DataSource,以便从各自的表中提取供应商名称和类别名称。您可以选择“硬汉”(愚蠢)的方式,编辑 SELECT 语句的源代码并自己进行 INNER JOIN,或者只需单击 DataSource 的 SmartTag 并单击“配置数据源”。
  • 单击 Next 一次,然后将 检索类型 更改为 自定义 SQL 语句
  • 单击 Next,并确保 Select 是高亮显示的选项卡。
  • 单击 Query Builder
  • 在显示 Products 表的窗格中右键单击,然后选择 Add table
  • 添加以下表:SuppliersCategories,然后关闭。
  • CompanyName(来自 Suppliers)和 CategoryName(来自 Categories)添加到查询结果中。
  • 您现在已完成 DataSource 的配置。现在,单击 GridView 的 SmartTag。在某个时候,会弹出一个警告框询问您是否要清除已完成的工作。只需回答否
  • 在 Tasklist 中单击 编辑模板
  • 选择 列 [2] - SupplierIDItemTemplate
  • 单击 Label 控件的 SmartTag,然后选择 编辑数据绑定
  • 在左侧窗格中选择 Text,并将 Bound to 列更改为 CompanyName

    重要提示:完成此操作后,请确保 双向绑定 仍然 选中

  • 关闭框。
  • 单击 GridView1 - 列 [2] - SupplierID 的 SmartTag。
  • 选择 列 [3] - CategoryIDItemTemplate
  • 单击 Label 控件的 SmartTag,然后选择 编辑数据绑定
  • 在左侧窗格中选择 Text,并将 Bound to 列更改为 CategoryName

    重要提示:完成此操作后,请确保 双向绑定 仍然 选中

  • 关闭框。
  • 单击 Column [3] - CategoryIDGridView1 SmartTag。
  • 单击 结束模板编辑
  • 生成并执行应用程序。

    现在,您将看到 CategoryIDSupplierID 数字已被名称替换。

  • 单击并编辑各种行,但不要保存 - 这是其他教程依赖的实时 NorthWind 数据库。我们接下来将学习添加,这样您就可以添加自己的行并随意修改/删除它们,而不会损坏真实数据。

按照以下步骤使 GridView 能够添加新记录

  • 单击 Product DataSource 的 SmartTag。
  • 单击 配置数据源
  • 单击 Next 两次。
  • 确保 Select 是高亮显示的选项卡。
  • 单击 Query Builder
  • 更改现有语句
    SELECT Products.ProductID, Products.ProductName, Products.SupplierID, 
      Products.CategoryID, Products.QuantityPerUnit, Products.UnitPrice, 
      Products.UnitsInStock, Products.UnitsOnOrder, Products.ReorderLevel, 
      Products.Discontinued, Categories.CategoryName, Suppliers.CompanyName 
      FROM Products INNER JOIN Categories ON 
      Products.CategoryID = Categories.CategoryID 
      INNER JOIN Suppliers ON 
      Products.SupplierID = Suppliers.SupplierID 
      ORDER BY Products.ProductName

    改为

    SELECT 0 as ProductID, '' as ProductName, 0 as SupplierID, 0 as 
      CategoryID, '' as QuantityPerUnit, 0.00 as UnitPrice, 
      0 as UnitsInStock, 0 as UnitsOnOrder, 0 ReorderLevel, 
      convert(bit, 0) as Discontinued,'' as CategoryName, 
      '' as CompanyName
    
    UNION
    
    SELECT Products.ProductID, Products.ProductName, Products.SupplierID, 
      Products.CategoryID, Products.QuantityPerUnit, Products.UnitPrice, 
      Products.UnitsInStock, Products.UnitsOnOrder, Products.ReorderLevel, 
      Products.Discontinued, Categories.CategoryName, Suppliers.CompanyName 
      FROM Products INNER JOIN Categories ON 
      Products.CategoryID = Categories.CategoryID INNER JOIN 
      Suppliers ON Products.SupplierID = Suppliers.SupplierID 
      ORDER BY Products.ProductName
  • 将以下 JavaScript 代码插入到 ASPX 文件的 <head> 部分
    <script>
    function FixGrid(idGrid, PageIndex, EditIndex)
    {
      var Start = 1;
      if(EditIndex != 0)
      {
        if(PageIndex == 0)
        {
          Start = 2;
          // see the actual source for explanation
          // replace Edit image with Add image, remove Delete
          idGrid.firstChild.childNodes[1].childNodes[0].childNodes[0].src=
            "Images/Add.gif";
          var i = 
            idGrid.firstChild.childNodes[1].
                   childNodes[0].innerHTML.indexOf("&nbsp;");
          idGrid.firstChild.childNodes[1].childNodes[0].innerHTML = 
            idGrid.firstChild.childNodes[1].childNodes[0].innerHTML.slice(0, i);
        }
      }
      // put delete confirmations in
      for(var i=Start; ; i++)
      {
        try
        {
          var ctl=idGrid.firstChild.childNodes[i].childNodes[0].childNodes[2];
          if(ctl.tagName == "INPUT")
          {
            var onc = ctl.onclick.toString();
            // window.alert(onc);
            // uncomment this to see what the onclick actually contains
            if(onc.indexOf("Delete$") == -1)
             continue;    // don't want to add confirm to "update cancel"
            var j = onc.indexOf("__do");
            var k = onc.indexOf(")", j)+1;
            onc = "if(confirm('Are you sure') == false)" + 
                  " return(false); "+onc.slice(j, k);
            ctl.onclick = onc;
            // if you don't do this then the onclick will not work.
            // it is probably related to how the onclick is actually
            // defined (see window.alert above)
            ctl.outerHTML = ctl.outerHTML;
          }
        }
        catch(e)
        {
          // when we land here, we have run the table rows out
    
          break;
        }
      }
    }
    </script>
  • 将此脚本放在 </form> 标签的关闭标签之后
    <script> 
      FixGrid(document.all.GridView1, 
         <%=GridView1.PageIndex.ToString()%>, 
         <%=EditIndex %>);     
    </script>
  • 右键单击 GridView 并选择 Properties
  • 单击 Event 按钮。
  • 双击 Action > RowUpdating。
  • 双击 Action > RowEditing。
  • 这将创建空的事件处理程序和所需的事件连接代码。
  • 现在,将空 ASPX.CS 文件中的代码替换为
    public partial class YOUR_CLASSNAME_HERE : System.Web.UI.Page
    {
        public int EditIndex = -1;
        protected void Page_Load(object sender, EventArgs e)
        {
    
        }
    
        protected void GridView1_RowUpdating(object sender, 
                       GridViewUpdateEventArgs e)
        {
          if (e.RowIndex > 0 || GridView1.PageIndex > 0)
            return;
            // only RowIndex 0 on Page 0 is the row we want to insert
          // find the DataSource for the GridView control
          System.Web.UI.WebControls.SqlDataSource ds = 
            (System.Web.UI.WebControls.SqlDataSource)
              this.FindControl(this.GridView1.DataSourceID);
          // Get the DataSource's connection string
          System.Data.SqlClient.SqlConnection conn = 
            new System.Data.SqlClient.SqlConnection(ds.ConnectionString);
          // open the connection
          conn.Open();
          // get the Insert command string
          string s = ds.InsertCommand;
    
          // create the new command
          System.Data.SqlClient.SqlCommand c = 
            new System.Data.SqlClient.SqlCommand(s, conn);
          System.Data.SqlClient.SqlParameter p;
          // the NewValues collection contains
          // the name value pairs that need to be inserted, 
          // make them parameters to the command
          foreach (System.Collections.DictionaryEntry x in e.NewValues)
          {
            p = new System.Data.SqlClient.SqlParameter("@" + x.Key, x.Value);
            c.Parameters.Add(p);
          }
          // execute the command
          c.ExecuteNonQuery();
          // the GridView framework will execute an update
          // on a row where the identity column=0
          // since none exists it will silently do nothing, but that is ok
        }
    
      protected void GridView1_RowEditing(object sender, 
                     GridViewEditEventArgs e)
      {
        EditIndex = e.NewEditIndex;
      }
    }
  • 现在是时候进行破解了:

    VS2005 生成的 INSERT 语句无法按原样执行。它最初是

    InsertCommand="INSERT INTO [Products] ([ProductName], 
       [SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice], 
       [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) 
       VALUES (@ProductName, @SupplierID, @CategoryID, @QuantityPerUnit, 
       @UnitPrice, @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued)"

    您需要将其更改为

    InsertCommand="INSERT INTO [Products] ([ProductName], [SupplierID], 
       [CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock], 
       [UnitsOnOrder], [ReorderLevel], [Discontinued]) 
       VALUES (@ProductName, @SupplierID, @CategoryID, 
       @QuantityPerUnit, convert(money, @UnitPrice), 
       @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued)"

    您可以在源代码中手动更改,也可以通过 IDE 更改。

复选框

复选框几乎是免费的。GridView 默认会将 SQL 数据类型为 bit(且仅为 bit)的列绑定到 CheckBoxField(否则会收到运行时转换异常)。如果您查看上面修改的 SelectCommand,我必须将 Discontinued 添加为 convert(bit, 0)

排序

默认情况下,字段的 SortExpression 与字段名相同。在本例中,我们在 SupplierIDSortExpression 字段中显示 CompanyNameCategoryName,因此我将 SupplierIDSortExpression 设置为 CompanyName,将 CategoryIDSortExpression 设置为 CategoryName

查看后台代码文件,您将看到如何添加了排序方向的图标(代码由 Dino Esposito 的 New Grid in Town 文章提供)。此代码使用了 OnRowCreated 事件,因此请确保在 GridView 的 Property Events 工作表中将其连接起来。

其他业务

数据表示

有两种方法可以格式化数据。

静态格式化数据

  • GridView 的 SmartTag 中选择 Edit Column
  • 在高亮显示左下角窗格中要格式化的列。滚动右侧窗格到底部。
  • 展开 ItemStyle 并进行更改。
  • 在此示例程序中,我已将每个项的 VerticalAlign 设置为 top,您将在有关验证的部分中理解原因。
  • 我还将数字列的 HorizontalAlign 设置为 right
  • 如果选择的项也是 ItemTemplate,您还可以通过如上所述导航到它并对 ItemTemplate 进行所需的更改(而不是 EditItemTemplate)来对其组成控件进行一些格式化更改。

在运行时格式化数据

您必须创建一个 RowDataBound 事件处理程序。执行此操作:

  • 右键单击 GridView 并选择 Properties
  • 单击 Properties 窗口顶部的 闪电图标 以访问事件工作表。
  • 双击 RowDataBound 右侧的输入区域以自动生成一个空的事件处理程序。

然后我修改了空模板,使其看起来像这样:

protected void GridView1_RowDataBound(object sender, 
                                      GridViewRowEventArgs e)
{
  if (e.Row.RowType == DataControlRowType.DataRow)
  {
    if (e.Row.RowIndex != EditIndex)
    {
      // highlight the UnitsInStock cell if it is zero
      int unitsInStock =
        Convert.ToInt32(System.Web.UI.DataBinder.Eval(e.Row.DataItem, 
        "UnitsInStock"));
      if (unitsInStock == 0)
        e.Row.Cells[7].BackColor = System.Drawing.Color.Yellow;
      // highlight the row that the mouse is over
      // save the original attribute in a custom attribute
      // so that you can restore it later
      e.Row.Attributes.Add("onmouseover", 
         "this.originalcolor=this.style.backgroundColor;" + 
         " this.style.backgroundColor='Silver';");
      e.Row.Attributes.Add("onmouseout", 
         "this.style.backgroundColor=this.originalcolor;");
    }
  }
}

以上代码的第一部分获取当前行的 UnitsInStock 字段的值,如果值为 0,则将单元格背景更改为黄色。如果您看到其他 RowDataBound 函数的示例,您可能会想为什么不直接使用 Convert.ToInt32(e.Row.Cells[7].Text)。好吧,这是没有人愿意告诉您的(或者至少不在任何地方文档化的)可怕秘密。一旦您将 BoundField 转换为 ItemTemplate,就必须访问原始数据,因为在调用 RowDataBound 时,数据还没有在单元格中供您访问。但如果您仔细想想,这是完全合理的!

RowDataBound 时,单元格仍然包含构成模板的控件,并且可能存在您想要在渲染之前操作这些控件的好理由。

第二部分向您展示了如何突出显示鼠标悬停的行。这里唯一的技巧是保存底层背景颜色(在自定义属性 originalcolor 中),然后再更改它,以便在 OnMouseOut 事件上恢复。我见过其他解决方案将背景颜色硬编码在恢复中,但这种技术不能处理交替的背景颜色。

验证

我在每个 EditItemTemplate 中添加了验证控件。每个控件在编辑时(即失去焦点时)都会进行验证。如果存在错误,错误消息将显示在控件下方。我之所以将每个控件格式化为 VerticalAlign = top,是因为这样在出现错误时行看起来会更整洁。

样式

所有元素,无论是 DataBoundItemTemplates,还是 ItemTemplate 中的控件,都公开一个 CssClass 元素,您可以为其分配一个或多个类来影响元素的外观。如果您指定了多个类,请用空格分隔。有趣的是,GridView 的任何元素都不支持通过大多数其他类支持的 CssStyle.Add(...) 方法进行内联样式。这意味着您无法向单元格添加单个内联样式元素。

图像

如果有人有更好的图片供我用于此示例,请发送给我或发送 URL。这些图片太糟糕了。

结论

我带您完成了 VS2005 中这个本应简单的表维护任务漫长而复杂的过程。虽然这是一个巨大的进步,但本可以做得更多。来吧,Visual Studio 团队,我们需要在 GridView 的 SmartTag TaskList 中添加一个按钮,来自动完成我刚才手动描述的大部分过程!

勘误

这是一篇漫长的分步文章,我确信其中存在错误。请善待。任何更正都将被如实认可,并且文章将被更正。

外部链接

如果您有任何其他建议,请发送给我。

SmashGrab / Redux 系列

我最近在 CodeProject 上开始了两个系列的文章。Smash & Grab 系列旨在提供关于特定代码技术的简短文章。Redux 系列旨在提供更长的文章,试图将复杂的主题(如 GridView)分解为其基本组成部分,并表明一旦您拥有了所有信息,它实际上并没有那么难。要查找 Smash&Grab 文章,请搜索关键词 SmashGrab。要查找 Redux 文章,请搜索关键词 Redux。我欢迎为这两个系列贡献内容,但请在提交文章时遵循相关指南。

© . All rights reserved.