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

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

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.76/5 (18投票s)

2008 年 4 月 2 日

GPL3

6分钟阅读

viewsIcon

76661

downloadIcon

2148

ASP.NET GridView 实现 AjaxControlToolkit 的评分控件、每行的下拉列表、数据库连接、正则表达式、JavaScript、CSS 等。本文是一个练习,旨在更好地理解在 GridView 中使用各种技术(服务器端和客户端)的方面。

引言

在浏览各种 ASP.NET 博客和论坛寻找答案时,我经常遇到以某种方式重复的某些主题。

本文是一个练习,它回答了一些常见的 GridView 问题。特别是,我在这里处理的是

  1. MS Access 数据库连接。
  2. MS AjaxControlToolkit 的 Rating 扩展器,它放置在每一行上,允许用户实时对每一行进行评分。换句话说,一旦用户对行进行评分(选择星数),就会向服务器发出异步(AJAX)调用,并更新数据库。
  3. 每个 GridView 行中的数据绑定下拉列表。每行都有一个供应商,该供应商显示在下拉列表中,以方便记录修改。
  4. 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"/>

然后,无论我需要在代码中使用数据库,我都会

  1. 在每个类的顶部添加 "using System.Data.OleDb;";
  2. 通过调用 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

以十六进制格式显示数值。

注意:格式字符不区分大小写,除了“X”,它以指定的大小写显示十六进制字符。

格式字符后的值(通用示例中的 xx)指定要显示的有效位数或小数位数。例如,格式字符串 "{0:F2}" 显示一个带两位小数的定点数。

有关字符串格式的更多信息,请参阅 这篇 MSDN 文章

兼容性

本项目已使用 IE 7、Firefox 2.0013 和 Opera 9.23 进行测试。

需要考虑的事项

  1. 不要使您的 GridView 页面太大,因为大量使用 JavaScript(无论是 AJAX 还是您自己的)都会显著减慢客户端浏览器的速度;实现分页。
  2. 请记住处理 Rating 控件中的 BehaviourID 属性。
  3. 不要将本文视为一个实际示例 - 它只是一个演示 GridView 和其他控件某些用法的练习。
  4. 不要在您的 GridView 中过度使用下拉列表,因为页面会变得太重。
  5. using” 的用法。我注意到很多人没有使用 .NET 2.0 中提供的“using”关键字。在这个例子中,它被多次使用并且是自解释的。它减少了代码量,并提供了对对象的更好控制,最重要的是在我们的案例中——数据库连接。特别是,如果您处理遗留数据库,如果您不关闭连接,连接会悬挂在服务器上,这变得至关重要。
© . All rights reserved.