Изогнутый Progress Bar c# WPF

461
05 августа 2017, 02:00

Ребят, подскажите как сделать полукруглый прогресс бар? С обычным вообще не возникает проблем. Находил только больно уж сложные, круглые примеры

Answer 1

Просто тут и не будет, придется помудрить с геометрией.

Ну что ж, давайте создадим UserControl, я назвал его MyProgress.

Рисовать будем с помощью Path. Контрол будет состоять из двух Path - закрашенного и незакрашенного (закрашенного другим цветом). Для упрощения расчетов я взял эллипс с радиусами 100 и центром в точке (100,100).

Нам нужно нарисовать 2 луча из центра и дугу:

<Path Fill="Blue" Stroke="Black">
    <Path.Data>
        <PathGeometry>
            <PathFigure StartPoint="100,100">
                <LineSegment Point="0,100"/>
                <ArcSegment Point="170.71,29.29"
                            Size="100,100"
                            SweepDirection="Clockwise"/>
                <LineSegment Point="100,100"/>
            </PathFigure>
        </PathGeometry>
    </Path.Data>
</Path>

Точку я рассчитал для варианта 75% заполненности, вот что уже получается:

Теперь из этого сектора нужно вырезать круг меньшего диаметра, сделаем это с помощью CombinedGeometry в режиме Exclude:

<Path Fill="Blue" Stroke="Black">
    <Path.Data>
        <CombinedGeometry GeometryCombineMode="Exclude">
            <CombinedGeometry.Geometry1>
                <PathGeometry>
                    <PathFigure StartPoint="100,100">
                        <LineSegment Point="0,100"/>
                        <ArcSegment Point="170.71,29.29"
                                    Size="100,100"
                                    SweepDirection="Clockwise"/>
                        <LineSegment Point="100,100"/>
                    </PathFigure>
                </PathGeometry>
            </CombinedGeometry.Geometry1>
            <CombinedGeometry.Geometry2>
                <EllipseGeometry Center="100,100" RadiusX="80" RadiusY="80"/>
            </CombinedGeometry.Geometry2>
        </CombinedGeometry>
    </Path.Data>
</Path>

Теперь аналогично рисуем вторую часть другим цветом:

<Path Fill="Cyan" Stroke="Black">
    <Path.Data>
        <CombinedGeometry GeometryCombineMode="Exclude">
            <CombinedGeometry.Geometry1>
                <PathGeometry>
                    <PathFigure StartPoint="100,100">
                        <LineSegment Point="200,100"/>
                        <ArcSegment Point="170.71,29.29"
                                    Size="100,100"
                                    SweepDirection="Counterclockwise"/>
                        <LineSegment Point="100,100"/>
                    </PathFigure>
                </PathGeometry>
            </CombinedGeometry.Geometry1>
            <CombinedGeometry.Geometry2>
                <EllipseGeometry Center="100,100" RadiusX="80" RadiusY="80"/>
            </CombinedGeometry.Geometry2>
        </CombinedGeometry>
    </Path.Data>
</Path>

С разметкой пока всё, останется только привязать координаты рассчитанной точки в ArcSegment.

Займемся кодом контрола:

Свойство зависимости Value:

public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double),
                    typeof(MyProgress), new FrameworkPropertyMetadata(OnValueChanged));

Вспомогательное свойство зависимости с координатами точки дуги:

protected static DependencyProperty AuxiliaryPointProperty = DependencyProperty.Register("AuxiliaryPoint",
                    typeof(Point), typeof(MyProgress));

Стандартные оболочки для этих свойств:

public double Value
{
    get => (double)GetValue(ValueProperty);
    set => SetValue(ValueProperty, value);
}
protected Point AuxiliaryPoint
{
    get => (Point)GetValue(AuxiliaryPointProperty);
    set => SetValue(AuxiliaryPointProperty, value);
}

Ну и, наконец, метод, который будет пересчитывать координаты точки при смене Value:

static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var myProgress = (MyProgress)d;
    var value = (double)e.NewValue;
    var angle = Math.PI * value / 100;
    var x = 100 - 100 * Math.Cos(angle);
    var y = 100 - 100 * Math.Sin(angle);
    myProgress.AuxiliaryPoint = new Point(x, y);
}

