驯服 FormView:理解如何有效实现 FormView 控件






4.60/5 (16投票s)
本文介绍如何利用 FormView 控件,在不同模式之间切换,并指导开发人员如何避免其固有的问题。
FormView 的挑战
掌握 FormView
控件的好处在于,可以利用微软最强大的控件之一来提升用户体验。然而,关于如何编写一个能够有效利用 FormView
控件的良好界面的描述和示例信息却非常 sparse。我还没有找到一本能够有效解释其用法的书。在搜索过的许多网站上,我也没找到足够的教程。
我同情许多国际公司(我在其中工作过)的用户所表达的沮丧。他们正在使用企业应用程序,并试图修改完全依赖于 GridView
控件的记录。通常,用户必须编辑包含五十多列的记录。这意味着他们必须先找到记录,使用滚动条找到正确的列,编辑字段,然后滚动回 GridView
的左侧来更新或插入新记录。或者,用户被要求使用主/详细信息模式来更新记录。这种模式很快就会变得相当令人困惑。当 FormView
与 MultiView
控件结合使用时,用户将获得更友好的体验。更好的是,开发人员将使用 AJAX 来增强应用程序的流畅性,但这超出了本文的范围。
本文重点介绍开发人员必须了解的 FormView
控件的技巧和窍门。这里的想法是指导开发人员避开该控件的陷阱和怪癖,并提供一个可靠的设计模式的基本示例,该模式可以在未来的项目中加以利用。
业务问题
管理层注意到,由于用户在网格视图中编辑和插入包含五十多列的记录,他们的生产力只有预期的一半。用户难以导航页面找到正确的列,进行更新,然后他们必须返回到行的开头来搜索另一条记录。解决方案是点击左侧的编辑按钮,将焦点定位到 FormView
的第一个字段,编辑记录,然后提交更改。添加新记录的过程也将类似。
最后,我们假设已经存在一个搜索功能,用户可以利用该功能导航到下一条记录。因此,在本例中将省略该功能,因为它不是必需的。示例中将只显示两列,以限制演示设计模式所需的源代码量。
演示用例
用户打开一个应用程序查看俱乐部列表,然后单击“编辑”按钮以修改记录。
响应编辑请求,系统以编辑模式显示 FormView
并填充记录。请注意,网格视图记录不处于编辑模式。此外,无需导航到其他页面并处理跨页面提交模式引入的复杂性。
用户提交更改,系统关闭表单。然后列出带有更新的记录。
接下来,用户必须添加一条记录。用户单击“添加记录”链接,系统通过以插入模式打开表单来响应。用户填写俱乐部信息,然后单击“提交”链接。
提交记录后,系统将关闭表单并将记录添加到俱乐部列表中。可选地,系统可以保持表单打开以便进行集中的数据录入。我选择关闭表单是为了说明如何为开发人员的参考管理切换结构中的控件。更多细节将在下面提供。
提示与技巧
- 设计表单时,请使用模板。“
ItemTemplate
”,无论它是否用于只读模式,都会导致 VS2008 设计器显示表单的格式。我建议在编辑、更新和插入项模板中使用相同的格式。 - 在“
ItemCommand
”处理程序中放入一个switch
语句。我通常建议忽略所有其他 FormView 事件处理程序,因为它们倾向于在系统设计中引入怪癖。使用简单的控件结构比尝试拦截其他事件更好。我发现使用 FormView 控件的其他事件只会增加设计的复杂性和烦人的怪癖。 - 始终使用链接或图像按钮将
FormView
控件置于“添加记录”模式。这处理了在没有记录可显示时的情况。否则,从空表单视图开始会遇到挑战。当没有记录时,空表单视图不会显示,控件也不会被实例化。这意味着链接将不存在。 FormView
控件需要一个集合或枚举对象。因此,使用以下技术将不起作用
FormView1.ChangeMode(FormViewMode.Edit);
TextBox tb = (TextBox) FormView1.FindControl("ClubName");
tb.Text = "Club Name";
相反,将表单置于 Edit
模式,并将 ArrayList 或 DataTable 等集合绑定到数据源。然后像这样调用 DataBind
方法
FormView1.ChangeMode(FormViewMode.Edit);
FormView1.DataSource = al;
FormView1.DataBind();
关键设计概念
管理 FormView
是通过处理 ItemCommand
事件来完成的。使用 switch
结构在 ItemCommand
处理程序中控制 FormView
控件的模式。FormView
控件有三种模式:插入、编辑和只读。FormView
的 ChangeMode()
方法用于切换控件的模式。
Club
类用于包含要更新的记录以及要插入到表中的新记录。
Club | |
实例变量 | |
ClubID |
一个整数,用于保存唯一的俱乐部标识。 |
ClubName |
俱乐部的名称。 |
URL |
组织机构的 URL。 |
ClubList
类是一个辅助类,在本例中代替了数据库表。它很简单,关键项列在下表中。
ClubList | |
实例变量 | |
ClubList cl |
该类使用单例设计模式。 |
DataTable _dt |
使用数据表来包含俱乐部记录。 |
方法 | |
BuildClubList() |
构建并填充一个默认的俱乐部列表,以便在应用程序启动时绑定到网格视图。 |
AddClub(Club c) |
将俱乐部添加到数据表中。Club 对象用于添加新记录。 |
UpdateClubByID(Club c) |
接受 Club 对象并更新数据表中的记录。 |
创建 ClubList
类,该类构建一个表来创建默认数据、添加和更新俱乐部记录。记录进入数据表。该类使用单例设计模式。
默认表单 (ASP.NET) | |
事件处理程序 | |
Page_Load |
当页面首次加载时,获取俱乐部列表并将其绑定到网格视图。 |
FormView1_ItemCommand |
每当发生 FormView ItemCommand 事件时,switch 结构使用 e.CommandName 来确定发生了什么事件。FormView 被切换到适当的模式,并调用 databind() 函数以进行更新。 |
AddNewClub_Click |
单击“添加新俱乐部”链接以处理切换表单到插入模式的事件。 |
gvClubList_RowEditing |
处理来自网格视图的编辑单击事件消息。在此,从数据表中检索俱乐部记录并显示在 FormView 控件中。取消网格视图的编辑事件,以防止控件进入编辑记录模式。 |
方法 | |
UpdateClub() |
在数据表中更新俱乐部记录。 |
AddClub() |
将新俱乐部添加到数据表中。 |
编码应用程序
步骤 1. 按如下方式创建 Club
类
/// <summary>
/// Jeff Kent - 11/15/2009
/// Table class of type club that helps
/// with customer record insertion and updates.
/// </summary>
public class Club
{
public Club()
{
}
private int _ClubID;
public int ClubID{
get { return _ClubID; }
set { _ClubID = value; }
}
private string _ClubName;
public string ClubName
{
get { return _ClubName; }
set { _ClubName = value; }
}
private string _URL;
public string URL
{
get { return _URL; }
set { _URL = value; }
}
}
步骤 2. 创建 ClubList
类,该类构建一个表来创建默认数据、添加和更新俱乐部记录。记录进入数据表。该类使用单例设计模式。
// <summary>
/// Jeff Kent - 11/14/2009
/// Helper that is used to populate the gridview,
/// add a new club, and expose the data table for binding.
/// This is intended only for use in demonstrating this application, and
/// it is not suitable for production use.
///
///
/// UTILIZE THE SINGLETON DESIGN PATTERN.
/// </summary>
public sealed class ClubList
{
private static readonly ClubList cl = new ClubList();
private static DataTable _dt;
private ClubList() { }
//PROPERTY THAT EXPOSES THE DATA TABLE FOR BINDING.
static public DataTable dt
{
get { return _dt; }
set { _dt = value; }
}
// CREATE A CLUB LIST AND RETURN THE DATA TABLE.
static public DataTable BuildClubList()
{
DataColumn col;
DataColumn col2;
DataColumn col3;
DataRow row;
if (!(dt == null))
{
return dt;
}
dt = new DataTable("ClubList");
// Create new Datacol, set DataType,
// colName and add to DataTable.
col = new DataColumn();
col.DataType = Type.GetType("System.Int32");
col.ColumnName = "ClubID";
col.AutoIncrement = true;
col.AutoIncrementSeed = 1000;
col.AutoIncrementStep = 10;
col.ReadOnly = true;
col.Unique = true;
dt.Columns.Add(col);
// Make the ID column the primary key column.
DataColumn[] PrimaryKeyColumns = new DataColumn[1];
PrimaryKeyColumns[0] = dt.Columns["ClubID"];
dt.PrimaryKey = PrimaryKeyColumns;
col2 = new DataColumn();
col2.DataType = Type.GetType("System.String");
col2.ColumnName = "ClubName";
col2.AutoIncrement = false;
col2.Caption = "Club Name";
col2.ReadOnly = false;
col2.Unique = false;
dt.Columns.Add(col2);
col3 = new DataColumn();
col3.DataType = Type.GetType("System.String");
col3.ColumnName = "URL";
col3.AutoIncrement = false;
col3.Caption = "URL";
col3.ReadOnly = false;
col3.Unique = false;
dt.Columns.Add(col3);
row = dt.NewRow();
row["ClubName"] = "Joe's Rocketry";
row["URL"] = "www.joes.com";
dt.Rows.Add(row);
row = dt.NewRow();
row["ClubName"] = "American Rocketry";
row["URL"] = "www.ar.com";
dt.Rows.Add(row);
row = dt.NewRow();
row["ClubName"] = "NSSR Rockets";
row["URL"] = "www.nssr.com";
dt.Rows.Add(row);
return dt;
}
//ADD A CLUB TO THE DATA TABLE.
public static DataTable AddClub(Club c)
{
DataRow row;
row = dt.NewRow();
//row["ClubID"] = c.ClubID;
row["ClubName"] = c.ClubName;
row["URL"] = c.URL;
dt.Rows.Add(row);
row = dt.NewRow();
return dt;
}
//GET THE CLUB BY THE ID.
public static Club GetClubByID(int ClubID)
{
Club c = new Club();
DataRow row = dt.Rows.Find(ClubID);
c.ClubID = (int)row["ClubID"];
c.ClubName = row["ClubName"].ToString();
c.URL = row["URL"].ToString();
return c;
}
//UPDATE THE CLUB RECORD BY ID.
public static void UpdateClubByID(Club c)
{
DataRow row = dt.Rows.Find(c.ClubID);
row.BeginEdit();
row["ClubName"] = c.ClubName;
row["URL"] = c.URL;
dt.AcceptChanges();
}
}
步骤 3. 创建标准的默认页面。添加表单和网格视图控件。我喜欢使用 divisions、级联样式表的选择器和表来组织页面。样式表包含在编码示例中。示例还包括本文使用的图像。
<div id="wrapper">
<form id="form1" runat="server">
<div id="header">
<asp:Image ID="Image1" runat="server"
ImageUrl="~/images/ClubAdmin.jpg" />
</div>
<div id="maincontent">
<asp:FormView ID="FormView1"
runat="server" DataKeyNames="ClubID"
OnItemCommand="FormView1_ItemCommand"
BorderStyle="Outset"
BorderWidth="2px" CssClass="FormView">
<ItemTemplate>
<table>
<tr>
<td class="FormViewHeader">
Club Name:
</td>
<td>
<asp:Label ID="ClubName" runat="server"
Text='<%# Bind("ClubName") %>'></asp:Label>
</td>
</tr>
<tr>
<td class="FormViewHeader">
URL:
</td>
<td>
<asp:Label
ID="URL" runat="server"
Text='<%# Bind("URL") %>'></asp:Label>
</td>
</tr>
</table>
<asp:LinkButton ID="LinkButton1"
runat="server" CommandName="EditInfo"
Text="Edit">Edit</asp:LinkButton>
<asp:LinkButton ID="LinkButton10" runat="server"
CommandName="InsertInfo"
Text="New">Insert</asp:LinkButton>
</ItemTemplate>
<PagerSettings Mode="NextPreviousFirstLast" />
<EditItemTemplate>
<table>
<tr>
<td class="FormViewHeader">
Club Name:
</td>
<td>
<asp:TextBox
ID="ClubName" runat="server"
Text='<%# Bind("ClubName") %>'></asp:TextBox>
</td>
</tr>
<tr>
<td class="FormViewHeader">
URL:
</td>
<td>
<asp:TextBox ID="URL"
runat="server"
Text='<%# Bind("URL") %>'></asp:TextBox>
</td>
</tr>
</table>
<asp:LinkButton ID="LinkButton2"
runat="server" CommandName="UpdateInfo"
Text="UpdateInfo">Update</asp:LinkButton>
<asp:LinkButton ID="LinkButton3"
runat="server" CommandName="CancelUpdate"
Text="CancelInfo">Cancel</asp:LinkButton>
</EditItemTemplate>
<InsertItemTemplate>
<table>
<tr>
<td class="FormViewHeader">
Club Name:
</td>
<td>
<asp:TextBox ID="ClubName" runat="server"
Text='<%# Bind("ClubName") %>'></asp:TextBox>
</td>
</tr>
<tr>
<td class="FormViewHeader">
URL:
</td>
<td>
<asp:TextBox ID="URL" runat="server"
Text='<%# Bind("URL") %>'></asp:TextBox>
</td>
</tr>
</table>
<asp:LinkButton ID="LinkButton20" runat="server"
CommandName="SubmitInfo"
Text="SubmitInfo">Commit</asp:LinkButton>
<asp:LinkButton ID="LinkButton30"
runat="server"
CommandName="CancelInsert"
Text="CanceInsert">Cancel</asp:LinkButton>
</InsertItemTemplate>
</asp:FormView>
<br />
<br />
<hr />
<asp:LinkButton ID="AddNewClub" runat="server"
OnClick="AddNewClub_Click"
CssClass="LinkButton">
Add New Club</asp:LinkButton>
<asp:GridView ID="gvClubList"
runat="server" AutoGenerateColumns="False"
DataKeyNames="ClubID"
OnRowEditing="gvClubList_RowEditing"
AlternatingRowStyle-CssClass="AlternatingRowStyle">
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:TemplateField InsertVisible="False">
<HeaderTemplate>
<table width="400px">
<thead>
<td class="TableHeader" style="width: 40%">
Club Name
</td>
<td class="TableHeader">
URL
</td>
</thead>
</table>
</HeaderTemplate>
<AlternatingItemTemplate>
<table width="400px">
<tr>
<td style="width: 40%">
<asp:Label
ID="ClubName" runat="server"
Text='<%# Bind("ClubName") %>'></asp:Label>
</td>
<td>
<asp:Label ID="URL"
runat="server" Text='<%# Bind("URL") %>'></asp:Label>
</td>
</tr>
</table>
</AlternatingItemTemplate>
<ItemTemplate>
<table width="400px">
<tr>
<td style="width: 40%">
<asp:Label ID="ClubName" runat="server"
Text='<%# Bind("ClubName") %>'></asp:Label>
</td>
<td>
<asp:Label ID="URL" runat="server"
Text='<%# Bind("URL") %>'></asp:Label>
</td>
</tr>
</table>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<br />
</div>
</form>
</div>
步骤 4. 为默认页面添加代码隐藏逻辑。如前所述,关键处理程序是 FormView
控件的 ItemCommand
和 GridView
的 RowUpdating
命令。特别注意如何取消 GridView
的编辑,以及如何将 FormView
设置为 Edit
模式并用记录填充。当用户选择向列表中添加新俱乐部时,LinkButton
控件的 Click
事件用于将表单置于插入模式。
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
gvClubList.DataSource = ClubList.BuildClubList();
gvClubList.DataBind();
FormView1.ChangeMode(FormViewMode.ReadOnly);
}
}
//MANAGE THE FORM VIEW CONTROL'S STATE IN RESPONSE TO CLICK EVENTS
//RELATED TO SUBMISSION, UPDATE, AND CANCEL REQUESTS.
protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)
{
switch (e.CommandName)
{
//case "EditInfo":
// FormView1.ChangeMode(FormViewMode.Edit);
// break;
case "UpdateInfo":
UpdateClub();
FormView1.ChangeMode(FormViewMode.ReadOnly);
gvClubList.DataSource = ClubList.dt;
gvClubList.DataBind();
break;
case "SubmitInfo":
AddClub();
FormView1.ChangeMode(FormViewMode.ReadOnly);
gvClubList.DataSource = ClubList.dt;
gvClubList.DataBind();
break;
case "CancelUpdate":
FormView1.ChangeMode(FormViewMode.ReadOnly);
break;
case "CancelInsert":
FormView1.ChangeMode(FormViewMode.ReadOnly);
break;
default:
break;
}
// FOR THE MODE TO CHANGE CALL DATA BIND.
FormView1.DataBind();
}
//Update an existing club.
protected void UpdateClub()
{
TextBox tb;
Club c = new Club();
c.ClubID = Int32.Parse(FormView1.DataKey.Value.ToString());
tb = (TextBox)FormView1.FindControl("ClubName");
c.ClubName = tb.Text;
tb = (TextBox)FormView1.FindControl("URL");
c.URL = tb.Text;
ClubList.UpdateClubByID(c);
}
//Add and new club to the table.
protected void AddClub()
{
TextBox tb;
Club c = new Club();
//c.ClubID = Int32.Parse(FormView1.DataKey.Value.ToString());
tb = (TextBox)FormView1.FindControl("ClubName");
c.ClubName = tb.Text;
tb = (TextBox)FormView1.FindControl("URL");
c.URL = tb.Text;
if (c.ClubName != String.Empty)
{
ClubList.AddClub(c);
}
}
//PUT THE FORM VIEW INTO THE INSERT MODE IN RESPONSE TO
//A CLICK EVENT FROM A LINK OR IMAGE BUTTON.
protected void AddNewClub_Click(object sender, EventArgs e)
{
//PUT THE FORM VIEW INTO INSERT MODE.
FormView1.ChangeMode(FormViewMode.Insert);
}
protected void gvClubList_RowEditing(object sender, GridViewEditEventArgs e)
{
int ClubID;
string ClubName;
string URL;
Label lb;
Club c;
ArrayList al = new ArrayList();
ClubID = Int32.Parse(gvClubList.DataKeys[e.NewEditIndex].Value.ToString());
lb = (Label)gvClubList.Rows[e.NewEditIndex].FindControl("ClubName");
ClubName = lb.Text;
lb = (Label)gvClubList.Rows[e.NewEditIndex].FindControl("URL");
URL = lb.Text;
//CANCEL PUTTING THE GRID VIEW INTO EDIT MODE.
e.Cancel = true;
//GET THE FORM CONTROLS AND BIND THE DATA.
//THE CLUB IS PUT INTO AN ARRAY LIST BECAUSE THE FORM VIEW.
c = ClubList.GetClubByID(ClubID);
al.Add(c);
FormView1.ChangeMode(FormViewMode.Edit);
FormView1.DataSource = al;
FormView1.DataBind();
}
}
结论
FormView
控件已被证明是执行插入、删除和更新以管理记录的最强大和最有用的控件之一。要找到有关如何避免该控件中的怪癖的良好信息是一个挑战。一旦理解,它就是一个可以在任何应用程序中实现的简单控件。我经常使用它。显然,当开发人员能够有效地实现控件时,用户体验和效率都会得到提升。我鼓励开发人员学习并利用本文所示的设计模式。