Martyn Ball
Martyn Ball

Reputation: 4885

Circular Progress Bar Issue

I have adapted some code I found online for a circular progress bar, it works, until the progress bar gets to 100%, then it disappears...

I can't work out what is wrong, and i'm assuming it's something to do with how ArcSegment works but I don't FULLY understand the mathematics behind it, could someone advise me?

Here is the ProgressBar and the Slider:

<Slider Value="{Binding Source={StaticResource runtimeVariables}, Path=uploadProgress}" Maximum="100" Margin="0,0,252,0" />
        <ProgressBar Width="150" Style="{StaticResource CircularProgress}" Value="{Binding Source={StaticResource runtimeVariables},Path=uploadProgress}" Maximum="100" HorizontalAlignment="Left" />

Here is the style and the converters

<CircleProgress:StartPointConverter x:Key="StartPointConverter" />
    <CircleProgress:ArcSizeConverter x:Key="ArcSizeConverter" />
    <CircleProgress:ArcEndPointConverter x:Key="ArcEndPointConverter" />
    <CircleProgress:LargeArcConverter x:Key="LargeArcConverter" />
    <CircleProgress:SizeTextOnParent x:Key="SizeTextOnParent" />
    <CircleProgress:Difference x:Key="Difference" />

    <Style TargetType="ProgressBar" x:Key="CircularProgress">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ProgressBar">
                    <Grid x:Name="PathGrid" Width="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Width}">
                        <Canvas>
                            <DockPanel Height="{Binding ElementName=PathGrid, Path=Width}" Width="{Binding ElementName=PathGrid, Path=Width}">
                                <TextBlock x:Name="PathPercentage" 
                                           Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Value, StringFormat={}{0}%}"
                                           Foreground="White"
                                           FontSize="{Binding ElementName=PathGrid, Path=ActualWidth, Converter={StaticResource SizeTextOnParent}}" 
                                           VerticalAlignment="Center" TextAlignment="Center">
                                </TextBlock>
                            </DockPanel>
                            <Path x:Name="pathRoot"
                                  Panel.ZIndex="1"
                                  Stroke="#8ab71c" 
                                  StrokeThickness="6" 
                                  HorizontalAlignment="Center" 
                                  VerticalAlignment="Top">
                                <Path.Data>
                                    <PathGeometry>
                                        <PathFigureCollection>
                                            <PathFigure StartPoint="{Binding ElementName=PathGrid, Path=ActualWidth, Converter={StaticResource StartPointConverter}, Mode=OneWay}">
                                                <ArcSegment Size="{Binding ElementName=PathGrid, Path=ActualWidth, Converter={StaticResource ArcSizeConverter}, Mode=OneWay}" SweepDirection="Clockwise">
                                                    <ArcSegment.IsLargeArc>
                                                        <MultiBinding Converter="{StaticResource LargeArcConverter}">
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
                                                        </MultiBinding>
                                                    </ArcSegment.IsLargeArc>
                                                    <ArcSegment.Point>
                                                        <MultiBinding Converter="{StaticResource ArcEndPointConverter}">
                                                            <Binding ElementName="PathGrid" Path="ActualWidth" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
                                                        </MultiBinding>
                                                    </ArcSegment.Point>
                                                </ArcSegment>
                                            </PathFigure>
                                        </PathFigureCollection>
                                    </PathGeometry>
                                </Path.Data>
                            </Path>
                            <Ellipse
                                x:Name="backCircle"
                                Panel.ZIndex="0"
                                Fill="Transparent"
                                Stroke="#434953" 
                                StrokeThickness="3" 
                                Width="{Binding ElementName=PathGrid, Path=Width}" 
                                Height="{Binding ElementName=PathGrid, Path=Width}" />
                        </Canvas>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

ArcEndPointConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace Test_Project.Converters
{
    public class ArcEndPointConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var actualWidth = values[0].ExtractDouble();
            var value = values[1].ExtractDouble();
            var minimum = values[2].ExtractDouble();
            var maximum = values[3].ExtractDouble();

            if (new[] { actualWidth, value, minimum, maximum }.AnyNan())
                return Binding.DoNothing;

            if (values.Length == 5)
            {
                var fullIndeterminateScaling = values[4].ExtractDouble();
                if (!double.IsNaN(fullIndeterminateScaling) && fullIndeterminateScaling > 0.0)
                {
                    value = (maximum - minimum) * fullIndeterminateScaling;
                }
            }

            var percent = maximum <= minimum ? 1.0 : (value - minimum) / (maximum - minimum);
            var degrees = 360 * percent;
            var radians = degrees * (Math.PI / 180);

            var centre = new Point(actualWidth / 2, actualWidth / 2);
            var hypotenuseRadius = (actualWidth / 2);

            var adjacent = Math.Cos(radians) * hypotenuseRadius;
            var opposite = Math.Sin(radians) * hypotenuseRadius;

            return new Point(centre.X + opposite, centre.Y - adjacent);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

ArcSizeConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace Test_Project.Converters
{
    public class ArcSizeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double && ((double)value > 0.0))
            {
                return new Size((double)value / 2, (double)value / 2);
            }

            return new Point();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
}

LargeArcConverter.cs

using System;
using System.Globalization;
using System.Windows.Data;

namespace Test_Project.Converters
{
    public class LargeArcConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var value = values[0].ExtractDouble();
            var minimum = values[1].ExtractDouble();
            var maximum = values[2].ExtractDouble();

            if (new[] { value, minimum, maximum }.AnyNan())
                return Binding.DoNothing;

            if (values.Length == 4)
            {
                var fullIndeterminateScaling = values[3].ExtractDouble();
                if (!double.IsNaN(fullIndeterminateScaling) && fullIndeterminateScaling > 0.0)
                {
                    value = (maximum - minimum) * fullIndeterminateScaling;
                }
            }

            var percent = maximum <= minimum ? 1.0 : (value - minimum) / (maximum - minimum);

            return percent > 0.5;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

StartPointConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace Test_Project.Converters
{
    public class StartPointConverter : IValueConverter
    {
        [Obsolete]
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double && ((double)value > 0.0))
            {
                return new Point((double)value / 2, 0);
            }

            return new Point();
        }

        [Obsolete]
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }

    }
}

Upvotes: 2

Views: 1038

Answers (1)

kirotab
kirotab

Reputation: 1306

I have a very quick workaround for you, where you won't have to change anything but it's a workaround nevertheless ;)

in your ArcEndPointConverter just add this row

if (degrees == 360) degrees = 359.99;

Just before converting to Radians, I think otherwise the value is equal to the value when your end degree is 0° so we just trick it to be almost 360° so that it could draw itself (with my test it's not visible for human eye)

...
var percent = maximum <= minimum ? 1.0 : (value - minimum) / (maximum - minimum);
var degrees = 360 * percent;
if (degrees == 360) degrees = 359.99;
var radians = degrees * (Math.PI / 180);
...

Upvotes: 5

Related Questions