Progress-O-Doom
一组可插拔的进度条组件。

引言
出于我无法解释的原因,我对进度条有一种非理性的迷恋。多年来,我看到喜欢的进度条,就会尝试用C#重现它供自己使用。这导致了几种具体的实现,它们都共享一个相对较小的功能集在一个父类中。然后,不久前,我看到几个我想要实现的,但我不想从头开始编写它们。然而,我注意到它们都有很多共同点。然后,我终于意识到,我可以通过一套可插拔的组件来构建几乎所有这些示例以及更多示例,这些组件可以绘制进度条的某些部分。这个项目就是这个启示的结果。
组件结构
基本上,这个想法是这样的……有两个具体的进度条实现(外加一个专门用于重现WinRar双进度条的),每个实现都建立在更通用的实现之上。这些实现使用一个IProgressBackgroundPainter
实例来绘制背景,一个IProgressPainter
实例,以及一个IProgressBorderPainter
实例来绘制边框。然后,您可以选择性地将任意数量的IGlossPainter
实例链接在一起,以修饰您的进度和/或背景画家。IProgressPainter
也可以使用自己的IProgressBorderPainter
。IProgressPainter
和IGlossPainter
都有abstract
实现,提供了基本功能。ChainedGlossPainter
,IGlossPainter
的abstract
实现,是一个装饰器,允许您将IGlossPainter
链接在一起以实现多种效果。
通过使所有这些功能部分成为Component
,您可以将它们拖放到设计器中,并在设计时设置它们的关联和属性。这使得尝试不同的可能性变得容易,以找到最适合您的需求和品味的方式。
下面是进度条实现、画家接口及其抽象实现的结构图

所有进度条实现都具有您期望看到的那些基本属性(例如,Maximum
、Minimum
、Value
)。它们还有ShowPercentage
,这是一个布尔属性,决定是否在条形图上绘制百分比文本,以及ProgressPadding
,它是一个int
,表示进度画家和边框之间的像素间距。DualProgressBar
添加了MasterMaximum
、MasterValue
、MasterPainter
和PaintMasterFirst
来控制主进度。DualProgressBar
的设计是为了像WinRar进度条那样工作,它显示一个银色的整体进度,然后叠加一个(始终更小的)金色进度来表示整体压缩比。然而,这个条形图可以用来显示单独的进度和整体进度,特别是当非主进度有填充时,以便可以看到下面的主进度。
背景画家
我目前有五个IProgressBackgroundPainter
的实现,其中三个是为特定的进度条(WinRar、FruityLoops和Candy Cane)构建的。

纯背景画家
PlainBackgroundPainter
只用单一颜色填充背景。我最常将这种背景画家与IGlossPainter
结合使用。它本身远非有趣,但它是添加光泽的完美基础。

渐变背景画家
GradientBackgroundPainter
是在构思光泽之前构建的。它允许您指定顶部和底部颜色,并在这些颜色之间填充渐变背景。使用PlainBackgroundPainter
和GradientGlossPainter
也可以达到相同的效果。所以,这个背景画家真的只是一个方便的快捷方式。

拐杖糖背景画家
CandyCaneBackgroundPainter
是从我旧的条形图收藏中已有的条形图构建的。它也可以从PlainBackgroundPainter
和光泽中重现。它与CandyCaneProgressPainter
匹配,但它也与几乎任何其他进度画家配合得很好。

FruityLoops背景画家
FruityLoopsBackgroundPainter
也是从我之前的一个条形图构建的,该条形图基于FruityLoops应用程序中的进度条。它有一个匹配的进度画家。这个背景画家可配置性不高,它是为特定集合设计的,以模仿FruityLoops条形图。

WinRar背景画家
RarBackgroundPainter
也是从一个基于WinRar进度条的旧条形图构建的。它也有匹配的进度和边框画家,这些画家被RarProgressBar
使用。因为它专门用于模仿WinRar条形图,所以可配置性不高;例如,您不能更改其颜色。

进度画家
有九个IProgressPainter
的实现,其中大多数是基于我旧的进度条收藏。它们都可以用光泽装饰,但纯粹的实现被设计为光泽的通用画布。

纯进度画家
PlainProgressPainter
,就像纯背景画家一样,只用实色填充进度,这使其成为光泽的绝佳起点。除了Color
属性外,您还可以为LeadingEdge
属性设置颜色。在不对进度条设置ProgressPadding
值的情况下,可以通过将纯进度画家的ProgressBorderPainter
属性设置为IProgressBorderPainter
实例来获得相同的效果。

