#1,206 – Changing Color of Custom Circular Progress

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%.

About Sean
Software developer in the Twin Cities area, passionate about .NET technologies. Equally passionate about my own personal projects related to family history and preservation of family stories and photos.

One Response to #1,206 – Changing Color of Custom Circular Progress

  1. Pingback: Dew Drop - May 1, 2017 (#2469) - Morning Dew

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: