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

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

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

508 C H A P T E R 1 4 U S E R C O N T R O L S

Note The design-time support for user controls is greatly improved in ASP.NET 2.0. Now you can see a real representation of the user control at design time (rather than a blank gray box), and you can use the smart tag to quickly jump to the corresponding .ascx file. Best of all, when you add the user control to a web page, Visual Studio makes the corresponding user control object available to your code automatically, as you’ll see in later examples.

Converting a Page to a User Control

Sometimes the easiest way to develop a user control is to put it in a web page first, test it on its own, and then translate the page to a user control. Even if you don’t follow this approach, you might still end up with a portion of a user interface that you want to extract from a page and reuse in multiple places.

Overall, this process is a straightforward cut-and-paste operation. However, you need to watch for a few points:

Remove all <html>, <body>, and <form> tags. These tags appear once in a page, so they can’t be added to user controls (which might appear multiple times in a single page).

If there is a Page directive, change it to a Control directive and remove the attributes that the Control directive does not support, such as AspCompat, Buffer, ClientTarget, CodePage, Culture, EnableSessionState, EnableViewStateMac, ErrorPage, LCID, ResponseEncoding, Trace, TraceMode, and Transaction.

If you aren’t using the code-behind model, make sure you still include a class name in the Control directive by supplying the ClassName attribute. This way, the web page that consumes the control can be strongly typed, which allows it to access properties and methods you’ve added to the control.

Change the file extension from .aspx to .ascx.

Adding Code to a User Control

The previous user control didn’t include any code. Instead, it simply provided a useful way to reuse a static block of a web-page user interface. In many cases, you’ll want to add some code to your user control creation, either to handle events or to add functionality that the client can access. Just like a web form, you can add this code to the user control class in a <script> block directly in the .ascx file, or you can use a separate .cs code-behind file.

Handling Events

To get a better idea of how this works, the next example creates a simple TimeDisplay user control with some event-handling logic. This user control encapsulates a single LinkButton control. Whenever the link is clicked, the time displayed in the link is updated. The time is also refreshed when the control first loads.

Here’s the user control, using inline code with a <script> block:

<%@ Control Language="C#" ClassName="TimeDisplay" %>

<asp:LinkButton runat="server" ID="lnkTime" OnClick="lnkTime_Click"/>

<script language="C#" runat="server">

protected void Page_Load(object sender, EventArgs e)

C H A P T E R 1 4 U S E R C O N T R O L S

509

{

if (!Page.IsPostBack) RefreshTime();

}

protected void lnkTime_Click (object sender, EventArgs e)

{

RefreshTime();

}

public void RefreshTime()

{

lnkTime.Text = DateTime.Now.ToLongTimeString();

}

</script>

Note that the lnkTime_Click event handler calls a method named RefreshTime(). Because this method is public, the code on the hosting web form can trigger a label refresh programmatically by calling the method at any time. Another important detail is the ClassName attribute in the Control directive. This indicates that the code will be compiled into a user control named TimeDisplay, with the methods in the script block.

Figure 14-2 shows the resulting control.

Figure 14-2. A user control that handles its own events

The code takes a turn for the better if you split it into a separate .ascx portion and a .cs codebehind file. In this case, the Control directive must specify the Src attribute with the name of the user control source code file or the Inherits attribute with the name of the compiled class. User controls created always use the Inherits attribute, as shown here:

<%@ Control Language="c#" AutoEventWireup="true" CodeFile="TimeDisplay.ascx.cs" Inherits="TimeDisplay" %>

<asp:LinkButton id="lnkTime" runat="server" OnClick="lnkTime_Click" />

And here’s the corresponding code-behind class:

public partial class TimeDisplay : System.Web.UI.UserControl

{

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

{

if (!Page.IsPostBack) RefreshTime();

}

protected void lnkTime_Click(object sender, System.EventArgs e)

{

RefreshTime();

}

510 C H A P T E R 1 4 U S E R C O N T R O L S

public void RefreshTime()

{

lnkTime.Text = DateTime.Now.ToLongTimeString();

}

}

Note that in this example, the user control receives and handles a Page.Load event. This event and event handler are completely separate from the Page.Load event that the web form can respond to (although they both are raised as a consequence of the same thing—a page being created). This makes it easy for you to add initialization code to a user control.

Adding Properties