斜角画家
BevelledProgressPainter
也用单一颜色填充进度,但它还用基于基本颜色的两像素边框来斜角处理进度。由于IProgressPainter
接口定义了这一点,所有进度画家都可以将其ProgressBorderPainter
属性设置为IProgressBorderPainter
的实例。

斜角渐变画家
BevelledGradientProgressPainter
基本上与BevelledProgressPainter
相同,除了它具有MinColor
和MaxColor
属性,它使用这些属性来用渐变填充进度。

金属进度画家
MetalProgressPainter
是一种微妙而优雅的自定义设计,它基于单一基本颜色构建。

理发店转柱
BarberPoleProgressPainter
是另一种自定义设计。这是两个进度画家之一,它们存在所有条形图ProgressPadding
属性的问题。当控件被调整大小时,画家会重建一个适合整个条形的图像,这样值更改只需要移动图像即可。这是我目前不太关心的一个限制。

Java画家
JavaProgressPainter
是一种自定义设计,基于我曾经在Java应用程序中看到的一个进度条。它总是带有填充,因此设置填充只会进一步修改它,尽管您也可以给它一个负填充来强制它填充进度空间。

拐杖糖
CandyCaneProgressPainter
与BarberPoleProgressPainter
共享相同的策略,因此相同的限制。它构建了一个基于单一基本颜色的半透明设计。

FruityLoops画家
FruityLoopsProgressPainter
本身只有一个可配置属性,即FruityType
属性,它有两个可能的值:DoubleLayer
和TripleLayer
。它填充良好,并且可以进行光泽和/或边框处理。但是,它与设计用来匹配它的FruityLoopsBackgroundPainter
配合得最好。

WinRar进度画家
RarProgressPainter
是一种非常简单的进度画家。尽管它被设计用来与RarBackgroundPainter
和RarBorderPainter
一起工作,但由于它很简单,它也能很好地与其他实现一起工作。ProgressType
属性对应于WinRar使用的两种颜色:Silver
和Gold
。ShowEdge
属性绘制了WinRar画家特有的前导边缘。

边框画家
边框画家可以用来边框整个进度条,也可以用来边框进度画家。IProgressBorderPainter
接口要求实现公开BorderWidth
属性,以便使用它的进度条可以确定进度Rectangle
的大小。与其他组件一样,它具有绘制和调整自身大小的方法。

纯边框画家
使用PlainBorderPainter
,您可以为平面边框指定颜色,或使用其Sunken
或Raised
实现。RoundedCorners
属性只是使边角的像素半透明,这会产生微妙的圆角效果。



风格化边框画家
StyledBorderPainter
使用System.Windows.Forms.Border3DStyle
值和ControlPaint.DrawBorder3D()
方法来绘制边框。正如您在下面的一些示例中看到的,其中一些Border3DStyle
边框在左侧有一个单像素宽,而在其他侧面有两个像素宽。由于IProgressBorderPainter
上的BorderWidth
属性只允许您指定单个边框宽度,因此此边框画家有一些不期望的副作用,但它们很微妙,在大多数情况下几乎不明显。
WinRar边框画家
RarBorderPainter
专门为与RarProgressPainter
和RarBackgroundPainter
一起使用而设计,但它确实为条形图提供了漂亮的凸起外观。

光泽画家
光泽被设计用来允许您将各种透明度不同的渐变应用于进度条和背景。有四种光泽实现,它们都有一个Successor
属性,允许您将它们链接在一起以实现多种效果。

平光
FlatGlossPainter
只用一种颜色覆盖一个区域。您可以通过设置Style
(Top
或Bottom
)属性和PercentageCovered
属性来控制该区域。它允许您为该颜色指定颜色和Alpha值。


渐变光泽
GradientGlossPainter
在顶部或底部的一个百分比范围内,或者在整个表面上创建一个渐变。它使用基本颜色与AlphaHigh
和AlphaLow
属性结合用于渐变。您还可以控制角度。示例使用90度角;270度角将反转显示的内容。



中部光泽
MiddleGlossPainter
从中间向外渐变。同样,通过Style
属性,您可以选择顶部、底部或整个表面的光泽。与GradientGlossPainter
一样,它使用一种颜色和两个Alpha值来创建渐变。



圆形光泽
RoundGlossPainter
在顶部和/或底部边缘渐变。与GradientGlossPainter
和MiddleGlossPainter
一样,它使用一种颜色和两个Alpha值来创建渐变。与其他光泽不同,这个光泽没有百分比,而是有一个TaperHeight
属性,允许您设置从边缘到渐变的像素数。在下面的示例中,TaperHeight
设置为8。



