Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Как сделать круглый ProgressBar в WPF?

#c_sharp #wpf #xaml


Есть изображение 



Требуется обвернуть картинку индикатором загрузки и менять цвет по мере продвижения
индикатора




    


Ответы

Ответ 1



Ну что же, давайте напишем стиль для ProgressBar'а, ведь в конце-концов вы показываете ProgressBar, правильно? Для начала, нам нужен будет конвертер, превращающий угол в геометрию, описывающую наш круглый прогрессбар. Вспомним тригонометрию и в путь! class RoundProgressPathConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values?.Contains(DependencyProperty.UnsetValue) != false) return DependencyProperty.UnsetValue; var v = (double)values[0]; // значение слайдера var min = (double)values[1]; // минимальное значение var max = (double)values[2]; // максимальное var ratio = (v - min) / (max - min); // какую долю окружности закрашивать var isFull = ratio >= 1; // для случая полной окружности нужна особая обработка var angleRadians = 2 * Math.PI * ratio; var angleDegrees = 360 * ratio; // внешний радиус примем за 1, растянем в XAML'е. var outerR = 1; // как параметр передадим долю радиуса, которую занимает внутренняя часть var innerR = System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture) * outerR; // вспомогательные штуки: вектор направления вверх var vector1 = new Vector(0, -1); // ... и на конечную точку дуги var vector2 = new Vector(Math.Sin(angleRadians), -Math.Cos(angleRadians)); var center = new Point(); var geo = new StreamGeometry(); geo.FillRule = FillRule.EvenOdd; using (var ctx = geo.Open()) { Size outerSize = new Size(outerR, outerR), innerSize = new Size(innerR, innerR); if (!isFull) { Point p1 = center + vector1 * outerR, p2 = center + vector2 * outerR, p3 = center + vector2 * innerR, p4 = center + vector1 * innerR; ctx.BeginFigure(p1, isFilled: true, isClosed: true); ctx.ArcTo(p2, outerSize, angleDegrees, isLargeArc: angleDegrees > 180, sweepDirection: SweepDirection.Clockwise, isStroked: true, isSmoothJoin: false); ctx.LineTo(p3, isStroked: true, isSmoothJoin: false); ctx.ArcTo(p4, innerSize, -angleDegrees, isLargeArc: angleDegrees > 180, sweepDirection: SweepDirection.Counterclockwise, isStroked: true, isSmoothJoin: false); Point diag1 = new Point(-outerR, -outerR), diag2 = new Point(outerR, outerR); ctx.BeginFigure(diag1, isFilled: false, isClosed: false); ctx.LineTo(diag2, isStroked: false, isSmoothJoin: false); } else { Point p1 = center + vector1 * outerR, p2 = center - vector1 * outerR, p3 = center + vector1 * innerR, p4 = center - vector1 * innerR; ctx.BeginFigure(p1, isFilled: true, isClosed: true); ctx.ArcTo(p2, outerSize, 180, isLargeArc: false, sweepDirection: SweepDirection.Clockwise, isStroked: true, isSmoothJoin: false); ctx.ArcTo(p1, outerSize, 180, isLargeArc: false, sweepDirection: SweepDirection.Clockwise, isStroked: true, isSmoothJoin: false); ctx.BeginFigure(p3, isFilled: true, isClosed: true); ctx.ArcTo(p4, innerSize, 180, isLargeArc: false, sweepDirection: SweepDirection.Clockwise, isStroked: true, isSmoothJoin: false); ctx.ArcTo(p3, innerSize, 180, isLargeArc: false, sweepDirection: SweepDirection.Clockwise, isStroked: true, isSmoothJoin: false); } } geo.Freeze(); return geo; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotSupportedException(); } } Окей, конвертер есть, теперь нам нужен сам стиль. Он несложен, единственный интересный пункт — состояние Indeterminate. Отлично, нам теперь нужно поменять цвета на кастомные, и можно запускать. Обыкновенный: То же, с IsIndeterminate="True": (он вращается).

Ответ 2



Можно использовать геометрию WPF. C# код: namespace PhoneApp1 { using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; public partial class PartialCircle : UserControl, INotifyPropertyChanged { private double radius = 100; private double percentage = 13; public PartialCircle() { this.InitializeComponent(); this.DataContext = this; } /// /// Задает процент /// public double Percentage { get { return this.percentage; } set { this.percentage = value; this.OnPropertyChanged(); this.OnPropertyChanged("Angle"); this.OnPropertyChanged("IsLarge"); this.OnPropertyChanged("EndPoint"); } } /// /// Радиус закругления /// public double Radius { get { return this.radius; } set { this.radius = value; this.OnPropertyChanged(); this.OnPropertyChanged("StartPoint"); this.OnPropertyChanged("Center"); this.OnPropertyChanged("Size"); this.OnPropertyChanged("EndPoint"); } } /// /// Координаты центра окружности /// public Point Center { get { return new Point(this.Width/2, this.Height/2); } } /// /// Gets the starting point of the path /// public Point StartPoint { get { return new Point(this.Center.X, this.Center.Y - this.radius); } } /// /// Размеры сегмента /// public Size Size { get { return new Size(this.radius, this.radius); } } /// /// Угол, на котором необходимо остановиться /// public double Angle { get { return 359.99 * this.Percentage / 100; } } /// /// Корректировка при переходе через 180 градусов /// public bool IsLarge { get { return (this.Percentage > 50); } } /// /// Вычисление конечной точки /// public Point EndPoint { get { // Pi correction var angle = this.Angle - 90; return this.CalculateCircleCoordinates(this.Center, this.radius, angle); } } private Point CalculateCircleCoordinates(Point center, double radius, double angle) { var radians = (angle * Math.PI) / 180; return new Point(center.X + radius * Math.Cos(radians), center.Y + radius * Math.Sin(radians)); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } } Использование в приложении: Изменяем Persentage (%). А картинку можно уже класть поверх или под низ нашего элемента. UPDATE 1: Вариант использования ThreadPool.QueueUserWorkItem((o) => { for (int i=0; i <= 100; i++) { this.Dispatcher.BeginInvoke((Action)(() => { this.RoundProgressBar.Percentage = i; })); Thread.Sleep(100); } });

Ответ 3



Советую сделать две картинки - с индикатором (image1) и без (image2). Поместить image1 поверх image2. Изначально сделать image1 прозрачным или вообще не показывать. По мере изменения прогресса показывать сектор image1. Думаю, что это решение лучше закрашивания линиями, и надеюсь, что WPF позволяет реализовать такое решение.

Комментариев нет:

Отправить комментарий