如何创建圆形样式进度条

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】:

你看过ValueConverters 吗?您可以使用 TemplateBinding 绑定到模板中的 Value 属性,并使用适当的值转换器将值更改为对循环进度条有用的值。

编辑:

在模板中:

    用黄色填充圆形。

    在顶部添加另一个橙色圆圈。

    使用值转换器(或多值转换器)为 2 中添加的圆返回剪切几何(可能使用圆弧段)。

    在 2 中剪裁圆,在 3 中返回几何图形。

    Downvoter 将我的代表退还给我。

【讨论】:

-1,这不能通过值转换器来实现。它必须在可视化树中解决。 我删除了反对票,因为是的,我猜你在技术上可以这样做。 :) 但是使用 UIElement 的子类执行此操作会简单得多,然后将其粘贴到 ProgressBar 的模板中。 某事看起来简单并不意味着应该这样做。当所需要的只是原始的不同视觉表示时,为内置控件创建新的自定义控件确实很难看,原因有很多。这就像在不了解自定义控件的真正含义的情况下构建 WPF 自定义控件。 这不是我要说的。我同意圆形进度条应该是带有修改模板的 ProgressBar 类型。我们不同意的是应该如何构建该模板。我认为可重复使用的 PieShape 或任何有意义的东西。它可以在图表、繁忙的指标等中重复使用。但最终结果将是一个可以应用于普通旧进度条的控件模板。 乔希,我明白你说的。问题是接受的答案另有建议。我有一个 UI 密集型应用程序,其中包含许多自定义形状,我可以肯定地告诉你,价值转换器路线将远比创建自定义形状更简单、更清晰。在创建自定义形状类时,需要了解很多与 WPF 布局引擎相关的东西,我认为 OP 不需要那么复杂。

以上是关于如何创建圆形样式进度条的主要内容,如果未能解决你的问题,请参考以下文章

android 进度条样式 怎么改

如何更改圆形进度条的颜色?

Android界面设计,下面图的那个圆形进度条怎么设计??

如何在 QT 中获得具有圆形边缘和圆形进度边缘的 QProgressBar?

android布局文件里的ProgressBar长形进度条怎么自定义样式

如何重新加载圆形进度条