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

Visual CSharp .NET Developer's Handbook (2002) [eng]

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

[012]{0,1}[24680]

The first set of numbers in square brackets indicates that the user can enter a 0, 1, or 2 as the first input value. The {0,1} part of the expression indicates that this first number can appear 0 or 1 times. The second set of square brackets indicates that the second input value must be even. Note that this second input value must appear once, and only once, in the textbox.

The code for this example is relatively simple. The buttons control the appearance of secondary data on screen. Listing 15.1 shows the code for the two test pushbuttons.

Listing 15.1: This Code Demonstrates the Use of Data Hiding

private void btnTest_Click(object sender, System.EventArgs e)

{

//Enable the output, display a value, and disable the Test button. lblOutput1.Text = "You Typed: " + txtInput.Text; lblOutput1.Enabled = true;

lblOutput1.Visible = true; btnTest.Enabled = false; btnTest.Visible = false; txtInput.ReadOnly = true;

//Enable the second group of controls. Label2.Visible = true;

Label2.Enabled = true; btnTest2.Enabled = true; btnTest2.Visible = true; txtInput2.Enabled = true; txtInput2.Visible = true;

//Enable the validators for the second group of controls. RequiredFieldValidator2.Enabled = true; RequiredFieldValidator2.Visible = true; CompareValidator1.Enabled = true; CompareValidator1.Visible = true; RegularExpressionValidator1.Enabled = true; RegularExpressionValidator1.Visible = true; RangeValidator2.Enabled = true;

RangeValidator2.Visible = true;

}

private void btnTest2_Click(object sender, System.EventArgs e)

{

//Enable the output, display a value, and disable the Test button. lblOutput2.Text = "You Typed: " + txtInput2.Text; lblOutput2.Enabled = true;

lblOutput2.Visible = true; btnTest2.Enabled = false; btnTest2.Visible = false; txtInput2.ReadOnly = true;

}

You should notice a few features about this code. First, it sets both the Enabled and the Visible properties for the affected controls. If you don't set both properties, the form can fail in

unanticipated ways. In addition, you want to ensure that the validators don't begin checking for valid input before the control is active.

Second, the code sets the input values to ReadOnly after the user inputs and accepts the value. This step ensures that the user won't change the value of previous inputs and therefore change the validation conditions of the form. This is an important consideration when you use datahiding, as we do in this example, because one change in a previous step can cascade into errors in the current step—errors you can't easily fix or repeat.

Finally, notice that the overlapped controls are displayed one at a time. When the user clicks the first Test button, it disappears and a label appears in its place. This step ensures the user can't go back to a previous step with any ease or click the Test button more than once (causing potential cascade errors). The use of data-hiding is also key in making maximum use of screen real estate. This is an important consideration when using validators, because the validator will use screen real estate even if the user never needs the error message it provides. Figure 15.3 shows initial output of this example with an error message in view.

Figure 15.3: The initial screen hides much of the data this application will present from view.

You can test the application out with various input values to see the error messages it will present. One thing you should notice is that only one of the error messages will appear in most cases—an important feature if you want to avoid confusing the user. One exception to the rule is if the user types something absurd in the second input value. For example, try typing 99 in the second input, and you'll see two error messages; the first says that the input value must be even, while the second says it must be in the correct range. Figure 15.4 shows the final output of the application. Notice how the data-hiding clears the pushbuttons from view and presents only the output values.

Figure 15.4: The final output hides both pushbuttons and shows only the answers.

Some Other Interesting ASP.NET Controls

ASP.NET provides access to a number of other interesting controls. For example, you'll find the SpecialHeader control indispensable when you want to create a web page with impact. The AdRotator control from ASP is still available (and enhanced) in ASP.NET. You'll also find the Literal control, which displays text like a Label, but doesn't allow text formatting. The main purpose for this control is to reduce the memory footprint of static text display.

You'll also find some new versions of existing controls. For example, the CheckBox and RadioButton controls are augmented by the CheckBoxList and RadioButtonList controls. Instead of creating control group members individually, the CheckBoxList and RadioButtonList controls enable you to create list members within one control.

Chatty versus Chunky Design

Developers realize that there's normally more than one way to design a component. However, one design normally performs better than another in a given situation. One of the biggest performance indicators for applications that run on the Internet is the number of trips required to complete a task. A chatty design requires more trips with small data payloads, while a chunky design requires fewer trips with a larger data payload. For example, consider the following two class designs:

public class Chatty

{

string _Name; string _Password; public string Name

{

get {return _Name;} set {_Name = value;}

}

public string Password

{

get {return _Password;} set {_Password = value;}

}

public void LogOn()

{

}

}

public class Chunky

