The Scenario
In this example I have written a simple WebForm to manage a list of contacts which I have stored in XML. The requirements are simply the ability to add new contacts and to be able to modify or delete existing contacts. The user should be able to modify or delete multiple contacts at one time. I have also allowed the user to sort their grid by the column of their choosing.
My example is written in C#. If you prefer the VB version of this code, both version are available in the download.
Contacts.xml
The XML data file for this example is fairly simple and straight forward. Due to the simplicity, I have not created a schema.
<?xml version="1.0" standalone="yes"?>
<Contacts>
<Contact>
<Email>myaddress@...</Email>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</Contact>
<Contact>
<Email>youraddress@...</Email>
<FirstName>Jane</FirstName>
<LastName>Doe</LastName>
</Contact>
</Contacts>
ContactList.aspx
Setting up the WebForm is simple as well. I placed a new DataGrid onto my form and gave it four columns, one for each field plus one to contain the CheckBox for deleting a contact. You will notice that the main thing I did here is created each column as a TemplateColumn. This allowed me to put the TextBox and CheckBox objects into the ItemTemplate. This is the trick to displaying something other than text in each row of the grid. In addition you'll notice that I've used the FooterTemplate to make the addition of new contacts easy and (hopefully!) intuitive.
I've also included a single LinkButton which is used to save user modifications and deletions. It is not, however, used to add new contacts. That is done by the link button in the FooterTemplate of the last column.
<asp:datagrid id="dgContacts" runat="server" ShowFooter="True" AllowSorting="True" Forefont color="Black" GridLines="None" CellPadding="2" Backfont color="LightGoldenrodYellow" BorderWidth="1px" Borderfont color="Tan" Width="499px" AutoGenerateColumns="False" DataKeyField="Email">
<SelectedItemStyle Forefont color="GhostWhite" Backfont color="DarkSlateBlue"></SelectedItemStyle>
<AlternatingItemStyle Backfont color="PaleGoldenrod"></AlternatingItemStyle>
<HeaderStyle Font-Bold="True" Backfont color="Tan"></HeaderStyle>
<FooterStyle Backfont color="Tan"></FooterStyle>
<Columns>
<asp:TemplateColumn SortExpression="FirstName" HeaderText="First Name">
<ItemTemplate>
<asp:TextBox id=First runat="server" Width="109px" Text='<%# DataBinder.Eval(Container, "DataItem.FirstName") %>'>
</asp:TextBox>
</ItemTemplate>
<FooterTemplate>
<asp:TextBox id="NewFirst" runat="server" Width="109px"></asp:TextBox>
</FooterTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn SortExpression="LastName" HeaderText="Last Name">
<ItemTemplate>
<asp:TextBox id=Last runat="server" Width="109px" Text='<%# DataBinder.Eval(Container, "DataItem.LastName") %>'>
</asp:TextBox>
</ItemTemplate>
<FooterTemplate>
<asp:TextBox id="NewLast" runat="server" Width="109px"></asp:TextBox>
</FooterTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn SortExpression="Email" HeaderText="Email">
<ItemTemplate>
<asp:TextBox id=Email runat="server" Text='<%# DataBinder.Eval(Container, "DataItem.Email") %>'>
</asp:TextBox>
</ItemTemplate>
<FooterTemplate>
<asp:TextBox id="NewEmail" runat="server"></asp:TextBox>
</FooterTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="Delete Contact">
<ItemStyle HorizontalAlign="Center"></ItemStyle>
<ItemTemplate>
<asp:CheckBox Runat="server" ID="chkDelete"></asp:CheckBox>
</ItemTemplate>
<FooterStyle HorizontalAlign="Center"></FooterStyle>
<FooterTemplate>
<asp:LinkButton Runat="server" Text="Add" CommandName="Add" ID="Linkbutton1" NAME="Linkbutton1"></asp:LinkButton>
</FooterTemplate>
</asp:TemplateColumn>
</Columns>
</asp:datagrid>

