WPF için ProgressRing

WPF üzerinde Windows 8’dekine benzer aktiviteyi gösteren bir kontrol oluşturabiliriz.

Not: MahApps.Metro kütüphanesi bu kontrolü ve daha bir çok farklı kontrolü içermektedir. Bunun yanında tema desteği de mevcuttur.
http://mahapps.com/

Kullanımı:
ProgressRing.cs dosyasını Controls klasörü altında oluşturulur.
ProgressRing.xaml dosyasını Styles altında oluşturulur.
App.xaml dosyasında Application.Resources içine ProgressRing.xaml resource dictionary olarak eklenir.
Kullanacağımız sayfada namespace tanımlanarak kontrol kullanılabilir.

ProgressRing.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace ProgressRingTest.Controls
{
    [TemplateVisualState(Name = "Large", GroupName = "SizeStates")]
    [TemplateVisualState(Name = "Small", GroupName = "SizeStates")]
    [TemplateVisualState(Name = "Inactive", GroupName = "ActiveStates")]
    [TemplateVisualState(Name = "Active", GroupName = "ActiveStates")]
    public class ProgressRing : Control
    {
        public static readonly DependencyProperty BindableWidthProperty = DependencyProperty.Register("BindableWidth", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double), BindableWidthCallback));

        public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(ProgressRing), new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsActiveChanged));

        public static readonly DependencyProperty IsLargeProperty = DependencyProperty.Register("IsLarge", typeof(bool), typeof(ProgressRing), new PropertyMetadata(true, IsLargeChangedCallback));

        public static readonly DependencyProperty MaxSideLengthProperty = DependencyProperty.Register("MaxSideLength", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));

        public static readonly DependencyProperty EllipseDiameterProperty = DependencyProperty.Register("EllipseDiameter", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));

        public static readonly DependencyProperty EllipseOffsetProperty = DependencyProperty.Register("EllipseOffset", typeof(Thickness), typeof(ProgressRing), new PropertyMetadata(default(Thickness)));

        private List<Action> _deferredActions = new List<Action>();

        static ProgressRing()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressRing), new FrameworkPropertyMetadata(typeof(ProgressRing)));
        }

        public ProgressRing()
        {
            SizeChanged += OnSizeChanged;
        }

        public double MaxSideLength
        {
            get { return (double)GetValue(MaxSideLengthProperty); }
            private set { SetValue(MaxSideLengthProperty, value); }
        }

        public double EllipseDiameter
        {
            get { return (double)GetValue(EllipseDiameterProperty); }
            private set { SetValue(EllipseDiameterProperty, value); }
        }

        public Thickness EllipseOffset
        {
            get { return (Thickness)GetValue(EllipseOffsetProperty); }
            private set { SetValue(EllipseOffsetProperty, value); }
        }

        public double BindableWidth
        {
            get { return (double)GetValue(BindableWidthProperty); }
            private set { SetValue(BindableWidthProperty, value); }
        }

        public bool IsActive
        {
            get { return (bool)GetValue(IsActiveProperty); }
            set { SetValue(IsActiveProperty, value); }
        }

        public bool IsLarge
        {
            get { return (bool)GetValue(IsLargeProperty); }
            set { SetValue(IsLargeProperty, value); }
        }

        private static void BindableWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var ring = dependencyObject as ProgressRing;
            if (ring == null)
                return;

            var action = new Action(() =>
            {
                ring.SetEllipseDiameter(
                    (double)dependencyPropertyChangedEventArgs.NewValue);
                ring.SetEllipseOffset(
                    (double)dependencyPropertyChangedEventArgs.NewValue);
                ring.SetMaxSideLength(
                    (double)dependencyPropertyChangedEventArgs.NewValue);
            });

            if (ring._deferredActions != null)
                ring._deferredActions.Add(action);
            else
                action();
        }

        private void SetMaxSideLength(double width)
        {
            MaxSideLength = width <= 20 ? 20 : width; } private void SetEllipseDiameter(double width) { EllipseDiameter = width / 8; } private void SetEllipseOffset(double width) { EllipseOffset = new Thickness(0, width / 2, 0, 0); } private static void IsLargeChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var ring = dependencyObject as ProgressRing; if (ring == null) return; ring.UpdateLargeState(); } private void UpdateLargeState() { Action action; if (IsLarge) action = () => VisualStateManager.GoToState(this, "Large", true);
            else
                action = () => VisualStateManager.GoToState(this, "Small", true);

            if (_deferredActions != null)
                _deferredActions.Add(action);

            else
                action();
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
        {
            BindableWidth = ActualWidth;
        }

        private static void IsActiveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var ring = dependencyObject as ProgressRing;
            if (ring == null)
                return;

            ring.UpdateActiveState();
        }

        private void UpdateActiveState()
        {
            Action action;

            if (IsActive)
                action = () => VisualStateManager.GoToState(this, "Active", true);
            else
                action = () => VisualStateManager.GoToState(this, "Inactive", true);

            if (_deferredActions != null)
                _deferredActions.Add(action);

            else
                action();
        }

        public override void OnApplyTemplate()
        {
            //make sure the states get updated
            UpdateLargeState();
            UpdateActiveState();
            base.OnApplyTemplate();
            if (_deferredActions != null)
                foreach (var action in _deferredActions)
                    action();
            _deferredActions = null;
        }
    }

    internal class WidthToMaxSideLengthConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double)
            {
                var width = (double)value;
                return width <= 20 ? 20 : width;
            }

            return null;
        }

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

