Graphical Controls

Graphical Controls

By writing to the output stream with HtmlTextWriter, a custom control can render anything that can be expressed in HTML. Controls that require more latitude than HTML provides can return graphical images. The images can be stored statically (for example, in JPEG files) on the server, or they can be generated at run time and even customized for each request. Returning dynamically generated images frees controls from the limitations of HTML and opens the door to a world of possibilities, from controls that render graphs and pie charts to controls that render maps, formatted reports, and virtually anything else you can dream up.

The secret to authoring a graphical control is to code its Render method to return an <img> tag. If the image is static that is, if it s stored in a file on the server the tag s Src attribute identifies the image file:

<img src="/books/4/347/1/html/2/staticimage.jpg">

If the image is dynamically generated, however, the Src attribute must point to a URL that creates the image on the fly. The following <img> tag references a URL that dynamically generates an image based on input passed in the query string:

<img src="/books/4/347/1/html/2/imagegen.ashx?shape=circle&color=red">

What is ImageGen.ashx? It s not a file; it s an HTTP handler. Specifically, it s an HTTP handler you write that parses a query string and returns a dynamically generated image. We haven t talked about HTTP handlers yet. Let s remedy that shortcoming right now.

HTTP Handlers

HTTP handlers are one of the fundamental building blocks of ASP.NET. An HTTP handler is a class that handles HTTP requests for a specific endpoint (URL) or set of endpoints on a Web server. HTTP handlers built into ASP.NET handle requests for ASPX files, ASCX files, and other ASP.NET file types. In addition, you can extend ASP.NET with HTTP handlers of your own. Entries in Web.config map URLs to HTTP handlers. (We haven t talked about Web.config and the role that it plays in configuring ASP.NET applications yet, but don t worry: you ll learn all about it in the next chapter.) The following statements in a Web.config file map requests for ImageGen.ashx targeted at this directory (the directory that hosts Web.config) and its subdirectories to a class named ImageGen in an assembly named DynaImageLib.dll:

<httpHandlers> <add verb="*" path="ImageGen.ashx" type="ImageGen, DynaImageLib" /> </httpHandlers>

When an HTTP request arrives for ImageGen.ashx, ASP.NET instantiates ImageGen and passes it the request. Assuming ImageGen is an image generator, it responds by creating an image and returning it in the HTTP response. Here s a generic template for an HTTP handler that creates an image in memory and returns it to the requestor as a JPEG. The hard part building the image and writing it out to the HTTP response as a JPEG-formatted bit stream is vastly simplified by the FCL s Bitmap and Graphics classes:

public class ImageGen : IHttpHandler { public void ProcessRequest (HttpContext context) { // Create a bitmap that measures 128 pixels square Bitmap bitmap = new Bitmap (128, 128, PixelFormat.Format32bppArgb); // Create a Graphics object for drawing to the bitmap Graphics g = Graphics.FromImage (bitmap); // TODO: Use Graphics methods to draw the image . . . // Set the response's content type to image/jpeg context.Response.ContentType = "image/jpeg"; // Write the image to the HTTP response bitmap.Save (context.Response.OutputStream, ImageFormat.Jpeg); // Clean up before returning bitmap.Dispose (); g.Dispose (); } public bool IsReusable { // Returning true enables instances of this class to be // pooled and reused. Return false if ImageGen instances // should NOT be reused. get { return true; } } } 

ImageGen can be deployed in its own assembly or in the same assembly as a control. Once deployed, it s invoked by ASP.NET whenever an HTTP request arrives for ImageGen.ashx. Invoke means ASP.NET instantiates ImageGen and calls its ProcessRequest method. ProcessRequest receives an HttpContext object whose Request property provides access to input parameters encoded in the query string. ProcessRequest writes to the HTTP response using the HttpContext object s Response property. To return an image, ProcessRequest saves the bits making up the image to the stream represented by HttpContext.Response.OutputStream.

Incidentally, it doesn t matter that there is no file named ImageGen.ashx; what s important is that ImageGen.ashx is mapped to ImageGen via Web.config. You don t have to use the file name extension ASHX for HTTP handlers, but ASHX is a widely used convention. Using ASHX as the handler s file name extension also prevents you from having to register a special file name extension in the IIS metabase. (If you picked an arbitrary file name extension say, IGEN for your HTTP handler, you d also have to map *.igen to Aspnet_isapi.dll in the IIS metabase. Otherwise, IIS wouldn t forward requests for IGEN files to ASP.NET.) ASHX files are already mapped to ASP.NET in the IIS metabase, so requests for files with ASHX file name extensions are automatically handed off to ASP.NET. You can also write ASPX files that generate and return images, but using a dedicated HTTP handler is cleaner because it requires no additional files on the server.

With this background in mind, let s close the chapter with a control that returns a dynamically generated image depicting an odometer. One application for such a control is to implement the ubiquitous hit counters found on sites all over the Web.

The Odometer Control

The Odometer control shown in Figure 8-29 renders itself using dynamically generated images. The control s programmatic interface consists of the following public properties:

Property

Description

Count

Gets and sets the value displayed by the control

Digits

Gets and sets the number of digits displayed

Width

Gets and sets the control s width

Height

Gets and sets the control s height

ForeColor

Gets and sets the color of the control s numbers

BackColor1

Gets and sets the first of two background colors behind the numbers

BackColor2

Gets and sets the second of two background colors behind the numbers

BorderColor

Gets and sets the color of the control s border

The two BackColor properties merit further explanation. The Odometer control uses a pair of LinearGradientBrushes (discussed in Chapter 4) to paint the background behind the numbers. It exposes the colors of these brushes through BackColor1 and BackColor2. By default, BackColor1 is black and BackColor2 is light gray, which produces a background that fades from black to light gray and then back to black again, yielding the realistic look depicted in Figure 8-29. If you prefer a flat background, set BackColor1 and BackColor2 to the same color. Setting both to red produces red cells behind the numbers.

Figure 8-29

The Odometer control in action.

It s simple to add an Odometer control to a Web page and configure it to display the number 1,000:

<%@ Register TagPrefix="win" Namespace="Wintellect" Assembly="OdometerControl" %> . . . <win:Odometer Count="1000" RunAt="server" />

The following statement configures the control to display five digits ( 01000 ) instead of the four that would normally be displayed for 1,000:

<win:Odometer Count="1000" Digits="5" RunAt="server" />

The next statement does the same thing, but it also configures the control to display numbers against a flat black background:

<win:Odometer Count="1000" Digits="5" RunAt="server" BackColor1="black" BackColor2="black" />

If you d like, you can set Count s value at run time by initializing it from a Page_Load handler:

<win:Odometer  RunAt="server" /> . . . <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { MyOdometer.Count = 1000; } </script>

In all likelihood, you d retrieve the count from a database or other data source rather than hardcode into the ASPX file.

Before using the control in a Web page, you must do the following:

