So far, the examples have used the GridView
control to show data using separate bound columns for each field. If you
want to place multiple values in the same cell, or you want the
unlimited ability to customize the content in a cell by adding HTML tags
and server controls, you need to use a TemplateField.
The TemplateField allows you to define a completely customized template
for a column. Inside the template you can add control tags, arbitrary
HTML elements, and data binding expressions. You have complete freedom
to arrange everything the way you want.
For example, imagine you want to create a column that
combines the in stock, on order, and reorder level information for a
product. To accomplish this trick, you can construct an ItemTemplate
like this:
<asp:TemplateField HeaderText="Status">
<ItemTemplate>
<b>In Stock:</b>
<%# Eval("UnitsInStock") %><br />
<b>On Order:</b>
<%# Eval("UnitsOnOrder") %><br />
<b>Reorder:</b>
<%# Eval("ReorderLevel") %>
</ItemTemplate>
</asp:TemplateField>
NOTE
Your template only has access to the fields that
are in the bound data object. So if you want to show the UnitsInStock,
UnitsOnOrder, and ReorderLevel fields, you need to make sure the
SqlDataSource query returns this information.
To create the data binding expressions, the template
uses the Eval() method, which is a shared method of the
System.Web.UI.DataBinder class. Eval() is an indispensable
convenience—it automatically retrieves the data item that's bound to the
current row, uses reflection to find the matching field, and retrieves
the value.
The Eval() method also adds the extremely useful
ability to format data fields on the fly. To use this feature, you must
call the overloaded version of the Eval() method that accepts an
additional format string parameter. Here's an example:
<%# Eval("BirthDate", "{0:MM/dd/yy}") %>
|
|
You'll notice that this example template includes
three data binding expressions. These expressions get the actual
information from the current row. The rest of the content in the
template defines static text, tags, and controls.
You also need to make sure the data source provides
these three pieces of information. If you attempt to bind a field that
isn't present in your result set, you'll receive a runtime error. If you
retrieve additional fields that are never bound to any template, no
problem will occur.
Here's the revised data source with these fields:
<asp:SqlDataSource ID="sourceProducts" runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT ProductID, ProductName, UnitPrice, UnitsInStock,
UnitsOnOrder,ReorderLevel FROM Products"
UpdateCommand="UPDATE Products SET ProductName=@ProductName,
UnitPrice=@UnitPrice WHERE ProductID=@ProductID">
</asp:SqlDataSource>
When you bind the GridView, it fetches the data from
the data source and walks through the collection of items. It processes
the ItemTemplate for each item, evaluates the data binding expressions,
and adds the rendered HTML to the table. You're free to mix template
columns with other column types. Figure 1 shows an example with several normal columns and the template column at the end.
1. Using Multiple Templates
The previous example uses a single template to
configure the appearance of data items. However, the ItemTemplate isn't
the only template that the TemplateField provides. In fact, the
TemplateField allows you to configure various aspects of its appearance
with a number of templates. Inside every template column, you can use
the templates listed in Table 1.
Table 1. TemplateField Templates
Mode | Description |
---|
HeaderTemplate | Determines the appearance and content of the header cell. |
FooterTemplate | Determines the appearance and content of the footer cell (if you set ShowFooter to True). |
ItemTemplate | Determines the appearance and content of each data cell. |
AlternatingItemTemplate | Determines
the appearance and content of even-numbered rows. For example, if you
set the AlternatingItemTemplate to have a shaded background color, the
GridView applies this shading to every second row. |
EditItemTemplate | Determines the appearance and controls used in edit mode. |
InsertItemTemplate | Determines
the appearance and controls used in edit mode. The GridView doesn't
support this template, but the DetailsView and FormView controls do. |
Of the templates listed in Table 2,
the EditItemTemplate is one of the most useful, because it gives you
the ability to control the editing experience for the field. If you
don't use template fields, you're limited to ordinary text boxes, and
you won't have any validation. The GridView also defines two templates
you can use outside any column. These are the PagerTemplate, which lets
you customize the appearance of pager controls, and the
EmptyDataTemplate, which lets you set the content that should appear if
the GridView is bound to an empty data object.
2. Editing Templates in Visual Studio
Visual Studio includes solid support for editing templates in the web page designer. To try this, follow these steps:
Create a GridView with at least one template column.
Select the GridView, and click Edit Templates in the smart tag. This switches the GridView into template edit mode.
In the smart tag, use the Display drop-down list to choose the template you want to edit (see Figure 2).
You can choose either of the two templates that apply to the whole
GridView (EmptyDataTemplate or PagerTemplate), or you can choose a
specific template for one of the template columns.
Enter your content in the control. You can enter static content, drag and drop controls, and so on.
When you're finished, choose End Template Editing from the smart tag.
3. Handling Events in a Template
In some cases, you might need to handle events that
are raised by the controls you add to a template column. For example,
imagine you want to add a clickable image link by adding an ImageButton
control. This is easy enough to accomplish:
<asp:TemplateField HeaderText="Status">
<ItemTemplate>
<asp:ImageButton ID="ImageButton1" runat="server"
ImageUrl="statuspic.gif" />
</ItemTemplate>
</asp:TemplateField>
The problem is that if you add a control to a
template, the GridView creates multiple copies of that control, one for
each data item. When the ImageButton is clicked, you need a way to
determine which image was clicked and to which row it belongs.
The way to resolve this problem is to use an event from the GridView, not
the contained button. The GridView.RowCommand event serves this
purpose, because it fires whenever any button is clicked in any
template. This process, where a control event in a template is turned
into an event in the containing control, is called event bubbling.
Of course, you still need a way to pass information
to the RowCommand event to identify the row where the action took place.
The secret lies in two string properties that all button controls
provide: CommandName and CommandArgument. CommandName sets a descriptive
name you can use to distinguish clicks on your ImageButton from clicks
on other button controls in the GridView. The CommandArgument supplies a
piece of row-specific data you can use to identify the row that was
clicked. You can supply this information using a data binding
expression.
Here's a template field that contains the revised ImageButton tag:
<asp:TemplateField HeaderText="Status">
<ItemTemplate>
<asp:ImageButton ID="ImageButton1" runat="server"
ImageUrl="statuspic.gif"
CommandName="StatusClick" CommandArgument='<%# Eval("ProductID") %>' />
</ItemTemplate>
</asp:TemplateField>
And here's the code you need in order to respond when an ImageButton is clicked:
Protected Sub GridView1_RowCommand(ByVal sender As Object, _
ByVal e As GridViewCommandEventArgs) Handles GridView1.RowCommand
If e.CommandName = "StatusClick" Then
lblInfo.Text = "You clicked product #" & e.CommandArgument.ToString()
End If
End Sub
This example displays a simple message with the ProductID in a label.
4. Editing with a Template
One of the best reasons to use a template is to
provide a better editing experienceYou saw
how the GridView provides automatic editing capabilities—all you need to
do is switch a row into edit mode by setting the GridView.EditIndex
property. The easiest way to make this possible is to add a CommandField
column with the ShowEditButton set to True. Then, the user simply needs
to click a link in the appropriate row to begin editing it. At this
point, every label in every column is replaced by a text box (unless the
field is read-only).
The standard editing support has several limitations:
It's not always appropriate to edit values using a text box:
Certain types of data are best handled with other
controls (such as drop-down lists). Large fields need multiline text
boxes, and so on.
You get no validation:
It would be nice to restrict the editing
possibilities so that currency figures can't be entered as negative
numbers, for example. You can do that by adding validator controls to an
EditItemTemplate.
The visual appearance is often ugly:
A row of text boxes across a grid takes up too much space and rarely seems professional.
In a template column, you don't have these issues.
Instead, you explicitly define the edit controls and their layout using
the EditItemTemplate. This can be a somewhat laborious process.
Here's the template column used earlier for stock information with an editing template:
<asp:TemplateField HeaderText="Status">
<ItemStyle Width="100px" />
<ItemTemplate>
<b>In Stock:</b> <%# Eval("UnitsInStock") %><br />
<b>On Order:</b> <%# Eval("UnitsOnOrder") %><br />
<b>Reorder:</b> <%# Eval("ReorderLevel") %>
</ItemTemplate>
<EditItemTemplate>
<b>In Stock:</b> <%# Eval("UnitsInStock") %><br />
<b>On Order:</b> <%# Eval("UnitsOnOrder") %><br /><br />
<b>Reorder:</b>
<asp:TextBox Text='<%# Bind("ReorderLevel") %>' Width="25px"
runat="server" id="txtReorder" />
</EditItemTemplate>
</asp:TemplateField>
Figure 3 shows the row in edit mode.
When binding an editable value to a control, you must
use the Bind() method in your data binding expression instead of the
ordinary Eval() method. Unlike the Eval() method, which can be placed
anywhere in a page, the Bind() method must be used to set a control
property. Only the Bind() method creates the two-way link, ensuring that
updated values will be returned to the server.
One interesting detail here is that even though the
item template shows three fields, the editing template allows only one
of these to be changed. When the GridView commits an update, it will
submit only the bound, editable parameters. In the previous example,
this means the GridView will pass back a @ReorderLevel parameter but not
a @UnitsInStock or @UnitsOnOrder parameter. This is important, because
when you write your parameterized update command, it must use only the
parameters you have available. Here's the modified SqlDataSource control
with the correct command:
<asp:SqlDataSource ID="sourceProducts" runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT ProductID, ProductName, UnitPrice, UnitsInStock,
UnitsOnOrder,ReorderLevel FROM Products"
UpdateCommand="UPDATE Products SET ProductName=@ProductName, UnitPrice=@UnitPrice,
ReorderLevel=@ReorderLevel WHERE ProductID=@ProductID">
</asp:SqlDataSource>
4.1. Editing with Validation
Now that you have your template ready, why not add an
extra frill, such as a validator, to catch editing mistakes? In the
following example, a RangeValidator prevents changes that put the
ReorderLevel at less than 0 or more than 100:
<asp:TemplateField HeaderText="Status">
<ItemStyle Width="100px" />
<ItemTemplate>
<b>In Stock:</b> <%# Eval("UnitsInStock") %><br />
<b>On Order:</b> <%# Eval("UnitsOnOrder") %><br />
<b>Reorder:</b> <%# Eval("ReorderLevel") %>
</ItemTemplate>
<EditItemTemplate>
<b>In Stock:</b> <%# Eval("UnitsInStock") %><br />
<b>On Order:</b> <%# Eval("UnitsOnOrder") %><br /><br />
<b>Reorder:</b>
<asp:TextBox Text='<%# Bind("ReorderLevel") %>' Width="25px"
runat="server" id="txtReorder" />
<asp:RangeValidator id="rngValidator" MinimumValue="0" MaximumValue="100"
ControlToValidate="txtReorder" runat="server"
ErrorMessage="Value out of range." Type="Integer"/>
</EditItemTemplate>
</asp:TemplateField>
Figure 4
shows the validation at work. If the value isn't valid, the browser
doesn't allow the page to be posted back, and no database code runs.
NOTE
The SqlDataSource is intelligent enough to handle
validation properly even if you disabled client-side validation (or the
browser doesn't support it). In this situation, the page is posted
back, but the SqlDataSource notices that it contains invalid data (by
inspecting the Page.IsValid property), and doesn't attempt to perform
its update.
4.2. Editing Without a Command Column
So far, all the examples you've seen have used a
CommandField that automatically generates edit controls. However, now
that you've made the transition over to a template-based approach, it's
worth considering how you can add your own edit controls.
It's actually quite easy. All you need to do is add a
button control to the item template and set the CommandName to Edit.
This automatically triggers the editing process, which fires the
appropriate events and switches the row into edit mode.
<ItemTemplate>
<b>In Stock:</b> <%# Eval("UnitsInStock") %><br />
<b>On Order:</b> <%# Eval("UnitsOnOrder") %><br />
<b>Reorder:</b> <%# Eval("ReorderLevel") %>
<br /><br />
<asp:LinkButton runat="server" Text="Edit"
CommandName="Edit" ID="LinkButton1" />
</ItemTemplate>
In the edit item template, you need two more buttons with CommandName values of Update and Cancel:
<EditItemTemplate>
<b>In Stock:</b> <%# Eval("UnitsInStock") %><br />
<b>On Order:</b> <%# Eval("UnitsOnOrder") %><br /><br />
<b>Reorder:</b>
<asp:TextBox Text='<%# Bind("ReorderLevel") %>' Width="25px"
runat="server" id="txtReorder" />
<br /><br />
<asp:LinkButton runat="server" Text="Update"
CommandName="Update" ID="LinkButton1" />
<asp:LinkButton runat="server" Text="Cancel"
CommandName="Cancel" ID="LinkButton2" CausesValidation="False" />
</EditItemTemplate>
Notice that the Cancel button must have its
CausesValidation property set to False to bypass validation. That way,
you can cancel the edit even if the current data isn't valid.
As long as you use these names, the GridView editing
events will fire and the data source controls will react in the same way
as if you were using the automatically generated editing controls. Figure 5 shows the custom edit buttons.