Chapter 11. User Controls and Graphics

In this chapter, you'll consider two ways to extend your web pages another notch.

First, you'll tackle user controls, which give you an efficient way to reuse a block of user interface markup—and the code that goes with it. User controls are a key tool for building modular web applications. They can also help you create consistent website designs and reuse your hard work.

Next, you'll explore custom drawing with GDI+. You'll see how you can paint exactly the image you need on request. You'll also learn the best way to incorporate these images into your web pages.

User Controls

A well-built web application divides its work into discrete, independent blocks. The more modular your web application is, the easier it is to maintain your code, troubleshoot problems, and reuse key bits of functionality.

Although it's easy enough to reuse code (you simply need to pull it out of your pages and put it into separate classes), it's not as straightforward to reuse web page markup. You can cut and paste blocks of HTML and ASP.NET control tags, but this causes endless headaches if you want to change your markup later. Instead, you need a way to wrap up web page markup in a reusable package, just as you can wrap up ordinary C# code. The trick is to create a user control.

User controls look pretty much the same as ASP.NET web forms. Like web forms, they are composed of a markup portion with HTML and control tags (the .ascx file) and can optionally use a code-behind file with event handling logic. They can also include the same range of HTML content and ASP.NET controls, and they experience the same events as the Page object (such as Load and PreRender). The only differences between user controls and web pages are as follows:

  • User controls use the file extension .ascx instead of .aspx, and their code-behind files inherit from the System.Web.UI.UserControl class. In fact, the UserControl class and the Page class both inherit from the same base classes, which is why they share so many of the same methods and events, as shown in the inheritance diagram in Figure 11-1.

  • The .ascx file for a user control begins with a <%@ Control %> directive instead of a <%@ Page %> directive.

  • User controls can't be requested directly by a web browser. Instead, they must be embedded inside other web pages.

    The Page and UserControl inheritance chain

    Figure 11.1. The Page and UserControl inheritance chain

Creating a Simple User Control

You can create a user control in Visual Studio in much the same way you add a web page. Just select Website

Creating a Simple User Control

The following user control contains a single Label control:

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

<asp:Label id="lblFooter" runat="server" />

Note that the Control directive uses the same attributes used in the Page directive for a web page, including Language, AutoEventWireup, and Inherits.

The code-behind class for this sample user control is similarly straightforward. It uses the UserControl.Load event to add some text to the label:

public partial class Footer : System.Web.UI.UserControl
{
    protected void Page_Load(Object sender, EventArgs e)
    {
        lblFooter.Text = "This page was served at ";
        lblFooter.Text += DateTime.Now.ToString();
    }
}

To test this user control, you need to insert it into a web page. This is a two-step process. First, you need to add a Register directive to the page that will contain the user control. You place the Register directive immediately after the Page directive. The Register directive identifies the control you want to use and associates it with a unique control prefix, as shown here:

<%@ Register TagPrefix="apress" TagName="Footer" Src="Footer.ascx" %>

The Register directive specifies a tag prefix and name. Tag prefixes group sets of related controls (for example, all ASP.NET web controls use the tag prefix asp). Tag prefixes are usually lowercase—technically, they are case-insensitive—and should be unique for your company or organization. The Src directive identifies the location of the user control template file, not the code-behind file.

Second, you can now add the user control whenever you want (and as many times as you want) in the page by inserting its control tag. Consider this page example:

<%@ Page Language="C#" AutoEventWireup="true"
    CodeFile="FooterHost.aspx.cs" Inherits="FooterHost"%>
<%@ Register TagPrefix="apress" TagName="Footer" Src="Footer.ascx" %>

<!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>Footer Host</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <h1>A Page With a Footer</h1><hr />
      Static Page Text<br /><br />
      <apress:Footer id="Footer1" runat="server" />
    </div>
    </form>
</body>
</html>

This example (shown in Figure 11-2) demonstrates a simple way that you can create a header or footer and reuse it in all the pages in your website just by adding a user control. In the case of your simple footer, you won't save much code. However, this approach will become much more useful for a complex control with extensive formatting or several contained controls.

A page with a user control footer

Figure 11.2. A page with a user control footer

Of course, this only scratches the surface of what you can do with a user control. In the following sections, you'll learn how to enhance a control with properties, methods, and events—transforming it from a simple "include file" into a full-fledged object.

Note

The Page class provides a LoadControl() method that allows you to create a user control dynamically at runtime from an .ascx file. The user control is returned to you as a control object, which you can then add to the Controls collection of a container control on the web page (such as PlaceHolder or Panel) to display it on the page. This technique isn't a good substitute for declaratively using a user control, because it's more complex. However, it does have some interesting applications if you want to generate a user interface dynamically.