ContactList.cs
Since I elected to store my data in an XML file, I have decided to use a DataSet for accessing it. Since the DataSet object has the ReadXml and WriteXml methods it was a fairly logical choice. My first step was to read in the XML. As you can see from the code, I also added a the foundation to handle sorting.
private DataSet _dsContacts;
private string _sSort;
private void Page_Load(object sender, System.EventArgs e)
{
// Load the XML file.
_dsContacts = new DataSet();
_dsContacts.ReadXml(Server.MapPath("Contacts.xml"));
DataColumn[] dcPk = {_dsContacts.Tables["Contact"].Columns["Email"]};
_dsContacts.Tables["Contact"].PrimaryKey = dcPk;
if (!Page.IsPostBack )
{
// Only bind at this point if this is the first page request.
BindContacts();
_sSort = "FirstName";
}
else
{
// Read the sort order from the view state.
_sSort = (string)ViewState["Sort"];
}
}
Next I created the method used to bind the data to the grid, which includes the logic for sorting the data and a method to persist the XML back to the disk.
private void BindContacts()
{
// Save the sort order to the view state.
ViewState["Sort"] = _sSort;
// Bind the grid to the sorted data view.
DataView dv = new DataView(_dsContacts.Tables["Contact"]);
dv.Sort = _sSort;
dgContacts.DataSource = dv;
dgContacts.DataBind();
}
private void SaveContacts()
{
_dsContacts.WriteXml(Server.MapPath("Contacts.xml"));
}
The ItemCommand event is where new contacts are added to the list. Note that I checked the CommandName argument for a value of Add. This is the value that was set back in the ASPX page for the LinkButton in the FooterTemplate of the last column in the grid.
private void dgContacts_ItemCommand(object source , System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
// Add the new item to the dataset. I use an array here for efficiency.
if (e.CommandName == "Add")
{
string[] sContact = {"", "", ""};
sContact[0] = ((TextBox)e.Item.FindControl("NewEmail")).Text;
sContact[1] = ((TextBox)e.Item.FindControl("NewFirst")).Text;
sContact[2] = ((TextBox)e.Item.FindControl("NewLast")).Text;
_dsContacts.Tables["Contact"].Rows.Add(sContact);
SaveContacts();
}
BindContacts();
}
I will skip the SortCommand code since there are a lot of other articles out there that deal with sorting in much more detail. If you download the code for this example it is included.
Finally I moved on to the OnClick event of the LinkButton on the form. This is where I looped through the items in the DataGrid to perform any necessary deletes and updates.
private void btnUpdate_Click(object sender, System.EventArgs e)
{
// Loop through the items in the datagrid.
foreach (DataGridItem di in dgContacts.Items)
{
// Make sure this is an item and not the header or footer.
if (di.ItemType == ListItemType.Item || di.ItemType == ListItemType.AlternatingItem)
{
// Get the current row for update or delete operations later.
DataRow dr = _dsContacts.Tables["Contact"].Rows.Find(dgContacts.DataKeys[di.ItemIndex]);
// See if this one needs to be deleted.
if (((CheckBox)di.FindControl("chkDelete")).Checked)
{
_dsContacts.Tables["Contact"].Rows.Remove(dr);
}
else
{
// Update the row instead.
dr["Email"] = ((TextBox)di.FindControl("Email")).Text;
dr["FirstName"] = ((TextBox)di.FindControl("First")).Text;
dr["LastName"] = ((TextBox)di.FindControl("Last")).Text;
}
}
}
// Save the changes if there are any.
if (_dsContacts.HasChanges())
{
SaveContacts();
}
BindContacts();
}
Conclusion
I could have just as easily referenced the controls by their positions within the the Cells(x) of each DataGridItem. The point is, there's more than one way to skin a cat and I'm sure that you can find some better ways to accomplish this task. As you can see it's quite easy to edit entire data grids at one time. This same approach will also work with paged grids with a little modification.