ProgressRing.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:ProgressRingTest.Controls">

<Style TargetType="controls:ProgressRing">
        <Setter Property="Foreground" Value="{DynamicResource AccentColorBrush}" />
        <Setter Property="IsHitTestVisible" Value="False" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="MinHeight" Value="20" />
        <Setter Property="MinWidth" Value="20" />
        <Setter Property="Height" Value="60" />
        <Setter Property="Width" Value="60" />
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:ProgressRing">
                    <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                        <Border.Resources>

<Style x:Key="ProgressRingEllipseStyle" TargetType="Ellipse">
                                <Setter Property="Opacity" Value="0" />
                                <Setter Property="HorizontalAlignment" Value="Left" />
                                <Setter Property="VerticalAlignment" Value="Top" />
                            </Style>


                        </Border.Resources>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="SizeStates">
                                <VisualState x:Name="Large">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="SixthCircle" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Small" />
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="ActiveStates">
                                <VisualState x:Name="Inactive" />
                                <VisualState x:Name="Active">
                                    <Storyboard RepeatBehavior="Forever">
                                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Ring" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1" Storyboard.TargetProperty="Opacity" BeginTime="0">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.167">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.334">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.501">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.668">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.835">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1R" BeginTime="0" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-110" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="10" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="93" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="205" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="357" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="439" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="585" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2R" BeginTime="00:00:00.167" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-116" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="4" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="87" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="199" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="351" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="433" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="579" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3R" BeginTime="00:00:00.334" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-122" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-2" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="81" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="193" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="345" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="427" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="573" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4R" BeginTime="00:00:00.501" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-128" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-8" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="187" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="339" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="421" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="567" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5R" BeginTime="00:00:00.668" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-134" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-14" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="69" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="181" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="331" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="415" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="561" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6R" BeginTime="00:00:00.835" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-140" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-20" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="63" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="175" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="325" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="409" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="555" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid x:Name="Ring" Margin="{TemplateBinding Padding}" MaxWidth="{Binding MaxSideLength, RelativeSource={RelativeSource Mode=TemplatedParent}}" MaxHeight="{Binding MaxSideLength, RelativeSource={RelativeSource Mode=TemplatedParent}}" Visibility="Collapsed" RenderTransformOrigin=".5,.5" FlowDirection="LeftToRight">

                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E1R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E1" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E2R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E2" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E3R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E3" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E4R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E4" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E5R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E5" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5" Visibility="Collapsed" x:Name="SixthCircle">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E6R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E6" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

 

Leave a Reply

Your email address will not be published. Required fields are marked *