ASP.NET GridView:在编辑模式下实现级联DropDownList






4.47/5 (4投票s)
本文演示了如何在ASP.NET GridView控件的编辑模式下实现横向级联的DropDownList。
引言
这个问题在各种技术论坛(例如 http://forums.asp.net)中被问了很多次,并且肯定已经有很多解决方案。网络上的大多数示例都使用数据源控件(例如 SqlDataSource
、ObjectDataSource
等)在 GridView 中实现级联 DropDownList,而我似乎找不到一个正式的示例来展示如何在不使用数据源控件的情况下实现它。
使用代码
注意
在继续之前,请确保您已经了解了 GridView 控件的基本用法以及如何将其与数据绑定,因为在此练习中我将不包含编辑、更新或删除场景的详细信息。
场景
回顾一下,级联 DropDownList
实现了一种常见场景,即一个列表的内容取决于另一个列表的选择。在此演示中,我们将展示 GridView
中 Product
订单列表,当用户编辑行时,我们将向用户提供一个 DropDownList
,其中包含可用产品系列的列表。一旦他们做出了选择,我们将在下一列中使用可用型号列表填充第二个 DropDownList
。一旦他们从第二个 DropDownList
中选择了型号,我们将更新该相应型号的价格字段。
让我们开始吧!
本文将探讨如何可能实现这一点。为了开始,让我们设置我们的 HTML
标记(.aspx)。为了简化此演示,我只是这样设置它。
<asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="false" DataKeyNames="ProductOrderID" onrowcancelingedit="gvProducts_RowCancelingEdit" onrowediting="gvProducts_RowEditing" onrowupdating="gvProducts_RowUpdating" onrowdatabound="gvProducts_RowDataBound"> <Columns> <asp:BoundField DataField="ProductOrderID" ReadOnly="true" /> <asp:TemplateField HeaderText="Make"> <ItemTemplate> <asp:Label ID="lblMake" runat="server" Text='<%# Bind("Make") %>' /> </ItemTemplate> <EditItemTemplate> <asp:DropDownList ID="ddlProductMake" runat="server" OnSelectedIndexChanged="ddlProductMake_SelectedIndexChanged" AutoPostBack="true"> </asp:DropDownList> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Model"> <ItemTemplate> <asp:Label ID="lblModel" runat="server" Text='<%# Bind("Model") %>' /> </ItemTemplate> <EditItemTemplate> <asp:DropDownList ID="ddlProductModel" runat="server" OnSelectedIndexChanged="ddlProductModel_SelectedIndexChanged" AutoPostBack="true"> </asp:DropDownList> </EditItemTemplate> </asp:TemplateField> <asp:BoundField DataField="Price" HeaderText="Price" ReadOnly="True" DataFormatString="{0:c}" /> <asp:BoundField DataField="Quantity" HeaderText="Quantity" /> <asp:CommandField ShowEditButton="True" /> </Columns> </asp:GridView>
上面的标记由 6 列各种类型的控件字段组成。第一列是 BoundField
,它包含 ProductOrderID
字段。您可能会注意到,我们将此字段设置为 GridView 中的 DataKeyName
,以便以后可以轻松引用行的 ID
。第 2 列和第 3 列是 TemplateFields
,其中包含 <ItemTemplate>
中的 Label
控件和 <EditItemTemplate>
中的 DropDownList
控件。这意味着在只读状态下将显示 Label,在编辑状态下将显示 DropDownList
以允许用户修改他们选择的内容。请注意,您需要将 AutoPostBack
设置为 TRUE,以便 DropDownList
触发 SelectedIndexChanged
事件。第 4 列和第 5 列是 BoundFields
,它们包含 Price
和 Quantity
字段。您可能会注意到 Price
字段有一个 DataFormatString="{0:c}"
属性。这将在浏览器中显示时将值转换为货币格式。最后,最后一列是一个启用了 ShowEditButton
的 CommandField
。当 GridView
绑定到数据时,这将生成 Edit
链接。
数据源
为了简化此练习,我没有使用数据库,而是创建了一些类来保存其中的属性。以下是类定义。
public class ProductMake
{
public int ProductMakeID { get; set; }
public string Make { get; set; }
}
public class ProductModel
{
public int ProductModelID { get; set; }
public int ProductMakeID { get; set; }
public string Model { get; set; }
public decimal Price { get; set; }
}
public class ProductOrder
{
public int ProductOrderID { get; set; }
public int ProductMakeID { get; set; }
public int ProductModelID { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public short Quantity { get; set; }
public decimal Price { get; set; }
}
请注意,这只是为了使本练习正常工作而举的一个例子。您可以随时将其替换为 DataTable、实体对象、自定义类等,这些类包含来自数据库的实际数据源。
ProductMake
类包含两个主要属性。我们在这里存储产品系列的列表。ProductModel
类存储每个系列的所有型号。ProductOrder
类存储用户选择的订单列表。现在,让我们继续为这个类提供一些示例数据。以下是代码块。
private List<ProductOrder> GetUserProductOrder()
{
List<ProductOrder> orders = new List<ProductOrder>();
ProductOrder po = new ProductOrder();
po.ProductOrderID = 1;
po.ProductMakeID = 1;
po.ProductModelID = 1;
po.Make = "Apple";
po.Model = "iPhone 4";
po.Quantity = 2;
po.Price = 499;
orders.Add(po);
po = new ProductOrder();
po.ProductOrderID = 2;
po.ProductMakeID = 2;
po.ProductModelID = 4;
po.Make = "Samsung";
po.Model = "Galaxy S2";
po.Quantity = 1;
po.Price = 449;
orders.Add(po);
po = new ProductOrder();
po.ProductOrderID = 3;
po.ProductMakeID = 3;
po.ProductModelID = 7;
po.Make = "Nokia";
po.Model = "Lumia";
po.Quantity = 1;
po.Price = 549;
orders.Add(po);
return orders;
}
private List<ProductMake> GetProductMakes()
{
List<ProductMake> products = new List<ProductMake>();
ProductMake p = new ProductMake();
p.ProductMakeID = 1;
p.Make = "Apple";
products.Add(p);
p = new ProductMake();
p.ProductMakeID = 2;
p.Make = "Samsung";
products.Add(p);
p = new ProductMake();
p.ProductMakeID = 3;
p.Make = "Nokia";
products.Add(p);
return products;
}
private List<ProductModel> GetProductModels()
{
List<ProductModel> productModels = new List<ProductModel>();
ProductModel pm = new ProductModel();
pm.ProductMakeID = 1;
pm.ProductModelID = 1;
pm.Model = "iPhone 4";
pm.Price = 499;
productModels.Add(pm);
pm = new ProductModel();
pm.ProductMakeID = 1;
pm.ProductModelID = 2;
pm.Model = "iPhone 4s";
pm.Price = 599;
productModels.Add(pm);
pm = new ProductModel();
pm.ProductMakeID = 1;
pm.ProductModelID = 3;
pm.Model = "iPhone 5";
pm.Price = 699;
productModels.Add(pm);
pm = new ProductModel();
pm.ProductMakeID = 2;
pm.ProductModelID = 4;
pm.Model = "Galaxy S2";
pm.Price = 449;
productModels.Add(pm);
pm = new ProductModel();
pm.ProductMakeID = 2;
pm.ProductModelID = 5;
pm.Model = "Galaxy S3";
pm.Price = 549;
productModels.Add(pm);
pm = new ProductModel();
pm.ProductMakeID = 2;
pm.ProductModelID = 6;
pm.Model = "Galaxy Note2";
pm.Price = 619;
productModels.Add(pm);
pm = new ProductModel();
pm.ProductMakeID = 3;
pm.ProductModelID = 7;
pm.Model = "Nokia Lumia";
pm.Price = 659;
productModels.Add(pm);
return productModels;
}
private List<ProductModel> GetProductModelByMake(int productMakeID)
{
var models = (from p in GetProductModels()
where p.ProductMakeID == productMakeID
select p);
return models.ToList();
}
GetUserProductOrder()
获取订单列表。稍后我们将使用它作为 GridView
中的 DataSource
。GetProductMakes()
方法获取所有可用的系列,在此示例中,我们添加了 3 个主要项目。GetProductModel()
方法获取每个系列的所有可用型号。GetProductModelByMake()
方法根据 ProductMakeID
获取特定的型号项目及其详细信息。此方法使用 LINQ 语法根据传递给它的参数查询 DataSource
。
实现
现在看起来我们已经有一些示例数据源可以使用了。现在,让我们继续进行本练习的重点(即级联下拉列表的实现)。以下是下面的代码块。
private void BindGrid()
{
gvProducts.DataSource = GetUserProductOrder();
gvProducts.DataBind();
}
protected void gvProducts_RowEditing(object sender, GridViewEditEventArgs e)
{
gvProducts.EditIndex = e.NewEditIndex;
BindGrid();
}
protected void gvProducts_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
gvProducts.EditIndex = -1;
BindGrid();
}
protected void gvProducts_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
if ((e.Row.RowState & DataControlRowState.Edit) > 0)
{
DropDownList ddlMake = (DropDownList)e.Row.FindControl("ddlProductMake");
ddlMake.DataSource = GetProductMakes();
ddlMake.DataValueField = "ProductMakeID";
ddlMake.DataTextField = "Make";
ddlMake.DataBind();
ddlMake.SelectedValue = gvProducts.DataKeys[e.Row.RowIndex].Value.ToString();
DropDownList ddlModel = (DropDownList)e.Row.FindControl("ddlProductModel");
ddlModel.DataSource = GetProductModelByMake(Convert.ToInt32(gvProducts.DataKeys[e.Row.RowIndex].Value));
ddlModel.DataValueField = "ProductModelID";
ddlModel.DataTextField = "Model";
ddlModel.DataBind();
ddlModel.SelectedValue = GetProductModelByMake(Convert.ToInt32(gvProducts.DataKeys[e.Row.RowIndex].Value))
.FirstOrDefault().ProductModelID.ToString();
}
}
}
protected void ddlProductMake_SelectedIndexChanged(object sender, EventArgs e)
{
DropDownList ddlMake = (DropDownList)sender;
GridViewRow row = (GridViewRow)ddlMake.NamingContainer;
if (row != null)
{
if ((row.RowState & DataControlRowState.Edit) > 0)
{
DropDownList ddlModel = (DropDownList)row.FindControl("ddlProductModel");
ddlModel.DataSource = GetProductModelByMake(Convert.ToInt32(ddlMake.SelectedValue));
ddlModel.DataValueField = "ProductModelID";
ddlModel.DataTextField = "Model";
ddlModel.DataBind();
}
}
}
protected void ddlProductModel_SelectedIndexChanged(object sender, EventArgs e)
{
DropDownList ddlModel = (DropDownList)sender;
GridViewRow row = (GridViewRow)ddlModel.NamingContainer;
if (row != null)
{
if ((row.RowState & DataControlRowState.Edit) > 0)
{
row.Cells[3].Text = string.Format("{0:C}", GetProductModels()
.Where(o => o.ProductModelID == Convert.ToInt32(ddlModel.SelectedValue))
.FirstOrDefault().Price);
}
}
}
gvProducts_RowDataBound
事件是我们使用数据从 DataSource
绑定 DropDownList 的地方。首先,我们检查 RowType
以确保我们只操作 DataRow
类型的行。请注意,GridView 由几种行类型组成,例如 Header
、DataRow
、EmptyDataRow
、Footer
、Pager
和 Separator
。上面的代码块中的下一行是关键部分,即确定编辑状态。
从 <EditItemTemplate>
中访问控件有些棘手,特别是如果您对 GridView 中的工作原理不太熟悉。将 RowState
等同于 DataControlState.Edit
实际上并不准确,这样做可能会导致异常。RowState
属性是按位组合的。因此,RowState
可能表明您处于编辑状态和备用状态。因此,在编辑模式下不能进行简单的相等性检查。您必须这样做。
if ((e.Row.RowState & DataControlRowState.Edit) > 0)
{
//do your stuff here
}
我们使用按位“&”运算符来确定 GridView
是否处于 Edit
模式,并检查结果是否大于零。有关按位运算符的详细信息,请参阅:C# 中的按位运算符。
一旦我们设法确定了编辑状态,我们就可以开始使用 FindControl() 方法访问控件,并将其与相应的 DataSources 绑定。如果您注意到,我已经为 ddlMake 和 ddlModel DropDownLists
设置了 SelectedValue
,这样当用户点击编辑时,DropDownList
将预先选中用户先前选择的项目。
ddlProductMake_SelectedIndexChanged
事件是我们在实际进行级联功能的地方,即根据第一个 DropDownList
中选定的值填充第二个 DropDownList
。但在此之前,我们需要将触发事件的对象发送者转换为类型,以确定 GridView
行中触发了哪个 DropDownList
。然后,我们将发送者的 NamingContainer 转换为 GridViewRow
类型,以确定用户正在编辑的实际行。
ddlProductModel_SelectedIndexChanged
事件是我们根据第二个 DropDownList
中选定的 Model
更新 Price
值的地方。它基本上使用 LINQ
语法查询 DataSource
,并根据 ddlModel 的选定值获取 Price
。
绑定 GridView
最后,让我们调用 BindGrid()
方法来填充 GridView
。以下是下面的代码块。
protected void Page_Load(object sender, EventArgs e){
if (!IsPostBack)
BindGrid();
}
输出
运行代码将产生以下输出:
摘要
在此练习中,我们学习了如何在 GridView 的编辑模式下实现横向级联 DropDownlist
。