如何创建圆形样式进度条
Posted
技术标签:
【中文标题】如何创建圆形样式进度条【英文标题】:How to create a Circular Style ProgressBar 【发布时间】:2011-06-19 18:24:09 【问题描述】:我需要帮助来实现这样的循环进度条:
我应该如何通过增加Value
属性来实现Circle来填充?
【问题讨论】:
相关链接已损坏。请在问题中插入图片。 【参考方案1】:您有几个选项 - 第一个是模板化 ProgressBar
控件。事实证明这有点棘手。我写了一篇博客文章,描述了如何use an attached ViewModel to achieve the required effect。
另一种选择是从头开始创建您自己的控件。您可以执行以下操作:
-
创建新的用户控件
为其添加新的 Value、Maximum 和 Minimum 依赖属性。
处理用户控件中的 Value、Maximum 和 Minimum 属性更改事件以计算 Angle 属性。
在后面的代码中构造两个“饼图”(请参阅 this post)并将它们添加到 UI。
【讨论】:
为什么不对现有的 ProgressBar 进行模板化呢? WPF 中的 CustomControls 正是为此目的而显得无神。当您不打算重用它时,如果您只需要在 UI 中以不同的方式表示它,那么经历创建无外观控件的所有麻烦有什么意义? @NVM,我原则上同意你的观点,但这里可能需要一些代码。使用 WPF 中的内置形状(使用纯 XAML)创建切割弧实际上并不是一种简单的方法。如果使用的是 Expression Blend SDK,那么有一个弧形可以很容易地做到这一点。因此,OP 可能需要创建某种可以绘制饼图的控件。但是进度条的实现应该是使用这个新的“饼图”控件的模板。 查看我编辑的答案。并不是你不能按照你建议的方式去做。我认为总的来说,编写尽可能少的代码会更好。【参考方案2】:我知道这是一个老问题,但无论如何这是我的解决方案:
对于 WINFORMS:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class CircularProgressBar : Control
#region Enums
public enum _ProgressShape
Round,
Flat
public enum _TextMode
None,
Value,
Percentage,
Custom
#endregion
#region Private Variables
private long _Value;
private long _Maximum = 100;
private int _LineWitdh = 1;
private float _BarWidth = 14f;
private Color _ProgressColor1 = Color.Orange;
private Color _ProgressColor2 = Color.Orange;
private Color _LineColor = Color.Silver;
private LinearGradientMode _GradientMode = LinearGradientMode.ForwardDiagonal;
private _ProgressShape ProgressShapeVal;
private _TextMode ProgressTextMode;
#endregion
#region Contructor
public CircularProgressBar()
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = SystemColors.Control;
this.ForeColor = Color.DimGray;
this.Size = new Size(130, 130);
this.Font = new Font("Segoe UI", 15);
this.MinimumSize = new Size(100, 100);
this.DoubleBuffered = true;
this.LineWidth = 1;
this.LineColor = Color.DimGray;
Value = 57;
ProgressShape = _ProgressShape.Flat;
TextMode = _TextMode.Percentage;
#endregion
#region Public Custom Properties
/// <summary>Determina el Valor del Progreso</summary>
[Description("Valor Entero que determina la posision de la Barra de Progreso."), Category("Behavior")]
public long Value
get return _Value;
set
if (value > _Maximum)
value = _Maximum;
_Value = value;
Invalidate();
[Description("Obtiene o Establece el Valor Maximo de la barra de Progreso."), Category("Behavior")]
public long Maximum
get return _Maximum;
set
if (value < 1)
value = 1;
_Maximum = value;
Invalidate();
[Description("Color Inicial de la Barra de Progreso"), Category("Appearance")]
public Color BarColor1
get return _ProgressColor1;
set
_ProgressColor1 = value;
Invalidate();
[Description("Color Final de la Barra de Progreso"), Category("Appearance")]
public Color BarColor2
get return _ProgressColor2;
set
_ProgressColor2 = value;
Invalidate();
[Description("Ancho de la Barra de Progreso"), Category("Appearance")]
public float BarWidth
get return _BarWidth;
set
_BarWidth = value;
Invalidate();
[Description("Modo del Gradiente de Color"), Category("Appearance")]
public LinearGradientMode GradientMode
get return _GradientMode;
set
_GradientMode = value;
Invalidate();
[Description("Color de la Linea Intermedia"), Category("Appearance")]
public Color LineColor
get return _LineColor;
set
_LineColor = value;
Invalidate();
[Description("Ancho de la Linea Intermedia"), Category("Appearance")]
public int LineWidth
get return _LineWitdh;
set
_LineWitdh = value;
Invalidate();
[Description("Obtiene o Establece la Forma de los terminales de la barra de progreso."), Category("Appearance")]
public _ProgressShape ProgressShape
get return ProgressShapeVal;
set
ProgressShapeVal = value;
Invalidate();
[Description("Obtiene o Establece el Modo como se muestra el Texto dentro de la barra de Progreso."), Category("Behavior")]
public _TextMode TextMode
get return ProgressTextMode;
set
ProgressTextMode = value;
Invalidate();
[Description("Obtiene el Texto que se muestra dentro del Control"), Category("Behavior")]
public override string Text get; set;
#endregion
#region EventArgs
protected override void OnResize(EventArgs e)
base.OnResize(e);
SetStandardSize();
protected override void OnSizeChanged(EventArgs e)
base.OnSizeChanged(e);
SetStandardSize();
protected override void OnPaintBackground(PaintEventArgs p)
base.OnPaintBackground(p);
#endregion
#region Methods
private void SetStandardSize()
int _Size = Math.Max(Width, Height);
Size = new Size(_Size, _Size);
public void Increment(int Val)
this._Value += Val;
Invalidate();
public void Decrement(int Val)
this._Value -= Val;
Invalidate();
#endregion
#region Events
protected override void OnPaint(PaintEventArgs e)
base.OnPaint(e);
using (Bitmap bitmap = new Bitmap(this.Width, this.Height))
using (Graphics graphics = Graphics.FromImage(bitmap))
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
//graphics.Clear(Color.Transparent); //<-- this.BackColor, SystemColors.Control, Color.Transparent
PaintTransparentBackground(this, e);
//Dibuja el circulo blanco interior:
using (Brush mBackColor = new SolidBrush(this.BackColor))
graphics.FillEllipse(mBackColor,
18, 18,
(this.Width - 0x30) + 12,
(this.Height - 0x30) + 12);
// Dibuja la delgada Linea gris del medio:
using (Pen pen2 = new Pen(LineColor, this.LineWidth))
graphics.DrawEllipse(pen2,
18, 18,
(this.Width - 0x30) + 12,
(this.Height - 0x30) + 12);
//Dibuja la Barra de Progreso
using (LinearGradientBrush brush = new LinearGradientBrush(this.ClientRectangle,
this._ProgressColor1, this._ProgressColor2, this.GradientMode))
using (Pen pen = new Pen(brush, this.BarWidth))
switch (this.ProgressShapeVal)
case _ProgressShape.Round:
pen.StartCap = LineCap.Round;
pen.EndCap = LineCap.Round;
break;
case _ProgressShape.Flat:
pen.StartCap = LineCap.Flat;
pen.EndCap = LineCap.Flat;
break;
//Aqui se dibuja realmente la Barra de Progreso
graphics.DrawArc(pen,
0x12, 0x12,
(this.Width - 0x23) - 2,
(this.Height - 0x23) - 2,
-90,
(int)Math.Round((double)((360.0 / ((double)this._Maximum)) * this._Value)));
#region Dibuja el Texto de Progreso
switch (this.TextMode)
case _TextMode.None:
this.Text = string.Empty;
break;
case _TextMode.Value:
this.Text = _Value.ToString();
break;
case _TextMode.Percentage:
this.Text = Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value));
break;
default:
break;
if (this.Text != string.Empty)
using (Brush FontColor = new SolidBrush(this.ForeColor))
int ShadowOffset = 2;
SizeF MS = graphics.MeasureString(this.Text, this.Font);
SolidBrush shadowBrush = new SolidBrush(Color.FromArgb(100, this.ForeColor));
//Sombra del Texto:
graphics.DrawString(this.Text, this.Font, shadowBrush,
Convert.ToInt32(Width / 2 - MS.Width / 2) + ShadowOffset,
Convert.ToInt32(Height / 2 - MS.Height / 2) + ShadowOffset
);
//Texto del Control:
graphics.DrawString(this.Text, this.Font, FontColor,
Convert.ToInt32(Width / 2 - MS.Width / 2),
Convert.ToInt32(Height / 2 - MS.Height / 2));
#endregion
//Aqui se Dibuja todo el Control:
e.Graphics.DrawImage(bitmap, 0, 0);
graphics.Dispose();
bitmap.Dispose();
private static void PaintTransparentBackground(Control c, PaintEventArgs e)
if (c.Parent == null || !Application.RenderWithVisualStyles)
return;
ButtonRenderer.DrawParentBackground(e.Graphics, c.ClientRectangle, c);
/// <summary>Dibuja un Circulo Relleno de Color con los Bordes perfectos.</summary>
/// <param name="g">'Canvas' del Objeto donde se va a dibujar</param>
/// <param name="brush">Color y estilo del relleno</param>
/// <param name="centerX">Centro del Circulo, en el eje X</param>
/// <param name="centerY">Centro del Circulo, en el eje Y</param>
/// <param name="radius">Radio del Circulo</param>
private void FillCircle(Graphics g, Brush brush, float centerX, float centerY, float radius)
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
using (System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath())
g.FillEllipse(brush, centerX - radius, centerY - radius,
radius + radius, radius + radius);
#endregion
实施:
-
将源代码放入 WinForms 项目中任意位置的新类中,将类命名为“CircularProgressBar.cs”。
编译项目。
编译后,您应该会在工具栏上看到一个新的控件或“组件”。
将此新控件拖放到任何表单中并自定义其属性。
控件如下所示:
享受吧。
【讨论】:
+1000。奇迹般有效。感谢您发布。节省了我很多时间。【参考方案3】:这有点棘手,但并非不可能。这是我使用平滑动画指导的实现。应该使用值转换器来创建 CircularProgressBar。
CircularProgressBar.cs
public partial class CircularProgressBar : ProgressBar
public CircularProgressBar()
this.ValueChanged += CircularProgressBar_ValueChanged;
void CircularProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
CircularProgressBar bar = sender as CircularProgressBar;
double currentAngle = bar.Angle;
double targetAngle = e.NewValue / bar.Maximum * 359.999;
DoubleAnimation anim = new DoubleAnimation(currentAngle, targetAngle, TimeSpan.FromMilliseconds(500));
bar.BeginAnimation(CircularProgressBar.AngleProperty, anim, HandoffBehavior.SnapshotAndReplace);
public double Angle
get return (double)GetValue(AngleProperty);
set SetValue(AngleProperty, value);
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(0.0));
public double StrokeThickness
get return (double)GetValue(StrokeThicknessProperty);
set SetValue(StrokeThicknessProperty, value);
// Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(10.0));
AngleToPointConverter.cs
class AngleToPointConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
double angle = (double)value;
double radius = 50;
double piang = angle * Math.PI / 180;
double px = Math.Sin(piang) * radius + radius;
double py = -Math.Cos(piang) * radius + radius;
return new Point(px, py);
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
throw new NotImplementedException();
AngleToIsLargeConverter.cs
class AngleToIsLargeConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
double angle = (double)value;
return angle > 180;
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
throw new NotImplementedException();
App.xaml
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:my="clr-namespace:WpfApplication1">
<Application.Resources>
<my:AngleToPointConverter x:Key="prConverter"/>
<my:AngleToIsLargeConverter x:Key="isLargeConverter"/>
<Style x:Key="circularProgressBar" TargetType="my:CircularProgressBar">
<Setter Property="Value" Value="10"/>
<Setter Property="Maximum" Value="100"/>
<Setter Property="StrokeThickness" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="my:CircularProgressBar">
<Canvas Width="100" Height="100">
<Ellipse Width="100" Height="100" Stroke="LightGray"
StrokeThickness="1"/>
<Path Stroke="TemplateBinding Background"
StrokeThickness="TemplateBinding StrokeThickness">
<Path.Data>
<PathGeometry>
<PathFigure x:Name="fig" StartPoint="50,0">
<ArcSegment RotationAngle="0" SweepDirection="Clockwise"
Size="50,50"
Point="Binding Path=Angle, Converter=StaticResource prConverter, RelativeSource=RelativeSource FindAncestor, AncestorType=ProgressBar"
IsLargeArc="Binding Path=Angle, Converter=StaticResource isLargeConverter, RelativeSource=RelativeSource FindAncestor, AncestorType=ProgressBar"
>
</ArcSegment>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Border Width="100" Height="100">
<TextBlock Foreground="Gray" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Binding Path=Value, StringFormat=%0,
RelativeSource=RelativeSource TemplatedParent"
FontSize="TemplateBinding FontSize"/>
</Border>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
可以通过添加更多属性来进行更多自定义,例如 InnerRadius、Radius 等。
【讨论】:
是的!真棒 请问如何减小你粘贴的进度条的大小@Ali, 用相同的代码问一个新问题更好吗?【参考方案4】:你看过ValueConverter
s 吗?您可以使用 TemplateBinding
绑定到模板中的 Value 属性,并使用适当的值转换器将值更改为对循环进度条有用的值。
编辑:
在模板中:
用黄色填充圆形。
在顶部添加另一个橙色圆圈。
使用值转换器(或多值转换器)为 2 中添加的圆返回剪切几何(可能使用圆弧段)。
在 2 中剪裁圆,在 3 中返回几何图形。
Downvoter 将我的代表退还给我。
【讨论】:
-1,这不能通过值转换器来实现。它必须在可视化树中解决。 我删除了反对票,因为是的,我猜你在技术上可以这样做。 :) 但是使用 UIElement 的子类执行此操作会简单得多,然后将其粘贴到 ProgressBar 的模板中。 某事看起来简单并不意味着应该这样做。当所需要的只是原始的不同视觉表示时,为内置控件创建新的自定义控件确实很难看,原因有很多。这就像在不了解自定义控件的真正含义的情况下构建 WPF 自定义控件。 这不是我要说的。我同意圆形进度条应该是带有修改模板的 ProgressBar 类型。我们不同意的是应该如何构建该模板。我认为可重复使用的 PieShape 或任何有意义的东西。它可以在图表、繁忙的指标等中重复使用。但最终结果将是一个可以应用于普通旧进度条的控件模板。 乔希,我明白你说的。问题是接受的答案另有建议。我有一个 UI 密集型应用程序,其中包含许多自定义形状,我可以肯定地告诉你,价值转换器路线将远比创建自定义形状更简单、更清晰。在创建自定义形状类时,需要了解很多与 WPF 布局引擎相关的东西,我认为 OP 不需要那么复杂。以上是关于如何创建圆形样式进度条的主要内容,如果未能解决你的问题,请参考以下文章
如何在 QT 中获得具有圆形边缘和圆形进度边缘的 QProgressBar?