可重用进度条服务器控件





4.00/5 (11投票s)
2005年2月16日
7分钟阅读

109466

2662
一个可重用的 ASP.NET 服务器页面进度条服务器控件。

引言
本文描述了创建一个可重用的进度条控件的过程,该控件可以在服务器端或客户端进行控制。
背景
在 Web UI 开发中,有时需要显示资源使用信息。进度条的一些应用包括显示队列中消息的数量与队列大小的比例,或者显示当前登录用户数与历史最大用户数的比例。可能性是无穷的。在我第一次尝试创建基于 HTML 的进度条时,我使用了 ASP.NET 服务器页面中的 span 元素来直观地表示进度条。然后,我使用 iframe 将该服务器页面嵌入到我想要显示进度条的位置。这种方法有一个严重的缺点,那就是为了正确显示进度条和样式化来自包含页面的交互,需要进行非常复杂的 span 嵌套。在显示进度条填充部分时也存在准确性问题。此外,使用嵌入的 iframe 元素还有几个问题,其中一些并非微不足道。然后,我决定创建一个可重用的 ASP.NET 服务器控件来托管进度条。span 元素存在所有相同的缺点,因此我决定寻找更好的方法,并开始研究 ASP.NET 条形图示例,这与进度条非常相似。我决定采用一种相当健壮的解决方案,使用 HTTP Handler 和“即时”渲染的图像。这就是我将在本文中介绍的方法。
使用代码
我将在本节中介绍以下主题。
- 基本服务器控件创建。
- 进度条服务器控件属性。
- 渲染进度条服务器控件。
- HTTP Handler CustomImageHandler,用于处理“即时”渲染的图像。
- 在服务器页面上使用进度条。
基本服务器控件创建
创建一个派生自 System.Web.UI.WebControls.WebControl 的新类,并添加如下所示的 using 语句
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.IO;
using System.Math;
namespace YourNamespace
{
    public class ProgressBar : System.Web.UI.WebControls.WebControl
    {
    }
}
接下来,在类文件顶部(在 using 语句下方)添加以下属性。
[assembly: TagPrefix("YourNamespace", "ShortName")]
将 YourNamespace 和 ShortName 替换为适合您项目的有意义的值。此属性控制可视化设计器在将服务器控件从工具箱拖放到服务器页面上时在 <@Register> 标记中放置的内容。它允许使用更适合您应用程序的命名方案。我将在“使用进度条”部分更详细地介绍这一点。
在类声明顶部添加以下代码
    /// <summary>
    /// This is the common graphical progress bar.
    /// </summary>
    /// <remarks>
    /// This custom control produces a graphical progress bar.
    /// </remarks>
    [DefaultProperty("Type")] 
    [ToolboxData("<{0}:ProgressBar runat=server></{0}:ProgressBar>")]
    [ToolboxBitmapAttribute( typeof(ProgressBar) ) ]
    public class ProgressBar : System.Web.UI.WebControls.WebControl
    {
请随意添加/替换声明上方的注释代码以满足您的需求。
DefaultProperty 属性将 Type 属性声明为默认属性。这允许您隐式引用该属性。
ProgressBar1 = BarType.Horizontal;
ToolboxData 属性声明了当控件从工具箱拖到服务器页面上时,工具箱将添加到服务器页面的文本。属性描述中的 {0} 部分是用于此控件注册命名空间的替换变量。这由上面描述的 TagPrefix 属性控制。当控件放置在服务器页面上时,可视化设计器将添加以下内容
<ShortName:ProgressBar id="ProgressBar1" 
         runat="server"></ShortName:ProgressBar>
ToolboxBitmapAttribute 属性描述了将在工具箱中显示的位图。如果您只提供一个类类型,而不提供名称,可视化设计器将在程序集中搜索同名的 16x16 位图。您可以创建一个 16x16 的位图并将其作为嵌入资源添加到项目中,以表示您的控件,否则可视化设计器将使用默认位图。
现在您有了一个基本控件,它将显示在工具箱中,并在从工具箱拖放到页面上时正确填充您的服务器页面。我将在“使用进度条”部分介绍如何将此控件添加到工具箱。在下一节中,我将介绍如何向进度条控件添加属性。
进度条服务器控件属性
已确定以下属性对于基本的进度条渲染是必需的:Type、Height、Width、FillColor、BackColor、Border、BarSize、FillPercent 和 Format。
Type 属性是一个枚举,其定义如下
public enum BarType
{
    Horizontal,
    Vertical
}
Height、Width 和 BarSize 属性都是 Unit 类型的值,分别描述进度条的高度、宽度和内部尺寸。
FillPercent 是一个 float 值,用于指定进度条的填充比例。
BackColor 和 FillColor 属性是无符号长整型值,分别指定背景颜色和条形/填充颜色。
Format 属性是一个枚举值,其定义如下
public enum BarFormat
{
    Gif,
    Jpeg,
    Png,
    Bmp
}
以下是这些属性的代码
#region Private member variables
BarFormat format;
BarType type;
bool border;
System.Drawing.Color fillColor;
double fillPercent;
Unit barSize;
#endregion
#region Public properties
[DefaultValue(BarFormat.Png), Category("ProgressBar Common"), 
                Description("Compression format for image.")]
public BarFormat Format
{
    get { return format; }
    set { format = value; }
}
[DefaultValue(false), Category("ProgressBar Common"), 
          Description("Flag to show border or not.")]
public bool Border
{
    get { return border; }
    set { border = value; }
}
[DefaultValue(BarType.Horizontal), Category("ProgressBar Common"), 
                   Description("Type of progress bar to render.")]
public BarType Type
{
    get { return type; }
    set { type = value; }
}
[Category("ProgressBar Common"), 
     Description("Background color of progress bar.")]
public System.Drawing.Color FillColor
{
    get { return fillColor; }
    set { fillColor = value; }
}
[DefaultValue(0.0), Category("ProgressBar Common"), 
   Description("Fill percentage of progress bar.")]
public double FillPercent
{
    get { return fillPercent; }
    set { fillPercent = value; }
}
[DefaultValue(0.0), Category("ProgressBar Common"), 
  Description("Size of progress bar inside frame")]
public Unit BarSize
{
    get { return barSize; }
    set { barSize = value; }
}
[Browsable(false)]
public string ContentType
{
    get { return "image/" + Format.ToString().ToLower(); }
}
最后一个属性 ContentType 使用 Browsable(false) 属性声明为不可浏览。此属性由“HTTP Handler CustomImageHandler”部分所述的 HTTP Handler 类使用。
渲染进度条服务器控件
ASP.NET 服务器控件有两种渲染模式。第一种是 DesignMode,它提供了一种方法让控件在可视化设计器中显示自身。由于进度条控件由“即时”渲染的图像组成,因此无法指定准确的表示,所以可以使用默认表示。
//Initial request for ProgressBar object
protected override void Render(HtmlTextWriter output)
{
    if (this.Site != null && this.Site.DesignMode )
    {
        // Be careful to specify only html that
        // can be embedded in any tag. This will prevent
        // problems when displaying in the Visual Designer.
        output.Write(string.Format("<font size='1'  " + 
            "color='SeaGreen' face='arial'> [ProgressBar::{0}]" + 
            "</font><br>", ID) );
    }
在运行时渲染控件需要介绍下一个主题 - 使用 HTTP Handler 来处理“即时”渲染的图像。此 Handler 将拦截对某个不存在的网页的请求,但该网页是在进度控件生成的 <img> 标记中指定的。我将在下一节详细介绍此 Handler。生成 <img> 标记的代码如下所示。
else
{
    string uniqueName = GenerateUniqueName();
    Page.Application[ProgressBarRenderStream.ImageNamePrefix + uniqueName] = this;
    string toolTip = string.Format( "{0}", 
        Enabled ? ((ToolTip == null || ToolTip == string.Empty) ? 
        Round(FillPercent, 2).ToString()+"%" : ToolTip) : "");
    //Write relative URL for image stream request
    output.Write( 
        string.Format("<img src='{0}?id={1}' " + 
          "border='{2}' height='{3}' width='{4}' alt='{5}'>", 
          ProgressBarRenderStream.ImageHandlerRequestFilename, 
          uniqueName, (this.Border ? "1" : "0"), 
          (int)this.Height.Value, (int)this.Width.Value, 
          toolTip );
}
//Generates a new name for this control & registers 
string GenerateUniqueName()
{
    string sControlName = System.Guid.NewGuid().ToString();
    //Identifies requested ProgressBar image
    return sControlName;
}
此代码将生成一个唯一名称,用于标识当前正在渲染输出的进度条类。此唯一名称用作 <img> 标记中的 src 属性,以便 HTTP Handler 稍后可以找到该类。ProgressBarRenderStream.ImageNamePrefix 和 ProgressBarRenderStream.ImageHandlerRequestFilename 的值在 HTTP Handler 类中声明,并在下一节中进行描述。此代码应生成一个类似于以下内容的 HTML 序列(值将根据属性值和生成的 GUID 值而有所不同)
<img src='image_stream.aspx?id=89f7f4ed-7433-460d-ae97-53c22aa2a232' 
          border='1' height='8' width='128' alt='25.5%'>
这仍然不足以渲染控件。以下函数将负责绘制进度条并将其渲染到内存流中。
public MemoryStream RenderProgressBar()
{
    try
    {
        if( Site != null && Site.DesignMode )
        {
            string sType = this.Type.ToString();
            return null;
        }
        else
        {
            return makeProgressBar();
        }
    }
    catch
    {
        return null;
    }
}
private MemoryStream makeProgressBar()
{
    // now convert percentage to width
    // (subtract 2 from width to adjust for border)
    Bitmap bmp = new Bitmap((int)Width.Value, 
          (int)Height.Value, PixelFormat.Format32bppArgb);
    MemoryStream memStream = new MemoryStream();
    Brush fillBrush = new SolidBrush( FillColor );
    Brush backgroundBrush = new SolidBrush( this.BackColor );
    // draw background
    System.Drawing.Graphics graphics = 
                   Graphics.FromImage( bmp );
    graphics.FillRectangle(backgroundBrush, 0, 
      0, (int)Width.Value, (int)Height.Value);
    double fillAmount;
    if( this.Type == BarType.Horizontal )
    {
        // draw a horizontal bar
        // draw only BarSize height, centered vertically
        // inside the frame
        fillAmount = Width.Value * (FillPercent/100.0);
        graphics.FillRectangle(fillBrush, 0, 
    ((int)Height.Value - (int)BarSize.Value)/2, 
    (int)fillAmount, (int)BarSize.Value);
    }
    else
    {
        // draw a vertical bar
        // draw only BarSize width, centered horizontally
        // inside the frame
        fillAmount = Height.Value * (FillPercent/100.0);
        graphics.FillRectangle(fillBrush, 
          ((int)Width.Value - (int)BarSize.Value)/2, 
          (int)Height.Value-(int)fillAmount, 
          (int)BarSize.Value, (int)Height.Value);
    }
    graphics.Save();
    System.Drawing.Imaging.ImageFormat imgformat = 
           System.Drawing.Imaging.ImageFormat.Png;
    switch( Format )
    {
        case BarFormat.Bmp:
            imgformat = ImageFormat.Bmp;
            break;
        case BarFormat.Gif:
            imgformat = ImageFormat.Gif;
            break;
        case BarFormat.Jpeg:
            imgformat = ImageFormat.Jpeg;
            break;
        case BarFormat.Png:
            imgformat = ImageFormat.Png;
            break;
    }
    // Render BitMap Stream Back To Client
    bmp.Save(memStream, imgformat);
    return memStream;
}
此代码将被下一节中描述的 ProgresBarRenderStream 类调用。
使用 HTTP Handler “即时”渲染图像
为了“即时”渲染图像,需要拦截 HTTP 请求并查找一个特定的、不存在的网页。此网页名称由进度条服务器控件在渲染过程中指定。HTTP Handler 将从查询字符串中找到该值,因为它检测到了“特殊”网页。然后,Handler 可以使用该值查找请求的进度条控件以进行“即时”渲染。
提供自定义 HTTP Handler 需要两个步骤。首先,创建一个派生自 IHttpModule 的类。此代码如下所示。
public class ProgressBarRenderStream : IHttpModule
{
    public const string ImageHandlerRequestFilename="image_stream.aspx";
    public const string ImageNamePrefix="i_m_g";
    public ProgressBarRenderStream()
    {
    }
    public virtual void Init( HttpApplication httpApp )
    {
        httpApp.BeginRequest += new EventHandler(httpApp_BeginRequest);
    }
    public virtual void Dispose()
    {
    }
    private void httpApp_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication httpApp = (HttpApplication)sender;
        ProgressBar pb = null;
        if( httpApp.Request.Path.ToLower().IndexOf(
                 ImageHandlerRequestFilename) != -1 )
        {
            pb = (ProgressBar)httpApp.Application[ImageNamePrefix + 
                 (string)httpApp.Request.QueryString["id"]];
            if( pb == null )
            {
                return; // 404 will be returned
            }
            else
            {
                try
                {
                    System.IO.MemoryStream memStream = pb.RenderProgressBar();
                    memStream.WriteTo(httpApp.Context.Response.OutputStream);
                    memStream.Close();
                    httpApp.Context.ClearError();
                    httpApp.Context.Response.ContentType = pb.ContentType;
                    httpApp.Response.StatusCode = 200;
                    httpApp.Application.Remove(ImageNamePrefix + 
                        (string)httpApp.Request.QueryString["id"]);
                    httpApp.Response.End();
                }
                catch(Exception ex)
                {
                    ex = ex;
                }
            }
        }
    }
}
上面的代码说明了 ProgressBar 控件在渲染 <img> 标记时使用的共享值。这些值随后在 httpApp_BeginRequest 函数中使用,该函数在 Init 函数中注册。
httpApp.BeginRequest += new EventHandler(httpApp_BeginRequest);
最后一步是将 HTTP Handler 添加到您的 web.config 文件中。必须将其添加到根级别的 web.config 文件才能正常工作。
<httpModules>
  <add name="ProgressBarRenderStream" 
     type="YourNamespace.ProgressBarRenderStream,YourAssemblyName" />
</httpModules>
在服务器页面上使用进度条
在服务器页面上使用进度条非常简单。首先,在您的项目文件中添加对该控件的引用。接下来,在服务器页面顶部添加以下指令。
<%@ Register TagPrefix="ShortName" 
        Namespace="YourNamespace" Assembly="YourAssemblyName" %>
最后,将控件添加到您的页面并根据需要更改参数。
<ShortName:ProgressBar  id="ProgressBar1" runat="server"></ShortName:ProgressBar>
结论
我开始研究时,认为我的问题有一个简单的解决方案 - 通过图形表示提供进程的直接反馈。深入研究后,我意识到我试图做的事情对于服务器控件来说非常困难,如果没有损害网站安全。进一步的研究使我找到了使用 HTTP Handler 方法来处理“即时”渲染的图像。这种方法非常强大,并且可以扩展到其他应用程序,其局限性仅在于您的想象力。
