Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Taming the FormView: Understanding how to Implement the FormView Control Effectively

4.60/5 (16 votes)
15 Nov 2009CPOL8 min read 114.2K   2K  
This article explains how to utilize the FormView control, switching between modes, and guides the developer on how to avoid the quirks.

The FormView Challenge

Mastering the FormView control has the benefit of leveraging one of Microsoft's most powerful controls to enhance the user experience. Yet, there is sparse information describing and illustrating how to code a well behaved interface that leverages the FormView control. I have yet to find a book that effectively explains its use. Nor have I found adequate tutorials on the many sites that I have searched.

I sympathize with users expressing frustration at many International firms where I have worked. They are using enterprise applications and attempting to modify records that rely solely upon the GridView control. Often, the user must edit records that contain over fifty columns. That means that they must first locate the record, use the slider to find the correct column, edit the field, and pan back to the left side of the GridView to update or insert a new record. Alternatively, the users are required to use the master/detail pattern for the record updates. That pattern can become quite confusing quickly. When the FormView is used in combination with the MultiView control, the user has a much friendlier experience. Better yet, the developer will use AJAX to enhance the smoothness of the application, but that is beyond the scope of this article.

This article focuses on exposing the tips-n-tricks that must be understood by the developer to work with the form view control. The idea here is to guide the developer in a direction such that the pit falls and quirks of the control are avoided, and to provide a basic example of a reliable design pattern that can be leveraged in future projects.

Business Problem

Management has observed that the users are operating at half their expected productivity because they are editing and inserting records composed of over fifty columns in a grid view. The users have difficulty navigating the page to find the correct column, make the update, and then they must return back to the beginning of the row to search for another record. The solution is to click an edit button on the left hand column, have the focus to the first field in a FormView, edit the record, and submit the change. The addition of a new record will follow a similar process.

Finally, it is assumed that a search function is already present that users can utilize to navigate to the next record. So, that will be omitted in this project because it is not required. Only two columns will be displayed in the example to limit the amount of source code necessary to demonstrate the design pattern.

Illustrating the Use Case

The user opens an application to view the club listings, and clicks on the Edit button to modify a record.

Initial view

In response to the edit request, the system displays the FormView in Edit mode with the record populated. Note that the grid view record is not in Edit mode. Also, there is no need to navigate away from the page and deal with the complexities introduced by cross page posting patterns.

Initial view

The user submits the change, and the system closes the form. It then lists the records with the updates.

Initial view

Next, the user must add a record. The user clicks the Add-record link and the system responds by opening the form in Insert mode. The user fills out the club information and clicks the Submit link.

Initial view

When the record is submitted, the system responds by closing the form and adding the record to the list of clubs. Optionally, the system could keep the form open for heads down data entry. I chose to close the form to illustrate how to manage the control in the switch structure for the developer's reference. Additional details will follow below.

Initial view

Tips and Tricks

  • When designing the form, use templates. The "ItemTemplate", regardless of whether it is used in read only mode, will cause the VS2008 designer to show the format of the form. I suggest using the same format in the edit, update, and insert item templates.
  • Put a switch statement in the "ItemCommand" handler. I suggest generally ignoring all of the other form view event handlers because they tend to introduce quirks into the system design. It is better to use a simple control structure than to attempt intercepting the other events. I have found that using the form view control's other events only introduces complexity in the design and irritating quirks.
  • Always use a link or image button to put the form view control into the Add-record mode. This handles the case where no record will be displayed when there is an empty record. Otherwise, there will be challenges encountered when starting with an empty form view. An empty form view is not displayed, and the control is not instantiated when there is no record. That means the links will not be present.
  • The FormView control expects an object that is a collection or enumeration. Thus, using the following technique will not work:
  • C#
    FormView1.ChangeMode(FormViewMode.Edit);
    TextBox tb = (TextBox) FormView1.FindControl("ClubName");
    tb.Text = "Club Name";

    Instead, put the form into Edit mode and bind a collection like an array list or data table to the data source. Then call the DataBind method as follows:

    C#
    FormView1.ChangeMode(FormViewMode.Edit);
    FormView1.DataSource = al;
    FormView1.DataBind();

Key Design Concepts

Managing the FormView is done by handing the ItemCommand event. A switch structure is used to control the mode of the FormView control within the ItemCommand handler. The FormView control has three modes that include insert, edit, and read only. The FormView's ChangeMode() method is used to switch the mode of the control.

The class Club is used to contain records that are updated and new records that are inserted into the table.

Club
Instance Variables
ClubIDAn integer used to hold the unique club's identification.
ClubNameThe name of the club.
URLThe URL of the organization.

The class ClubList is a helper class that substitutes for a database table in this example. It is straightforward, and the key items are listed in the following table:

ClubList
Instance Variables
ClubList clThe class uses the singleton design pattern.
DataTable _dtA data table is used to contain the club records.
Methods
BuildClubList()Builds and populates a default club list for binding to the grid view when the application starts.
AddClub(Club c) Adds a club to the data table. The Club object is used to add a new record.
UpdateClubByID(Club c) Accepts the Club object and updates the record in the data table.

Create the ClubList class that builds a table for creating default data, adding, and updating club records. The records go into a data table. The class uses the singleton design pattern.

Default Form (ASP.NET)
Event Handlers
Page_LoadWhen the page loads for the first time, get the club list and bind it to the grid view.
FormView1_ItemCommandWhenever a form view ItemCommand event occurs, the switch structure uses e.CommandName to determine what event took place. The FormView is changed into the appropriate mode and the databind() function is called so that the update will take place.
AddNewClub_ClickClick on the Add New Club link to handle the event to switch the form into Insert mode.
gvClubList_RowEditingHandles the edit click event message from the grid view. Here, the club record is retrieved from the data table and displayed in the FormView control. The edit event for the grid view is cancelled to prevent the control from entering the edit record mode.
Methods
UpdateClub()Update a club record in the data table.
AddClub()Add a new club to the data table.

Coding the Application

Step 1. Create the Club class as follows:

C#
/// <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; }
  }
}

Step 2. Create the ClubList class that builds a table for creating default data, adding, and updating club records. The records go into a data table. The class uses the singleton design pattern.

C#
// <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();
  }
}

Step 3. Create your standard default page. Add the form and grid view controls. I like to organize the page using divisions, selectors from a cascading style sheet, and tables. The style sheet is included in the coding sample. The sample also includes the images used in this article.

ASP.NET
<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>

Step 4. Add the logic to the code-behind for the default page. As mentioned, the key handlers are the FormView control's ItemCommand and the GridView's RowUpdating command. Take special note of how the edit is cancelled for the GridView, and the FormView is set into Edit mode and populated with the record. The LinkButton control's Click event is used to put the form into Insert mode when the user chooses to add a new club to the list.

C#
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();

    }
}

Conclusion

The FormView control has proven to be one of the most powerful and useful controls for performing inserts, deletes, and updates related to managing records. It is a challenge to find good information that explains how to avoid the quirks present in this control. Once understood, it is a simple control to implement in any application. I use it often. Clearly, the user experience and efficiency is enhanced when the developer is able to effectively implement the control. I encourage developers to learn and leverage the pattern illustrated in this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)