Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
104
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

358C H A P T E R 1 0 R I C H D ATA C O N T R O L S

public int CountEmployees()

{

//Create the Command and the Connection. string sql = "SELECT COUNT(*) FROM Employees";

SqlConnection con = new SqlConnection(connectionString); SqlCommand cmd = new SqlCommand(sql, con);

con.Open();

//Execute the command and use the return value for the

//VirtualItemCount property. Datagrid1.VirtualItemCount = (int)cmd.ExecuteScalar(); con.Close();

}

This example uses the COUNT() aggregate function to calculate the number of records in the table and returns that information using the ExecuteScalar() method of the Command object. This method is bound to the ObjectDataSource using the SelectCountMethod property:

<asp:ObjectDataSource ID="sourceEmployees" runat="server" EnablePaging="True" SelectCountMethod="CountEmployees" ... />

When you use custom paging, the SelectCountMethod is executed for every postback. If you want to reduce database work at the risk of providing an incorrect count, you could cache this information and reuse it.

A Stored Procedure to Get Paged Records

The next part of the solution is a little trickier. Instead of retrieving a collection with all the employee records, the GetEmployees() method must retrieve records for the current page only. To accomplish this feat, this example uses a stored procedure named GetEmployeePage. This stored procedure copies all the employee records into a temporary table that has one additional column— a unique autoincrementing ID that will number each row. Next, the stored procedure retrieves a selection from that table that corresponds to the requested page of data, using the supplied @Start and @Count parameters.

Here’s the complete stored procedure code:

CREATE PROCEDURE GetEmployeePage @Start int, @Count int

AS

-- create a temporary table with the columns we are interested in CREATE TABLE #TempEmployees

(

ID

int IDENTITY PRIMARY KEY,

EmployeeID

int,

LastName

nvarchar(20),

FirstName

nvarchar(10),

TitleOfCourtesy

nvarchar(25),

)

-- fill the temp table with all the employees INSERT INTO #TempEmployees

(

EmployeeID, LastName, FirstName, TitleOfCourtesy

)

SELECT

EmployeeID, LastName, FirstName, TitleOfCourtesy

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

359

FROM

Employees ORDER BY EmployeeID ASC

--declare two variables to calculate the range of records

--to extract for the specified page

DECLARE @FromID int

DECLARE @ToID int

--calculate the first and last ID of the range of records we need SET @FromID = @Start

SET @ToID = @Start + @Count - 1

--select the page of records

SELECT * FROM #TempEmployees WHERE ID >= @FromID AND ID <= @ToID

GO

Note This stored procedure uses a SQL Server–specific approach. Other databases might have other possible optimizations. For example, Oracle databases allow you to use the ROWNUM operator in the WHERE clause of a query to return a range of rows. For example, the query SELECT * FROM Employees WHERE ROWNUM > 100 AND ROWNUM < 200 retrieves the page of rows from 101 to 199.

The Paged Selection Method

The final step is to create an overload of the GetEmployees() method that performs paging. This method receives two arguments—the index of the row that starts the page (starting at 0) and the page size (maximum number of rows). You specify the parameter names you want to use for these two details through the StartRowIndexParameterName and MaximumRowsParameterName properties. If not set, the defaults are startRowIndex and maximumRows.

Here’s the GetEmployees() method you need to use the stored procedure shown in the previous example:

public EmployeeDetails[] GetEmployees(int startRowIndex, int maximumRows)

{

SqlConnection con = new SqlConnection(connectionString); SqlCommand cmd = new SqlCommand("GetEmployeePage", con); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add(new SqlParameter("@Start", SqlDbType.Int, 4)); cmd.Parameters["@Start"].Value = startRowIndex + 1; cmd.Parameters.Add(new SqlParameter("@Count", SqlDbType.Int, 4)); cmd.Parameters["@Count"].Value = maximumRows;

// Create a collection for all the employee records. ArrayList employees = new ArrayList();

try

{

con.Open();

SqlDataReader reader = cmd.ExecuteReader();

while (reader.Read())

{

EmployeeDetails emp = new EmployeeDetails( (int)reader["EmployeeID"], (string)reader["FirstName"], (string)reader["LastName"], (string)reader["TitleOfCourtesy"]);

360 C H A P T E R 1 0 R I C H D ATA C O N T R O L S

employees.Add(emp);

}

reader.Close();

return (EmployeeDetails[])employees.ToArray(typeof(EmployeeDetails));

}

catch (SqlException err)

{

throw new ApplicationException("Data error.");

}

finally

{

con.Close();

}

}

When you run this page, you’ll see that the output is the same as the output generated by the previous page using automatic pagination, and the pager controls work the same way.

Customizing the Pager Bar

The GridView paging controls are remarkably flexible. In their default representation, you’ll see a series of numbered links (see Figure 10-10). However, you customize them thoroughly using the PagerStyle property (for foreground and background colors, the font, color, size, and so on) and the PagerSettings property. The most important detail in the PagerSettings.Mode property, which specifies how to render the paging links according to one of several styles, as described in Table 10-7.