  • Compile Odometer.cs into a DLL and place the DLL in the application root s bin directory. The @ Register directive in the preceding example assumes the DLL s name is OdometerControl.dll.

  • Copy the Web.config file in Figure 8-30 to the application root (or to any subdirectory containing an ASPX file that uses an Odometer control). If the directory already contains a Web.config file, simply add the <httpHandlers> section to the existing file.

Odometer outputs <img> tags whose Src attributes reference OdometerImageGen.ashx. Web.config maps requests for OdometerImageGen.ashx to the HTTP handler OdometerImageGen, which lives in the same DLL as the control.

Web.config

<configuration> <system.web> <httpHandlers> <add verb="*" path="OdometerImageGen.ashx" type="Wintellect.OdometerImageGen, OdometerControl" /> </httpHandlers> </system.web> </configuration>
Figure 8-30

Web.config file for the Odometer control.

How the Odometer Control Works

Odometer.cs (Figure 8-31) is a fine example of how to write custom controls that output dynamically generated images. The Odometer class represents the control itself. Its Render method outputs an <img> tag that points to OdometerImageGen.ashx as the image source. The URL includes a query string that provides the handler with all the information it needs regarding the odometer s appearance:

<img src="/books/4/347/1/html/2/OdometerImageGen.ashx?Count=1000&Digits=5...">

When the browser fetches OdometerImageGen.ashx from the server, ASP.NET activates OdometerImageGen thanks to the following statement in Web.config:

<add verb="*" path="OdometerImageGen.ashx" type="Wintellect.OdometerImageGen, OdometerControl" />

OdometerImageGen is implemented in Odometer.cs alongside Odometer. Its ProcessRequest method generates an image based on the inputs contained in the query string. ProcessRequest offloads the actual work of creating the image to a local method named GenerateImage. It then transmits the image back in the HTTP response by calling Bitmap.Save on the Bitmap returned by GenerateImage and directing the output to the Response object s OutputStream.

Odometer.cs

using System; using System.Web; using System.Web.UI; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Text; namespace Wintellect { public class Odometer : Control { int MyCount = 0; int MyDigits = 0; int MyWidth = 128; int MyHeight = 48; Color MyForeColor = Color.White; Color MyBackColor1 = Color.Black; Color MyBackColor2 = Color.LightGray; Color MyBorderColor = Color.Gray; public int Count

 { get { return MyCount; } set { if (value >= 0) MyCount = value; else throw new ArgumentOutOfRangeException (); } } public int Digits { get { return MyDigits; } set { if (value >= 0) MyDigits = value; else throw new ArgumentOutOfRangeException (); } } public int Width { get { return MyWidth; } set { if (value >= 0) MyWidth = value; else throw new ArgumentOutOfRangeException (); } } public int Height { get { return MyHeight; } set { if (value >= 0) MyHeight = value; else throw new ArgumentOutOfRangeException (); } } public Color ForeColor { get { return MyForeColor; } set { MyForeColor = value; } } public Color BackColor1 { get { return MyBackColor1; } set { MyBackColor1 = value; } } public Color BackColor2 { get { return MyBackColor2; } set { MyBackColor2 = value; } } public Color BorderColor { get { return MyBorderColor; } set { MyBorderColor = value; } } protected override void Render (HtmlTextWriter writer) { StringBuilder builder = new StringBuilder (); builder.Append ("OdometerImageGen.ashx?"); builder.Append ("Count="); builder.Append (Count); builder.Append ("&Digits="); builder.Append (Digits); builder.Append ("&Width="); builder.Append (Width); builder.Append ("&Height="); builder.Append (Height); builder.Append ("&ForeColor="); builder.Append (ForeColor.ToArgb ().ToString ()); builder.Append ("&BackColor1="); builder.Append (BackColor1.ToArgb ().ToString ()); builder.Append ("&BackColor2="); builder.Append (BackColor2.ToArgb ().ToString ()); builder.Append ("&BorderColor="); builder.Append (BorderColor.ToArgb ().ToString ()); writer.WriteBeginTag ("img"); writer.WriteAttribute ("src", builder.ToString ()); if (ID != null) writer.WriteAttribute ("id", ClientID); writer.Write (HtmlTextWriter.TagRightChar); } } public class OdometerImageGen : IHttpHandler { public void ProcessRequest (HttpContext context) { // Extract input values from the query string int Count = Convert.ToInt32 (context.Request["Count"]); int Digits = Convert.ToInt32 (context.Request["Digits"]); int Width = Convert.ToInt32 (context.Request["Width"]); int Height = Convert.ToInt32 (context.Request["Height"]); Color ForeColor = Color.FromArgb (Convert.ToInt32 (context.Request["ForeColor"])); Color BackColor1 = Color.FromArgb (Convert.ToInt32 (context.Request["BackColor1"])); Color BackColor2 = Color.FromArgb (Convert.ToInt32 (context.Request["BackColor2"])); Color BorderColor = Color.FromArgb (Convert.ToInt32 (context.Request["BorderColor"])); // Generate an image to return to the client Bitmap bitmap = GenerateImage (Count, Digits, Width, Height, ForeColor, BackColor1, BackColor2, BorderColor); // Set the content type to image/jpeg context.Response.ContentType = "image/jpeg"; // Write the image to the HTTP response bitmap.Save (context.Response.OutputStream, ImageFormat.Jpeg); // Clean up bitmap.Dispose (); } public bool IsReusable { get { return true; } } Bitmap GenerateImage (int Count, int Digits, int Width, int Height, Color ForeColor, Color BackColor1, Color BackColor2, Color BorderColor) { const int BorderWidth = 4; const int MinCellWidth = 16; const int MinCellHeight = 24; // Make sure Digits is sufficient for Count to be displayed int digits = Digits; int places = Places (Count); if (digits < places) digits = places; // Compute the width of a single character cell and // the width and height of the entire image int CellWidth = System.Math.Max (Width / digits, MinCellWidth); Width = (CellWidth * digits) + BorderWidth; Height = System.Math.Max (Height, MinCellHeight); // Create an in-memory bitmap Bitmap bitmap = new Bitmap (Width, Height, PixelFormat.Format32bppArgb); // Create the fonts and brushes that will be used to // generate the image Font font = new Font ("Arial", Height / 2); Brush brushForeColor = new SolidBrush (ForeColor); Brush brushBorderColor = new SolidBrush (BorderColor); // Create a Graphics object that can be used to draw to // the bitmap Graphics g = Graphics.FromImage (bitmap); // Fill the bitmap with the border color g.FillRectangle (brushBorderColor, 0, 0, Width, Height); // Create a StringFormat object for displaying text // that is centered horizontally and vertically StringFormat format = new StringFormat (); format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; // Initialize the values used to extract individual // digits from Count int div1 = (int) System.Math.Pow (10, digits); int div2 = div1 / 10; // Draw the digits and their backgrounds for (int i=0; i<digits; i++) { Rectangle rect = new Rectangle (i * CellWidth + BorderWidth, BorderWidth, CellWidth - BorderWidth, Height - (2 * BorderWidth)); Rectangle top = rect; top.Height = (rect.Height / 2) + 1; Rectangle bottom = rect; bottom.Y += rect.Height / 2; bottom.Height = rect.Height / 2; Brush brushBackColor1 = new LinearGradientBrush (top, BackColor1, BackColor2, LinearGradientMode.Vertical); Brush brushBackColor2 = new LinearGradientBrush (bottom, BackColor2, BackColor1, LinearGradientMode.Vertical); g.FillRectangle (brushBackColor2, bottom); g.FillRectangle (brushBackColor1, top); string num = ((Count % div1) / div2).ToString (); g.DrawString (num, font, brushForeColor, rect, format); div1 /= 10; div2 /= 10; brushBackColor1.Dispose (); brushBackColor2.Dispose (); } // Clean up and return font.Dispose (); brushForeColor.Dispose (); brushBorderColor.Dispose (); g.Dispose (); return bitmap; } // Compute the number of places (digits) in an input value int Places (int val) { int count = 1; while (val / 10 > 0) { val /= 10; count++; } return count; } } }
Figure 8-31

Odometer control.



Programming Microsoft  .NET
Applied MicrosoftNET Framework Programming in Microsoft Visual BasicNET
ISBN: B000MUD834
EAN: N/A
Year: 2002
Pages: 101

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