Custom Controls


In the past it has been tricky to implement custom-built controls, especially on large-scale systems where complex registration procedures might be required in order to use them. Even on simple systems, the coding required to create a custom control could become a very involved process. The scripting capabilities of older Web languages also suffered by not giving you complete access to your cunningly crafted object models, which resulted in poor performance.

The .NET Framework provides an ideal setting for the creation of custom controls, using simple programming techniques. Every aspect of ASP.NET server controls is exposed for you to customize, including such capabilities as templating and client-side scripting. However, there is no need to write code for all of these eventualities; simpler controls can be a lot easier to create.

In addition, the dynamic discovery of assemblies that is inherent in a .NET system makes installation of Web applications on a new Web server as simple as copying the directory structure containing your code. To make use of the controls you have created, you simply copy the assemblies containing those controls along with the rest of the code. You can even place frequently used controls in an assembly located in the global assembly cache (GAC) on the Web server, so that all Web applications on the server have access to them.

This chapter discusses two different kinds of controls:

  • User controls (and how to convert existing ASP.NET pages into controls)

  • Custom controls (and how to group the functionality of several controls, extend existing controls, and create new controls from scratch)

User controls are illustrated with a simple control that displays a card suit (club, diamond, heart, or spade), so that you can embed it in other ASP.NET pages with ease. In the case of custom controls, you won’t go into too much depth, although you will see the basic principles and be directed to where you can find out more.

User Controls

User controls are controls that you create using ASP.NET code, just as you use in standard ASP.NET Web pages. The difference is that after you have created a user control you can reuse it in multiple ASP.NET pages.

For example, say that you have created a page that displays some information from a database, perhaps information about an order. Instead of creating a fixed page that does this, it is possible to place the relevant code into a user control, and then insert that control into as many different Web pages as you want.

In addition, it is possible to define properties and methods for user controls. For example, you can specify a property for the background color for displaying your database table in a Web page, or a method to re-run a database query to check for changes.

To start with, in this chapter, you will create a simple user control. As is the case with the other chapters, you can download the code for the sample projects in this chapter from the Wrox Web site at www.wrox.com.

A Simple User Control

In Visual Studio .NET, create a new Web site called PCSUserCWebApp1 in the directory C:\ProCSharp \Chapter33. After the standard files have been generated, select the Website image from book Add New Item... menu option and add a Web User Control called PCSUserC1.ascx, as shown in Figure 33-1.

image from book
Figure 33-1

The files added to your project, with the extensions .ascx and .ascx.cs, work in a very similar way to the .aspx files you’ve seen already. The .ascx file contains your ASP.NET code and looks very similar to a normal .aspx file. The .ascx.cs file is your code-behind file, which defines custom code for the user control, much in the same way that forms are extended by .aspx.cs files.

The .ascx files can be viewed in Design or HTML view, just like .aspx files. Looking at the file in HTML view reveals an important difference: there is no HTML code present, and in particular no <form> element. This is because user controls are inserted inside ASP.NET forms in other files and so don’t need a <form> tag of their own. The generated code is as follows:

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

This is very similar to the <%@ Page %> directive generated in .aspx files, except that Control is specified rather than Page. It specifies the additional code-behind file and the class name that will be generated. The generated code in the .ascx.cs file is, as in auto-generated .aspx.cs files, empty.

Your simple control will be one that displays a graphic corresponding to one of the four standard suits in cards (club, diamond, heart, or spade). The graphics required for this were shipped as part of a previous version of Visual Studio .NET; you can find them in the downloadable code for this chapter, in the CardSuitImages directory, with the file names CLUB.BMP, DIAMOND.BMP, HEART.BMP, and SPADE.BMP. Copy these files into a new Images subdirectory of your project’s directory, so that you can use them in a moment. If you do not have access to this download, you can use any images you like for this example because they are not important to the functionality of the code.

Tip 

Note that unlike earlier versions of Visual Studio, changes you make to the Web site structure outside of Visual Studio are automatically reflected in the IDE. You have to hit the refresh button in the Solution Explorer window, but you should see the new Images directory and bitmap files appear automatically.

Now add some code to your new control. In the HTML view of PCSUserC1.ascx, add the following:

 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="PCSUserC1.ascx.cs"     Inherits="PCSUserC1" %> <table cellspacing="4">   <tr valign="middle">     <td>       <asp:Image Runat="server"  ImageURL="~/Images/club.bmp"/>     </td>     <td>       <asp:Label Runat="server" >Club</asp:Label>     </td>   </tr> </table> 

This defines a default state for your control, which is a picture of a club along with a label. The ~ in the path to the image means “start at the root directory of the Web site.” Before you add functionality, you’ll test this default by adding this control to your project Web page webForm1.aspx.

