Using WPF - how can I create a graph that looks like the Windows Progress bar - doughnut chart? https://guyterry.files.wordpress.com/2015/07/upgradingrelax.jpg
Asked
Active
Viewed 478 times
0
-
1Possible duplicate of [WPF Doughnut ProgressBar](http://stackoverflow.com/questions/36752183/wpf-doughnut-progressbar) – Mark Feldman May 03 '16 at 23:31
-
2Does this answer your question? [How to create a Circular Style ProgressBar](https://stackoverflow.com/questions/4871263/how-to-create-a-circular-style-progressbar) – ARJUN Mar 31 '23 at 16:14
2 Answers
1
Try to make it as a user control using ring component from available drag and drop components. Add label and make some properties which you can modify to get certain result. See this post. Or try Tutorial

Community
- 1
- 1

Nikolai Arsenov
- 464
- 2
- 10
0
Try this:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication1
{
public class CircleProgress : Canvas
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(180d, OnValueChanged));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(0d, OnValueChanged));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(360d, OnValueChanged));
public static readonly DependencyProperty BackgroundCircleStrokeProperty =
DependencyProperty.Register("BackgroundCircleStroke", typeof(Brush),
typeof(CircleProgress), new FrameworkPropertyMetadata(Brushes.Gray, OnStrokeChanged));
public static readonly DependencyProperty BackgroundCircleStrokeThicknessProperty =
DependencyProperty.Register("BackgroundCircleStrokeThickness", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(5d, OnStrokeChanged));
public static readonly DependencyProperty MainCircleStrokeProperty =
DependencyProperty.Register("MainCircleStroke", typeof(Brush),
typeof(CircleProgress), new FrameworkPropertyMetadata(Brushes.DeepSkyBlue, OnStrokeChanged));
public static readonly DependencyProperty MainCircleStrokeThicknessProperty =
DependencyProperty.Register("MainCircleStrokeThickness", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(5d, OnStrokeChanged));
public static readonly DependencyProperty TextStrokeProperty =
DependencyProperty.Register("TextStroke", typeof(Brush),
typeof(CircleProgress), new FrameworkPropertyMetadata(Brushes.Black, OnStrokeChanged));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public Brush BackgroundCircleStroke
{
get { return (Brush) GetValue(BackgroundCircleStrokeProperty); }
set { SetValue(BackgroundCircleStrokeProperty, value); }
}
public Brush MainCircleStroke
{
get { return (Brush)GetValue(MainCircleStrokeProperty); }
set { SetValue(MainCircleStrokeProperty, value); }
}
public Brush TextStroke
{
get { return (Brush)GetValue(MainCircleStrokeProperty); }
set { SetValue(MainCircleStrokeProperty, value); }
}
public double BackgroundCircleStrokeThickness
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public double MainCircleStrokeThickness
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
private readonly Path _backEllipse = new Path()
{
StrokeThickness = 5,
Stroke = Brushes.Gray
};
private readonly Path _mainEllipse = new Path()
{
StrokeThickness = 5,
Stroke = Brushes.DeepSkyBlue
};
private readonly Path _text = new Path()
{
StrokeThickness = 1,
Fill = Brushes.Black
};
private double _radius = 10;
private Point _center = new Point(0,0);
private Point _startPoint = new Point(0,0);
public CircleProgress()
{
_backEllipse.Data = new EllipseGeometry(new Point(Width/2, Height/2), _radius, _radius);
_mainEllipse.Data = new PathGeometry()
{
Figures = new PathFigureCollection()
{
new PathFigure(new Point(Width/2, 0), new PathSegmentCollection()
{
new ArcSegment(
(new RotateTransform(Value*(360/(Maximum - Minimum)), _center.X, _center.Y)).Transform(
_startPoint), new Size(_radius*2, _radius*2), 0, true, SweepDirection.Clockwise, false)
}, false)
}
};
var text = new FormattedText(Value.ToString(CultureInfo.CurrentCulture),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Palatino"),
0.8* _radius,
Brushes.Black);
_text.Data = text.BuildGeometry(new Point(_center.X - text.Width/2, _center.Y - text.Height/2));
SizeChanged += OnSizeChanged;
Children.Add(_backEllipse);
Children.Add(_mainEllipse);
Children.Add(_text);
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var quadSize = e.NewSize.Height <= e.NewSize.Width ? e.NewSize.Height : e.NewSize.Width;
_radius = quadSize / 2;
_center = new Point(e.NewSize.Width/2, e.NewSize.Height / 2);
_startPoint = _center - new Vector(0, _radius);
UpdateCircle(this);
}
/// <summary>
/// Action when Value, Minimum or Maximum changed.
/// </summary>
/// <param name="d">Dependecy object.</param>
/// <param name="e">EventArgs.</param>
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var circle = d as CircleProgress;
UpdateCircle(circle);
}
private static void OnStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var circle = d as CircleProgress;
circle._backEllipse.Stroke = circle.BackgroundCircleStroke;
circle._backEllipse.StrokeThickness = circle.BackgroundCircleStrokeThickness;
circle._mainEllipse.Stroke = circle.BackgroundCircleStroke;
circle._mainEllipse.StrokeThickness = circle.BackgroundCircleStrokeThickness;
circle._text.Fill = circle.TextStroke;
}
/// <summary>
/// Update Background and Main circles.
/// </summary>
/// <param name="circle">Reference to CircleProgress control.</param>
private static void UpdateCircle(CircleProgress circle)
{
circle._backEllipse.Data = new EllipseGeometry(circle._center, circle._radius, circle._radius);
if (Math.Abs(circle.Value*(360/(circle.Maximum - circle.Minimum)) - 360) < 0.0001)
circle._mainEllipse.Data = new EllipseGeometry(circle._center, circle._radius, circle._radius);
else
{
circle._mainEllipse.Data = new PathGeometry()
{
Figures = new PathFigureCollection()
{
new PathFigure(circle._startPoint, new PathSegmentCollection()
{
new ArcSegment(
(new RotateTransform(circle.Value*(360/(circle.Maximum - circle.Minimum)),
circle._center.X, circle._center.Y)).Transform(
circle._startPoint), new Size(circle._radius, circle._radius), 0,
!(circle.Value*(360/(circle.Maximum - circle.Minimum)) <= 180), SweepDirection.Clockwise,
true)
}, false)
}
};
}
var text = new FormattedText($"{circle.Value:##}",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Palatino"),
0.8 * circle._radius,
Brushes.Black);
circle._text.Data = text.BuildGeometry(new Point(circle._center.X - text.Width / 2, circle._center.Y - text.Height / 2));
}
}
}
XAML Usage:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" x:Name="Main">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="472*"/>
<ColumnDefinition Width="45*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="27"/>
</Grid.RowDefinitions>
<local:CircleProgress Grid.Row="0" Grid.Column="0" Margin="75" Minimum="{Binding ElementName=MinTextBox, Path=Text}" Maximum="{Binding ElementName=MaxTextBox, Path=Text}" Value="{Binding ElementName=slider, Path=Value}"/>
<Slider x:Name="slider" Grid.Column="1" Grid.Row="0" Orientation="Vertical" TickPlacement="BottomRight" Minimum="{Binding ElementName=MinTextBox, Path=Text}" Maximum="{Binding ElementName=MaxTextBox, Path=Text}"/>
<StackPanel Grid.Column="0" Grid.Row="1" Orientation="Horizontal">
<Label Content="Minimum" />
<TextBox Width="200" x:Name="MinTextBox"/>
<Label Content="Maximum"/>
<TextBox Width="150" x:Name="MaxTextBox"/>
</StackPanel>
</Grid>
</Window>

Denis Kosov
- 697
- 6
- 21