Currently, the TimeDisplay user control allows only limited interaction with the page that hosts it. All you can really do in your web-form code is call RefreshTime() to update the display. To make a user control more flexible and much more reusable, developers often add properties.

The next example shows a revised TimeDisplay control that adds a public Format property. This property accepts a standard .NET format string, which configures the format of the displayed date. The RefreshTime() method has been updated to take this information into account.

public class TimeDisplay : System.Web.UI.UserControl

{

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

{

if (!Page.IsPostBack) RefreshTime();

}

private string format; public string Format

{

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

}

protected void lnkTime_Click(object sender, System.EventArgs e)

{

RefreshTime();

}

public void RefreshTime()

{

if (format == "")

{

lnkTime.Text = DateTime.Now.ToLongTimeString();

}

else

{

//This will throw an exception for invalid format strings,

//which is acceptable.

lnkTime.Text = DateTime.Now.ToString(format);

}

}

}

C H A P T E R 1 4 U S E R C O N T R O L S

511

In the hosting page, you have two choices. You can set the Format property at some point in your code by manipulating the control object, as shown here:

TimeDisplay1.Format = "dddd, dd MMMM yyyy HH:mm:ss tt (GMT z)";

Your second option is to configure the user control when it’s first initialized by setting the value in the control tag, as shown here:

<apress:TimeDisplay id="TimeDisplay1"

Format="dddd, dd MMMM yyyy HH:mm:ss tt (GMT z)" runat="server" /> <hr />

<apress:TimeDisplay id="TimeDisplay2" runat="server" />

In this example, two versions of the TimeDisplay control are created, one with a control that displays the date in the default format and another one with a custom format applied. Figure 14-3 shows the resulting page on the browser.

Figure 14-3. Two instances of a dynamic user control

Tip If you use simple property types such as int, DateTime, float, and so on, you can still set them with string values when declaring the control on the host page. ASP.NET will automatically convert the string to the property type defined in the class. Technically, ASP.NET employs a type converter—a special type of object often used to convert data types to and from string representations, which is described in Chapter 28.

When you begin adding properties to a user control, it becomes more important to understand the sequence of events. Essentially, page initialization follows this order:

1.The page is requested.

2.The user control is created. If you have any default values for your variables, or if you perform any initialization in a class constructor, it’s applied now.

3.If any properties are set in the user control tag, these are applied now.

4.The Page.Load event in the page executes, potentially initializing the user control.

5.The Page.Load event in the user control executes, potentially initializing the user control.

Once you understand this sequence, you’ll realize that you shouldn’t perform user control initialization in the Page.Load event of the user control that might overwrite the settings specified by the client.

512 C H A P T E R 1 4 U S E R C O N T R O L S

Using Custom Objects

Many user controls are designed to abstract away the details of common scenarios with a higherlevel control model. For example, if you need to enter address information, you might group several text box controls into one higher-level AddressInput control. When you’re modeling this sort of control, you’ll need to use more complex data than individual strings and numbers. Often, you’ll want to create custom classes designed expressly for communication between your web page and your user control.

To demonstrate this idea, the next example develops a LinkTable control that renders a set of hyperlinks in a formatted table. Figure 14-4 shows the LinkTable control.

Figure 14-4. A user control that displays a table of links

To support this control, you need a custom class that defines the information needed for each link:

public class LinkTableItem

{

private string text; public string Text

{

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

}

private string url; public string Url

{

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

}

// Default constructor. public LinkTableItem() {}

public LinkTableItem(string text, string url)

{

C H A P T E R 1 4 U S E R C O N T R O L S

513

this.text = text; this.url = url;

}

}

This class could be expanded to include other details, such as an icon that should appear next to the control. The LinkTable simply uses the same icon for every item.

Next, consider the code-behind class for the LinkTable. It defines a Title property that allows you to set a caption and an Items collection that accepts an array of LinkTableItem objects, one for each link that you want to display in the table.

public partial class LinkTable : System.Web.UI.UserControl

{

public string Title

{

get { return lblTitle.Text; } set { lblTitle.Text = value; }

}

private LinkTableItem[] items; public LinkTableItem[] Items

{

get { return items; } set

{

items = value;

// Refresh the grid. listContent.DataSource = items; listContent.DataBind();

}

}

}

The control itself uses data binding to render most of its user interface. Whenever the Items property is set or changed, a DataList in the LinkTable control is rebound to the item collection. The DataList contains a single template that, for each link, displays each HyperLink control, which appears with an exclamation mark icon next to it.

<%@ Control Language="c#" AutoEventWireup="false" Codebehind="LinkTable.ascx.cs" Inherits="LinkTable" %>

<table border="1" width="100%" cellspacing="0" cellpadding="2" height="43"> <tr>

<td width="100%" height="1">

<asp:Label id="lblTitle" runat="server" ForeColor="#C00000" Font-Bold="True" Font-Names="Verdana" Font-Size="Small"> [Title Goes Here]</asp:Label>

</td>

</tr>

<tr>

<td width="100%" height="1">

<asp:DataList id="listContent" runat="server"> <ItemTemplate>

<img height="23" src="exclaim.gif" width="25" align="absMiddle" border="0">

<asp:HyperLink id="HyperLink1"

NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "Url") %>'

514 C H A P T E R 1 4 U S E R C O N T R O L S

Font-Names="Verdana" Font-Size="XX-Small" ForeColor="#0000cd" Text='<%# DataBinder.Eval(Container.DataItem, "Text") %>' runat="server">

</asp:HyperLink>

</ItemTemplate>

</asp:DataList>

</td>

</tr>

</table>

Finally, here’s the typical web-page code you would use to define a list of links and display it by binding it to the LinkTable user control:

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

{

// Set the title.

LinkTable1.Title = "A List of Links";

// Set the hyperlinked item list. LinkTableItem[] items = new LinkTableItem[3];

items[0] = new LinkTableItem("Test Item 1", "http://www.apress.com"); items[1] = new LinkTableItem("Test Item 2", "http://www.apress.com"); items[2] = new LinkTableItem("Test Item 3", "http://www.apress.com"); LinkTable1.Items = items;

}

Once it’s configured, the web-page code never needs to interact with this control again. When the user clicks one of the links, the user is just forwarded to the new destination without needing any additional code. Another approach would be to design the LinkTable so that it raises a serverside click event. You’ll see that approach in the next section.

Adding Events

Another way that communication can occur between a user control and a web page is through events. With methods and properties, the user control reacts to a change made by the web-page code. With events, the story is reversed—the user control notifies the web page about an action, and the web-page code responds.

Usually, you’ll delve into events when you create a user control that the user can interact with. After the user takes a certain action—such as clicking a button or choosing an option from a list— your user control intercepts a web control event and then raises a new, higher-level event to notify your web page.

The first version of LinkTable control is fairly functional, but it doesn’t use events. Instead, it simply creates the requested links. To demonstrate how events can be used, the next example revises the LinkTable so that it notifies the user when an item is clicked. Your web page can then determine what action to take based on which item was clicked.

The first step to implement this design is to define the events. Remember that to define an event you must use the event keyword with a delegate that represents the signature of the event. The .NET standard for events specifies that every event should use two parameters. The first one provides a reference to the control that sent the event, and the second one incorporates any additional information. This additional information is wrapped into a custom EventArgs object, which inherits from the System.EventArgs class. (If your event doesn’t require any additional information, you can just use the generic System.EventArgs object, which doesn’t contain any additional data. Many events in ASP.NET, such as Page.Load or Button.Click, follow this pattern.)

C H A P T E R 1 4 U S E R C O N T R O L S

515

In the LinkTable example, it makes sense to transmit basic information about what link was clicked. To support this design, you can create the following EventArgs object, which adds a readonly property that has the corresponding LinkTableItem object:

public class LinkTableEventArgs : EventArgs

{

private LinkTableItem selectedItem; public LinkTableItem SelectedItem

{

get { return selectedItem; }

}

private bool cancel = false; public bool Cancel

{

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

}

public LinkTableEventArgs(LinkTableItem item)

{

selectedItem = item;

}

}

Notice that the LinkTableEventArgs defines two new details—a SelectedItem property that allows the user to get information about the item that was clicked and a Cancel property that the user can set to prevent the LinkTable from navigating to the new page. One reason you might set Cancel is if you want to respond to the event in your web-page code and handle the redirect yourself. For example, you might want to show the target link in a server-side <iframe> or use it to set the content for an <img> tag rather than navigating to a new page.

Next, you need to create a new delegate that represents the LinkClicked event signature. Here’s what it should look like:

public delegate void LinkClickedEventHandler(object sender, LinkTableEventArgs e);

