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

GridView 无xxxDataSource(SqlDataSource、ObjectDataSource 等)批量编辑

2008年6月15日

CPOL

4分钟阅读

viewsIcon

229956

downloadIcon

7390

如何在没有xxxDataSource(SqlDataSource、ObjectDataSource 等)的情况下使用GridView实现批量编辑

Screenshot2.jpg

引言

ASP.NET的GridView是一个非常出色的控件,它能够非常轻松地实现多行数据的编辑。前几天,我需要实现GridView的批量编辑功能。我在网上搜索,找到了Matt Dotson在MSDN上的一篇非常好的文章。

不幸的是,他使用了SQLDataSource,而我更喜欢通过DataSource属性和Databind方法来绑定Grid。GridView在没有xxxDataSourceSQLDataSourceObjectDataSource等)的情况下,并不是设计得很好,尤其是在分页、排序和编辑方面。

本教程旨在解释如何实现一个扩展版本的GridView,允许在没有任何xxxDataSource的情况下编辑/更新多行数据。

背景

批量编辑的核心思想与Matt Dotson实现的控件非常相似,但由于GridView在手动绑定时工作方式不同,我不得不更改大部分代码。在重写GridView的某些方法时,我发现手动绑定存在问题,因为某些对象没有正确初始化。我使用Reflector进行分析,发现GridView内部的大部分代码严格依赖于DataSourceID

控件实现

BulkEditGridView继承自GridView,并添加/重写了以下属性/方法:

  • BulkEdit:布尔属性,用于维护批量编辑的状态。此属性将值存储在ViewState中。
  • CreateRow(重写):与BulkEdit结合使用,使所有行都可编辑。
  • BulkUpdate:用于调用所有行的GridViewRowUpdating事件。
  • GetOldValueGetNewValue:用于获取旧值和新值。

通过重写CreateRow方法并使用BulkEdit属性,我可以使所有行都可编辑。

protected override GridViewRow CreateRow(int rowIndex, int dataSourceIndex, 
          DataControlRowType rowType, DataControlRowState rowState)
{
    GridViewRow row;

    if (this.BulkEdit)
        row = base.CreateRow(rowIndex, dataSourceIndex, 
              rowType, rowState | DataControlRowState.Edit);
    else
        row = base.CreateRow(rowIndex, dataSourceIndex, rowType, rowState);

    return row;
}

BulkUpdate方法使用GridViewUpdateRow方法对所有行进行更新。

public void BulkUpdate()
{
    foreach (GridViewRow row in this.Rows)
    {
        this.UpdateRow(row.RowIndex, false);
    }
}

GetOldValueGetNewValue方法(使用泛型)有两个重载,一个用于模板列,一个用于数据绑定列。旧值来自控件在单元格中可用的值。新值来自Request集合,并使用私有方法ConvertValue进行转换。

private T ConvertValue<T>(string strValue)
{
    object value = default(T);

    if (strValue != null)
    {
        if (typeof(T) == typeof(string))
        {
            value = strValue;
        }
        else if (typeof(T) == typeof(int))
        {
            value = Convert.ToInt32(strValue);
        }
        else if (typeof(T) == typeof(double))
        {
            value = Convert.ToDouble(strValue);
        }
        else if (typeof(T) == typeof(bool))
        {
            if (strValue.ToLower() == "on" || strValue.ToLower() == 
                        "true" || strValue.ToLower() == "1")
                value = true;
            else
                value = false;
        }
        else if (typeof(T) == typeof(DateTime))
        {
            value = Convert.ToDateTime(strValue);
        }
    }

    return (T)value;
}

public T GetOldValue<T>(int rowIndex, int cellIndex)
{
    BoundField bf = this.Columns[cellIndex] as BoundField;
    
    T retVal = default(T);

    if (bf != null)
    {
        if (bf.ReadOnly)
        {
            DataControlFieldCell cell = 
              this.Rows[rowIndex].Cells[cellIndex] as DataControlFieldCell;
            retVal = ConvertValue<T>(cell.Text);
        }
        else
        {
            Control ctrl = this.Rows[rowIndex].Cells[cellIndex].Controls[0];

            if (ctrl.GetType() == typeof(TextBox))
            {
                retVal = this.ConvertValue<T>(((TextBox)ctrl).Text);
            }
            else if (ctrl.GetType() == typeof(CheckBox))
            {
                retVal = this.ConvertValue<T>(((CheckBox)ctrl).Checked.ToString());
            }
            else if (ctrl.GetType() == typeof(DropDownList))
            {
                retVal = this.ConvertValue<T>(((DropDownList)ctrl).SelectedValue);
            }
        }
    }
    else
    {
        throw new ArgumentException("The cell selected is not a DataBoundControl!");
    }

    return retVal;
}

public T GetOldValue<T>(int rowIndex, string controlName)
{
    Control ctrl = this.Rows[rowIndex].FindControl(controlName);
    
    T retVal = default(T);

    if (ctrl != null)
    {
        if (ctrl.GetType() == typeof(TextBox))
        {
            retVal = this.ConvertValue<T>(((TextBox)ctrl).Text);
        }
        else if (ctrl.GetType() == typeof(CheckBox))
        {
            retVal = this.ConvertValue<T>(((CheckBox)ctrl).Checked.ToString());
        }
        else if (ctrl.GetType() == typeof(DropDownList))
        {
            retVal = this.ConvertValue<T>(((DropDownList)ctrl).SelectedValue);
        }
    }
    else
    {
        throw new ArgumentException("The controlName not found!");
    }

    return retVal;
}

private T GetNewValue<T>(string uniqueID)
{
    string strValue = this.Page.Request[uniqueID];
    return ConvertValue<T>(strValue);
}