一些代码
大部分魔力只是简单的GDI+操作。真正强大的地方在于组件如何排列以及它们如何控制传递给其他组件的Rectangle
。然而,有几点可能值得注意。
PropertiesChanged 事件
以下内容用于促进对PropertiesChanged
事件的访问,以避免多次注册监听器。也许我有点懒惰,但这可以避免长期的麻烦。当有这么多组件相互通信时,我希望确保不会不必要地重复重绘或其他函数调用。
private EventHandler onPropertiesChanged;
/// <summary></summary>
public event EventHandler PropertiesChanged {
add {
if (onPropertiesChanged != null) {
foreach (Delegate d in onPropertiesChanged.GetInvocationList()) {
if (object.ReferenceEquals(d, value)) { return; }
}
}
onPropertiesChanged = (EventHandler)Delegate.Combine(onPropertiesChanged, value);
}
remove { onPropertiesChanged = (EventHandler)Delegate.Remove
(onPropertiesChanged, value); }
}
使用DualProgressBar
双进度条的概念与大多数人习惯的工作方式有所不同。为了帮助理解它的作用,下面的代码是在演示应用程序的DualTests窗体中使用的。它将主最大值设置为10,000,将最大值设置为2000(因为我事先知道我想进行五次迭代)。然后,当它循环五次时,它会重置Value
属性,并同时递增Value
和MasterValue
属性。这个例子展示了双进度条如何用于显示整体进度和个体进度。
private bool go = false;
private void button1_Click(object sender, EventArgs e) {
go = true;
dualProgressBar1.Value = 0;
dualProgressBar1.MasterValue = 0;
dualProgressBar1.Maximum = 2000;
dualProgressBar1.MasterMaximum = 10000;
for (int i = 0; i < 5; i++) {
if (!go) { break; }
dualProgressBar1.Value = 0;
for (int j = 0; j < 2000; j++) {
if (!go) { break; }
dualProgressBar1.Value = j;
dualProgressBar1.MasterValue++;
Application.DoEvents();
}
}
dualProgressBar1.Value = 0;
dualProgressBar1.MasterValue = 0;
}
虽然主进度与非主进度的Minimum
值相同,但通过MasterMaximum
和MasterValue
属性,它的行为与其他任何进度一样。
需要注意的事项
IGlossPainter
的Successor
属性确实会进行测试,以确保您没有将一个光泽设置为它自身的后继,但它不会测试更长的引用循环。由于每个光泽都会请求其后继(如果不是null
)来重绘自身,因此光泽引用中的循环将导致无限重绘循环。由于这发生在设计时,Visual Studio会因此崩溃。如果任何人有关于如何测试此类引用循环的建议,我将非常乐意倾听。
public abstract class ChainedGlossPainter : Component, IGlossPainter, IDisposable {
private IGlossPainter successor = null;
/// <summary></summary>
public IGlossPainter Successor {
get { return successor; }
set {
if (object.ReferenceEquals(this, value)) {
throw new ArgumentException("Gloss cannot be it's own successor,
an infinite loop will result");
}
successor = value;
if (successor != null) {
successor.PropertiesChanged +=
new EventHandler(successor_PropertiesChanged);
}
FireChange();
}
}
...
使用组件
首先,您需要将一个进度条(ProgressBarEx
、DualProgressBar
或RarProgressBar
)拖到您的窗体上,然后还添加一个背景画家、边框画家和进度画家到您的窗体上,以及您可能需要的任何光泽实例。

添加组件后,将进度条的ProgressPainter
、BorderPainter
和BackgroundPainter
属性设置为相应的组件。

然后,如果您将光泽添加到窗体上,您可以设置其他画家的GlossPainter
属性。每个光泽画家也可以用于多个组件,因此背景画家和进度画家可以共享一个光泽。

如果您想将光泽链接在一起,请将每个要链接的光泽的Successor
属性设置为链中的下一个。请记住,它们是按照链接的顺序绘制的。此外,请记住,您必须避免创建导致光泽在链中的任何地方引用自身的链,这会导致无限重绘循环,从而杀死Visual Studio。

结论
这个项目是为了好玩而完成的,我几乎毫不怀疑其中仍然存在一些错误。我不是GDI+或自定义控件方面的专家,所以这里很可能还有很大的改进空间,我很乐意听取比我更有经验的人(甚至是没有经验的人)的建议。但是,它是功能性的,并且看起来很漂亮,用途广泛。下面是一些在开发和测试过程中创建的示例。正如您所看到的,这些组件有很多可能性。

左侧的所有示例都是用纯粹的控件和光泽完成的。右侧的示例是使用特定的画家完成的,除了非WinRar双进度条,它们都是用纯粹的画家和光泽完成的。
未来计划
- 我想添加一些功能来支持分段进度条,就像标准的Windows进度条一样。
- 我还想为圆角边框和条形图提供更多选项。
- 当然,我还想找到上述限制的解决方案(可变宽度的边框、基于图像的条形图的填充问题等)。
历史
-
12/19/2008
首次发布
-
12/23/2008
- 正如Acshi所建议的,
ChainedGlossPainter
中的Successor
访问器已更新,以更好地检测循环引用。 - 此外,此更新包括跑马灯条的功能。
AbstractProgressBar
类有四个新属性:ProgressType
、MarqueeSpeed
、MarqueeStep
和MarqueePercentage
(我稍后会尝试更新图表)。ProgressType
是一个enum
,具有Smooth
(正常)、MarqueeWrap
(从右侧跑出框架,然后从左侧重新进入框架)、MarqueeBounce
(在右侧和左侧之间弹跳)和MarqueeBounceDeep
(也弹跳,但在反弹前超出框架)的值。Speed是更新之间的毫秒数。Step是每次更新移动的像素数。MarqueePercentage决定了跑马灯条的宽度。 AbstractProgressBar
还包括三个用于跑马灯功能的abstract
方法,它们在ProgressBarEx
中实现:MarqueeStart()
、MarqueePause()
和MarqueeStop()
。我一直不喜欢默认进度条处理跑马灯操作的方式,并且看到许多人对此感到困惑和/或抱怨。所以我决定使用方法来开始和停止动画。- 由于
DualProgressBar
扩展了ProgressBarEx
,它继承了此功能。您仍然可以正常使用主进度。这可能有助于您处理长时间运行的操作,您希望显示实际进度,但仍然在等待期间显示动画。这个场景在新的跑马灯测试窗体中进行了模拟。请确保将PaintMasterFirst
属性设置为true
。我不确定我对这个实现是否完全满意,但它为这项功能建立了一个可用的接口,并且它似乎效果足够好(除了
BarberPoleProgressPainter
和CandyCaneProgressPainter
,它们由于前面提到的限制而表现不正确)。
- 正如Acshi所建议的,
-
01/18/2009
ProgressType
枚举有一个新值Animated
。- 添加了
IAnimatedProgressPainter
接口。
ProgressBarEx.ProgressPainter
属性现在进行了一些验证。ProgressType.Animated
值只有在ProgressBarEx.ProgressPainter
是IAnimatedProgressPainter
的实例时才有效。如果将非动画进度画家分配给ProgressBarEx.ProgressPainter
,并且它当前具有ProgressType.Animated
值,则ProgressType
将更改为ProgressType.Smooth
。- 如果将
ProgressBarEx.ProgressType
设置为ProgressType.Animated
,同时将ProgressBarEx.ProgressPainter
设置为非动画进度画家,则会抛出ArgumentException
。 ProgressBarEx
有两个新方法:StartAnimation()
和StopAnimation()
。它管理一个内部计时器,该计时器用于(与MarqueeSpeed
一起)更新动画。在动画期间,它会设置其动画进度画家的Animating
属性,然后其计时器回调方法只会使控件失效以进行重绘(动画实现会更新一个内部变量来跟踪帧,如果它的Animating
属性为false
,它则不使用帧计数器)。
新增类

有两个新的进度画家实现了IAnimatedProgressPainter
:StripedProgressPainter
和WaveProgressPainter
。StripedProgressPainter
在外观上与BarberPoleProgressPainter
非常相似,但它不共享其填充限制,因为它使用GraphicsPaths
而不是移动图像。它还允许您设置两种颜色。WaveProgressPainter
在功能上是相似的,除了它使用贝塞尔曲线而不是条纹。(我对波浪画家实际上有点失望,它不像我预期的那么酷。也许您可以做得更多,也许您会比我更喜欢它)演示将进度条的Value
属性设置为最大值以显示动画,但这并非必需。
要使用动画,您只需使用ProgressBarEx.StartAnimation()
和ProgressBarEx.StopAnimation()
方法。如果当前进度画家是非动画进度画家,则这些方法将不起作用。