Table 10-7. Pager Modes

Mode

Description

Numeric

The grid will render as many links to other pages as specified by the

 

PageButtonCount property. If that number of links is not enough to

 

link to every page of the grid, the pager will display ellipsis links (…)

 

that, when clicked, display the previous or next set of page links.

NextPrevious

The grid will render only two links for jumping to the previous and

 

next pages. If you choose this option, you can also define the text

 

for the two links through the NextPageText and PreviousPageText

 

properties (or use image links through NextPageImageUrl and

 

PreviousPageImageUrl).

NumericFirstLast

The same as Numeric, except there are additional links for the first

 

page and the last page.

NextPreviousFirstLast

The same as NextPrevious, except there are additional links for the

 

first page and the last page. You can set the text for these links through

 

FirstPageText and LastPageText properties (or images through

 

FirstPageImageUrl and LastPageImageUrl).

 

 

If you don’t like the default pager bar, you can implement your own using the template feature described in the next section by creating a PagerTemplate. You can then use any control you want, such as a text box where the user can type the index of the page and a button to submit the request and load the new page. The code for extracting and binding the records for the current page would remain the same.

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

361

GridView Templates

So far, the examples have used the DataGrid control to show data in using separate bound columns for each field. If you want to place multiple values in the same cell, or have 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 first name, last name, and courtesy fields. To accomplish this trick, you can construct an ItemTemplate like this:

<asp:TemplateField HeaderText="Name"> <ItemTemplate>

<%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %> <%# Eval("LastName") %>

</ItemTemplate>

</asp:TemplateField>

Now when you bind the GridView, the GridView 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. This template is quite simple— it simply defines three data-binding expressions. When evaluated, these expressions are converted to ordinary text.

Note 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.

You’ll notice that these expressions use Eval(), which is a static method of the System.Web.UI.Data-Binder 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 (for a row) or property (for a custom object), and retrieves the value. This process of reflection

adds a little bit of extra work. However, this overhead is unlikely to add much time to the processing of a request. Without the Eval() method, you’d need to access the data object through the Container.DataItem property and use typecasting code like this:

<%# ((EmployeeDetails)Container.DataItem)["FirstName"] %>

The problem with this approach is that you need to know the exact type of data object. For example, the data-binding expression shown previously assumes you’re binding to an array of EmployeeDetails objects through the ObjectDataSource. If you switch to the SqlDataSource, or if you rename the EmployeeDetails class, your page will break. On the other hand, if you use the Eval() method, your data binding expressions will keep working as long as the data object has a property with the given name. In other words, using the Eval() method allows you to create pages that are loosely bound to your data access layer.

Tip When binding to a SqlDataSource in DataSet mode, the data item is a DataRowView. When binding to a SqlDataSource in DataReader mode, the data item is a DbDataRecord.

362 C H A P T E R 1 0 R I C H D ATA C O N T R O L S

The Eval() method also adds the extremely useful ability to format data fields on the fly. To use this feature, you must use 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 can use any of the format strings defined in Table 10-3 and Table 10-4 with the Eval() method.

You’re free to mix templated columns with other column types. Or, you could get rid of every other column and put all the information from the Employees table into one formatted template:

<asp:GridView ID="gridEmployees" runat="server" DataSourceID="sourceEmployees" AutoGenerateColumns="False" ...>

<!-- Styles omitted. -->

<Columns>

<asp:TemplateField HeaderText="Employees"> <ItemTemplate>

<b>

<%# Eval("EmployeeID") %> -

<%# 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") %> </small>

</ItemTemplate>

</asp:TemplateField>

</Columns>

</asp:GridView>

Figure 10-11 shows the result.

Using Multiple Templates

The previous example used a single template to configure the appearance of data items. However, the ItemTemplate isn’t the only template that the GridView provides. In fact, the GridView 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 10-8.

Out of the templates listed in Table 10-8, the EditItemTemplate is one of the most useful, as it gives you the ability to control the editing experience for the field. If you don’t use templated fields, you’re limited to ordinary text boxes, and you won’t have any validation. The GridView also defines two templates that you can use outside of 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.

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

363

Figure 10-11. Creating a templated column

Table 10-8. GridView Templates

Mode

Description

HeaderTemplate

Determines the appearance and content of the header cell

FooterTemplate

Determines the appearance and content of the footer cell

ItemTemplate

Determines the appearance and content of each data cell (if you

 

aren’t using the AlternatingItemTempalte) or every odd-numbered

 

data cell (if you are)

AlternatingItemTemplate

Used in conjunction with the ItemTemplate to format even-

 

numbered and odd-numbered rows differently

EditItemTemplate

Determines the appearance and controls used in edit mode

 

 

Editing Templates in Visual Studio

Visual Studio 2005 includes greatly improved support for editing templates in the web-page designer. To try this, follow these steps:

1.Create a GridView with at least one templated column.

2.Select the GridView and click Edit Templates in the smart tag. This switches the GridView into template editing mode.