public T GetNewValue<T>(int rowIndex, int cellIndex)
{
    BoundField bf = this.Columns[cellIndex] as BoundField;

    if (bf != null)
    {
        if (bf.ReadOnly)
        {
            DataControlFieldCell cell = 
              this.Rows[rowIndex].Cells[cellIndex] as DataControlFieldCell;
            return ConvertValue<T>(cell.Text);
        }
        else
        {
            string uniqueID = this.Rows[rowIndex].Cells[cellIndex].Controls[0].UniqueID;
            return this.GetNewValue<T>(uniqueID);
        }
    }
    else
    {
        throw new ArgumentException("The cell selected is not a DataBoundControl!");
    }
}

public T GetNewValue<T>(int rowIndex, string controlName)
{
    string uniqueID = this.Rows[rowIndex].FindControl(controlName).UniqueID;
    return this.GetNewValue<T>(uniqueID);
}

使用控件

在示例Web项目中,我使用了一个SQLite数据库中的单个表(*tblProducts*)。我使用SQLite是因为它功能强大、体积小、速度快,而且我的免费主机不支持数据库。SQLite是基于文件的数据库,具有现代RDBMS的大部分功能。它支持大部分SQL语句、视图、触发器等。有关更多详细信息,请参阅此链接

SQLite是一个用C语言开发的开源项目,但有一个特定的ADO.NET提供程序,可以非常容易地在.NET中使用它。您可以从这里下载。

Web项目由以下文件组成:

  1. Default.aspx:包含使用BulkEditGridView示例的页面。
  2. Progress.gif:在UpdateProgress控件(ASP.NET AJAX Framework)中使用。
  3. ProductEntity.cs:一个简单的类,其属性包含单个产品的数据(用于手动绑定BulkEditGridView)。
  4. DataAccessLayer.cs:一个单例类,用于访问数据库。它检索ProductEntity实例列表并保存新数据。

项目使用ASP.NET AJAX框架(UpdatePanel)使一切更加美观和快速,但这不是控件所必需的。Default.aspx页面包含一个BulkEditGridView实例和三个按钮(Edit、Update和Cancel),用于更改Grid的状态和更新数据。Grid使用内置的分页机制实现分页(我知道它不是很智能,但很容易实现!:))。代码中最重要的部分如下:

private void RefreshButtons(bool editMode)
{
    if (editMode)
    {
        this.btnEdit.Visible = false;
        this.btnUpdate.Visible = true;
        this.btnCancel.Visible = true;
    }
    else
    {
        this.btnEdit.Visible = true;
        this.btnUpdate.Visible = false;
        this.btnCancel.Visible = false;
    }
}

protected void btnEdit_Click(object sender, EventArgs e)
{
    this.grdProducts.BulkEdit = true;
    RefreshGrid();
    RefreshButtons(true);
}

protected void btnUpdate_Click(object sender, EventArgs e)
{
    this.grdProducts.BulkEdit = true;
    RefreshGrid();

    this.grdProducts.BulkUpdate();

    this.grdProducts.BulkEdit = false;
    RefreshGrid();
    RefreshButtons(false);
}
    
protected void btnCancel_Click(object sender, EventArgs e)
{
    this.grdProducts.BulkEdit = false;
    RefreshGrid();
    RefreshButtons(false);
}

protected void grdProducts_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    int productID = this.grdProducts.GetNewValue<int>(e.RowIndex, 0);
    string productName = this.grdProducts.GetNewValue<string>(e.RowIndex, 
                                                       "txtProductName");
    double unitPrice = this.grdProducts.GetNewValue<double>(e.RowIndex, 2);
    bool discontinued = this.grdProducts.GetNewValue<bool>(e.RowIndex, 3);

    string oldProductName = this.grdProducts.GetOldValue<string>(e.RowIndex, 
                                                          "txtProductName");
    double oldUnitPrice = this.grdProducts.GetOldValue<double>(e.RowIndex, 2);
    bool oldDiscontinued = this.grdProducts.GetOldValue<bool>(e.RowIndex, 3);

    if (productName != oldProductName || unitPrice != 
             oldUnitPrice || discontinued != oldDiscontinued)
    {
        List<SQLiteParameter> parameters = new List<SQLiteParameter>();
        parameters.Add(new SQLiteParameter("@ProductID", productID));
        parameters.Add(new SQLiteParameter("@ProductName", productName));
        parameters.Add(new SQLiteParameter("@UnitPrice", unitPrice));
        parameters.Add(new SQLiteParameter("@Discontinued", discontinued));
        parameters.Add(new SQLiteParameter("@LastChange", DateTime.Now));

        DataAccessLayer.Instance.ExecuteQuery(
                        @"Update tblProducts set ProductName=@ProductName," + 
                        @" UnitPrice=@UnitPrice, Discontinued=@Discontinued, " + 
                        @"LastChange=@LastChange where ProductID=@ProductID", 
                        parameters.ToArray());
    }

在Edit按钮的Click事件中,我将Grid的BulkEdit属性设置为true,绑定数据,并隐藏Edit按钮,显示Update和Cancel按钮。此操作使Grid的所有行都处于编辑模式。Update上的Click事件会恢复Grid的编辑模式(这对于恢复控件状态是必需的),然后我调用Grid的BulkUpdate方法。此方法会调用Grid中每一行的RowUpdating事件。在RowUpdating中,我使用GetOldValueGetNewValue方法来比较差异,并仅保存已更改的行。两个方法都有两个重载,以便从简单的数据绑定列或模板列中获取数据。

关注点

也许,这个解决方案不是最好的,因为我必须手动比较行中的更改,但我还没有找到一个通用的方法来解决这个问题。欢迎任何建议!:)

历史

  • 2008年6月15日 - 初版发布。
© . All rights reserved.