【问题标题】:How to create a Circular Style ProgressBar如何创建圆形样式进度条
【发布时间】:2011-06-19 18:24:09
【问题描述】:

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

我应该如何通过增加Value属性来实现Circle来填充?

【问题讨论】:

  • 相关链接已损坏。请在问题中插入图片。

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


【解决方案1】:

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

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

  1. 创建新的用户控件
  2. 为其添加新的 Value、Maximum 和 Minimum 依赖属性。
  3. 处理用户控件中的 Value、Maximum 和 Minimum 属性更改事件以计算 Angle 属性。
  4. 在后面的代码中构造两个“饼图”(请参阅​​ 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
}

实施:

  1. 将源代码放入 WinForms 项目中任意位置的新类中,将类命名为“CircularProgressBar.cs”。
  2. 编译项目。
  3. 编译后,您应该会在工具栏上看到一个新的控件或“组件”。
  4. 将此新控件拖放到任何表单中并自定义其属性。

控件如下所示:

享受吧。

【讨论】:

  • +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 属性,并使用适当的值转换器将值更改为对循环进度条有用的值。

编辑:

在模板中:

  1. 用黄色填充圆形。

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

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

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

  5. Downvoter 将我的代表退还给我。

【讨论】:

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