CircularProgressControl is a progress control in circular format. It inherits properties Value, Minimum and Maximum from base class System.Windows.Controls.Primitives.RangeBase. As with the base class, the default value of Minimum is 0 and Maximum is 1.

To display the value of Value in the control, set DisplayValue to true. This will make visible a text block positioned in the control’s center. Font size is set to 10 point by the default style, but can be changed through progress control’s FontSize property. (Likewise for other font properties inherited from FrameworkElement.)

An extensive sample project for CircularProgressControl is included with the installation of DotNetBar for WPF.

Modes

Two Modes are supported, Endless and Percent.

In Percent mode, progress is indicated by the angle of the display where the angle is determined by the value of Value as a percentage of the distance between Minimum and Maximum. Of course, the value of Value must be updated by the client as progress is made.

In Endless mode, the control’s content will rotate continuously, indicating “busy”. (Note that endless mode can serve as a progress reporter and not just a “busy” indicator by setting DisplayValue to true and updating Value as progress is made.) The speed of rotation can be controlled with property RotationSpeedMultiplier. Values less than 1 will slow the rotation, greater than 1 will speed it up.

Types

CircularProgressControl has 5 built-in Types, which determine the look-and-feel. Included is a type simulating the Vista wait cursor and a type that simulates the Silverlight download wait control. Also included is a Content type for using content of your choosing in the rotating portion of the control.

Note: by default, the contents are clipped to the circular bounds of the control. It is possible to override this by setting ClipToBounds = false.

Below is an screen shot showing the available Types in various states and stages of progress which was taken of the included sample project.

CircularProgressControl

ProgressBrush is used for element which indicates progress.

Use BorderBrush and BorderThickness to control the stroke of the outer circumference. Note that BorderThickness is inherited property of type Thickness, but the default template only uses the value assigned to Left. Background can be used to achieve different effects depending on the Type.

Use property InnerRadius to achieve the effect of a ring. InnerBorderThickness and InnerBorderBrush control the stroke of the inner circumference. (For consistency with inherited property BorderBrush, InnerBorderThickness is also of type Thickness.)

It is possible to define your own custom type. To do so, create a ControlTemplate targeting type Control and set it as value of property AnimatingControlTemplate.

Following is a list of the built-in Types:

  • Custom – Use when you want to set up a custom AnimatingControlTemplate.
  • Basic
  • Pie
  • Wedge (use InternalValueOverride to set the relative angle of the wedge. If this is not done, then the wedge size is reflective of the value of Value.)
  • Line – Has 12 lines.
  • Line3 – Has 3 lines.
  • Dot – Has 12 dots.
  • Dot3 – Has 3 dots.
  • Cone – Applies the shader effect ConeEffect (defined in DevComponents.WPF.Controls namespace) to the ProgresBrush, which turns the two ends of the linear gradient into each other creating a circle.
  • Content (set ClipToBounds to false if content is unwantedly clipped by circular border.)
  • Pod (aka Vista Busy) – Use PalleteColor to change the color scheme of the background while maintaining same look and feel. Setting Background directly will override the default look.
  • VanishingDot (aka Silverlight) – Use PalleteColor to change color scheme of dots but maintain same general look. Setting ProgressBrush directly will override the default look.

Properties

Here is a complete list of properties defined by CircularProgressControl for your reference:

AnimatingControlTemplatet – A ControlTemplate for a Control which is responsible for animating progress.

PalleteColor – An optional color which is used by certain Types for creating default look and feel. In particular, the Pod (Vista Busy) type uses PalleteColor as input for creating a default Background to achieve the Vista look. VanishingDot (Silverlight) uses it to create default ProgressBrush used as fill for the dots.

Content – Content which will be rotated when Type is Content. By setting a value for Content, Type is automatically set to Content unless it has already been set to something else.

ContentTemplate – a DataTemplate which shows how to render Content.

DisplayValue – Determines whether to display the value of Value inside the control. Default value is false.

IsRunning – Determines whether the progress control is rotating when mode is Endless. Is ignored if mode is not Endless.

InnerRadius – Sets the radius of the inner border of the circular control. Value can be either absolute or relative. Values less than or equal to 1 will be treated as relative to Radius. Values greater than 1 are treated as absolute.

InnerBorderThickness – Thickness of the stroke used to draw the inner radius. Note that because the inherited property BorderThickness is of type Thickness, this property is also a Thickness for consistency. (Only the left value is used.)

InternalValueOverride – Provides a means of directly controlling the angle of the progress display. Must be a double between 0 and 1.

Mode – Either Percent or Endless.

ProgressBrush – Brush used for the UI Element which indicates progress.

Radius – A read-only property indicating the radius of the circle.

RotationSpeedMultiplier – Use to control speed of rotation when in Endless mode. Value of 1 is the default speed. Less than 1 slows down and greater than 1 speeds up the rotation.

Type – Determines the look and feel of the circular progress control.

ValueStringFormat – Optional format string for displaying Value, when DisplayValue is true. (Hint: to show percentage when Minimum and Maximum are their default values of 0 and 1 use 0% as format string.)

Styles and Templates

The default style for CircularProgressControl is provided here for your reference.

<Style TargetType="ctrls:CircularProgressControl">
    <Style.Resources>
        <ctrls:MultiplicationConverter x:Key="multiplicationConverter" />
        <ctrls:ThicknessToDoubleConverter x:Key="thicknessToDoubleConverter" />
        <ctrls:DoubleToPointConverter x:Key="doubleToPointConverter" />
    </Style.Resources>
    <Setter Property="MinHeight" Value="10" />
    <Setter Property="MinWidth" Value="10" />
    <Setter Property="FontSize" Value="10" />
    <Setter Property="ProgressBrush" Value="DarkSlateGray" />
    <Setter Property="InnerBorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrush}" />
    <Setter Property="InnerBorderThickness" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderThickness}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ctrls:CircularProgressControl">
                <Grid Name="SquareGrid" SnapsToDevicePixels="True">
                    <Grid Name="ClippedGrid" Background="{TemplateBinding Background}">
                        <Control Name="AnimatingControl" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True"
                                DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{TemplateBinding AnimatingControlTemplate}" />
                        <Grid.Clip>
                            <GeometryGroup>
                                <EllipseGeometry Center="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius, Converter={StaticResource doubleToPointConverter}}" 
                                                    RadiusX="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius}" 
                                                    RadiusY="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius}" />
                                <EllipseGeometry Center="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius, Converter={StaticResource doubleToPointConverter}}" 
                                                    RadiusX="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InnerRadius}" 
                                                    RadiusY="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InnerRadius}" />
                            </GeometryGroup>
                        </Grid.Clip>
                    </Grid>
                    <Ellipse Stretch="Uniform" Stroke="{TemplateBinding BorderBrush}" 
                                StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness, Converter={StaticResource thicknessToDoubleConverter}}" />
                    <Ellipse Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center"  Stroke="{TemplateBinding InnerBorderBrush}"
                                Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InnerRadius, Converter={StaticResource multiplicationConverter}, ConverterParameter=2}" 
                                Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InnerRadius, Converter={StaticResource multiplicationConverter}, ConverterParameter=2}"
                                StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InnerBorderThickness, Converter={StaticResource thicknessToDoubleConverter}}" />
                    <TextBlock Name="ValueTextBlock" VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="DisplayValue" Value="False">
                        <Setter TargetName="ValueTextBlock" Property="Visibility" Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="ClipToBounds" Value="False">
                        <Setter TargetName="ClippedGrid" Property="Clip" Value="{x:Null}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="Type" Value="Basic">
            <Setter Property="BorderBrush" Value="#FF2F4F4F" />
            <Setter Property="InnerBorderBrush" Value="#602F4F4F" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="ProgressBrush">
                <Setter.Value>
                    <LinearGradientBrush>
                        <GradientStop Color="Transparent" Offset="0" />
                        <GradientStop Color="#FF2F4F4F" Offset="1" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.DefaultAnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="Cone">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.ConeAnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="Line">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.LineAnimatingControlTemplateKey}}" />
            <Setter Property="InnerRadius" Value="0.25" />
        </Trigger>
        <Trigger Property="Type" Value="Line3">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.Line3AnimatingControlTemplateKey}}" />
            <Setter Property="InnerRadius" Value="0.25" />
        </Trigger>
        <Trigger Property="Type" Value="Dot">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.DotAnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="Dot3">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.Dot3AnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="Pie">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.PieAnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="Wedge">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.WedgeAnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="Content">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.ContentAnimatingControlTemplateKey}}" />
        </Trigger>
        <Trigger Property="Type" Value="VanishingDot">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.VanishingDotAnimatingControlTemplateKey}}" />
            <Setter Property="PalleteColor" Value="#FF2C64DE" />
            <Setter Property="ProgressBrush" Value="{x:Null}" />
        </Trigger>
        <Trigger Property="Type" Value="Pod">
            <Setter Property="AnimatingControlTemplate" Value="{DynamicResource {x:Static ctrls:CircularProgressControl.PodAnimatingControlTemplateKey}}" />
            <Setter Property="InnerRadius" Value="0.50" />
            <Setter Property="BorderBrush" Value="#FF0D3662" />
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="InnerBorderThickness" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderThickness, Converter={StaticResource multiplicationConverter}, ConverterParameter=2.35}" />
            <Setter Property="ProgressBrush">
                <Setter.Value>
                    <RadialGradientBrush>
                        <GradientStop Color="#D0FFFFFF" Offset="0.35" />
                        <GradientStop Color="#10000000" Offset=".93" />
                    </RadialGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="PalleteColor" Value="#FFEEFFBC" />
            <Setter Property="Background">
                <Setter.Value>
                    <DrawingBrush>
                        <DrawingBrush.Drawing>
                            <DrawingGroup>
                                <GeometryDrawing>
                                    <GeometryDrawing.Brush>
                                        <SolidColorBrush Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ctrls:CircularProgressControl}}, Path=PalleteColor}" />
                                    </GeometryDrawing.Brush>
                                    <GeometryDrawing.Geometry>
                                        <EllipseGeometry RadiusX="1" RadiusY="1" Center="0.5,0.5" />
                                    </GeometryDrawing.Geometry>
                                </GeometryDrawing>
                                <GeometryDrawing>
                                    <GeometryDrawing.Brush>
                                        <RadialGradientBrush>
                                            <GradientStop Color="#A0000000" Offset="0"/>
                                            <GradientStop Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ctrls:CircularProgressControl}}, Path=PalleteColor}"/>
                                            <GradientStop Color="#90000000" Offset="1"/>
                                            <GradientStop Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ctrls:CircularProgressControl}}, Path=PalleteColor}" Offset="0.8"/>
                                            <GradientStop Color="#A0000000" Offset="0.3"/>
                                        </RadialGradientBrush>
                                    </GeometryDrawing.Brush>
                                    <GeometryDrawing.Geometry>
                                        <EllipseGeometry  RadiusX="1" RadiusY="1" Center="0.5,0.5" />
                                    </GeometryDrawing.Geometry>
                                </GeometryDrawing>
                            </DrawingGroup>
                        </DrawingBrush.Drawing>
                    </DrawingBrush>
                </Setter.Value>
            </Setter>
        </Trigger>
    </Style.Triggers>
