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






4.35/5 (15投票s)
如何在没有xxxDataSource(SqlDataSource、ObjectDataSource 等)的情况下使用GridView实现批量编辑
引言
ASP.NET的GridView
是一个非常出色的控件,它能够非常轻松地实现多行数据的编辑。前几天,我需要实现GridView的批量编辑功能。我在网上搜索,找到了Matt Dotson在MSDN上的一篇非常好的文章。
不幸的是,他使用了SQLDataSource
,而我更喜欢通过DataSource
属性和Databind
方法来绑定Grid。GridView
在没有xxxDataSource
(SQLDataSource
、ObjectDataSource
等)的情况下,并不是设计得很好,尤其是在分页、排序和编辑方面。
本教程旨在解释如何实现一个扩展版本的GridView
,允许在没有任何xxxDataSource
的情况下编辑/更新多行数据。
背景
批量编辑的核心思想与Matt Dotson实现的控件非常相似,但由于GridView
在手动绑定时工作方式不同,我不得不更改大部分代码。在重写GridView
的某些方法时,我发现手动绑定存在问题,因为某些对象没有正确初始化。我使用Reflector进行分析,发现GridView
内部的大部分代码严格依赖于DataSourceID
。
控件实现
类BulkEditGridView
继承自GridView
,并添加/重写了以下属性/方法:
BulkEdit
:布尔属性,用于维护批量编辑的状态。此属性将值存储在ViewState中。
CreateRow
(重写):与BulkEdit
结合使用,使所有行都可编辑。BulkUpdate
:用于调用所有行的GridView
的RowUpdating
事件。GetOldValue
和GetNewValue
:用于获取旧值和新值。
通过重写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
方法使用GridView
的UpdateRow
方法对所有行进行更新。
public void BulkUpdate()
{
foreach (GridViewRow row in this.Rows)
{
this.UpdateRow(row.RowIndex, false);
}
}
GetOldValue
和GetNewValue
方法(使用泛型)有两个重载,一个用于模板列,一个用于数据绑定列。旧值来自控件在单元格中可用的值。新值来自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项目由以下文件组成:
- Default.aspx:包含使用
BulkEditGridView
示例的页面。 - Progress.gif:在
UpdateProgress
控件(ASP.NET AJAX Framework)中使用。 - ProductEntity.cs:一个简单的类,其属性包含单个产品的数据(用于手动绑定
BulkEditGridView
)。 - 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
中,我使用GetOldValue
和GetNewValue
方法来比较差异,并仅保存已更改的行。两个方法都有两个重载,以便从简单的数据绑定列或模板列中获取数据。
关注点
也许,这个解决方案不是最好的,因为我必须手动比较行中的更改,但我还没有找到一个通用的方法来解决这个问题。欢迎任何建议!:)
历史
- 2008年6月15日 - 初版发布。