GridView Redux






4.47/5 (25投票s)
2006 年 1 月 9 日
11分钟阅读

253788

2037
如何以风格添加/编辑 GridView。
引言
ASP.NET 2.0 的 GridView
是一个强大的控件,可以让你创建新的数据库行、原地编辑,甚至提供 DropDownList
和 CheckBox
来辅助你的输入过程。它甚至还可以进行数据验证。
在“开箱即用”的基础上,我添加了以下附加功能:
- 插入新行。
- 通过
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
。 - 单击
GridView
的 SmartTag 打开其 Tasklist。 - 启用 分页、排序、编辑 和 删除。
- 选择 编辑列。
- 在右下角的窗格中,最上面的项是
CommandField
。 - 单击它,右上角的窗格将显示属性。
- 将“外观 > 按钮类型”更改为
Image
。 - 将
CancelButtonUrl
设置为 Images/Cancel.gif。 - 相应地设置其余的
ButtonUrl
。 - 从左下角的窗格中选择 ProductID。
- 按右侧的红色 X 删除它,因为它不是一个可编辑字段。
- 生成并运行项目。
现在您有了一个可编辑/可删除的表维护程序。
使用 DropDownLists 进行编辑
- 创建一个新的
SqlDataSouce
(位于工具箱的“数据”下)。 - 将此
DataSource
配置为连接到 Suppliers 表,并在SELECT
语句中,选择SupplierID
和CompanyName
。 - 创建一个新的
SqlDataSouce
(您知道它在哪里)。 - 将此
DataSource
配置为连接到 Categories 表,并在SELECT
语句中,选择CategoryID
和CategoryName
。 - 单击
GridView
的 SmartTag 以打开 Tasklist。 - 单击“编辑列...”。
- 在左下角的窗格中,高亮显示 SupplierID,然后单击右侧的“将此字段转换为模板”短语。
- 对 CategoryID 也执行相同的操作。
- 单击 OK。
- 现在您应该会看到
GridView
的 Tasklist。 - 单击底部的 编辑模板。
- 单击向下箭头,您将看到一个模板列表,选择(在 列 [2] - SupplierID 下)“
EditItemTemplate
”。 - 不要选择“
ItemTemplate
”。 - 选择文本框并删除它。
- 从工具箱拖动一个
DropDownList
控件并将其放置在文本框原来的位置。 - 单击其 SmartTag 并单击“选择数据源”。
- 选择您之前设置的供应商
DataSource
。 - 将显示字段绑定到
CompanyName
,将值字段绑定到SupplierID
。 - 单击
GridView
的 SmartTag。 - 现在,选择“
EditItemTemplate
”,但这次是在 列 [3] - CategoryID 下。 - 重复您刚才所做的操作来连接
CategoryID
。 - 现在,生成并运行应用程序。
- 您将看到
SupplierID
和CategoryID
列中显示的是供应商/类别 ID(我们很快就会解决这个问题),但当您单击 编辑 时,它们会神奇地变成下拉列表(嗯,这并非真正的魔法,但确实令人印象深刻)。 - 单击 取消(因为我们还没有准备好进行更新)。
- 关闭应用程序。
- 现在,我们将把那些烦人的供应商/类别 ID 变成更用户友好的名称。
- 您需要修改您创建的第一个
DataSource
,以便从各自的表中提取供应商名称和类别名称。您可以选择“硬汉”(愚蠢)的方式,编辑SELECT
语句的源代码并自己进行INNER JOIN
,或者只需单击DataSource
的 SmartTag 并单击“配置数据源”。 - 单击 Next 一次,然后将 检索类型 更改为 自定义 SQL 语句。
- 单击 Next,并确保 Select 是高亮显示的选项卡。
- 单击 Query Builder。
- 在显示 Products 表的窗格中右键单击,然后选择 Add table。
- 添加以下表:Suppliers、Categories,然后关闭。
- 将 CompanyName(来自 Suppliers)和 CategoryName(来自 Categories)添加到查询结果中。
- 您现在已完成
DataSource
的配置。现在,单击GridView
的 SmartTag。在某个时候,会弹出一个警告框询问您是否要清除已完成的工作。只需回答否! - 在 Tasklist 中单击 编辑模板。
- 选择 列 [2] - SupplierID 的 ItemTemplate。
- 单击
Label
控件的 SmartTag,然后选择 编辑数据绑定。 - 在左侧窗格中选择 Text,并将 Bound to 列更改为
CompanyName
。重要提示:完成此操作后,请确保 双向绑定 仍然 选中。
- 关闭框。
- 单击 GridView1 - 列 [2] - SupplierID 的 SmartTag。
- 选择 列 [3] - CategoryID 的
ItemTemplate
。 - 单击
Label
控件的 SmartTag,然后选择 编辑数据绑定。 - 在左侧窗格中选择 Text,并将 Bound to 列更改为
CategoryName
。重要提示:完成此操作后,请确保 双向绑定 仍然 选中。
- 关闭框。
- 单击 Column [3] - CategoryID 的
GridView1
SmartTag。 - 单击 结束模板编辑。
- 生成并执行应用程序。
现在,您将看到
CategoryID
和SupplierID
数字已被名称替换。 - 单击并编辑各种行,但不要保存 - 这是其他教程依赖的实时 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(" "); 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
与字段名相同。在本例中,我们在 SupplierID
和 SortExpression
字段中显示 CompanyName
和 CategoryName
,因此我将 SupplierID
的 SortExpression
设置为 CompanyName
,将 CategoryID
的 SortExpression
设置为 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
,是因为这样在出现错误时行看起来会更整洁。
样式
所有元素,无论是 DataBound
、ItemTemplate
s,还是 ItemTemplate
中的控件,都公开一个 CssClass
元素,您可以为其分配一个或多个类来影响元素的外观。如果您指定了多个类,请用空格分隔。有趣的是,GridView
的任何元素都不支持通过大多数其他类支持的 CssStyle.Add(...)
方法进行内联样式。这意味着您无法向单元格添加单个内联样式元素。
图像
如果有人有更好的图片供我用于此示例,请发送给我或发送 URL。这些图片太糟糕了。
结论
我带您完成了 VS2005 中这个本应简单的表维护任务漫长而复杂的过程。虽然这是一个巨大的进步,但本可以做得更多。来吧,Visual Studio 团队,我们需要在 GridView
的 SmartTag TaskList 中添加一个按钮,来自动完成我刚才手动描述的大部分过程!
勘误
这是一篇漫长的分步文章,我确信其中存在错误。请善待。任何更正都将被如实认可,并且文章将被更正。
外部链接
如果您有任何其他建议,请发送给我。
- GridView 类 (System.Web.UI.WebControls).
- 让开 DataGrid,有个新网格来了!.
- 格式化数字.
- fredrik.nsquared2.com.
- http://www.gridviewguy.com/.
- http://www.gridviewgirl.com/ (是不是有什么猫腻?嘿嘿 ;-)。
SmashGrab / Redux 系列
我最近在 CodeProject 上开始了两个系列的文章。Smash & Grab 系列旨在提供关于特定代码技术的简短文章。Redux 系列旨在提供更长的文章,试图将复杂的主题(如 GridView
)分解为其基本组成部分,并表明一旦您拥有了所有信息,它实际上并没有那么难。要查找 Smash&Grab 文章,请搜索关键词 SmashGrab。要查找 Redux 文章,请搜索关键词 Redux。我欢迎为这两个系列贡献内容,但请在提交文章时遵循相关指南。