(原创文章,转载请注明来源: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()全部左边区域且无闪动感觉,比上文的实现简单。主要代码如下:
- protected override void OnPaint(PaintEventArgs e)
- {
- if (m_ProgressBarBorderStyle == TBorderStyle.Fixed3D)
- {
- this.Draw3DBorder(e.Graphics);
- }
- else if (m_ProgressBarBorderStyle == TBorderStyle.Single)
- {
- this.DrawSingleBoard(e.Graphics);
- }
- else if (m_ProgressBarBorderStyle == TBorderStyle.Fixed2D)
- {
- this.Draw2DBorder(e.Graphics);
- }
- this.DrawProgressBar(e.Graphics);
- if (m_ProgressBarPercent)
- {
- base.Text = ((double)m_Value / (double)m_Maximum).ToString("##0 %");
- }
- else
- {
- base.Text = string.Empty;
- }
- base.OnPaint(e);
- }
- private void DrawProgressBar(Graphics g)
- {
- int top = this.ClientRectangle.Top + this.GetTopOffSet();
- int height = this.ClientRectangle.Height - this.GetTopOffSet() * 2;
- double percent = (double)m_Value / (double)m_Maximum;
- if (percent > 1.0)
- {
- percent = 1.0;
- }
- int valueWidth = (int)((this.ClientRectangle.Width - this.GetLeftOffSet() * 2) * percent);
- int blockWidth = (valueWidth / (m_ProgressBarBlockWidth + m_ProgressBarBlockSpace)) * (m_ProgressBarBlockWidth + m_ProgressBarBlockSpace);
- if (percent > 0.99)
- {
- if (this.ClientRectangle.Width - this.GetLeftOffSet() * 2 - blockWidth > 0)
- {
- blockWidth += (this.ClientRectangle.Width - this.GetLeftOffSet() * 2 - blockWidth) / (m_ProgressBarBlockWidth + m_ProgressBarBlockSpace);
- }
- }
- int left = this.ClientRectangle.Left + this.GetLeftOffSet();
- int filledWidth = m_ProgressBarBlockWidth + m_ProgressBarBlockSpace;
- while (filledWidth <= blockWidth)
- {
- g.FillRectangle(m_ProgressBarFillBrush, left, top, m_ProgressBarBlockWidth, height);
- left += m_ProgressBarBlockWidth + m_ProgressBarBlockSpace;
- filledWidth += m_ProgressBarBlockWidth + m_ProgressBarBlockSpace;
- }
- int lastBarWidth = this.ClientRectangle.Width - left - this.GetLeftOffSet();
- if (lastBarWidth > 0 && lastBarWidth < m_ProgressBarBlockWidth + m_ProgressBarBlockSpace)
- {
- filledWidth = this.ClientRectangle.Width - left - this.GetLeftOffSet();
- if (filledWidth > 0)
- {
- g.FillRectangle(m_ProgressBarFillBrush, left, top, filledWidth, height);
- }
- }
- }
实现时碰到的另一个技术难题就是,有两个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日。