65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展进度条

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.35/5 (25投票s)

2005年6月17日

7分钟阅读

viewsIcon

80387

downloadIcon

2189

带百分比指示器的进度条。

Sample Image

引言

这是我在 CodeProject 的第一篇文章,也是我(英文)文章中的第一篇,所以请原谅我的语法错误和蹩脚的表达方式(英语不是我的母语)。

在编写应用程序时,我遇到过一种情况,我想要一个指示应用程序工作进度的指示器,所以我放弃了 .NET 的 ProgressBar 并完成了我的工作。在测试运行我的应用程序后,我注意到这个默认控件不适合我的情况。我认为它也不适合许多其他情况,并且在处理一个严肃的、设计精良的 UI 应用程序时不够用。因此,我开始搜索和寻找一些更强大的进度控件来帮助我。我在网上找到了几十个,但没有一个符合我想要的百分比指示器目标,所以我决定实现自己的控件,而这正是我这篇文章的开端。

使用代码

让我们一步一步地开始这篇中等篇幅的文章(我认为初学者最好下载源代码,以便能够理解可能令人困惑的任何解释)。

准备场景

首先,当您编写一些密集的绘图功能时,可能会出现轻微的闪烁,这会影响绘图操作的标准化外观,或者至少反映出糟糕的绘图算法。嗯……这是一种可以进行一些底层优化的场景,但这种底层优化需要更多的经验和更艰苦的努力才能实现。幸运的是,.NET 通过调用 SetStyleSystem.Windows.Froms.Control.SetStyle)函数并提供适当的参数,只需一行代码就能提供完美的解决方案。所有(幕后)工作都由它为您完成。

SetStyle 函数需要一个 ControlStyles 枚举的值(System.Windows.Forms.ControlStyles),该值指定如何处理控件。这个枚举具有 FlagsAttribute,它表示该枚举的成员可以使用按位 OR 运算符(|)组合。在这个场景中,我们关心的 ControlStyles 成员是那些可以减少我们控件绘图时闪烁的成员。

  • ControlStyles.UserPaint:使控件自行绘制,而不是由操作系统绘制。
  • ControlStyles.AllPaintingInWmPaint:使控件忽略 WM_ERASEBKGND 消息。
  • ControlStyles.DoubleBuffer:使控件在绘图操作中使用缓冲区。

此时,您只需要理解,组合所有这些参数将减少闪烁并满足我们的目标。为了更好地理解这些成员,您可以参考 MSDN

减少闪烁

this.SetStyle(ControlStyles.UserPaint | 
  ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer,true);

虽然我们的控件已经使用了 SetStyle 函数,但它还有另一种在此场景下可能适用的用法(如果不行,我决定为了完整性而提及它)。我所说的这个功能是控件的背景透明度,即控件显示其父控件的背景颜色而不是自己的背景颜色(当然,当其背景设置为 Transparent 时!)。

要启用控件的透明度,您可以使用 SetStyle 函数并打开 ControlStyles.SupportsTransparentBackColor 标志。此选项允许您的控件接受 alpha 分量小于 255 的颜色作为其 BackColor 属性(alpha 分量为 255 的颜色是完全不透明的颜色,alpha 分量为 0 的颜色是完全透明的颜色)。

允许控件的背景颜色透明(可选,可跳过)

this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); 

编写实际工作

您现在可能在想:“这个人到底在说什么?进度相关的部分在哪里?有没有真正的代码解释?”嗯……是的,有这样的解释,而且数量不少,但请记住,编写进度条控件的实际想法,特别是计算其百分比值,是数学家的事,我不会给您讲数学、加法、乘法……等课程,也不会解释(矩形面积、圆形大小和向量长度)的规则。

我们真正需要的是一个逻辑宽度来绘制工作进度。您可能会想,我们可以使用直到目前的值(当前绘制的边缘)并根据它进行绘制,同时考虑控件的宽度!这是一个完美的解决方案,但如果控件的用户决定最大值必须大于控件宽度或小于控件宽度怎么办?完美的解决方案将不再完美。它在控件宽度和最大值相等的情况下工作良好。要解决这个问题,请考虑以下计算:

  • ( (控件宽度) * (当前绘制值) / (最大值 - 最小值) )
drawingWidth = (int)(this.Width * _value) / (maxValue - minValue);

这将给出绘制应该到达的确切点(作为一个 float 值,它将被转换为 int)。

当前点的百分比呢?另一个计算就足够了:

  • ( (当前绘制值 / 最大值) * 100 )
percentageValue = (_value / maxValue) * 100;

在计算出这些值之后,我们可以继续将我们的控件绘制为一个从 (0,0) 到 (逻辑宽度, 控件高度) 的填充矩形,这就是全部!

