#1,155 – A Circular Progress Indicator

Using the earlier custom pie shape control as a base, we can now create a custom control that serves as a circular progress indicator.

Here’s the code for the circular progress control:

    public class CircularProgress : Shape
    {
        static CircularProgress()
        {
            Brush myGreenBrush = new SolidColorBrush(Color.FromArgb(255, 6, 176, 37));
            myGreenBrush.Freeze();

            StrokeProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(myGreenBrush));
            FillProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(myGreenBrush));
        }

        // 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;
            }
        }
    }

Here’s an example of using this control in XAML. The example also includes a traditional progress bar, so that we can compare them.

    <StackPanel>
        <loc:CircularProgress
                 Height="100" Width="100" Margin="5"
                 Value="{Binding PctComplete}"
                 HorizontalAlignment="Center"/>
        <ProgressBar x:Name="prog2" Maximum="100"
                     Value="{Binding PctComplete}"
                     Height="25" Margin="10"/>
        <Button Content="Start Timer" Click="Button_Click"
                HorizontalAlignment="Center"
                Padding="12,7"/>
    </StackPanel>

Finally, here’s the code-behind, including the PctComplete property that we bind to and code that kicks off a timer that updates the property periodically.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            this.DataContext = this;
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged(string prop)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }

        private double pctComplete = 0.0;
        public double PctComplete
        {
            get { return pctComplete; }
            set
            {
                if (pctComplete != value)
                {
                    pctComplete = value;
                    OnPropertyChanged("PctComplete");
                }
            }
        }

        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, 30);  // 2/sec
            timer.Start();
        }
    }

Here’s the control in action:

1155-001

Advertisement

#1,033 – ProgressBar Can Be Horizontal or Vertical

The ProgressBar is rendered horizontally by default, but you can orient it vertically by setting the Orientation property to Vertical.

    <StackPanel>
        <ProgressBar Value="{Binding TheProgress}"
                     Height="15" Margin="15,15,15,0"/>
        <Label Content="Doing some work..."
               Margin="10,0"/>
        <ProgressBar Value="{Binding TheProgress}"
                     Width="15" Height="100" Margin="15"
                     HorizontalAlignment="Left"
                     Orientation="Vertical"/>
        <Button Margin="15" Padding="15,3" HorizontalAlignment="Center"
            Content="Start" Click="Button_Click"/>
    </StackPanel>

1033-001

#1,031 – Update a ProgressBar from a Background Thread

If you try to update a ProgressBar in a chunk of code that is running on the main UI thread and doing some work, the ProgressBar won’t update until all of the work is done.  In the example below, the ProgressBar won’t actually update until the loop finishes executing.

for (int i = 0; i < 100; i++)
 {
     pbTest.Value = i;
     Thread.Sleep(50);
 }

To allow the ProgressBar to update immediately, you need to do two things:

  • Do the work on a background thread
  • Periodically update the ProgressBar using a bound property (rather than directly)

We do the work on the background thread to avoid blocking the UI thread.  But we’re not allowed to update the ProgressBar from this thread because we aren’t the thread that created the control.  Instead, we update progress by setting a property that we then bind the ProgressBar.Value to.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            new Thread(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    TheProgress = i;
                    Thread.Sleep(50);
                }
            }).Start();
        }

#1,030 – Displaying an Indeterminate Progress Bar

When you display a ProgressBar to show progress on some background task, you often know how much work needs to be done and how much of the work has already been done.  The ProgressBar below shows that we’ve loaded 5 out of 12 records, so the ProgressBar is 42% full.

1074-001

In other cases, you may want to use a ProgressBar to show the user that something is happening, but you might not know how long the operation will take or how much of the total work has already been done.  In these cases, you can display an “indeterminate” progress bar by setting the IsIndeterminate property to true.  A green bar will repeatedly slide across the face of the ProgressBar to show that something is happening.

        <ProgressBar IsIndeterminate="True"
                     Height="15" Margin="15,15,15,0"/>
        <Label Content="Connecting to database..."
               Margin="10,0"/>

1030-001
When the operation is done, you can just set IsIndeterminate to false and set Value to 0.

#1,029 – Simple ProgressBar Example

Here’s an example of how we could use a ProgressBar in an application to show # records processed.

    <StackPanel>
        <ProgressBar Value="{Binding NumRecsLoaded}" Maximum="{Binding TotalNumRecs}"
                     Height="15" Margin="15,15,15,0"/>
        <Label Content="{Binding RecsLoadedMessage}"
               Margin="10,0"/>
        <Button Margin="15" Padding="15,3" HorizontalAlignment="Center"
            Content="Start" Click="Button_Click"/>
    </StackPanel>

In code, we declare properties for total number of records and number already loaded. When the user clicks on the Button, we set up a timer that increments the # records loaded each time that it ticks (twice a second).  (Note–to use the DispatcherTimer, you need to use namespace System.Windows.Threading).

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private int numRecsLoaded = 0;
        public int NumRecsLoaded
        {
            get { return numRecsLoaded; }
            protected set
            {
                numRecsLoaded = value;
                RaisePropertyChanged("NumRecsLoaded");
                RaisePropertyChanged("RecsLoadedMessage");
            }
        }

        private int totalNumRecs = 12;
        public int TotalNumRecs
        {
            get { return totalNumRecs; }
            protected set
            {
                totalNumRecs = value;
                RaisePropertyChanged("TotalNumRecs");
            }
        }

        public string RecsLoadedMessage
        {
            get { return string.Format("Loaded {0} records...", numRecsLoaded);  }
        }

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

        }

        // INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void RaisePropertyChanged(string propName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            DispatcherTimer timer = new DispatcherTimer();
            timer.Tick += (s,ea) =>
            {
                NumRecsLoaded++;
                if (NumRecsLoaded == TotalNumRecs)
                    timer.Stop();
            };
            timer.Interval = new TimeSpan(0, 0, 0, 0, 500);  // 2/sec
            timer.Start();
        }
    }

1074-001

#1,028 – ProgressBar Basics

You’ll often want to let a user know that something is happening in the background in your application.  For example, you might be processing some records or calculating a result.

You could use a simple busy indicator to show that something is happening.  But you may also want to show how much of the operation is complete, giving the user a sense of how much work remains and how fast the work is being done.

To show progress in this way, you can use a ProgressBar control.  The ProgressBar has a range, represented by its Minimum and Maximum values and a current Value falling within that range.  You typically update the Value periodically, so that it increases from Minimum to Maximum as the work progresses.

In the example below, we’ve set the Value to 75, where Minimum and Maximum are 0 and 100, respectively.  The Value is represented by the shaded green bar.

1073-001