WPF : Circular Progress Bar

Ever wanted a slightly different progress bar from the standard WPF Progress bar which looks like the following (IsIndeterminate is like the WinForms Marquee enum value) :

   1:  <ProgressBar VerticalAlignment="Top"
   2:      Height="22" IsIndeterminate="True"/>

image

What I would like is one that is more like the ones you see all over the web, where we have a round wheel sort of progress indicator. Would you like one of those?

Well fear not your search is over, here is a very simple idea, just arrange some Ellipses in a circle within a Canvas and do a constant Rotate StoryBoard and bam, a circular progress bar.

Here is the XAML for such a UserControl.

 

   1:  <UserControl x:Class="Sonic.CircularProgressBar"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      Height="120" Width="120" Background="Transparent">
   5:      <Grid x:Name="LayoutRoot" Background="Transparent"
   6:            HorizontalAlignment="Center" VerticalAlignment="Center">
   7:          <Grid.RenderTransform>
   8:              <ScaleTransform x:Name="SpinnerScale"
   9:                              ScaleX="1.0" ScaleY="1.0" />
  10:          </Grid.RenderTransform>
  11:          <Canvas RenderTransformOrigin="0.5,0.5"
  12:                  HorizontalAlignment="Center"
  13:                  VerticalAlignment="Center"
  14:                  Width="120" Height="120" >
  15:              <Ellipse Width="21.835" Height="21.862"
  16:                       Canvas.Left="20.1696"
  17:                       Canvas.Top="9.76358"
  18:                       Stretch="Fill" Fill="Orange"
  19:                       Opacity="1.0"/>
  20:              <Ellipse Width="21.835" Height="21.862"
  21:                       Canvas.Left="2.86816"
  22:                       Canvas.Top="29.9581" Stretch="Fill"
  23:                       Fill="Black" Opacity="0.9"/>
  24:              <Ellipse Width="21.835" Height="21.862"
  25:                       Canvas.Left="5.03758e-006"
  26:                       Canvas.Top="57.9341" Stretch="Fill"
  27:                       Fill="Black" Opacity="0.8"/>
  28:              <Ellipse Width="21.835" Height="21.862"
  29:                       Canvas.Left="12.1203"
  30:                       Canvas.Top="83.3163" Stretch="Fill"
  31:                       Fill="Black" Opacity="0.7"/>
  32:              <Ellipse Width="21.835" Height="21.862"
  33:                       Canvas.Left="36.5459"
  34:                       Canvas.Top="98.138" Stretch="Fill"
  35:                       Fill="Black" Opacity="0.6"/>
  36:              <Ellipse Width="21.835" Height="21.862"
  37:                       Canvas.Left="64.6723"
  38:                       Canvas.Top="96.8411" Stretch="Fill"
  39:                       Fill="Black" Opacity="0.5"/>
  40:              <Ellipse Width="21.835" Height="21.862"
  41:                       Canvas.Left="87.6176"
  42:                       Canvas.Top="81.2783" Stretch="Fill"
  43:                       Fill="Black" Opacity="0.4"/>
  44:              <Ellipse Width="21.835" Height="21.862"
  45:                       Canvas.Left="98.165"
  46:                       Canvas.Top="54.414" Stretch="Fill"
  47:                       Fill="Black" Opacity="0.3"/>
  48:              <Ellipse Width="21.835" Height="21.862"
  49:                       Canvas.Left="92.9838"
  50:                       Canvas.Top="26.9938" Stretch="Fill"
  51:                       Fill="Black" Opacity="0.2"/>
  52:              <Ellipse Width="21.835" Height="21.862"
  53:                       Canvas.Left="47.2783"
  54:                       Canvas.Top="0.5" Stretch="Fill"
  55:                       Fill="Black" Opacity="0.1"/>
  56:              <Canvas.RenderTransform>
  57:                  <RotateTransform x:Name="SpinnerRotate"
  58:                                   Angle="0" />
  59:              </Canvas.RenderTransform>
  60:              <Canvas.Triggers>
  61:                  <EventTrigger RoutedEvent="ContentControl.Loaded">
  62:                      <BeginStoryboard>
  63:                          <Storyboard>
  64:                              <DoubleAnimation
  65:                                  Storyboard.TargetName
  66:                                      ="SpinnerRotate"
  67:                                   Storyboard.TargetProperty
  68:                                      ="(RotateTransform.Angle)"
  69:                                   From="0" To="360"
  70:                                   Duration="0:0:01"
  71:                                   RepeatBehavior="Forever" />
  72:                          </Storyboard>
  73:                      </BeginStoryboard>
  74:                  </EventTrigger>
  75:              </Canvas.Triggers>
  76:          </Canvas>
  77:      </Grid>
  78:  </UserControl>