e.Graphics.FillRectangle(_Drawer, 0, 0, drawingWidth, this.Height);

到目前为止,我们拥有一个常规但带颜色的进度控件,我们只需要将(刚刚计算的)百分比值添加到其中,这只需调用一次简单的 DrawString 函数并传入正确的值即可。这些值取决于我们想绘制的百分比样式。百分比绘图有三种样式(NoneCenterMovable),不要感到困惑,处理它们非常简单。现在,请执行以下操作:

  • 对于 None 样式,根本不绘制任何百分比(代码太难了吗?!)。
  • 对于 Center 样式,只需将百分比值垂直和水平居中绘制(代码中显示的就是这样)。
  • 对于最后的 Movable 样式,将百分比值垂直和水平居中绘制,并将其放置在我们的逻辑宽度值处(也已编码)。

还有什么?

我使用了其他一些我认为值得解释的技术。

  1. LinearGradientBrush 位于 System.Drawing.Drawing2D 中,它是一种画笔,具有比基本颜色更多的颜色,并且以渐变的方式混合在一起。
  2. ColorBlend 也位于 System.Drawing.Drawing2D 中,用于在渐变画笔中混合两种以上的颜色。
  3. ControlPaint 位于 System.Windows.Forms 中,用于提取指定颜色的深浅值。

请注意,我刚才所说的绝不是对这些类或其功能的解释,一点也不是。我所说的一切仅用于本文,并不代表实际使用它们的参考。这些类中的每一个都值得专门写一篇文章,但如果您感兴趣,可以随时参考 MSDN 进行进一步的研究。

最终版本

是将前面的细节写在 Main 函数中还是写在另一个函数中,这不是一个好问题,但对于那些还没有明白的人(如果有的话),您应该将其放在 Paint 事件处理程序中,实际代码应该如下所示:

private void ProgressEx_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
    // Just draw inside the control.

    if(_value != 0 & _value <= maxValue)
    {
        // Calculate the right side of drawing edge

        drawingWidth=(int)(this.Width * _value) / (maxValue - minValue);

        // Calculate the Percentage according to the logical value reached

        percentageValue = (_value / maxValue) * 100;

        // Tie our color mixer with the just created brush

        _Drawer.InterpolationColors = gradientBlender;

        // Now we ready to draw, so do the actual drawing

        e.Graphics.FillRectangle(_Drawer, 0, 0, drawingWidth, this.Height);

        // Prepare for Percentage writing only when required

        if(percentageDrawingMode != PercentageDrawingMode.None)
        {
            string text=((int)percentageValue).ToString() + "%";

            // Calculate Percentage rectangle size

            SizeF textSize=e.Graphics.MeasureString(text,writingFont);

            if(percentageDrawingMode == PercentageDrawingMode.Movable)
            {
                e.Graphics.DrawString(text,writingFont, 
                   writingBrush,drawingWidth,
                   (e.ClipRectangle.Height / 2 - textSize.Height / 2));
            }
            else if(percentageDrawingMode == PercentageDrawingMode.Center)
            {
                e.Graphics.DrawString(text, writingFont, writingBrush, 
                  new PointF((e.ClipRectangle.Width / 2 - textSize.Width / 2),
                  (e.ClipRectangle.Height / 2 - textSize.Height / 2)));
            }
        }
    }
}

任务完成

在我们完成自定义用户控件之际,我想说几句话。

此控件不继承自 ProgressBar 类(System.Window.Forms.ProressBar)。相反,它继承自 Control 类(System.Windows.Forms.Control)。这就是为什么它没有重写 PaintMaximum(或 ProgressBar 控件中存在的任何其他成员)。如果您对源代码感到不适,那么您需要查找一篇关于创建自定义控件(继承自 System.Windows.Forms.ControlSystem.ComponentModel.Component)或相关内容的完整文章(CodeProject 上有一些关于这个主题的好文章,请搜索并阅读其中一些)。

关注点

智者说:“不要重复造轮子”(我不知道这些智者是谁,也不知道他们是否说过这句话,甚至他们是否存在),我完全同意他们!我想说的是:

如果您想要一个方形的轮子,不要费心去重新发明它,只需继承现有的轮子,然后覆盖它的圆形形状,这样就可以了。但另一方面,进行一些(从头开始)的用户控件工作是值得的,并且是一个很好的实践,所以请尝试一下。请记住,从 System.Windows.Forms.Control 继承一个控件并不意味着改变控件的可用性(如何使用它),它只是改变了功能(它必须如何行动或执行)。也就是说,即使您冒着风险从头开始创建一个看起来或行为与现有控件相似的用户控件,也要改变它的工作方式,但不要改变它的使用方式。

就是这样,感谢您阅读本文,如果您觉得有用,请留下您的反馈。

享受!:)

© . All rights reserved.