In Visual Studio, you have a useful shortcut for adding a user control to a page without typing the Register directive by hand. Start by opening the web page you want to use. Then, find the .ascx file for the user control in the Solution Explorer. Drag it from the Solution Explorer and drop it onto the visual design area of your web form (not the source view area). Visual Studio will automatically add the Register directive for the user control, as well as an instance of the user control tag.

Another option is to configure your user control in the web.config file for your web application. Here's an example that registers the Footer control in this way:

<configuration>
  <system.web>
    <pages>
      <controls>
        <add tagPrefix="apress" src="~Footer.ascx" tagName="Footer" />
      </controls>
</pages>
  </system.web>
</configuration>

You can add more <add> elements to the <controls> section to register as many user controls as you want.

When you take this step, the user control is available in every page, with no Register directives required. Visual Studio will also respect your configuration, so if you drop a user control onto a page, it will use the defined tag prefix and won't add a Register directive.

Independent User Controls

Conceptually, two types of user controls exist: independent and integrated. Independent user controls don't interact with the rest of the code on your form. The Footer user control is one such example. Another example might be a LinkMenu control that contains a list of buttons offering links to other pages. This LinkMenu user control can handle the events for all the buttons and then run the appropriate Response.Redirect() code to move to another web page. Or it can just be an ordinary HyperLink control that doesn't have any associated server-side code. Every page in the website can then include the same LinkMenu user control, enabling painless website navigation with no need to worry about frames.

Note

You can use the more feature-rich navigation controls to provide website navigation. Creating your own custom controls gives you a more flexible (and more tedious) approach to providing navigation. You're most likely to use custom controls rather than a whole site map for straightforward navigation between a few pages.

The following sample defines a simple control that presents an attractively formatted list of links. Note that the style attribute of the <div> tag (which defines fonts and formatting) has been omitted for clarity.

<%@ Control Language="C#" AutoEventWireup="true"
    CodeFile="LinkMenu.ascx.cs" Inherits="LinkMenu" %>
<div>
  Products:<br />
  <asp:HyperLink id="lnkBooks" runat="server"
    NavigateUrl="MenuHost.aspx?product=Books">Books
  </asp:HyperLink><br />
  <asp:HyperLink id="lnkToys" runat="server"
    NavigateUrl="MenuHost.aspx?product=Toys">Toys
  </asp:HyperLink><br />
  <asp:HyperLink id="lnkSports" runat="server"
    NavigateUrl="MenuHost.aspx?product=Sports">Sports
  </asp:HyperLink><br />
  <asp:HyperLink id="lnkFurniture" runat="server"
    NavigateUrl="MenuHost.aspx?product=Furniture">Furniture
  </asp:HyperLink>
</div>

The links don't actually trigger any server-side code—instead, they render themselves as ordinary HTML anchor tags with a hard-coded URL.

To test this menu, you can use the following MenuHost.aspx web page. It includes two controls: the Menu control and a Label control that displays the product query string parameter. Both are positioned using a table.

<%@ Page Language="C#" AutoEventWireup="true"
    CodeFile="MenuHost.aspx.cs" Inherits="MenuHost"%>
<%@ Register TagPrefix="apress" TagName="LinkMenu" Src="LinkMenu.ascx" %>

<!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>Menu Host</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <table>
        <tr>
          <td><apress:LinkMenu id="Menu1" runat="server" /></td>
          <td><asp:Label id="lblSelection" runat="server" /></td>
        </tr>
      </table>
    </div>
    </form>
</body>
</html>

When the MenuHost.aspx page loads, it adds the appropriate information to the lblSelection control:

protected void Page_Load(Object sender, EventArgs e)
{
    if (Request.Params["product"] != null)
    {
        lblSelection.Text = "You chose: ";
        lblSelection.Text += Request.Params["product"];
    }
}

Figure 11-3 shows the end result. Whenever you click a button, the page is posted back, and the text is updated.

The LinkMenu user control

Figure 11.3. The LinkMenu user control

You could use the LinkMenu control to repeat the same menu on several pages. This is particularly handy in a situation where you can't use master pages to standardize layout (possibly because the pages are too different).

Integrated User Controls

Integrated user controls interact in one way or another with the web page that hosts them. When you're designing these controls, the class-based design tips you learned in Chapter 4 really become useful.

A typical example is a user control that allows some level of configuration through properties. For instance, you can create a footer that supports two different display formats: long date and short time. To add a further level of refinement, the Footer user control allows the web page to specify the appropriate display format using an enumeration.

