C#组件设计技术兼谈带百分比进度条组件TSmartProgressBar的实现

(原创文章,转载请注明来源:http://blog.csdn.net/hulihui)

   一直用Delphi做应用系统,比较而言VS 2005提供的组件不够灵活美观。例如,进度条ProgressBar,样式过于单一。于是,利用假期时间,学习学习C#的组件设计技术,搞个灵活点的进度条组件。设计目的:既可以选择进度条式样,也可以显示进度数字。
    直接从ProgressBar派生类是行不通的,因为ProgressBar根本不允许override核心方法OnPaint()。Google一下,找到最权威的代码文章: http://support.microsoft.com/kb/323116。其基本思路如下:
    1)派生自UserControl,重写OnPaint()方法
    2)在Value变化时,捕获到变化区域UpdateRect,只对该区域标记为无效, 即:Invalidate(UpdateRect),这样做的目的是,消除了那种快速变化时背景(白色)闪动的感觉。如果直接刷新当前Value值对应左边全部区域,将有空白闪动的现象。
    按上述思路编写的进度条的确比自己做的效果好多了,但存在如下问题:
    1)原文中忘记了Update方法,即在Update(UpdateRect)后,应立即调用控件的Update()方法;
    2)实现了Smooth进度条,但也丢失了ProgressBar的Block的美观;
    3)如果直接在OnPaint()方法中显示进度条百分比,必须Invalidate()文字区域。但使用时还是有一小块背景(白色)闪动的感觉,不够圆满。
    多次尝试后,发现直接从Label组件派生,既可以显示数字、还可直接Invalidate()全部左边区域且无闪动感觉,比上文的实现简单。主要代码如下:
  1.        protected override void OnPaint(PaintEventArgs e)
  2.         {
  3.             if (m_ProgressBarBorderStyle == TBorderStyle.Fixed3D)
  4.             {
  5.                 this.Draw3DBorder(e.Graphics);
  6.             }
  7.             else if (m_ProgressBarBorderStyle == TBorderStyle.Single)
  8.             {
  9.                 this.DrawSingleBoard(e.Graphics);
  10.             }
  11.             else if (m_ProgressBarBorderStyle == TBorderStyle.Fixed2D)
  12.             {
  13.                 this.Draw2DBorder(e.Graphics);
  14.             }

  15.             this.DrawProgressBar(e.Graphics);

  16.             if (m_ProgressBarPercent)
  17.             {
  18.                 base.Text = ((double)m_Value / (double)m_Maximum).ToString("##0 %");
  19.             }
  20.             else
  21.             {
  22.                 base.Text = string.Empty;
  23.             }

  24.             base.OnPaint(e);
  25.         }
  1.        private void DrawProgressBar(Graphics g)
  2.         {
  3.             int top = this.ClientRectangle.Top + this.GetTopOffSet();
  4.             int height = this.ClientRectangle.Height - this.GetTopOffSet() * 2;
  5.             double percent = (double)m_Value / (double)m_Maximum;
  6.             if (percent > 1.0)
  7.             {
  8.                 percent = 1.0;
  9.             }
  10.             int valueWidth = (int)((this.ClientRectangle.Width - this.GetLeftOffSet() * 2) * percent);  // 值对应的宽度
  11.             int blockWidth = (valueWidth / (m_ProgressBarBlockWidth + m_ProgressBarBlockSpace)) * (m_ProgressBarBlockWidth + m_ProgressBarBlockSpace);  // 对应实际绘制块的宽度
  12.             if (percent > 0.99)  // 防止舍入误差, 补充块长度
  13.             {
  14.                 if (this.ClientRectangle.Width - this.GetLeftOffSet() * 2 - blockWidth > 0)
  15.                 {
  16.                     blockWidth += (this.ClientRectangle.Width - this.GetLeftOffSet() * 2 - blockWidth) / (m_ProgressBarBlockWidth + m_ProgressBarBlockSpace);
  17.                 }
  18.             }
  19.             int left = this.ClientRectangle.Left + this.GetLeftOffSet();
  20.             int filledWidth = m_ProgressBarBlockWidth + m_ProgressBarBlockSpace;
  21.             while (filledWidth <= blockWidth)
  22.             {
  23.                 g.FillRectangle(m_ProgressBarFillBrush, left, top, m_ProgressBarBlockWidth, height);
  24.                 left += m_ProgressBarBlockWidth + m_ProgressBarBlockSpace;
  25.                 filledWidth += m_ProgressBarBlockWidth + m_ProgressBarBlockSpace;
  26.             }
  27.             int lastBarWidth = this.ClientRectangle.Width - left - this.GetLeftOffSet();
  28.             if (lastBarWidth > 0 && lastBarWidth < m_ProgressBarBlockWidth + m_ProgressBarBlockSpace)  // 剩下的尾巴不能绘制了
  29.             {
  30.                 filledWidth = this.ClientRectangle.Width - left - this.GetLeftOffSet();
  31.                 if (filledWidth > 0)
  32.                 {
  33.                     g.FillRectangle(m_ProgressBarFillBrush, left, top, filledWidth, height);  // 绘制部分 bar
  34.                 }
  35.             }
  36.         }
    实现时碰到的另一个技术难题就是,有两个Label属性必须在TSmartProgressBar构造函数中设置其初始值:base.AutoSize = false、base.TextAlign = ContentAlignment.MiddleCenter。实际情况是,在构造函数中赋值根本不起作用。因为这两个属性值将由窗体的InitializeComponent()方法给值。显然,这两个属性在Label中是特殊标记的,即有如下类似语句:
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public bool AutoSize
    {get;set}
经修改后,部分代码如下:
    public class TSmartProgressBar: Label
    {
        public TSmartProgressBar()
        {
            base.AutoSize = false;  // AutoSize 是 Designer 属性
            base.TextAlign = ContentAlignment.MiddleCenter;   // TextAlign 是 Designer 属性
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]  // 不在窗体中产生该属性语句
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]  // 在属性编辑器中不显示
        public new bool AutoSize
        {
            get
            {
                return base.AutoSize;  // 在构造函数中设置值、屏蔽该属性
            }
        }
    }   

    有关属性标记请参考: http://hi.baidu.com/wanhongnan/blog/item/eab3d2efb5088c35acafd540.html
   
    最后实现的TSmartProgressBar见本站的: http://download.csdn.net/source/612515(因与Delphi的TProgressBar同名,笔者的组件改名TSmartProgressBar)。组件功能单一,但做出实际东西来的收获还是不少的,主要有:1)知道了组件属性的继承、设置、屏蔽等技巧;2)如何重载OnPaint;3)理解Invalidate()的真正技术含义。显然,这将促进笔者正在做的几个实用组件:压缩组件:TGZipCompressBar、多层表头组件:TDataGridViewEx。

    附注:已上传源码及演示程序到著名开源网 www.codeproject.com,感兴趣者可以到网页 hulihui_TSmartProgressBar浏览与下载,请不要忘记给我投上一票哦。2008年9月26日。

你可能感兴趣的:(c,C#,Google,Class,Delphi)