And here is all the C# codebehind

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Data;
   8:  using System.Windows.Documents;
   9:  using System.Windows.Input;
  10:  using System.Windows.Media;
  11:  using System.Windows.Media.Animation;
  12:  using System.Windows.Media.Imaging;
  13:  using System.Windows.Navigation;
  14:  using System.Windows.Shapes;
  15:  
  16:  namespace Sonic
  17:  {
  18:      /// <summary>
  19:      /// Provides a circular progress bar
  20:      /// </summary>
  21:      public partial class CircularProgressBar : UserControl
  22:      {
  23:          public CircularProgressBar()
  24:          {
  25:              InitializeComponent();
  26:  
  27:              //Use a default Animation Framerate of 20, which uses less CPU time
  28:              //than the standard 50 which you get out of the box
  29:              Timeline.DesiredFrameRateProperty.OverrideMetadata(
  30:                  typeof(Timeline),
  31:                      new FrameworkPropertyMetadata { DefaultValue = 20 } );
  32:          }
  33:      }
  34:  }

And here is an example of one of these CircularProgressBar  controls in use:

   1:  <Window
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          xmlns:local="clr-namespace:Sonic"
   5:      x:Class="Sonic.MainWindow">
   6:  
   7:  
   8:      <Grid>
   9:              <local:MediaView/>
  10:          </Grid>
  11:  
  12:  </Window>

And here it is in action (obviously it looks better in run time)….Enjoy

image

And for those that are interested here is a little demo project, progress.zip

