#1,206 – Changing Color of Custom Circular Progress
April 28, 2017 1 Comment
A couple of earlier posts (#1,155 and #1,156) demonstrated how to create a simple custom control that displays progress in a circular fashion. A reader of the blog asked how you would change the color of the progress indicator when it got to a certain value. The example below shows how to do that.
Below is the code for the indicator itself. This matches post #1,156 except that we’ve removed the static constructor that overrides the stroke and fill color of the indicator.
public class CircularProgress : Shape { // Value (0-100) public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } // DependencyProperty - Value (0 - 100) private static FrameworkPropertyMetadata valueMetadata = new FrameworkPropertyMetadata( 0.0, // Default value FrameworkPropertyMetadataOptions.AffectsRender, null, // Property changed callback new CoerceValueCallback(CoerceValue)); // Coerce value callback public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(CircularProgress), valueMetadata); private static object CoerceValue(DependencyObject depObj, object baseVal) { double val = (double)baseVal; val = Math.Min(val, 99.999); val = Math.Max(val, 0.0); return val; } protected override Geometry DefiningGeometry { get { double startAngle = 90.0; double endAngle = 90.0 - ((Value / 100.0) * 360.0); double maxWidth = Math.Max(0.0, RenderSize.Width - StrokeThickness); double maxHeight = Math.Max(0.0, RenderSize.Height - StrokeThickness); double xStart = maxWidth / 2.0 * Math.Cos(startAngle * Math.PI / 180.0); double yStart = maxHeight / 2.0 * Math.Sin(startAngle * Math.PI / 180.0); double xEnd = maxWidth / 2.0 * Math.Cos(endAngle * Math.PI / 180.0); double yEnd = maxHeight / 2.0 * Math.Sin(endAngle * Math.PI / 180.0); StreamGeometry geom = new StreamGeometry(); using (StreamGeometryContext ctx = geom.Open()) { ctx.BeginFigure( new Point((RenderSize.Width / 2.0) + xStart, (RenderSize.Height / 2.0) - yStart), true, // Filled true); // Closed ctx.ArcTo( new Point((RenderSize.Width / 2.0) + xEnd, (RenderSize.Height / 2.0) - yEnd), new Size(maxWidth / 2.0, maxHeight / 2), 0.0, // rotationAngle (startAngle - endAngle) > 180, // greater than 180 deg? SweepDirection.Clockwise, true, // isStroked false); ctx.LineTo(new Point((RenderSize.Width / 2.0), (RenderSize.Height / 2.0)), true, false); } return geom; } } }
Next is a snippet from the code-behind for a main window. This is just a click handler for a button that updates a property. (Note the use of SetProp method from post #1,205)
private double _pctComplete = 0.0; public double PctComplete { get { return _pctComplete; } set { SetProp(ref _pctComplete, value); } } private void Button_Click(object sender, RoutedEventArgs e) { PctComplete = 0.0; DispatcherTimer timer = new DispatcherTimer(); timer.Tick += (s, ea) => { PctComplete += 1.0; if (PctComplete >= 100.0) timer.Stop(); }; timer.Interval = new TimeSpan(0, 0, 0, 0, 150); timer.Start(); }
We then have a XAML fragment, where we define an instance of the progress indicator. We also set up a data trigger to set the Stroke and Fill properties to change color when they reach a certain level.
<Window.Resources> <loc:GreaterThanConverter x:Key="greaterThanConverter"/> <Style x:Key="progChangeColor" TargetType="loc:CircularProgress"> <Setter Property="Stroke" Value="Red"/> <Setter Property="Fill" Value="Red"/> <Style.Triggers> <DataTrigger Binding="{Binding PctComplete, Converter={StaticResource greaterThanConverter}, ConverterParameter=75}" Value="True"> <Setter Property="Stroke" Value="Green"/> <Setter Property="Fill" Value="Green"/> </DataTrigger> </Style.Triggers> </Style> </Window.Resources> <StackPanel> <loc:CircularProgress Style="{StaticResource progChangeColor}" Height="100" Width="100" Margin="5" Value="{Binding PctComplete}" HorizontalAlignment="Center"/> <ProgressBar Maximum="100" Value="{Binding PctComplete}" Height="25" Margin="10"/> <Button Content="Start Timer" Click="Button_Click" HorizontalAlignment="Center" Padding="12,7"/> </StackPanel>
Note that we’re making use of a value converter that takes a value in (e.g. PctComplete) and outputs True when that value passes a certain point (e.g. 75). Here’s the code for that converter:
public class GreaterThanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return ((double)value) > double.Parse(parameter as string); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
When everything is wired up and we run this, we see that the indicator starts out red.
And it then turns green when progress gets past 75%.
Pingback: Dew Drop - May 1, 2017 (#2469) - Morning Dew