{

string _Name; string _Password;

public void LogOn(string Name, string Password)

{

}

}

Both of these designs accomplish the same task—log a user into the system. However, the Chatty class requires three trips to the server in place of the single trip used by the Chunky class. If you view this situation from a pure performance perspective, the Chunky class is the correct method to use when creating a class. On the other hand, you might require the additional flexibility provided by the Chatty class in some situations. Notice that Chatty enables you to retrieve user name and password information—something that Chunky doesn't provide. This is a simplification of an important design principle, but one you need to consider. Perhaps your solution doesn't rest in using either Chatty or Chunky, but something in between that uses elements of both.

Working with the DataGrid

The most common use of the DataGrid is as a means for displaying information from a database on screen. In some cases, you'll also need to provide a means for editing the data and storing it within the database. We'll explore this use of the DataGrid in the "DataGrid Editing Example" section of the chapter. However, using the DataGrid exclusively for database applications misses the point of the DataGrid—you can use it for all types of data display.

Tip You can use the PlaceHolder control to reserve space on a web page for controls that you intend to create programmatically. The PlaceHolder control acts as a container for the controls you want to display.

This section of the chapter looks at another way to use the DataGrid. We'll create an application that uses the DataGrid as a replacement for the common HTML table. This smart table replacement will display text and graphic links, allow the user to sort the information as needed, and even offer a level of data-hiding. We'll use an Access database for this example (located in the \Chapter 15\Data folder on the CD). Of course, the end result of this example is that you'll learn some new techniques for using the DataGrid in your web applications.

Creating a DataGrid with Hyperlinks

We'll begin with a standard web application that includes a DataGrid and a bit of text. Figure 15.5 shows the output of this example.

Figure 15.5: The DataGrid Demonstration creates a static table of information based on database content.

As you can see, the example contains links for changing the sort order by the Name or the Stock Number fields. It isn't always necessary to allow sorting by all fields in the DataGrid, even though the code for doing so isn't that difficult to create. The last column of the table provides links to pictures of the product. These links appear as text within the database. You could also store the actual pictures in the database, but this proves problematic in some cases. The DataGrid doesn't provide any easy method for displaying graphics directly, which is why the link option is often your best choice.

Now that we've gotten some of the basics out of the way, let's look at the application code. Listing 15.2 shows the code you'll need to make this web page functional. Notice that this example emphasizes the use of Code Behind, rather than ASXP code, to create the web page. The results are essentially the same, but Code Behind offers more flexibility in some areas (and less in others). For example, try creating a TemplateColumn using Code Behind, and you'll find that it isn't nearly as easy as using ASPX. In short, each technique has advantages and you need to select the method that best meets your application needs.

Listing 15.2: An Example of a DataGrid that Relies on Code Behind

// Create some database connectivity objects. protected OleDbConnection FGSConnect;

protected System.Web.UI.WebControls.DataGrid DataDisplay; protected OleDbDataAdapter FGSAdapter;

private void Page_Load(object sender, System.EventArgs e)

{

BoundColumn

NewColumn;

// Data column in table.

HyperLinkColumn

PictureColumn;

// Link to picture of product.

string

SortOrder = "";

// Holds sorting information.

// Determine if there is a request for sorting the data. try

{

SortOrder = Request.Cookies["SortOrder"].Value;

}

catch

{

}

try

{

// Create a connection to the database. FGSConnect = new OleDbConnection(

"Provider=Microsoft.Jet.OLEDB.4.0;" + "Password='';" +

"User ID='Admin';" +

"Data Source=D:\\Data\\Garden.mdb;" + "Mode=Share Deny None");

FGSAdapter = new OleDbDataAdapter("Select * From Implement", FGSConnect);

// Change the sort order if necessary. if (SortOrder != "")

{

FGSAdapter.SelectCommand.CommandText = FGSAdapter.SelectCommand.CommandText + " ORDER BY " +

SortOrder;

}

// Fill the dataset with data. FGSAdapter.Fill(GardenDS);

}

catch (System.Exception error)

{

//Clear the buffer. Response.ClearContent();

//Send a response. Response.Write("<HTML>\r\n<HEAD>\r\n");

Response.Write("<TITLE>Database Error Message</TITLE>\r\n"); Response.Write("</HEAD>\r\n<BODY>\r\n"); Response.Write("<H1><CENTER>Database Error</CENTER></H1>\r\n"); Response.Write("Couldn't Create the Database Connection!\r\n"); Response.Write("<P><B>Details</B>"); Response.Write("<P>Message: " + error.Message); Response.Write("\r\n<BR>Source: " + error.Source); Response.Write("\r\n<BR>Stack Trace: " + error.StackTrace); Response.Write("</BODY>\r\n</HTML>");

//End the message.

Response.End();

}

//Name the table to make it easy to access. GardenDS.Tables[0].TableName = "Implement"; DataDisplay.DataMember = "Implement";

//Enable sorting.

DataDisplay.AllowSorting = true;

//Change the display characteristics. DataDisplay.BorderStyle = BorderStyle.Double; DataDisplay.BorderWidth = 3; DataDisplay.BorderColor = Color.Black; DataDisplay.CellPadding = 3;

//Create Data Grid Columns—map a database field to

// each of the standard columns. NewColumn = new BoundColumn(); NewColumn.DataField = "StockNumber"; NewColumn.HeaderText = "Stock Number"; NewColumn.SortExpression = "StockNumber"; NewColumn.ItemStyle.Wrap = false; DataDisplay.Columns.Add(NewColumn);

NewColumn = new BoundColumn();

NewColumn.DataField = "Name";

NewColumn.HeaderText = "Name";

NewColumn.SortExpression = "Name";

DataDisplay.Columns.Add(NewColumn);

NewColumn = new BoundColumn();

NewColumn.DataField = "Description";

NewColumn.HeaderText = "Description";

DataDisplay.Columns.Add(NewColumn);

NewColumn = new BoundColumn(); NewColumn.DataField = "Cost"; NewColumn.DataFormatString = "{0:C}"; NewColumn.HeaderText = "Cost in Dollars";

NewColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right; DataDisplay.Columns.Add(NewColumn);

PictureColumn = new HyperLinkColumn();

PictureColumn.DataTextField = "Picture";

PictureColumn.DataNavigateUrlField = "Picture";

PictureColumn.HeaderText = "Select Picture";

DataDisplay.Columns.Add(PictureColumn);

//Fill the grid view with data. DataDisplay.DataBind();

//Close the connection. FGSConnect.Close();

}

private void DataDisplay_SortCommand(object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e)

{

// Save the sort order in a cookie.

Response.SetCookie(new HttpCookie("SortOrder", e.SortExpression));

}

Most of the action in this example takes place in the Page_Load() method because we need to set the DataGrid functionality before the application displays it on screen. The code begins with a check for a SortOrder cookie because it uses the same sorting technique as the example in Chapter 14. However, in this case, we have to build the database connection before the SortOrder cookie becomes useful, so you won't find the ordering code immediately.

The next step is to create a database connection. However, this is an error-prone process that you'll want to place within a try...catch structure. Notice that the catch portion isn't empty in this case. It contains a generic error message and then uses the error object to further define the problem. We'll discuss a more generic version of this message-sending technique in the

"Understanding Code Behind" section of the chapter. What you need to consider for now is that this is the standard MessageBox replacement for a web application.

Opening the data connection is much like the desktop equivalent. The code creates a connection containing all of the connection information including the actual source of data on the server's hard drive. The DataAdapter selects data from the database using the selection string. Notice that the code doesn't include any update, delete, or insert logic because we're simply displaying data. If you want to create an interactive web page, you'll need to include these three commands and any associated parameters. We discussed these issues as part of the ODBC.NET example in Chapter 11.

Once you have a DataAdapter, you can modify the selection command to reflect any sort order information passed by a previous iteration of the web page through a cookie. The final step is to fill GardenDS (the DataSet) with data. At this point, we could close the connection and free the DataAdapter, because GardenDS contains everything it needs for the data presentation on screen. The remaining code focuses on the task of presenting the information on screen. This type of data access is great for static information. If you set the web page up for caching, it might be days before the application actually accesses the database again.

The code begins the data presentation process by giving the GardenDS DataTable a name. It uses this name to assign the DataTable to the DataDisplay.DataMember property. At this point, DataDisplay could present information in the DataGrid, except the example has set the AutoGenerateColumns property to false. It's important to set this property to false if you want to create a custom information display as we will in this example.

DataDisplay also needs to have the AllowSorting property set to true so that any columns with SortExpression property values will appear on screen with a link and the DataGrid will activate the SortCommand() event. The example also sets various DataDisplay border style properties, which includes the CellPadding property. Setting CellPadding is important if you plan to highlight the DataGrid lines or if you have data that aligns to both the left and right side of the cells.

When working with a BoundColumn, you must create a new copy of the object, provide a DataField property value, and add the column to the DataGrid. It's also a good idea to use the HeaderText property to give the BoundColumn a name. If you want the user to be able to sort on the BoundColumn, it's also essential to provide a SortExpression value.