To use a custom control in an .aspx file, you first need to specify how you will refer to it, that is, the name of the tag that will represent the control in your HTML. To do this you use the <%@ Register %> directive at the top of the code in Default.aspx, as follows:

 <%@ Register TagPrefix="pcs" TagName="UserC1" src="/books/4/80/1/html/2/PCSUserC1.ascx" %> 

The TagPrefix and TagName attributes specify the tag name to use (in the form <TagPrefix:TagName>), and you use the Src attribute to point to the file containing your user control. Now you can use the control by adding the following element:

 <form  method="post" runat="server">   <div>     <pcs:UserC1 Runat="server" />   </div> </form>

This is all you need to do to test your user control. Figure 33-2 shows the results of running this code.

image from book
Figure 33-2

As it stands, this control groups two existing controls, an image and a label, in a table layout. Therefore, it falls into the category of a composite control.

To gain control over the displayed suit, you can use an attribute on the <PCS:UserC1> element. Attributes on user control elements are automatically mapped to properties on user controls, so all you have to do to make this work is add a property to the code behind your control, PCSUserC1.ascx.cs. Call this property Suit, and let it take any suit value. To make it easier for you to represent the state of the control, you define an enumeration to hold the four suit names. The best way to do this is to add an App_Code directory to your Web site (App_Code is another “special” directory, like App_Data, whose functionality is defined for you - in this case it holds additional code files for your Web application), then add a .cs file called suit.cs in this directory with code as follows:

 using System; public enum suit {    club, diamond, heart, spade } 