The first step is to create an enumeration in the custom Footer class. Remember, an enumeration is simply a type of constant that is internally stored as an integer but is set in code by using one of the allowed names you specify. Variables that use the FooterFormat enumeration can take the value FooterFormat.LongDate or FooterFormat.ShortTime:

public enum FooterFormat
{
  LongDate,
  ShortTime
}

The next step is to add a property to the Footer class that allows the web page to retrieve or set the current format applied to the footer. The actual format is stored in a private variable called format, which is set to the long date format by default when the control is first created. (If you're hazy on how property procedures work, feel free to review the explanation in Chapter 3.)

private FooterFormat format = FooterFormat.LongDate;

public FooterFormat Format
{
    get { return format; }
    set { format = value; }
}

Finally, the UserControl.Load event handler needs to take account of the current footer state and format the output accordingly. The following is the full Footer class code:

public partial class Footer : System.Web.UI.UserControl
{
    public enum FooterFormat
    { LongDate, ShortTime }

    private FooterFormat format = FooterFormat.LongDate;
    public FooterFormat Format
    {
        get { return format; }
        set { format = value; }
    }

    protected void Page_Load(Object sender, EventArgs e)
    {
        lblFooter.Text = "This page was served at ";

        if (format == FooterFormat.LongDate)
        {
            lblFooter.Text += DateTime.Now.ToLongDateString();
        }
        else if (format == FooterFormat.ShortTime)
        {
            lblFooter.Text += DateTime.Now.ToShortTimeString();
        }
    }
}

To test this footer, you need to create a page that modifies the Format property of the Footer user control. Figure 11-4 shows an example page, which automatically sets the Format property for the user control to match a radio button selection whenever the page is posted back.

The modified footer

Figure 11.4. The modified footer

Note that the user control property is modified in the Page.Load event handler, not the cmdRefresh.Click event handler. The reason is that the Load event occurs before the user control has been rendered each time the page is created. The Click event occurs after the user control has been rendered, and though the property change is visible in your code, it doesn't affect the user control's HTML output, which has already been added to the page.

public partial class FooterHost : System.Web.UI.Page
{
    protected void Page_Load(Object sender, EventArgs e)
    {
        if (optLong.Checked)
        {
            Footer1.Format = Footer.FooterFormat.LongDate;
        }
        else if (optShort.Checked)
        {
            Footer1.Format = Footer.FooterFormat.ShortTime;
        }
        else
        {
            // The default value in the Footer class will apply.
        }
    }
}

You can also set the initial appearance of the footer in the control tag:

<apress:Footer Format="ShortTime" id="Footer1" runat="server" />

As with all web control markup, ASP.NET converts strings (like "ShortTime") to the corresponding enumeration value without a problem.

User Control 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.

Creating a web control that uses events is fairly easy. In the following example, you'll see a version of the LinkMenu control that uses events. Instead of navigating directly to the appropriate page when the user clicks a button, the control raises an event, which the web page can choose to handle.

The first step to create this control is to define the events. Remember, to define an event, you must first choose an event signature. 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, while the second 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 predefined EventArgs class, which doesn't contain any additional data. Many events in ASP.NET, such as Page.Load or Button.Click, follow this pattern.) You can refer to Chapter 4 for a quick overview of how to use events in .NET.

The LinkMenu2 control uses a single event, which indicates when a link is clicked:

public partial class LinkMenu2 : System.Web.UI.UserControl
{
    public event EventHandler LinkClicked;

    ...
}

This code defines an event named LinkClicked. The LinkClicked event has the signature specified by the System.EventHandler delegate, which includes two parameters—the event sender and an ordinary EventArgs object. That means that any event handler you create to handle the LinkClicked event must look like this:

protected void LinkMenu_LinkClicked(object sender, EventArgs e)
{ ... }

This takes care of defining the event, but what about raising it? This part is easy. To fire the event, the LinkMenu2 control simply calls the event by name and passes in the two parameters, like this:

// Raise the LinkClicked event, passing a reference to
// the current object (the sender) and an empty EventArgs object.
LinkClicked(this, EventArgs.Empty);

The LinkMenu2 control actually needs a few more changes. The original version used the HyperLink control. This won't do, because the HyperLink control doesn't fire an event when the link is clicked. Instead, you'll need to use the LinkButton. The LinkButton fires the Click event, which the LinkMenu2 control can intercept, and then raises the LinkClicked event to the web page.

The following is the full user control code:

public partial class LinkMenu2 : System.Web.UI.UserControl
{
    public event EventHandler LinkClicked;

    protected void lnk_Click(object sender, EventArgs e)
    {
        // One of the LinkButton controls has been clicked.
        // Raise an event to the page.
        if (LinkClicked != null)
        {
            LinkClicked(this, EventArgs.Empty);
        }
    }
}

Notice that before raising the LinkClicked event, the LinkMenu2 control needs to test the LickedClick event for a null reference. A null reference exists if no event handlers are attached to the event. In this case, you shouldn't try to raise the event, because it would only cause an error.

You can create a page that uses the LinkMenu2 control and add an event handler. Unfortunately, you won't be able to connect these event handlers using the Visual Studio Properties window, because the Properties window won't show the custom events that the user control provides. Instead, you'll need to modify the LinkMenu2 tag directly, as shown here:

<apress:LinkMenu2 id="Menu1" runat="server" OnLinkClicked="LinkClicked" />

and here's the event handler that responds in the web page:

protected void LinkClicked(object sender, EventArgs e)
{
    lblClick.Text = "Click detected.";
}

Note

You won't be able to create user control event handlers through the Visual Studio Properties window. Instead, you'll need to type in your event handler by hand.

Figure 11-5 shows the result.

Using the LinkMenu2 user control

Figure 11.5. Using the LinkMenu2 user control

Conceptually, this approach should give your web page more power to customize how the user control works. Unfortunately, that's not the case at the moment, because a key piece of information is missing. When the LinkClicked event occurs, the web page has no way of knowing what link was clicked, which prevents it from taking any kind of reasonable action. The only way to solve this problem is to create a more intelligent event that can transmit some information through event arguments. You'll see how in the next section.

Passing Information with Events

In the current LinkMenu2 example, no custom information is passed along with the event. In many cases, however, you want to convey additional information that relates to the event. To do so, you need to create a custom class that derives from EventArgs.

The LinkClickedEventArgs class that follows allows the LinkMenu2 user control to pass the URL that the user selected through a Url property. It also provides a Cancel property. If set to true, the user control will stop its processing immediately. But if Cancel remains false (the default), the user control will send the user to the new page. This way, the user control still handles the task of redirecting the user, but it allows the web page to plug into this process and change it or stop it (for example, if there's unfinished work left on the current page).

public class LinkClickedEventArgs : EventArgs
{
    public string Url {get; set;}
    public bool Cancel {get; set;}

    public LinkClickedEventArgs(string url)
    {
        Url = url;
    }
}

To use this custom EventArgs class, you need to modify the definition of the LinkClicked event so it uses the LinkClickedEventArgs object:

public event EventHandler<LinkClickedEventArgs> LinkClicked;

It's the magic of generics that makes this work. Essentially, you're configuring the generic EventHandler class to use the EventArgs class you want—in this case, LinkClickedEventArgs.

Next, your user control code for raising the event needs to submit the required information when calling the event. But how does the user control determine what link was clicked? The trick is to switch from the LinkButton.Click event to the LinkButton.Command event. The Command event automatically gets the CommandArgument that's defined in the tag. So if you define your LinkButton controls like this:

<asp:LinkButton ID="lnkBooks" runat="server"
  CommandArgument="Menu2Host.aspx?product=Books" OnCommand="lnk_Command">Books
</asp:LinkButton><br />
<asp:LinkButton ID="lnkToys" runat="server"
  CommandArgument="Menu2Host.aspx?product=Toys" OnCommand="lnk_Command">Toys
</asp:LinkButton><br />
<asp:LinkButton ID="lnkSports" runat="server"
  CommandArgument="Menu2Host.aspx?product=Sports" OnCommand="lnk_Command">Sports
</asp:LinkButton><br />
<asp:LinkButton ID="lnkFurniture" runat="server"
  CommandArgument="Menu2Host.aspx?product=Furniture" OnCommand="lnk_Command">
Furniture</asp:LinkButton>

you can pass the link along to the web page like this:

LinkClickedEventArgs args = new LinkClickedEventArgs((string)e.CommandArgument);
LinkClicked(this, args);

Here's the complete user control code. It implements one more feature. After the event has been raised and handled by the web page, the LinkMenu2 checks the Cancel property. If it's false, it goes ahead and performs the redirect using Reponse.Redirect().

public partial class LinkMenu2 : System.Web.UI.UserControl
{
    public event LinkClickedEventHandler LinkClicked;

    protected void lnk_Command(object sender, CommandEventArgs e)
    {
        // One of the LinkButton controls has been clicked.
        // Raise an event to the page.
        if (LinkClicked != null)
        {
            // Pass along the link information.
            LinkClickedEventArgs args =
              new LinkClickedEventArgs((string)e.CommandArgument);
            LinkClicked(this, args);

            // Perform the redirect.
            if (!args.Cancel)
            {
                // Notice we use the Url from the LinkClickedEventArgs
                // object, not the original link. That means the web page
// can change the link if desired before the redirect.
                Response.Redirect(args.Url);
            }
        }
    }
}

Finally, you need to update the code in the web page (where the user control is placed) so that its event handler uses the new signature. In the following code, the LinkClicked event handler checks the URL and allows it in all cases except one:

protected void LinkClicked(object sender, LinkClickedEventArgs e)
{
    if (e.Url == "Menu2Host.aspx?product=Furniture")
    {
        lblClick.Text = "This link is not allowed.";
        e.Cancel = true;
    }
    else
    {
        // Allow the redirect, and don't make any changes to the URL.
    }
}

If you click the Furniture link, you'll see the message shown in Figure 11-6.

Handling a user control event in the page

Figure 11.6. Handling a user control event in the page

Dynamic Graphics

One of the features of the .NET Framework is GDI+, a set of classes designed for drawing images. You can use GDI+ in a Windows or an ASP.NET application to create dynamic graphics. In a Windows application, the graphics you draw would be copied to a window for display. In ASP.NET, your code can render the graphics you want and send them directly to the client browser.

In general, using GDI+ code to draw a graphic is slower than using a ready-made image file. However, GDI+ gives you much more freedom. For example, you can tailor your image to suit a particular purpose, incorporating information such as the date or current user name. You can also mingle text, shapes, and other bitmaps to create a complete picture.

Basic Drawing

You need to follow four basic steps when using GDI+. First, you have to create an in-memory bitmap. This is the drawing space where you'll create your masterpiece. To create the bitmap, declare a new instance of the System.Drawing.Bitmap class. You must specify the height and width of the image in pixels. Be careful—don't make the bitmap larger than required, or you'll needlessly waste memory.

// Create an in-memory bitmap where you will draw the image.
// The Bitmap is 300 pixels wide and 50 pixels high.
Bitmap image = new Bitmap(300, 50);

The next step is to create a GDI+ graphics context for the image, which is represented by a System.Drawing.Graphics object. This object provides the methods that allow you to render content to the in-memory bitmap. To create a Graphics object from an existing Bitmap object, you just use the static Graphics.FromImage() method, as shown here:

Graphics g = Graphics.FromImage(image);

Note

The Graphics.FromImage() method works with any Image object. Classes such as Bitmap derive from Image, so they work fine.

Now comes the interesting part. Using the methods of the Graphics class, you can draw text, shapes, and images on the bitmap. Table 11-1 lists some of the most fundamental methods of the Graphics class. The methods that begin with the word Draw draw outlines, while the methods that begin with the word Fill draw solid regions. The only exceptions are the DrawString() method, which draws filled-in text using a font you specify, and the methods for copying bitmap images, such as DrawIcon() and DrawImage().

Table 11.1. Drawing Methods of the Graphics Class

Method

Description

DrawArc()

Draws an arc representing a portion of an ellipse specified by a pair of coordinates, a width, and a height (or some other combination of information, if you use one of the overloaded versions of this method).

DrawBezier() and DrawBeziers()

Draws the infamous and attractive Bezier curve, which is defined by four control points.

DrawClosedCurve()

Draws a curve and then closes it off by connecting the end points.

DrawCurve()

Draws a curve (technically, a cardinal spline).

DrawEllipse()

Draws an ellipse defined by a bounding rectangle specified by a pair of coordinates, a height, and a width.

DrawIcon() and DrawIconUnstretched()

Draws the icon represented by an Icon object and (optionally) stretches it to fit a given rectangle.

DrawImage() and DrawImageUnscaled()

Draws the image represented by an Image-derived object (such as a Bitmap object) and (optionally) stretches it to fit a given rectangle.

DrawLine()and DrawLines()

Draws one or more lines. Each line connects the two points specified by a coordinate pair.

DrawPie()

Draws a "piece of pie" shape defined by an ellipse specified by a coordinate pair, a width, a height, and two radial lines.

DrawPolygon()

Draws a multisided polygon defined by an array of points.

DrawRectangle() and DrawRectangles()

Draws one or more ordinary rectangles. Each rectangle is defined by a starting coordinate pair, a width, and a height.

DrawString()

Draws a string of text in a given font.

DrawPath()

Draws a more complex shape that's defined by the Path object.

FillClosedCurve()

Draws a curve, closes it off by connecting the end points, and fills it.

FillEllipse()

Fills the interior of an ellipse.

FillPie()

Fills the interior of a "piece of pie" shape.

FillPolygon()

Fills the interior of a polygon.

FillRectangle() and FillRectangles()

Fills the interior of one or more rectangles.

FillPath()

Fills the interior of a complex shape that's defined by the Path object.

When calling the Graphics class methods, you need to specify several parameters to indicate the pixel coordinates for what you want to draw. For example, when drawing a rectangle, you need to specify the location of the top-left corner and its width and height. Here's an example of how you might draw a solid rectangle in yellow:

// Draw a rectangle starting at location (0, 0)
// that is 300 pixels wide and 50 pixels high.
g.FillRectangle(Brushes.Yellow, 0, 0, 300, 50);

When measuring pixels, the point (0, 0) is the top-left corner of your image in (x, y) coordinates. The x coordinate increases as you go farther to the right, and the y coordinate increases as you go farther down. In the current example, the image is 300 pixels wide and 50 pixels high, which means the point (299, 49) is the bottom-right corner.

Note

This code performs its drawing on the in-memory Bitmap object created earlier. Until this image is rendered (a skill you'll pick up shortly), you won't actually see anything on the web page.

You'll also notice that you need to specify either a Brush or a Pen object when you draw most content. (Both of these classes are defined in the System.Drawing namespace, alongside the Graphics class.) Methods that draw shape outlines require a Pen, while methods that draw filled-in shapes require a Brush. You can create your own custom Pen and Brush objects, but .NET provides an easier solution with the Brushes and Pens classes. These classes expose static properties that provide various Brushes and Pens for different colors. For example, Brushes.Yellow returns a Brush object that fills shapes using a solid yellow color, and Pens.Yellow returns a Pen object that draws shape outlines using the same solid yellow color.

Once the image is complete, you can send it to the browser using the Image.Save() method. Conceptually, you "save" the image to the browser's response stream. It then gets sent to the client and displayed in the browser.

// Render the image to the HTML output stream.
image.Save(Response.OutputStream,
  System.Drawing.Imaging.ImageFormat.Gif);

Tip

You can save an image to any valid stream, including the FileStream class described in Chapter 17. This technique allows you to save dynamically generated images to disk so you can use them later in other web pages.

Finally, you should explicitly release your image and graphics context when you're finished, because both hold onto some unmanaged resources that might not be released right away if you don't:

g.Dispose();
image.Dispose();

Using GDI+ is a specialized technique, and its more advanced features are beyond the scope of this book. However, you can learn a lot by considering a couple of straightforward examples.

Drawing a Custom Image

Using the techniques you've learned, it's easy to create a simple web page that uses GDI+. The next example uses GDI+ to render some text in a bordered rectangle with a happy-face graphic next to it.

Here's the code you'll need:

protected void Page_Load(Object sender, EventArgs e)
{
    // Create an in-memory bitmap where you will draw the image.
    // The Bitmap is 300 pixels wide and 50 pixels high.
    Bitmap image = new Bitmap(300, 50);

    // Get the graphics context for the bitmap.
    Graphics g = Graphics.FromImage(image);

    // Draw a solid yellow rectangle with a red border.
    g.FillRectangle(Brushes.LightYellow, 0, 0, 300, 50);
    g.DrawRectangle(Pens.Red, 0, 0, 299, 49);

    // Draw some text using a fancy font.
    Font font = new Font("Alba Super", 20, FontStyle.Regular);
    g.DrawString("This is a test.", font, Brushes.Blue, 10, 0);

    // Copy a smaller gif into the image from a file.
    System.Drawing.Image icon = Image.FromFile(Server.MapPath("smiley.gif"));
    g.DrawImageUnscaled(icon, 240, 0);

    // Render the entire bitmap to the HTML output stream.
    image.Save(Response.OutputStream,
      System.Drawing.Imaging.ImageFormat.Gif);

    // Clean up.
    g.Dispose();
    image.Dispose();
}

This code is easy to understand. It follows the basic pattern set out earlier—it creates the in-memory Bitmap, gets the corresponding Graphics object, performs the painting, and then saves the image to the response stream. This example uses the FillRectangle(), DrawRectangle(), DrawString(), and DrawImageUnscaled() methods to create the complete drawing shown in Figure 11-7.

Tip

Because this image is generated on the web server, you can use any font that is installed on the server. The client doesn't need to have the same font, because the client receives the text as a rendered image.

Drawing a custom image

Figure 11.7. Drawing a custom image

Placing Custom Images Inside Web Pages

The Image.Save() approach demonstrated so far has one problem. When you save an image to the response stream, you overwrite whatever information ASP.NET would otherwise use. If you have a web page that includes other content and controls, this content won't appear at all in the final web page. Instead, the dynamically rendered graphics replace it.

Fortunately, this has a simple solution: you can link to a dynamically generated image using the HTML <img> tag or the Image web control. But instead of linking your image to a fixed image file, link it to the .aspx file that generates the picture.

For example, you could create a file named GraphicalText.aspx that writes a dynamically generated image to the response stream. In another page, you could show the dynamic image by adding an Image web control and setting the ImageUrl property to GraphicalText.aspx. In fact, you'll even see the image appear in Visual Studio's design-time environment before you run the web page!

When you use this technique to embed dynamic graphics in web pages, you also need to think about how the web page can send information to the dynamic graphic. For example, what if you don't want to show a fixed piece of text, but instead you want to generate a dynamic label that incorporates the name of the current user? (In fact, if you do want to show a fixed piece of text, it's probably better to create the graphic ahead of time and store it in a file, rather than generating it using GDI+ code each time the user requests the page.) One solution is to pass the information using the query string, as described in Chapter 8. The page that renders the graphic can then check for the query string information it needs.

Here's how you'd rewrite the dynamic graphic generator with this in mind:

// Get the user name.
if (Request.QueryString["Name"] == null)
{
    // No name was supplied.
    // Don't display anything.
}
else
{
string name = Request.QueryString["Name"];

    // Create an in-memory bitmap where you will draw the image.
    Bitmap image = new Bitmap(300, 50);

    // Get the graphics context for the bitmap.
    Graphics g = Graphics.FromImage(image);

    g.FillRectangle(Brushes.LightYellow, 0, 0, 300, 50);
    g.DrawRectangle(Pens.Red, 0, 0, 299, 49);

    // Draw some text based on the query string.
    Font font = new Font("Alba Super", 20, FontStyle.Regular);
    g.DrawString(name, font, Brushes.Blue, 10, 0);

    // Render the entire bitmap to the HTML output stream.
    image.Save(Response.OutputStream,
      System.Drawing.Imaging.ImageFormat.Gif);

    g.Dispose();
    image.Dispose();
}

Conceptually, this code isn't much different than the examples you've seen before. The only change is that one piece of information—the string that's used with the DrawString() method—is retrieved from the query string.

Figure 11-8 shows a page that uses this dynamic graphic page, along with two Label controls. The page passes the query string argument Joe Brown to the page. The full Image.ImageUrl thus becomes GraphicalText.aspx?Name=Joe%20Brown, as shown here:

<asp:Label id="Label1" runat="server">Here is some content.</asp:Label>
<br /><br />
<asp:Image id="Image1" runat="server"
  ImageUrl="GraphicalText.aspx?Name=Joe%20Brown"></asp:Image>
<br /><br />
<asp:Label id="Label2" runat="server">Here is some more content.</asp:Label>
Mingling custom images and controls on the same page

Figure 11.8. Mingling custom images and controls on the same page

It's possible that you might need to send more information or more complex information to the page that draws the image. For example, you might want to pass a data object to a page that draws a pie chart. In this case, the query string isn't good enough, and you'll need to use a different type of state management. One option is session state, as described in Chapter 8.

Image Format and Quality

When you render an image, you can also choose the format you want to use. JPEG offers the best color support and graphics, although it uses compression that can lose detail and make text look fuzzy. GIF (the standard used in the examples so far) is often a better choice for graphics containing text, but it doesn't offer good support for color. In .NET, every GIF uses a fixed palette with 256 generic colors. If you use a color that doesn't map to one of these presets, the color will be dithered, leading to a less-than-optimal graphic.

However, the best format choice is PNG. PNG is an all-purpose format that always provides high quality by combining the lossless compression of GIFs with the rich color support of JPEGs. (The only hiccup is versions of Internet Explorer before version 7 don't correctly deal with PNG content returned from a web page. If you're using one of these old versions of IE, you won't see the picture content—instead, you'll receive a message prompting you to download the picture and open it in another program. To sidestep this problem, you need to use the <img> tag approach shown in the previous example.)

You also need to be aware of one more quirk when using PNG—you can't use the Bitmap.Save() method shown in earlier examples. If you do, an error will occur. Technically, the problem is the Save() method requires a seekable stream—a stream where the position can be changed at will. That's because .NET needs to be able to move back and forth through the picture content while it's being generated.

The solution is easy to implement, if a little awkward. Instead of saving directly to Response.OutputStream, you can create a System.IO.MemoryStream object, which represents an in-memory buffer of data. The MemoryStream is always seekable, so you can save the image to this object. Once you've performed this step, you can easily copy the data from the MemoryStream to the Response.OutputStream. The only disadvantage is that this technique requires more memory because the complete rendered content of the graphic needs to be held in memory at once. However, the graphics you use in web pages generally aren't that large, so you probably won't observe any reduction in performance.

To implement this solution, start by importing the System.IO namespace:

using System.IO;

Now you can replace the previous example with this modified code that saves the image in PNG format. The changed lines are highlighted.

// Get the user name.
if (Request.QueryString["Name"] == null)
{
    // No name was supplied.
    // Don't display anything.
}
else
{
    string name = Request.QueryString["Name"];

    // Create an in-memory bitmap where you will draw the image.
    Bitmap image = new Bitmap(300, 50);

    // Get the graphics context for the bitmap.
    Graphics g = Graphics.FromImage(image);

    g.FillRectangle(Brushes.LightYellow, 0, 0, 300, 50);
    g.DrawRectangle(Pens.Red, 0, 0, 299, 49);

    // Draw some text based on the query string.
    Font font = new Font("Alba Super", 20, FontStyle.Regular);
    g.DrawString(name, font, Brushes.Blue, 10, 0);

    Response.ContentType = "image/png";

    // Create the PNG in memory.
    MemoryStream mem = new MemoryStream();
    image.Save(mem, System.Drawing.Imaging.ImageFormat.Png);

    // Write the MemoryStream data to the output stream.
    mem.WriteTo(Response.OutputStream);

    g.Dispose();
    image.Dispose();
}

Note

You'll learn more about streams when you tackle file access in Chapter 17.

Quality isn't just determined by the image format. It also depends on the way you draw the image content onto the in-memory bitmap. GDI+ allows you to choose between optimizing your drawing code for appearance or speed. When you choose to optimize for the best appearance, .NET uses extra rendering techniques such as antialiasing to improve the drawing.

Antialiasing smooths jagged edges in shapes and text. It works by adding shading at the border of an edge. For example, gray shading might be added to the edge of a black curve to make a corner look smoother. Technically, antialiasing blends a curve with its background. Figure 11-9 shows a close-up of an antialiased ellipse.

Antialiasing with an ellipse

Figure 11.9. Antialiasing with an ellipse

To use smoothing in your applications, you set the SmoothingMode property of the Graphics object. You can choose between None, HighSpeed (the default), AntiAlias, and HighQuality (which is similar to AntiAlias but uses other, slower optimizations that improve the display on LCD screens). The Graphics.SmoothingMode property is one of the few stateful Graphics class members. This means you set it before you begin drawing, and it applies to any text or shapes you draw in the rest of the paint session (until the Graphics object is released).

g.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias;

Tip

Antialiasing makes the most difference when you're displaying curves. That means it will dramatically improve the appearance of ellipses, circles, and arcs, but it won't make any difference with straight lines, squares, and rectangles.

You can also use antialiasing with fonts to soften jagged edges on text. You can set the Graphics.TextRenderingHint property to ensure optimized text. You can choose between SingleBitPerPixelGridFit (fastest performance and lowest quality), AntiAliasGridFit (better quality but slower performance), and ClearTypeGridFit (the best quality on an LCD display). Or you can use the SystemDefault value to apply whatever font-smoothing settings the user has configured. SystemDefault is the default setting, and the default system settings for most computers enable text antialiasing. Even if you don't set this, your dynamically rendered text will probably be drawn in high quality. However, because you can't necessarily control the system settings of the web server, it's a good practice to specify this setting explicitly if you need to draw text in an image.

The Last Word

In this chapter, you put two more tools in your ASP.NET toolkit. First, you saw how user controls allow you to reuse a block of user interface in more than one web page. Next, you considered how custom drawing allows you to create made-to-measure graphics.

In the following chapter, you'll learn about themes and master pages—two features that complement user controls and give you even more ways to standardize the look and feel of your web pages. Themes are more fine-grained than user controls—they group together formatting presets that you can apply to individual controls to ensure a slick, consistent style throughout your application. Master pages are broader than user controls—they allow you to define a standardized page template that you can apply to lock down the appearance and layout of multiple pages, giving you complete consistency. Learning how to mix all these ingredients is part of the fine art of ASP.NET programming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.62.105