37 Comments so far »

  1. Josh Smith said

    am February 3 2009 @ 8:55 pm

    Sweeeet!

  2. Karl Shifflett said

    am February 3 2009 @ 10:08 pm

    You are the Man!

  3. Walt Ritscher said

    am February 4 2009 @ 12:14 am

    Another nice and easy article. Keep ‘em coming.

  4. Eric Burke said

    am February 4 2009 @ 1:02 am

    Nice, Sacha!

    One thing you might consider doing is lowering the DesiredFrameRate on the Storyboard to something that is still acceptable in terms of smoothness. This will go a long way in saving you CPU load, which directly translates into power savings.

  5. sacha said

    am February 4 2009 @ 8:42 am

    Eric thanks for that idea, Ill look into this later.

  6. Pete O'Hanlon said

    am February 4 2009 @ 10:07 am

    Very nice there sir. Such a simple idea, and yet so cool.

  7. sacha said

    am February 4 2009 @ 10:39 am

    Thanks Guys, A am just going to do the Framerate metadata override as suggested by Eric tonight. Its so easy though.

  8. Dew Drop - February 4, 2009 | Alvin Ashcraft's Morning Dew said

    am February 4 2009 @ 4:59 pm

    [...] WPF : Circular Progress Bar (Sacha Barber) [...]

  9. Raul Mainardi Neto said

    am February 4 2009 @ 8:23 pm

    Simple, yet fantastic… as usual… keep up the excelent work. and thanks for the aid with my project =D cheers mate

  10. sacha said

    am February 4 2009 @ 8:28 pm

    Thanks Raul

  11. Martin C said

    am February 4 2009 @ 11:57 pm

    For other WPF newbies (like myself), don’t forget to define the “local” namespace. It’s just one extra attribute in the Window element in the XAML using the control:

    xmlns:local=”clr-namespace:Sonic”

  12. Brian Wang said

    am February 11 2009 @ 2:44 am

    Excellent!

    BTW, I want to ask you a question about the article in

    http://www.codeproject.com/KB/WPF/Ink1.aspx?display=PrintAll&f&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26&select=2073180#SaveAsISF

    since the article was posted years before, I’m not sure if anything question posted following that page would be viewed by you, so I write here.

    my question is when save the ink as bmp file, why there are black black strips on the top and left sides? how to remove them?

    thanks a lot!

  13. sacha said

    am February 11 2009 @ 8:44 am

    Brian

    I know the article you mean, and that doesn’t happen on my PC. Sorry to inform you.

  14. Thomas Freudenberg said

    am February 24 2009 @ 10:30 am

    Great work. However, it would be awesome if the control would be resizable. Currently it has a fixed size of 120×120, it should seamlessly adopt the available space (and support some kind of DesiredSize)

  15. sacha said

    am February 24 2009 @ 10:38 am

    Thomas

    This is easily fixedjust wrap it in a ViewBox where you need to use it and give the ViewBox a Width and Height.

    Bingo, job done.

  16. Paley said

    am February 26 2009 @ 2:49 pm

    Am I being daft or is there a way to get the source code without the line numbers?

  17. sacha said

    am February 27 2009 @ 9:28 am

    Doh, sorry I didnt post the source for this, ill fix this after this weekend ok.

  18. Martin said

    am March 2 2009 @ 2:19 pm

    I’m just getting started on WPF. I’m having trouble incorporating this into an example WPF app that I’ve made. What steps do I need to take to get it to work in another VS solution?

    Any help is greatly appreciated.

  19. sacha said

    am March 2 2009 @ 3:41 pm

    Ill post some demo project very soon.

  20. sacha said

    am March 2 2009 @ 9:21 pm

    Martin, there is now a small demo project. Enjoy

  21. kdawg said

    am April 5 2009 @ 3:09 pm

    Dude, You amaze me. Splendid.

  22. sacha said

    am April 5 2009 @ 3:11 pm

    Kdawg the simple ones are often the best.

  23. Aarthy said

    am April 9 2009 @ 12:26 pm

    The progress bar is really cool. But I’m having some problem running it in the background. The circular bar stops rotating whenever some work is being done, even if I use Background Worker or Dispatcher. Please give a demo of how to use it in the background.

    E.g. This is what I want to do.
    OnButtonClick()
    {
    //show the progress bar
    perform some long operation
    //hide progress bar
    }

    Although the progress bar is shown, it stops rotating once it enters the long operation.

    How to rectify that?

  24. sacha said

    am April 9 2009 @ 12:31 pm

    We are using it running in the background and it works fine.

    We are using something like

    Dispatcher.BgeingInvoke((Action) delegate {

    //show progress
    //do work
    //hide progress

    }, DispatcherPriority.Background);

  25. Aarthy said

    am April 9 2009 @ 12:53 pm

    If you don’t mind, could you mail me a small demo of the application where you have used it in the background? Because I have already tried what you have suggested and it doesn’t work.

  26. ivana said

    am June 9 2009 @ 2:11 pm

    I am using it and it works just fine, but i have this same problem:

    OnButtonClick()
    {
    //show the progress bar
    perform some long operation
    //hide progress bar
    }

    Although the progress bar is shown, it stops rotating once it enters the long operation.

  27. sacha said

    am June 9 2009 @ 2:22 pm

    We are using it running in the background and it works fine.

    We are using something like

    Dispatcher.BgeingInvoke((Action) delegate {

    //show progress
    //do work
    //hide progress

    }, DispatcherPriority.Background);

    This works for us

  28. ivana said

    am June 9 2009 @ 2:34 pm

    But i have this:
    When i define it in xaml I put:

    so that i don´t see it when i start.

    Then, when i click on a combobox, y load some things and:

    private void cboServidores_MouseEnter(object sender, MouseEventArgs e)
    {

    if (tablaServidores.Rows.Count == 0)
    {
    barra.Visibility=Visibility.Visible;
    loadThings();
    barra.Visibility=Visibility.Hidden;

    }

    if I use:

    Dispatcher.BgeingInvoke((Action) delegate {

    barra.Visibility=Visibility.Visible;
    loadThings();
    barra.Visibility=Visibility.Hidden;

    }, DispatcherPriority.Background);

    it shows me the progress circile after the loadThings is finished

    ANY HELP!!!
    THANKS

  29. ivana said

    am June 10 2009 @ 5:55 pm

    any ideas?

  30. Krunal said

    am July 7 2009 @ 1:58 am

    Sacha,
    Any ideas on how to use this circular progress bar as animated cursor?

  31. Robert said

    am August 18 2009 @ 7:35 am

    I like it! Thanks!

  32. sacha said

    am August 18 2009 @ 8:44 am

    Robert

    Thanks. One thing to note we use this at work, and its good, but when you no longer need to the progress bar, you need to remove it from any container, as the animation never stops, so its eats CPU even if not visible, at least it did for our app at work.

  33. Steven Karan said

    am January 12 2010 @ 3:12 pm

    Ivana you have to set the UserControl Visibility to Visible outside the Thread Code and after Finishing processing, then the thread tells the dispatcher to set Visibility as Hidden.

    What I did, I set the UserControls Visibility like:

    I used the class ProgressVisibility:

    public class ProgressVisibility : Binding, IValueConverter
    {
    public ProgressVisibility()
    : base()
    {
    this.Converter = this;
    }

    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    if ((bool)value)
    {
    return Visibility.Collapsed;
    }
    return Visibility.Visible;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    throw new NotImplementedException();
    }

    #endregion
    }

  34. sacha said

    am January 12 2010 @ 3:16 pm

    You could use my new more improved progress wheel http://sachabarber.net/?p=639

  35. Steven Karan said

    am January 13 2010 @ 6:20 am

    That’s awesome! Thanks for both…

  36. vijrumbhan said

    am February 24 2010 @ 8:12 am

    awesome thanks a lot

  37. sacha said

    am February 24 2010 @ 8:41 am

    No worries

Comment RSS · TrackBack URI

Leave a comment

Name: (Required)

eMail: (Required)

Website:

Comment: