带 AJAX 评分控件、下拉列表、JavaScript 以启用/禁用行的 GridView - 练习






2.76/5 (18投票s)
ASP.NET GridView 实现 AjaxControlToolkit 的评分控件、每行的下拉列表、数据库连接、正则表达式、JavaScript、CSS 等。本文是一个练习,旨在更好地理解在 GridView 中使用各种技术(服务器端和客户端)的方面。
引言
在浏览各种 ASP.NET 博客和论坛寻找答案时,我经常遇到以某种方式重复的某些主题。
本文是一个练习,它回答了一些常见的 GridView
问题。特别是,我在这里处理的是
- MS Access 数据库连接。
- MS AjaxControlToolkit 的
Rating
扩展器,它放置在每一行上,允许用户实时对每一行进行评分。换句话说,一旦用户对行进行评分(选择星数),就会向服务器发出异步(AJAX)调用,并更新数据库。 - 每个
GridView
行中的数据绑定下拉列表。每行都有一个供应商,该供应商显示在下拉列表中,以方便记录修改。 - JavaScript 客户端行操作。特别是,在复选框单击时,Product 文本框被锁定/解锁。
项目配置和要求
为了实现我所需要的,我需要在 Visual Studio 2005 中创建一个 AJAX Enabled Web Application。我还需要安装 AjaxControlToolkit。如果您的项目类型中没有出现 AJAX Enabled Web Application 选项,或者您缺少 AJAX 控件,您需要访问 http://asp.net/ajax/ 进行设置。不要忘记点击“Learn”;在那里您将找到大量关于如何从 A 到 Z 实现 AJAX 的教程。
如果您使用的是 Visual Studio 2008,那么 AJAX 应该已经存在。
数据库连接
在这个项目中,我使用了标准的 MS Access Northwind 数据库。为了能够连接到它,我首先在 web.config 文件中放置了一个连接字符串
<add name="db1"
connectionString="Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=App_Data\db1.mdb"/>
然后,无论我需要在代码中使用数据库,我都会
- 在每个类的顶部添加 "
using System.Data.OleDb;
"; - 通过调用
ConfigurationManager.ConnectionStrings["db1"].ToString()
引用我的连接字符串。
我检索和显示的主要数据存储在 Products 表中。我通过以下方式检索数据
/// <summary>
/// Selects products from the database and displays them in DataView
/// </summary>
private void ShowData()
{
// I select TOP 20 only for the purpose of speedy page response in this exersize
// OVerall, I wouldn't recommend having more than 20 rows when implementing
// UpdatePanel in each row - it's better to page your grid in this case
// However in this exercise I am not going to overomplicate it with paging
using (OleDbDataAdapter da = new OleDbDataAdapter(
"SELECT TOP 20 Products.ProductID, Products.ProductName," +
" Products.UnitPrice, Products.SupplierID, " +
"Products.CustomerRating FROM Products",
new OleDbConnection(
ConfigurationManager.ConnectionStrings["db1"].ToString())))
{
DataTable dt = new DataTable();
da.SelectCommand.Connection.Open();
da.Fill(dt);
this.GridView1.DataSource = dt;
this.GridView1.DataBind();
}
// NOTE the "using" keyword.
// It became available with .NET 2.0 and is quite handy
// especially when dealing with Database connectivity objects.
// Now we don't need to worry about connection closing
// and disposing (disregarding whether there was an error or not);
// In short, whatever is set in and within "Using"
// will be destroyed as soon as it goes out including in case of an Exception;
// It makes for smaller nicer structured code and easier object manager;
}
每个 GridView 行中的 AJAX 评分
假设我们的用户需要对每一行进行评分。我们在每一行上提供了 Rating
控件。一旦用户对给定行进行评分,就会向服务器发出 AJAX 调用,并且数据库会更新新的评分。
为此,我们在 GridView
中创建一个模板列,其中放置一个 UpdatePanel
来承载此 Rating
控件
<asp:TemplateField HeaderText="Rating">
<ItemTemplate>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<cc1:Rating ID="Rating1" runat="server"
CurrentRating='<%# Bind("CustomerRating") %>'
StarCssClass="ratingStar"
WaitingStarCssClass="savedRatingStar"
FilledStarCssClass="filledRatingStar"
EmptyStarCssClass="emptyRatingStar"
OnChanged="Rating1_Changed"
>
</cc1:Rating>
</ContentTemplate>
</asp:UpdatePanel>
</ItemTemplate>
</asp:TemplateField>
如您所见,Rating
控件的所有属性都已设置,包括 CurrentRating
,它是数据绑定的,用于显示已存储在数据库中的评分。
注意:默认情况下,Rating
控件具有 BehaviourId
属性 - 将其完全删除(它无论如何都会动态添加);否则,您将无法在页面上使用多个 Rating
控件。
对于所需的星级 CSS,我复制了这些标准样式
<style>
/* The following styles are for the Rating */
.ratingStar {
font-size: 0pt;
width: 13px;
height: 12px;
margin: 0px;
padding: 0px;
cursor: pointer;
display: block;
background-repeat: no-repeat;
}
.filledRatingStar {
background-image: url(Images/FilledStar.png);
}
.emptyRatingStar {
background-image: url(Images/EmptyStar.png);
}
.savedRatingStar {
background-image: url(Images/SavedStar.png);
}
/******************************************/
</style>
当用户点击星星时会发生什么?
首先,星星被点亮并设置为新值;这由 AjaxControlToolkit 为您准备的代码处理。
其次,Rating1_Changed
启动并在服务器上按如下方式处理(阅读注释)
/// <summary>
/// When user rates each line this one fires via AJAX call
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Rating1_Changed(object sender,
AjaxControlToolkit.RatingEventArgs e)
{
// cast rating control which has initiated the call:
AjaxControlToolkit.Rating myRating =
(AjaxControlToolkit.Rating)sender;
// regular expression which will help identifying row number:
System.Text.RegularExpressions.Regex rexLineNo =
new System.Text.RegularExpressions.Regex("ctl\\d+");
// update the record based on the recodrd id
this.updateRating(this.ProductId(
rexLineNo.Match(myRating.UniqueID).ToString()), e.Value);
// the above line does the following:
// rexLineNo.Match(myRating.UniqueID).ToString()
// - finds "ctl and line number
}
/// <summary>
/// Goes through the rows of the products gridview and
/// locates the lblProduct Id with the same
/// line number (ctl) part in the name;
/// </summary>
/// <param name="LineNo">A ctl thing</param>
/// <returns></returns>
private string ProductId(string LineNo)
{
foreach( GridViewRow r in this.GridView1.Rows)
{
Label lblProductId = (Label)r.FindControl("lblProductID");
// if label's uniqueID (the one created by ASP
// and rendered as a long thing with prefixes and line numbers)
// contains the same number (and prefix) as the one passed in
if (lblProductId.UniqueID.Contains(LineNo))
{
// return label's text, which is your product ID:
return lblProductId.Text;
}
}
// you need this "dummy" return so it compiles with no warnings:
return string.Empty;
}
/// <summary>
/// Updates given product with new rating
/// </summary>
/// <param name="ProductId">Product Id</param>
/// <param name="Rating">New rating</param>
private void updateRating(string ProductId, string Rating)
{
// put your values into parameters:
OleDbParameter paramRating = new OleDbParameter("@Rating", Rating);
OleDbParameter paramProductId =
new OleDbParameter("@ProductId", ProductId);
using(OleDbCommand cmd = new OleDbCommand(
"UPDATE Products SET CustomerRating " +
"= @Rating WHERE Products.ProductID=@ProductId",
new OleDbConnection(
ConfigurationManager.ConnectionStrings["db1"].ToString())))
{
cmd.Parameters.Add(paramRating);
cmd.Parameters.Add(paramProductId);
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
上面的代码非常直观。唯一需要注意的是找到 ProductId
值的思想,这是通过正则表达式和 .NET 在控件位于 GridView
中时命名控件的方式完成的。此代码将检索您的产品 ID,并根据它更新您的数据库。在这种情况下,您不需要重新绑定 GridView
,因为除了评分之外没有数据发生更改,并且评分是在客户端设置的。
每个 GridView 行中的数据绑定 DropDownList
仅出于练习目的,我们将每个产品的供应商显示在 DropDownList
中。
为此,我们首先需要放入下拉列表。同样,我们将创建一个模板列并将下拉列表放入其中
<asp:TemplateField HeaderText="Supplier">
<ItemTemplate>
<asp:DropDownList ID="ddlSupplier" runat="server" Width="180px">
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
现在,当每个 GridView
行进行数据绑定时,我们根据供应商 ID 填充供应商下拉列表并设置其选择为适当的选项(阅读代码注释)
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
// on each row which is DataRow populate Suppliers dropdown list
// and select the supplier according to the id in the Data:
if (e.Row.RowType == DataControlRowType.DataRow)
{
// find and assign the dropdown list:
DropDownList ddlSuppliers =
(DropDownList)e.Row.FindControl("ddlSupplier");
// populate and select:
this.BindAndSetSupplier(DataBinder.Eval(e.Row.DataItem,
"SupplierID").ToString(), ref ddlSuppliers);
}
}
/// <summary>
/// Selects suppliers from database and select
/// supplier according to the id which is passed in
/// </summary>
/// <param name="SupplierID">Obtained from
/// the product data for each row</param>
/// <param name="Suppliers">Found in each row</param>
private void BindAndSetSupplier( string SupplierID ,
ref DropDownList Suppliers)
{
using (OleDbDataAdapter da = new OleDbDataAdapter(
"SELECT Suppliers.SupplierID, " +
"Suppliers.CompanyName FROM Suppliers",
new OleDbConnection(
ConfigurationManager.ConnectionStrings["db1"].ToString())))
{
DataTable dt = new DataTable();
da.SelectCommand.Connection.Open();
da.Fill(dt);
Suppliers.DataValueField = "SupplierID";
Suppliers.DataTextField = "CompanyName";
Suppliers.DataSource = dt;
Suppliers.DataBind();
Suppliers.SelectedValue = SupplierID;
}
}
DropDownList
到此为止。这里的关键是
GridView1_RowDataBound
允许您对每一行及其单元格执行几乎任何操作的事件。
JavaScript 客户端行操作
这个很简单。许多人问:“如何在复选框 OnClick
事件中打开我的字段进行编辑”。我们这样做的方法是
首先,创建一个 JavaScript 函数来处理您的复选框 OnClick
事件(阅读代码注释)
function GridViewRowChecked(what)
{
var Ctl = new RegExp("ctl\\d+")
// this regular expression will get
// the line nubmer with the ctl prefix
//which is put automatically by ASP.NET
// for GridViews and DataGrids ;
var LineNumber = Ctl.exec(what.name).toString();
//get the control names to operate on:
var txtProduct = document.getElementById( "GridView1_" +
LineNumber + "_txtProduct");
//set Product textbox to readonly
//or not accroding to the checkbox check:
if (what.checked)
{
//in order to accomodate for IE and other
//browsers I set the readonly attribute in two ways:
txtProduct.setAttribute("readonly", "none");
txtProduct.readOnly = false;
//it's not worth it to use Ifs to determine
//which browser it is cuz it will only slow id down
}
else
{
txtProduct.setAttribute("readonly", "readonly");
txtProduct.readOnly = true;
}
}
并将其放入页面的 <head>
部分。
请注意,此处使用的正则表达式与上一节中用于在同一 GridView
行中获取控件的正则表达式相同;只是现在,我们正在进行客户端操作。
为了让上述 JavaScript 函数在您点击复选框时启动,您需要将处理程序放在某个地方。我选择将其直接放在页面的 XHTML 中
<asp:TemplateField HeaderText="Edit">
<ItemTemplate>
<asp:CheckBox ID="chkEdit"
runat="server"
OnClick="GridViewRowChecked(this)" />
</ItemTemplate>
</asp:TemplateField>
不理会设计器的 “警告 1 验证 (ASP.NET):属性 'OnClick' 不是元素 'CheckBox' 的有效属性。” 谁在乎呢?它有效,并且放在那里更好。但是,如果您想要更高级或更灵活,您可以在 GridView1_RowBound
事件中添加此事件处理程序。
现在,每次用户点击复选框时,GridViewChecked(what)
都会启动并启用/禁用复选框所在同一行中的文本框。
就是这样。
一点额外内容 - 货币格式
实际上,我本不打算把它放在这里,但是……无论如何,这是一个在 GridView
数据绑定字段中“即时”字符串格式化的小例子
<asp:BoundField DataField="UnitPrice"
DataFormatString="{0:C2}" HeaderText="Price" />
其他格式是
格式字符 |
描述 |
---|---|
C |
以货币格式显示数值。 |
D |
以十进制格式显示数值。 |
E |
以科学(指数)格式显示数值。 |
F |
以固定格式显示数值。 |
G |
以通用格式显示数值。 |
N |
以数字格式显示数值。 |
X |
以十六进制格式显示数值。 |
格式字符后的值(通用示例中的 xx)指定要显示的有效位数或小数位数。例如,格式字符串 "{0:F2}" 显示一个带两位小数的定点数。
有关字符串格式的更多信息,请参阅 这篇 MSDN 文章。
兼容性
本项目已使用 IE 7、Firefox 2.0013 和 Opera 9.23 进行测试。
需要考虑的事项
- 不要使您的
GridView
页面太大,因为大量使用 JavaScript(无论是 AJAX 还是您自己的)都会显著减慢客户端浏览器的速度;实现分页。 - 请记住处理
Rating
控件中的BehaviourID
属性。 - 不要将本文视为一个实际示例 - 它只是一个演示
GridView
和其他控件某些用法的练习。 - 不要在您的
GridView
中过度使用下拉列表,因为页面会变得太重。 - “
using
” 的用法。我注意到很多人没有使用 .NET 2.0 中提供的“using
”关键字。在这个例子中,它被多次使用并且是自解释的。它减少了代码量,并提供了对对象的更好控制,最重要的是在我们的案例中——数据库连接。特别是,如果您处理遗留数据库,如果您不关闭连接,连接会悬挂在服务器上,这变得至关重要。