각도 기반 그라데이션 효과를 만들어 봤습니다.

예전부터, 도대체 왜 WPF에 AngleGradientBrush가 없는지 의문이었습니다.
그래서 이번에 각도 기반 그라데이션 효과를 만들어 보게 되었습니다.

하지만 안타깝게도 WPF의 브러시는 재정의 구현이 불가능했습니다.
그래서 ShaderEffect 클래스를 기반으로 AngleGradientEffect 클래스를 만들게 되었습니다.
AngleGradientEffect 클래스에 대한 픽셀 셰이더는 AngleGradientEffect.fx 파일에 작성했습니다.

샘플 프로그램을 만들면서, 로딩 인디케이터도 함께 만들어 봤습니다.
혹시라도 이런 기능이 필요했던 분들께 도움이 되었으면 좋겠습니다.

21개의 좋아요

이거보니 생각났는데 원형 progress bar도 없어서 찾아서 썼었는데 공유해봅니다

2개의 좋아요

좋은 정보 감사합니다. Shape 클래스를 상속받아 구현한 예시군요~

저도 예전에 그런 기능이 필요해서 만들어 쓰긴 했었는데요,
그냥 대충 Attached Property 하나 정의해서 Ellipse의 StrokeDashArray를 조작하는 방식으로 만들었었습니다.
하나 만들어 놓고 나름 잘 써먹었던 것 같네요~ ㅎㅎ

<Ellipse
    Stroke="Green" StrokeThickness="50"
    Width="100" Height="100"
    local:EllipseProgress.Value="{Binding Value}"/>
public static class EllipseProgress
{
    public static double GetValue(Ellipse ellipse) => (double)ellipse.GetValue(ValueProperty);
    public static void SetValue(Ellipse obj, double value) => obj.SetValue(ValueProperty, value);

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.RegisterAttached("Value", typeof(double), typeof(EllipseProgress), new PropertyMetadata(0d, OnPropertyChanged));

    private static readonly DependencyProperty EllipseProgressBindingProperty =
        DependencyProperty.RegisterAttached("EllipseProgressBinding", typeof(MultiBinding), typeof(EllipseProgress), new PropertyMetadata(null));

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Ellipse ellipse && !(ellipse.GetValue(EllipseProgressBindingProperty) is MultiBinding))
        {
            var multiBinding = new MultiBinding { Converter = new Converter() };
            multiBinding.Bindings.Add(new Binding { Path = new PropertyPath(ValueProperty), Source = ellipse });
            multiBinding.Bindings.Add(new Binding(nameof(ellipse.ActualWidth)) { Source = ellipse });
            multiBinding.Bindings.Add(new Binding(nameof(ellipse.ActualHeight)) { Source = ellipse });
            multiBinding.Bindings.Add(new Binding(nameof(ellipse.StrokeThickness)) { Source = ellipse });
            BindingOperations.SetBinding(ellipse, Shape.StrokeDashArrayProperty, multiBinding);
            ellipse.SetValue(EllipseProgressBindingProperty, multiBinding);
        }
    }

    class Converter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length >= 4
                && values[0] != DependencyProperty.UnsetValue
                && values[1] is double width
                && values[2] is double height
                && values[3] is double thickness)
            {
                width -= thickness;
                height -= thickness;
                var total = Math.PI * Math.Sqrt((width * width + height * height) / 2) / thickness;
                var value = Math.Min(1, Math.Max(0, System.Convert.ToDouble(values[0]) / 100)) * total;
                var quarter = total * 0.25;
                return new DoubleCollection() { Math.Max(0, value - quarter), Math.Min(total - quarter, total - value), value, total };
            }
            return null;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            => throw new NotImplementedException();
    }
}
1개의 좋아요