如何创建循环样式ProgressBar

时间:2011-02-02 06:00:27

标签: c# wpf user-controls progress-bar

我需要帮助实现这样的循环进度条:

CircularProgressbar

如何通过增加Value属性来实现圆圈填充?

4 个答案:

答案 0 :(得分:22)

您有几个选项 - 第一个是模板ProgressBar控件。事实证明这有点棘手。我写了一篇博文,介绍了如何use an attached ViewModel to achieve the required effect

另一种方法是从头开始创建自己的控件。您可以执行以下操作:

  1. 创建新的用户控件
  2. 向其添加新的Value,Maximum和Minimum依赖项属性。
  3. 处理用户控件中的Value,Maximum和Minimum属性更改事件以计算Angle属性。
  4. 在代码后面构建两个“饼图”(请参阅​​this post)并将其添加到用户界面。

答案 1 :(得分:12)

这有点棘手,但并非不可能。这是我使用平滑动画指导的实现。应使用值转换器来创建CircularProgressBar。

enter image description here

<强> 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等)来进行更多自定义。

答案 2 :(得分:8)

我知道这是一个老问题,但无论如何,这是我的解决方案:

FOR WINFORMS:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public class CircularProgressBar : Control
{
    /*  CREDITS:
     *  Autor: Sajjad Arif Gul / October 12, 2016 / C#, Source Codes
     *  https://www.csharpens.com/c-sharp/circular-progress-bar-in-c-sharp-windows-form-applications-23/
     *  Modified by Jhollman Chacon, 2017 */

#region Enums

public enum _ProgressShape
{
    Round,
    Flat
}

#endregion

#region Variables

private long _Value;
private long _Maximum = 100;
private Color _ProgressColor1 = Color.Orange;
private Color _ProgressColor2 = Color.Orange;
private Color _LineColor = Color.Silver;
private _ProgressShape ProgressShapeVal;

#endregion

#region Custom Properties

public long Value
{
    get { return _Value; }
    set
    {
        if (value > _Maximum)
            value = _Maximum;
        _Value = value;
        Invalidate();
    }
}

public long Maximum
{
    get { return _Maximum; }
    set
    {
        if (value < 1)
            value = 1;
        _Maximum = value;
        Invalidate();
    }
}

public Color ProgressColor1
{
    get { return _ProgressColor1; }
    set
    {
        _ProgressColor1 = value;
        Invalidate();
    }
}

public Color ProgressColor2
{
    get { return _ProgressColor2; }
    set
    {
        _ProgressColor2 = value;
        Invalidate();
    }
}

public Color LineColor
{
    get { return _LineColor; }
    set
    {
        _LineColor = value;
        Invalidate();
    }
}

public _ProgressShape ProgressShape
{
    get { return ProgressShapeVal; }
    set
    {
        ProgressShapeVal = value;
        Invalidate();
    }
}

#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
public CircularProgressBar()
{
    Size = new Size(130, 130);
    Font = new Font("Segoe UI", 15);
    MinimumSize = new Size(100, 100);
    DoubleBuffered = true;
    Value = 57;
    ProgressShape = _ProgressShape.Flat;
    this.ForeColor = Color.DimGray;
}

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.SmoothingMode = SmoothingMode.AntiAlias;
            graphics.Clear(this.BackColor);

            // Dibuja la Linea
            using (Pen pen2 = new Pen(LineColor))
            {
                graphics.DrawEllipse(pen2, 0x18 - 6, 0x18 - 6, (this.Width - 0x30) + 12, (this.Height - 0x30) + 12);
            }

            //Dibuja la Barra de Progreso
            using (LinearGradientBrush brush = new LinearGradientBrush(this.ClientRectangle, this._ProgressColor1, this._ProgressColor2, LinearGradientMode.ForwardDiagonal))
            {
                using (Pen pen = new Pen(brush, 14f))
                {
                    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 el 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)));
                }
            }

            //Dibuja el Texto de Progreso:
            Brush FontColor = new SolidBrush(this.ForeColor);
            SizeF MS = graphics.MeasureString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font);
            graphics.DrawString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font, FontColor, Convert.ToInt32(Width / 2 - MS.Width / 2), Convert.ToInt32(Height / 2 - MS.Height / 2));
            e.Graphics.DrawImage(bitmap, 0, 0);
            graphics.Dispose();
            bitmap.Dispose();
        }
    }
} 
#endregion
}

实现:

  1. 将源代码放入WinForms项目中任意位置的新类中,将类命名为&#39; CircularProgressBar.cs &#39;。
  2. 编译项​​目。
  3. 编译完成后,您应该会看到一个新的控件或&#39;组件&#39;在工具栏上。
  4. 将此新控件拖放到任何表单中并自定义其属性。
  5. 控制看起来像这样:

    Control Preview

    享受。

答案 3 :(得分:6)

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

编辑:

在模板中:

  1. 添加黄色圆圈填充。

  2. 在顶部添加另一个圆圈,颜色为橙色。

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

  4. 将圆圈剪裁为2.将几何体返回到3。

  5. Downvoter给了我我的回复。