364C H A P T E R 1 0 R I C H D ATA C O N T R O L S

3.In the smart tag, use the drop-down Display list to choose the template you want to edit (see Figure 10-12). 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 templated columns.

Figure 10-12. Editing a template in Visual Studio

4.Enter your content in the control. You can type in static content, drag-and-drop controls, and so on.

5.When you’re finished choose End Template Editing from the smart tag.

Binding to a Method

One of the benefits of templates is that they allow you to use data binding expressions that extend the ways you can format and present bound data. One key technique that recurs in many scenarios is using a method in your page class to process a field value. This removes the limitations of simple data binding and lets you incorporate dynamic information and conditional logic.

For example, you might create a column where you want to display an icon next to each row. However, you don’t want to use a static icon—instead, you want to choose the best image based on the data in the row. Figure 10-13 shows an example where check marks indicate when there is a large quantity of a given item in stock (more than 50 units) and an X indicates when stock is fully depleted.

Here’s how you would define the status column:

<asp:TemplateField HeaderText="Status"> <ItemTemplate>

<img src='<%# GetStatusPicture(Container.DataItem) %>' /> </ItemTemplate>

</asp:TemplateField>

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

365

Figure 10-13. Flagging rows conditionally

And here’s the GetStatusPicture() method that examines the data item and chooses the right picture URL:

protected string GetStatusPicture(object dataItem)

{

int units = Int32.Parse(DataBinder.Eval(dataItem, "UnitsInStock").ToString()); if (units == 0)

return "Cancel.gif"; else if (units > 50)

return "OK.gif";

else

return "blank.gif";

}

This technique turns up in many scenarios. For example, you could use it to adjust prices to take into consideration the current exchange rates. Or, you could use it to translate a numeric code into a more meaningful piece of text. You might even want to create completely calculated columns— for example, use the EmployeeDateOfBirth field to calculate a value for an EmployeeAge column.

Note If you use data binding expressions to bind to methods, you can no longer use callbacks to optimize the GridView refresh process. To prevent an error, make sure you do not set GridView.EnableSortingAndPaging-

Callbacks to true. If you don’t want to sacrifice the callback features, you can get similar functionality by modifying the item when it first appears using the GridView.ItemCreated event, as described earlier in the “FormattingSpecific Values” section of this chapter.

366 C H A P T E R 1 0 R I C H D ATA C O N T R O L S

Handling Events in a Template

In some cases, you might need to handle events that are raised by the controls you add to a templated column. For example, imagine you changed the previous example so that instead of showing a static status icon, it created a clickable image link through the ImageButton control. This is easy enough to accomplish:

<asp:TemplateField HeaderText="Status"> <ItemTemplate>

<asp:ImageButton ID="ImageButton1" runat="server" ImageUrl='<%# GetStatusPicture(Container.DataItem) %>' />

</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 which row it belongs to.

The way to resolve this problem is to use an event from the GridView, not the contained button. The 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 of all button controls: 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 the revised ImageButton tag:

<asp:TemplateField HeaderText="Status"> <ItemTemplate>

<asp:ImageButton ID="ImageButton1" runat="server" ImageUrl='<%# GetStatusPicture(Container.DataItem) %>'

CommandName="StatusClick" CommandArgument='<%# Eval("ProductID") %>' /> </ItemTemplate>

</asp:TemplateField>

And here’s the code you need to respond when an ImageButton is clicked:

protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)

{

if (e.CommandName == "StatusClick")

lblInfo.Text = "You clicked product #" + e.CommandArgument;

}

This example simply displays the ProductID in a label.

Tip Remember, you can simplify your life using the GridView’s built-in selection support. Just set the CommandName to Select and handle the SelectIndexChanged event, as described in the section “Using a Data Field As a Select Button” earlier in this chapter. Although this approach gives you easy access to the clicked row, it won’t help you if you want to provide multiple buttons that perform different tasks.

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

367

Editing with a Template

One of the best reasons to use a template is to provide a better editing experience. In the previous chapter, you saw how the GridView provides automatic editing capabilities—all you need to do is switch a row into edit mode by setting the GridView.EditItemIndex 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, and so on. You can do that by adding validator controls to an EditItemTemplate.

It’s often ugly: A row of text boxes across a grid takes up too much space and rarely seems professional.

In a templated 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 an edit template that allows editing of a single field—the Notes field:

<EditItemTemplate>

<b>

<%# Eval("EmployeeID") %> -

<%# 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>

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. Only the Bind() method creates the twoway link, ensuring that updated values will be sent back to the server.

Another important fact to keep in mind is that 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 single @Notes parameter for the Notes field. This is important, because when you write your parameterized update command (if you’re using the SqlDataSource), you must use only one parameter, as shown here:

UpdateCommand="UPDATE Employees SET Notes=@Notes WHERE EmployeeID=@EmployeeID"

Similarly, if you’re using the ObjectDataSource, you must make sure your update method takes only one parameter, named Notes.

Figure 10-14 shows the row in edit mode.