</Style>

Here is the ControlTemplate which is used as the value of property AnimatingControlTemplate when Type equals Basic:

<ControlTemplate x:Key="{x:Static ctrls:CircularProgressControl.BasicAnimatingControlTemplateKey}">
    <ControlTemplate.Resources>
        <ctrls:MultiplicationConverter x:Key="multiplicationConverter" />
        <ctrls:ThicknessToDoubleConverter x:Key="thicknessToDoubleConverter" />
        <ctrls:DoubleToThicknessConverter x:Key="doubleToThicknessConverter" />
        <Storyboard x:Key="IsRunningStoryboard" RepeatBehavior="Forever">
            <DoubleAnimation Storyboard.TargetName="AnimatedGrid" Storyboard.TargetProperty="(Grid.RenderTransform).(RotateTransform.Angle)" Duration="00:00:01.25" From="0" To="359.99" />
        </Storyboard>
    </ControlTemplate.Resources>
    <Grid Name="AnimatedGrid" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center"
            Width="{Binding Path=Radius, Converter={StaticResource multiplicationConverter}, ConverterParameter=2}" 
            Height="{Binding Path=Radius, Converter={StaticResource multiplicationConverter}, ConverterParameter=2}">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Ellipse Grid.RowSpan="2" Stretch="Uniform" Fill="{Binding ProgressBrush}" />
        <Rectangle Name="ProgressMarker" HorizontalAlignment="Center" Width="1" Fill="{Binding BorderBrush}" Stretch="Fill" />
        <Grid.RenderTransform>
            <RotateTransform Angle="{Binding Path=NormalizedValue, Converter={StaticResource multiplicationConverter}, ConverterParameter=360}" />
        </Grid.RenderTransform>
    </Grid>
    <ControlTemplate.Triggers>
        <DataTrigger Binding="{Binding Mode}" Value="Endless">
            <Setter TargetName="ProgressMarker" Property="Visibility" Value="Collapsed" />
        </DataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Related posts:

  1. WPF Color Control Quick Start Guide
  2. Brush Control Quick Start Guide
  3. Rating Control for WPF Quick Start Guide
  4. WPF EnumPicker
  5. How to make a password editor in WPF PropertyGrid.