The PCSUserC1 class needs a member variable to hold the suit type, currentSuit:

 public partial class PCSUserC1 : System.Web.UI.UserControl {    protected suit currentSuit; 

And a property to access this member variable, Suit:

  public suit Suit {    get    {       return currentSuit;    }    set    {       currentSuit = value;       suitPic.ImageUrl = "~/Images/" + currentSuit.ToString() + ".bmp";       suitLabel.Text = currentSuit.ToString();    } } 

The set accessor here sets the URL of the image to one of the files you copied earlier, and the text displayed to the suit name.

Next, you must add code to Default.aspx so you can access this new property. You could simply specify the suit using the property you have just added:

 <PCS:UserC1 Runat="server"  Suit="diamond"/>

The ASP.NET processor is intelligent enough to get the correct enumeration item from the string provided. To make things a bit more interesting and interactive, though, you’ll use a radio button list to select a suit:

 <form  runat="server">   <div>     <pcs:UserC1  runat="server" />     <asp:RadioButtonList Runat="server"  AutoPostBack="True">       <asp:ListItem Value="club" Selected="True">Club</asp:ListItem>       <asp:ListItem Value="diamond">Diamond</asp:ListItem>       <asp:ListItem Value="heart">Heart</asp:ListItem>       <asp:ListItem Value="spade">Spade</asp:ListItem>     </asp:RadioButtonList>   </div> </form>

You also need to add an event handler for the SelectedIndexChanged event of the list, which you can do simply by double-clicking the radio button list control in Design view.

Tip 

Note that you have set the AutoPostBack property of this list to True, because the suitList_SelectedIndexChanged() event handler won’t be executed on the server unless a postback is in operation, and this control doesn’t trigger a postback by default.

The suitList_SelectedIndexChanged() method requires the following code in Default.aspx.cs:

 public partial class Default {    protected void suitList_SelectedIndexChanged(object sender, EventArgs e)    {       myUserControl.Suit = (suit)Enum.Parse(typeof(suit),                                             suitList.SelectedItem.Value);    } }

You know that the Value attributes on the <ListItem> elements represent valid values for the suit enumeration you defined earlier, so you simply parse these as enumeration types and use them as values of the Suit property of your user control. You cast the returned object type to suit using simple casing syntax, as this can’t be achieved implicitly.

Now you can change the suit when you run your Web application (see Figure 33-3).

image from book
Figure 33-3

Next, you’ll give your control some methods. Again, this is very simple; you just add methods to the PCSUserC1 class:

  public void Club() {    Suit = suit.club; } public void Diamond() {    Suit = suit.diamond; } public void Heart() {    Suit = suit.heart; } public void Spade() {    Suit = suit.spade; } 

These four methods - Club(), Diamond(), Heart(), and Spade() - change the suit displayed on the screen to the respective suit clicked.

You’ll call these functions from four ImageButton controls in your .aspx page:

 </asp:RadioButtonList> <asp:ImageButton Runat="server"    ImageUrl="~/Images/CLUB.BMP" OnClick="clubButton_Click" /> <asp:ImageButton Runat="server"    ImageUrl="~/Images/DIAMOND.BMP" OnClick="diamondButton_Click" /> <asp:ImageButton Runat="server"    ImageUrl="~/Images/HEART.BMP" OnClick="heartButton_Click" />     <asp:ImageButton Runat="server"        ImageUrl="~/Images/SPADE.BMP" OnClick="spadeButton_Click" />   </div> </form>

You’ll use the following event handlers:

  protected void clubButton_Click(object sender, ImageClickEventArgs e) {    myUserControl.Club();    suitList.SelectedIndex = 0; } protected void diamondButton_Click(object sender, ImageClickEventArgs e) {    myUserControl.Diamond();    suitList.SelectedIndex = 1; } protected void heartButton_Click(object sender, ImageClickEventArgs e) {    myUserControl.Heart();    suitList.SelectedIndex = 2; } protected void spadeButton_Click(object sender, ImageClickEventArgs e) {    myUserControl.Spade();    suitList.SelectedIndex = 3; } 

Tip 

Note that you could use a single event handler for all four buttons, because they have identical method signatures, and detect which button has been pressed and so which method of myUserControl to call, and which index to set dynamically. In this case, though, there wouldn’t be a huge difference in the amount of code required, so for simplicity things are kept separate.

Now you have four new buttons you can use to change the suit, as shown in Figure 33-4.

image from book
Figure 33-4

Now that you’ve created your user control, you can use it in any other Web page simply by using the <%@ Register %> directive and the two source code files (PCSUserC1.ascx and PCSUserC1.ascx.cs) you have created for the control.

User Controls in PCSDemoSite

In the PCSDemoSite, the meeting room booker application from the last chapter has been converted into a user control for ease of reuse. To see the control, you have to log in to the site as User1, with password User1!! (you will learn how the logging in system works later in the chapter), and navigate to the Meeting Room Booker page, as shown in Figure 33-5.

image from book
Figure 33-5

Apart from the obvious change in style, which is achieved by themes, as you see later in this chapter, the major modifications are as follows:

  • The username is automatically taken from user details.

  • There is no extra data display at the bottom of the page, and corresponding DataBind() calls are removed from the code behind.

  • There is no result label beneath the control - the user gets enough feedback by seeing events added to the calendar and event list, without being told that event addition was successful.

  • The page containing the user control uses a master page.

The code modifications to achieve all this are remarkably simple, and you won’t look at them here. You’ll come back to this control later in the chapter, when you look at logging in.

Custom controls go a step beyond user controls in that they are entirely self-contained in C# assemblies, requiring no separate ASP.NET code. This means that you don’t need to go through the process of assembling a user interface (UI) in an .ascx file. Instead, you have complete control over what is written to the output stream, that is, the exact HTML generated by your control.

In general, it will take longer to develop custom controls than user controls, because the syntax is more complex and you often have to write significantly more code to get results. A user control may be as simple as a few other controls grouped together, as you’ve seen, whereas a custom control can do just about anything short of making you a cup of coffee.

To get the most customizable behavior for your custom controls, you can derive a class from System.Web .UI.WebControls.WebControl. If you do this, you are creating a full custom control. Alternatively, you can extend the functionality of an existing control, creating a derived custom control. Finally, you can group existing controls together, much as you did in the last section but with a more logical structure, to create a composite custom control.

Whatever you create can be used in ASP.NET pages in pretty much the same way. All you need to do is place the generated assembly in a location where the Web application that will use it can find it, and register the element names to use with the <%@ Register %> directive. For this location, you have two options: you can either put the assembly in the bin directory of the Web application, or place it in the GAC if you want all Web applications on the server to have access to it. Alternatively, if you are just using a user control on a single Web site, you can just put the .cs file for the control in the App_Code directory for the site.

The <%@ Register %> directive takes a slightly different syntax for custom controls:

 <%@ Register TagPrefix="PCS" Namespace="PCSCustomWebControls"              Assembly="PCSCustomWebControls"%>

You use the TagPrefix option in the same way as before, but you don’t use the TagName or Src attributes. This is because the custom control assembly you use may contain several custom controls, and each of these will be named by its class, so TagName is redundant. In addition, because you can use the dynamic discovery capabilities of the .NET Framework to find your assembly, you simply have to name it and the namespace in it that contains your controls.

In the previous line of code, you are instructing the program to use an assembly called PCSCustomWebControls.dll with controls in the PCSCustomWebControls namespace, and use the tag prefix PCS. If you have a control called Control1 in this namespace, you could use it with the ASP.NET code:

 <PCS:Control1 Runat="server" />

The Assembly attribute is optional - if you have custom controls in the App_Code directory of your site, you can omit this, and the Web site will look at code here for controls. One thing though - the Namespace attribute is not optional. You must include a namespace in code files for custom controls, or the ASP.NET runtime will not be able to find them.

With custom controls it is also possible to reproduce some of the control nesting behavior that exists in list controls:

 <asp:DropDownList  Runat="server" Width="160px">    <asp:ListItem Value="1">The Happy Room</asp:ListItem>    <asp:ListItem Value="2">The Angry Room</asp:ListItem>    <asp:ListItem Value="3">The Depressing Room</asp:ListItem>    <asp:ListItem Value="4">The Funked Out Room</asp:ListItem> </asp:DropDownList>

You can create controls that should be interpreted as being children of other controls in a very similar way to this. This is one of the more advanced techniques that you won’t be looking at in this book.

Custom Control Sample

Now it’s time to put some of this theory into practice. You’ll use a single Web site called PCSCustomCWebApp1 in the C:\ProCSharp\Chapter33\ directory, with a custom control in its App_Code directory to illustrate a simple custom control. The control here will be a multicolored version of the existing Label control, with the ability to cycle through a set of colors for each letter in its text.

The code for the control, RainbowLabel, in the file App_Code\Rainbow.cs, starts with the following using statements:

  using System; using System.Web.UI; using System.Web.UI.WebControls; using System.Drawing; 

These are fairly standard, giving you access to the library of Web types as well as the System.Drawing namespace for the Color enumeration. The class maintains an array of colors to use for letters in its text in a private Color array called colors:

  namespace PCSCustomWebControls {    public class RainbowLabel : Label    {       private Color[] colors = new Color[] {Color.Red,                                             Color.Orange,                                             Color.Yellow,                                             Color.GreenYellow,                                             Color.Blue,                                             Color.Indigo,                                             Color.Violet}; 

To enable color cycling, you also store an integer offset value in a private offset property:

  private int offset {    get    {       object rawOffset = ViewState["_offset"];       if (rawOffset != null)       {          return (int)rawOffset;       }       else       {          ViewState["_offset"] = 0;          return 0;       }    }    set    {       ViewState["_offset"] = value;    } } 

Note that this property isn’t as simple as just storing a value in a member field. This is due to the way ASP.NET maintains state, as discussed in the previous chapter. Controls are instantiated on each postback operation, so to store values you must make use of view state. This is easy to access - you simply use the ViewState collection, which can store any object that is serializable. Otherwise, offset would revert to its initial value between each postback.

To modify offset, you use a method called Cycle():

  public void Cycle() {    offset = ++offset; } 

This simply increments the value stored in the view state for offset.

Finally, you come to perhaps the most important method override for any custom control - Render(). This is where you output HTML, and as such it can be a very complicated method to implement. If you were to take into account all the browsers that may view your controls, and all the variables that could affect rendering, this method could get very big. Fortunately, for this example, it’s quite simple:

  protected override void Render(HtmlTextWriter output) {    string text = Text;    for (int pos = 0; pos < text.Length; pos++)    {             int rgb = colors[(pos + offset) % colors.Length].ToArgb()                                                           & 0xFFFFFF;             output.Write(string.Format(                "<font color=\"#{0:X6}\">{1}</font>", rgb, text[pos]));          }       }    } } 

This method gives you access to the output stream to display your control content. There are only two cases where you don’t need to implement this method:

  • When you are designing a control that has no visual representation (usually known as a component)

  • When you are deriving from an existing control and don’t need to change its display characteristics

Custom controls can also expose custom methods, raise custom events, and respond to child controls (if any). In the case of RainbowLabel, you don’t have to worry about any of this.

Next, you need to modify Default.aspx to view the control and provide access to Cycle(), as follows:

 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"   Inherits="_Default" %>    <%@ Register TagPrefix="pcs" Namespace="PCSCustomWebControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server">   <title>Untitled Page</title> </head> <body>   <form  runat="server">     <div>        <pcs:RainbowLabel runat="server"          Text="Multicolored label!" />       <asp:Button Runat="server"  Text="Cycle colors"         OnClick="cycleButton_Click" />     </div>   </form> </body> </html>

The required code in Default.aspx.cs is simply:

 public partial class _Default : System.Web.UI.Page {    protected void cycleButton_Click(object sender, EventArgs e)    {       rainbowLabel1.Cycle();    } }

Now you can view the sample and cycle the colors in the sample text, as shown in Figure 33-6.

image from book
Figure 33-6

You can do a lot more with custom controls; indeed, the possibilities are practically limitless, but you’ll have to experiment on your own to find out more.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net