Теперь в разметке привяжемся к рассчитанной точке:

Point="{Binding ElementName=myProgress, Path=AuxiliaryPoint}"

Это нужно сделать для обоих ArcSegment

В разметке я дал имя контролу для упрощения кода привязки:

<UserControl ...
             Name="myProgress">

Всё. Это уже минимально рабочий прогрессбар, который можно добавить в окно:

<local:MyProgress x:Name="Progress1" Value="0"/>

Выглядит так:

Имейте ввиду, чтобы сделать этот контрол более-менее универсальным, вам потребуется его еще доработать, например, вынести в свойства зависимости цвета дуг и фона, минимальное и максимальное значение прогрессбара, диаметр внешнего и внутреннего круга и т.д.

Привожу код контрола полностью, MyProgress.xaml:

<UserControl x:Class="WpfProgress.MyProgress"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" Name="myProgress">
    <Grid>
        <Path Fill="Blue" Stroke="Black">
            <Path.Data>
                <CombinedGeometry GeometryCombineMode="Exclude">
                    <CombinedGeometry.Geometry1>
                        <PathGeometry>
                            <PathFigure StartPoint="100,100">
                                <LineSegment Point="0,100"/>
                                <ArcSegment Point="{Binding ElementName=myProgress, Path=AuxiliaryPoint}"
                                            Size="100,100" SweepDirection="Clockwise"/>
                                <LineSegment Point="100,100"/>
                            </PathFigure>
                        </PathGeometry>
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <EllipseGeometry Center="100,100" RadiusX="80" RadiusY="80"/>
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>
        <Path Fill="Cyan" Stroke="Black">
            <Path.Data>
                <CombinedGeometry GeometryCombineMode="Exclude">
                    <CombinedGeometry.Geometry1>
                        <PathGeometry>
                            <PathFigure StartPoint="100,100">
                                <LineSegment Point="200,100"/>
                                <ArcSegment Point="{Binding ElementName=myProgress, Path=AuxiliaryPoint}"
                                            Size="100,100" SweepDirection="Counterclockwise"/>
                                <LineSegment Point="100,100"/>
                            </PathFigure>
                        </PathGeometry>
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <EllipseGeometry Center="100,100" RadiusX="80" RadiusY="80"/>
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>
    </Grid>
</UserControl>

MyProgress.xaml.cs:

using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfProgress
{
    public partial class MyProgress : UserControl
    {
        public MyProgress()
        {
            InitializeComponent();
        }
        public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double),
                            typeof(MyProgress), new FrameworkPropertyMetadata(OnValueChanged));
        protected static DependencyProperty AuxiliaryPointProperty = DependencyProperty.Register("AuxiliaryPoint",
                            typeof(Point), typeof(MyProgress));
        static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var myProgress = (MyProgress)d;
            var value = (double)e.NewValue;
            var angle = Math.PI * value / 100;
            var x = 100 - 100 * Math.Cos(angle);
            var y = 100 - 100 * Math.Sin(angle);
            myProgress.AuxiliaryPoint = new Point(x, y);
        }
        public double Value
        {
            get => (double)GetValue(ValueProperty);
            set => SetValue(ValueProperty, value);
        }
        protected Point AuxiliaryPoint
        {
            get => (Point)GetValue(AuxiliaryPointProperty);
            set => SetValue(AuxiliaryPointProperty, value);
        }
    }
}
READ ALSO
Как создать правильно бизнес модель в devexpress xaf?

Как создать правильно бизнес модель в devexpress xaf?

Добрый день У меня возникла проблема, так как в использовании данного фреймворка я новичокЯ хотел создать самый простой и обычный task manager

286
C# &mdash; поиск элемента со значением value

C# — поиск элемента со значением value

Есть код, написанный на PhantomJS driver:

397
Зачем усложнять жизнь&hellip; Converter в Binding

Зачем усложнять жизнь… Converter в Binding

Здравствуйте, скажите пожалуйста зачем и в чём удобства Converter'а в Binding'е?

250
Можно ли выбрать элемент на SVG картинке?

Можно ли выбрать элемент на SVG картинке?

У меня есть SVG, который содержит электрическую схемуИспользуя Xamarin я вывел схему на экран

297