Using the LinkClickedEventHandler, the LinkTable class defines a single event:

public event LinkClickedEventHandler LinkClicked;

To intercept the server click, you need to replace the HyperLink control with a LinkButton, because only the LinkButton raises a server-side event. (The HyperLink simply renders as an anchor that directs the user straight to the target when clicked.) Here’s the new template you need:

<ItemTemplate>

<img height="23" src="exclaim.gif" width="25" align="absMiddle" border="0">

<asp:LinkButton id="HyperLink1" Font-Names="Verdana" Font-Size="XX-Small" ForeColor="#0000cd" runat="server"

Text='<%# DataBinder.Eval(Container.DataItem, "Text") %>' CommandArgument='<%# DataBinder.Eval(Container.DataItem, "Url") %>'> </asp:LinkButton>

</ItemTemplate>

516 C H A P T E R 1 4 U S E R C O N T R O L S

You can then intercept the server-side click event and forward it along to the web page as a LinkClicked event. Here’s the code you need:

protected void listContent_ItemCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e)

{

if (LinkClicked != null)

{

//Get the HyperLink object that was clicked. LinkButton link = (LinkButton)e.Item.Controls[1];

//Construct the event arguments.

LinkTableItem item = new LinkTableItem(link.Text, link.CommandArgument);

LinkTableEventArgs args = new LinkTableEventArgs(item);

//Fire the event. LinkClicked(this, args);

//Navigate to the link if the event recipient didn't

//cancel the operation.

if (!args.Cancel)

{

Response.Redirect(item.Url);

}

}

}

Note that when you raise an event, you must first check to see if the event variable contains a null reference. If it does, it signifies that no event handlers are registered yet (perhaps the control hasn’t been created). Trying to fire the event at this point will generate a null reference exception. If the event variable isn’t null, you can fire the event by using the name and passing along the appropriate event parameters.

Consuming this event isn’t quite as easy as it is for the standard set of ASP.NET controls. The problem is that user controls don’t provide much in the way of design-time support. (Custom controls, which you’ll look at in Chapter 27, do provide design-time support.) As a result, you can’t use the Properties window to wire up the event handler at design time. Instead, you need to write the event handler and the code that attaches it yourself.

Here’s an example of an event handler that has the required signature (as defined by the LinkClickedEventHandler):

protected void LinkClicked(object sender, LinkTableEventArgs e)

{

lblInfo.Text = "You clicked '" + e.SelectedItem.Text +

"' but this page chose not to direct you to '" + e.SelectedItem.Url + "'.";

e.Cancel = true;

}

You have two options to wire up the event handler. You can do it manually in the Page.Load event handler using delegate code:

LinkTable1.LinkClicked += new LinkClickedEventHandler(LinkClicked);

Alternatively, you can do it in the control tag. Just add the prefix On in front of the event name, as shown here:

<apress:LinkTable ID="LinkTable1" runat="server" OnLinkClicked="LinkClicked" />

C H A P T E R 1 4 U S E R C O N T R O L S

517

Figure 14-5 shows the result when a link is clicked.

Figure 14-5. A user control that fires an event

Exposing the Inner Web Control

One important detail to remember is that the user control’s constituent controls can be accessed only by the user control. That means the web page that hosts the user control can receive the events, set the properties, or call the methods of these contained controls. For example, in the TimeDisplay user control the web page has no ability to access the LinkButton control that it uses.

Usually, this behavior is exactly what you want. It means your user control can add public properties to expose specific details without giving the web page free reign to tamper with everything and potentially introduce invalid or inconsistent changes. For example, if you want to give the web page the ability to tweak the foreground color of the LinkButton control, you might add a ForeColor property to your user control. Here’s an example:

public Color ForeColor

{

get { return lnkTime.ForeColor; } set { lnkTime.ForeColor = value; }

}

To change the foreground color in your web-page code, you would now use code like this:

TimeDisplay1.ForeColor = System.Drawing.Color.Green;

This example maps the lnkTime.ForeColor property to the ForeColor property of the user control. This trick is usually the best approach, but it can become tedious if you need to expose a large number of properties. For example, your user control might render a table, and you might want to let the user configure the formatting of each table cell.

In this case, it might make sense to expose the complete control object. Here’s an example that exposes the lnkTime control for the TimeDisplay user control:

public LinkButton InnerLink

{

get { return lnkTime; }

}