Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
368 C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S
Figure 10-14. Editing with a template
Editing with Advanced Controls
Template based-editing really shines if you need to bind to more interesting controls, such as lists. For example, you could change the previous example to make the TitleOfCourtesy field editable through a drop-down list. Here’s the template you need, with the new details in bold:
<EditItemTemplate>
<b>
<%# Eval("EmployeeID") %> -
<asp:DropDownList runat="server" ID="EditTitle" SelectedIndex='<%# GetSelectedTitle(Eval("TitleOfCourtesy")) %>' DataSource='<%# TitlesOfCourtesy %>' />
<%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %> <%# Eval("LastName") %>
</b> <hr />
<small><i>
<%# Eval("Address") %><br />
<%# Eval("City") %>, <%# Eval("Country") %>, <%# Eval("PostalCode") %><br />
<%# Eval("HomePhone") %></i> <br /><br />
<asp:TextBox Text='<%# Bind("Notes") %>' runat="server" id="textBox" TextMode="MultiLine" Width="413px" />
</small>
</EditItemTemplate>
This template allows the user to pick a title of courtesy from a limited selection of possible titles. To create this list, you need to resort to a little trick—setting the DropDownList.DataSource
370 C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S
Unfortunately, this still doesn’t complete the example. Now you have a list box that is populated in edit mode, with the correct item automatically selected. However, if you change the selection, the value isn’t sent back to the data source. In this example, you could tackle the problem by using the Bind() method with the SelectedValue property, because the text in the control exactly corresponds to the text you want to commit to the record. However, sometimes life isn’t as easy, because you need to translate the value into a different database representation. In this situation, the only option is to handle the RowUpdating event, find the list control in the current row, and extract
the text. You can then dynamically add the extra parameter, as shown here:
protected void gridEmployees_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
//Get the reference to the list control. DropDownList title = (DropDownList)
(gridEmployees.Rows[e.RowIndex].FindControl("EditTitle"));
//Add it to the parameters. e.NewValues.Add("TitleOfCourtesy", title.Text);
}
This will now successfully update both the Notes field and the TitleOfCourtesy. As you can see, editable templates give you a great deal of power, but they often aren’t quick to code.
■Tip To make an even more interesting EditItemTemplate, you could add validator controls to verify input values, as discussed in Chapter 4.
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>
<%# Eval("EmployeeID") %> - <%# Eval("TitleOfCourtesy") %> <%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %> <%# Eval("LastName") %>
</b> <hr />
<small><i>
<%# Eval("Address") %><br />
<%# Eval("City") %>, <%# Eval("Country") %>, <%# Eval("PostalCode") %><br />
<%# Eval("HomePhone") %></i> <br /><br />
<%# Eval("Notes") %> <br /><br />
<asp:LinkButton runat="server" Text="Edit" CommandName="Edit" ID="Linkbutton1" />
</small>
</ItemTemplate>
372 C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S
The DetailsView and FormView
The GridView excels at showing a dense table with multiple rows of information. However, sometimes you want to provide a detailed look at a single record. Although you could work out a solution using a template column in a GridView, ASP.NET also includes two controls that are tailored for this purpose: the DetailsView and FormView. Both show a single record at a time but can include optional pager buttons that let you step through a series of records (showing one per page). The difference between the DetailsView and the FormView is their support for templates. The DetailsView is built out of field objects, in the same way that the GridView is built out of column objects. On
the other hand, the FormView is based on templates that work in the same way as a GridView templated column, which requires a little more work but gives you much more flexibility.
Now that you understand the features of the GridView, you can get up to speed with the DetailsView and FormView quite quickly. That’s because both the DetailsView and the FormView borrow a portion of the GridView model.
The DetailsView
The DetailsView is designed to display a single record at a time. It places each piece of information (be it a field or a property) in a separate row of a table.
You saw how to create a basic DetailsView to show the currently selected record in Chapter 9. The DetailsView can also bind to a collection of items. In this case, it shows the first item in the group. It also allows you to move from one record to the next using paging controls, if you’ve set the AllowPaging property to true. You can configure the paging controls using the PagingStyle and PagingSettings properties in the same way as you tweak the pager for the GridView. The only difference is that there’s no support for custom paging, which means the full data source object is always retrieved.
Figure 10-17 shows the DetailsView when it’s bound to a set of employee records, with full employee information.
It’s tempting to use the DetailsView pager controls to make a handy record browser. Unfortunately, this approach can be quite inefficient. First, a separate postback is required each time the user moves from one record to another (whereas a grid control can show multiple records at once). But the real drawback is that each time the page is posted back, the full set of records is retrieved, even though only a single record is shown. If you choose to implement a record browser page with the DetailsView, at a bare minimum you must enable caching to reduce the database work (see Chapter 11).
Often, a better choice is to create your own pager controls using a subset of the full data. For example, you could create a drop-down list and bind this to a data source that queries just the employee names. Then, when a name is selected from the list, you could retrieve the full details for just that record using another data source. Of course, several metrics can determine which approach is best, including the size of the full record (how much bigger it is than just the first and last name), the usage patterns (whether the average user browses to just one or two records or needs to see them all), and how many records there are in total. (You can afford to retrieve them all at once if there are dozens of records, but you need to think twice if there are thousands.)
Defining Fields
The DetailsView uses reflection to generate the fields it shows. That means it examines the data object and creates a separate field for each field (in a row) or property (in a custom object), just like the GridView. You can disable this automatic field generation by setting AutoGenerateRows to false. It’s then up to you to declare the field objects.
C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S |
373 |
Figure 10-17. The DetailsView with paging
Interestingly, you use the same field object to build a DetailsView as you used to design a GridView. For example, fields from the data item are represented with the BoundField tag, buttons can be created with the ButtonField, and so on. For the full list, refer to Table 10-1. The only GridView column type that the DetailsView doesn’t support is the TemplateField.
Following is a portion of the field declarations for a DetailsView:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"> <Fields>
<asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" /> <asp:BoundField DataField="FirstName" HeaderText="FirstName" /> <asp:BoundField DataField="LastName" HeaderText="LastName" /> <asp:BoundField DataField="Title" HeaderText="Title" />
<asp:BoundField DataField="TitleOfCourtesy" HeaderText="TitleOfCourtesy" /> <asp:BoundField DataField="BirthDate" HeaderText="BirthDate" />
...
</Fields>
...
</asp:DetailsView>
You can use the BoundField tag to set properties such as header text, formatting string, editing behavior, and so on (see Table 10-2). In addition, you can use a certain property with a BoundField that has no effect in a GridView. When it’s false, the header text is left out of the row, and the field data takes up both cells.
374 C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S
The field model isn’t the only part of the GridView that the DetailsView control adopts. It also uses a similar set of styles, a similar set of events, and a similar editing model. The only difference is that instead of creating a dedicated column for editing controls, you simply set Boolean properties such as AutoGenerateDeleteButton, AutoGenerateEditButton, and AutoGenerateInsertButton. The links for these tasks are added to the bottom of the DetailsView. When you add or edit a record, the DetailsView always uses standard text box controls such as the GridView (see Figure 10-18). For more editing flexibility, you’ll want to use the FormView control.
Figure 10-18. Editing in the DetailsView
The FormView
The DetailsView supports every type of GridView column except for templated columns. If you need the ultimate flexibility of templates, the FormView provides a template-only control for displaying and editing a single record.
The beauty of the FormView template model is that it matches the model of the TemplateField in the GridView quite closely. This means you have the following templates to work with:
•ItemTemplate
•EditItemTemplate
•InsertItemTemplate
•FooterTemplate
•HeaderTemplate
•EmptyDataTemplate
•PagerTemplate
This means you can take the exact template content you put in a TemplateField in a GridView and place it inside the FormView. Here’s an example based on the earlier templated GridView:
<asp:FormView ID="FormView1" runat="server" DataSourceID="sourceEmployees"> <ItemTemplate>
<b>
<%# Eval("EmployeeID") %> -
<%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %> <%# Eval("LastName") %>
C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S |
375 |
</b> <hr />
<small><i>
<%# Eval("Address") %><br />
<%# Eval("City") %>, <%# Eval("Country") %>, <%# Eval("PostalCode") %><br />
<%# Eval("HomePhone") %></i> <br /><br />
<%# Eval("Notes") %> <br /><br />
</small>
</ItemTemplate>
</asp:FormView>
Figure 10-19 shows the result.
Figure 10-19. A GridView with a footer summary
If you want to support editing, you need to add button controls that trigger the Edit and Update process, as described in the section “Editing with a Template” in the GridView section.
Advanced Grids
In the following sections, you’ll consider a few ways to extend the GridView. You’ll learn how to show summaries, create a complete master-details report on a single page, and display image data that’s drawn from a database. You’ll also see an example that uses advanced concurrency handling to warn the user about potential conflicts when updating a record.
Summaries in the GridView
Although the prime purpose of a GridView is to show a set of records, you can also add some more interesting information, such as summary data. The first step is to add the footer row by setting the GridView.ShowFooter property to true. This displays a shaded footer row (which you can customize freely), but it doesn’t show any data. To take care of that task, you need insert the content into the GridView.FooterRow.
376 C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S
For example, imagine you’re dealing with a list of products. A simple summary row could display the total or average product price. In the next example, the summary row displays the total value of all the in-stock products.
The first step is to decide when to calculate this information. If you’re using manual binding, you could retrieve the data object and use it to perform your calculations before binding it to the GridView. However, if you’re using declarative binding, you need another technique. You have two basic options—you can retrieve the data from the data object before the grid is bound, or you can retrieve it from the grid itself after the grid has been bound. The following example uses the latter approach because it gives you the freedom to use the same calculation code no matter what data source was used to populate the control. It also gives you the ability to total just the records that are displayed on the current page, if you’ve enabled paging. The disadvantage is that your code is tightly bound to the GridView, because you need to pull out the information you want by position, using hard-coded column index numbers.
The basic strategy is to react to the GridView.DataBound event. This occurs immediately after the GridView is populated with data. At this point, you can’t access the data source any longer, but you can navigate through the GridView as a collection of rows and cells. Once this total is calculated, it’s inserted into the footer row.
Here’s the complete code:
protected void GridView1_DataBound(object sender, EventArgs e)
{
decimal valueInStock = 0;
//The Rows collection includes rows only on the current page
//(not "virtual" rows).
foreach (GridViewRow row in GridView1.Rows)
{
decimal price = Decimal.Parse(row.Cells[2].Text); int unitsInStock = Int32.Parse(row.Cells[3].Text); valueInStock += price * unitsInStock;
}
// Update the footer.
GridViewRow footer = GridView1.FooterRow;
//Set the first cell to span over the entire row. footer.Cells[0].ColumnSpan = 3; footer.Cells[0].HorizontalAlign = HorizontalAlign.Center;
//Remove the unneeded cells.
footer.Cells.RemoveAt(2);
footer.Cells.RemoveAt(1);
// Add the text.
footer.Cells[0].Text = "Total value in stock (on this page): " + valueInStock.ToString("C");
}
The summary row has the same number of columns as the rest of the grid. As a result, if you want your text to be displayed over multiple cells (as it is in this example), you need to configure cell spanning by setting the ColumnSpan property of the appropriate cell. In this example, the first cell spans over three columns (itself, and the next two on the right). Figure 10-20 shows the final result.
C H A P T E R 1 0 ■ R I C H D ATA C O N T R O L S |
377 |
Figure 10-20. A GridView with a footer summary
A Parent/Child View in a Single Table
Earlier in this chapter, you saw a master/detail page that used a GridView and DetailsView. This gives you the flexibility to show the child records for just the currently selected parent record. However, sometimes you want to create a parent/child report that shows all the records from the child table, organized by parent. For example, you could use this to create a complete list of products organized by category. The next example demonstrates how you show a complete, subgrouped product list in a single grid.
The basic technique is to create a GridView for the parent table that contains an embedded GridView for each row. These child GridView controls are inserted into the parent GridView using a TemplateField. The only trick is that you can’t bind the child GridView controls at the same time that you bind the parent GridView, because the parent rows haven’t been created yet. Instead, you need to wait for the GridView.DataBound event to fire in the parent.
In this example, the parent GridView defines two columns, both of which are the TemplateField type. The first column combines the category name and category description:
<asp:TemplateField HeaderText="Category">
<ItemStyle VerticalAlign="Top" Width="20%"></ItemStyle> <ItemTemplate>
<br />
<b><%# Eval("CategoryName") %></b> <br /><br />
<%# Eval("Description" ) %> <br />
</ItemTemplate>
</asp:TemplateField>