Providing the bare minimum for a BoundColumn isn't always optimal. For example, consider the Stock Number column. Generally, you won't want this column to wrap unless the stock number is exceptionally long. Setting the ItemStyle.Wrap property false ensures the column won't wrap (the property is true by default). There are a number of other ItemStyle property settings such as the HorizontalAlign property used for the Cost in Dollars column. Note that, in some cases, you'll need to use the enumerated value associated with a property, as in the case of HorizontalAlign.

The final BoundColumn consideration for this example is the DataFormatString. You'll normally need to set this property value when working with currency, time, or date values. It also comes in handy for numeric types where you want to control the output. For example, you might want to use scientific notation or limit the number of decimal point values.

This example makes use of a HyperLinkColumn to create a link between the picture placeholder and the actual picture. It would be nice to display the pictures directly within the table, but you'd need to perform some custom coding to do it. In most cases, displaying a link to a large view of the item is preferable to shrinking the picture down to fit within the confines of a table anyway.

A HyperLinkColumn always requires a new copy of the object, a text version of the link, and the URL that the link points to. As with the BoundColumn, you'll want to include a HeaderText value so the user knows what the column represents. Once you create the HyperLinkColumn, you add it to the DataGrid as you would a BoundColumn.

Notice how the example uses the same database field for both the DataTextField and the DataNavigateUrlField property values. If you use this technique, you'll need to restrict the entry to a simple filename in most cases. The DataGrid assumes that you want to use the application directory as a base URL and that the filename points to an object within the application directory. If you want to see any other behavior, you'll need a minimum of two database fields to hold the data value and the associated URL for the resource.

Adding Graphics to a DataGrid

Let's get back to the Select Picture column again. Remember that I mentioned there's a way to code the example so it will display pictures. Our example is relatively simple, so the coding isn't difficult. However, it can become quite a task when working with complex databases. The following code (commented out in the source code found in the \Chapter 15\GridTable folder on the CD) will enable the application to display pictures on screen.

//Create column and row objects for the dataset. Add a calculated

//column to the dataset.

DataColumn DC = GardenDS.Tables[0].Columns.Add("Picture2");

DataRow DR;

//Parse each row in the dataset. Create a calculated column

//entry that contains an <img> tag.

for (int Counter = 0;

Counter < GardenDS.Tables[0].Rows.Count; Counter ++)

{

DR = GardenDS.Tables[0].Rows[Counter]; DR["Picture2"] = "<img src=" + DR["Picture"] + ">";

}

//Display the column using the calculated column in place

//of the standard text column.

PictureColumn = new HyperLinkColumn();

PictureColumn.DataTextField = "Picture2";

PictureColumn.DataNavigateUrlField = "Picture";

PictureColumn.HeaderText = "Select Picture";

DataDisplay.Columns.Add(PictureColumn);

As you can see, the code begins by creating a calculated column in the dataset. The calculated column will contain an <img> tag that directs the DataGrid to display an image instead of text. The application must create a calculated entry for every row in the dataset, so large datasets could impose a huge performance penalty. Notice that the DataTextField property

relies on the calculated field value in place of the standard database entry. Figure 15.6 shows the new output from the application.

Figure 15.6: Using graphics in a DataGrid is possible, but can prove a performance burden.

There are many things that can go wrong with this or any other approach to the problem of displaying the graphics. The method shown in this section is the least error-prone because the <img> tag value is calculated immediately before the image is displayed. However, this method also presents the greatest performance penalty. You can get around the problem by creating the <img> tag in a separate database field during data entry. Unfortunately, while this technique does improve application performance, it also requires that the application track two versions of the same data. In addition to errors that the dual entry tracking could produce, using the dual entry method also increases the size of the database.

Understanding Code Behind

Code Behind is one of the most requested features for ASP.NET. It enables the developer to separate the user interface of a web application from the underlying code. The end result is that applications are easier to understand and maintain. In addition, using Code Behind helps an organization use developer resources more efficiently. Developers who know how to create web interfaces can work on the HTML code independently of those who create the low-level code. One group can consider the needs of the interface, while a second group makes the interface usable and functional. You've already learned a little about Code Behind in the "Understanding Coding Differences" section of the chapter. This section discusses the topic in greater detail.

In many respects, Code Behind works the same as the applications you create for the desktop. The Code Behind portion of a web application relies on namespaces, uses the .NET Framework, works within classes, and even uses the same coding constructs. As a result, you'll find a lot of familiar elements in a web application that relies on Code Behind. Of course, one of the big differences is that Code Behind is merely the back end of the HTML front end. Consequently, there's little display code within a Code Behind module—the display code appears within the ASPX file.

A Code Behind file always has the same name as the ASPX file with a suitable extension added for the programming language. When using C#, you'll find that the Code Behind file