如何使用 C# 和 WPF 创建圆形进度条选框并仅填充进度条的灰色区域

问题描述 投票:0回答:2
c# wpf xaml progress-bar
2个回答
0
投票

Marquee 效果制作旋转动画非常简单,只需为

RenderTransform.RotateTranform
属性设置动画即可。

你可以像这样实现它。这是

Storyboard
的声明,带有一些额外的旋转效果,以及由
IsMouseOver
在选定的
Path
上触发的示例。

<UserControl.Resources>
    <Storyboard x:Key="SpinStoryboardSpinning" RepeatBehavior="Forever">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)">
            <EasingDoubleKeyFrame KeyTime="0:0:0" Value="90"/>
            <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="180"/>
            <EasingDoubleKeyFrame KeyTime="0:0:1" Value="450">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseInOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>

<Path Fill="Gold"
      Data="M 0 100 a 100,100 0 0 1 100,-100 v 30 a 70,70 0 0 0 -70,70"
      RenderTransformOrigin="1,1">
    <Path.RenderTransform>
            <RotateTransform Angle="90"/>
    </Path.RenderTransform>
    <Path.Style>
        <Style TargetType="{x:Type Path}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Trigger.EnterActions>
                        <BeginStoryboard Storyboard="{StaticResource SpinStoryboardSpinning}"/>
                    </Trigger.EnterActions>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Path.Style>
</Path>

但是要创建完整的进度条,这是更复杂的任务。

首先,您需要控制创建的

Storyboards
,即在需要时停止和启动它们。另外,对于
ProgressBar
本身,我建议首先从
ProgressBar
控件派生并根据需要设置其样式。

好的开始是首先创建 standard,但基于 MSDN 中的此示例设计

ProgressBar
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/progressbar-styles -和-模板?view=netframeworkdesktop-4.8


0
投票

圆形进度条示例。

自定义控件和转换器:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using static System.Math;

namespace Core2023.RoundProgressBar
{
    public class RoundProgressBar : RangeBase
    {
        static RoundProgressBar()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RoundProgressBar), new FrameworkPropertyMetadata(typeof(RoundProgressBar)));
        }
    }

    [ValueConversion(typeof(ProgressBar), typeof(Geometry))]
    public class ProgressBarToGeometryConverter : IMultiValueConverter
    {
        private static readonly double valPI = 2 * PI;
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double min = (double)values[0];
            double max = (double)values[1];
            double value = (double)values[2];

            double angle = (value - min) / (max - min);
            angle *= valPI;
            if (!double.IsNormal(angle))
                return DependencyProperty.UnsetValue;

            double cos = Cos(angle);
            double sin = Sin(angle);
            double x1 = 100 * cos + 100;
            double y1 = 100 * sin + 100;
            double x2 = 70 * cos + 100;
            double y2 = 70 * sin + 100;


            string data;
            if (angle < PI)
            {
                data = @$"
M200,100
A100,100 0 0 1 {x1.ToString(culture)},{y1.ToString(culture)}
L{x2.ToString(culture)},{y2.ToString(culture)}
A070,070 0 0 0 170,100
z";
            }
            else
            {
                data = @$"
M200,100
A100,100 0 1 1 {x1.ToString(culture)},{y1.ToString(culture)}
L{x2.ToString(culture)},{y2.ToString(culture)}
A070,070 0 1 0 170,100
z";
            }
            return Geometry.Parse(data);
        }

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

        private ProgressBarToGeometryConverter() { }
        public static ProgressBarToGeometryConverter Instance { get; } = new();
    }

    [MarkupExtensionReturnType(typeof(ProgressBarToGeometryConverter))]
    public class ProgressBarToGeometryExtension : MarkupExtension
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return  ProgressBarToGeometryConverter.Instance;
        }
    }
}

主题 Generic.xaml:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Core2023"
    xmlns:rpb="clr-namespace:Core2023.RoundProgressBar">


    <Style TargetType="{x:Type rpb:RoundProgressBar}">
        <Setter Property="Background" Value="LightGray"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type rpb:RoundProgressBar}">
                    <ControlTemplate.Resources>
                        <Geometry x:Key="round">
                            M 0 100 a 100,100 0 1 1 200,0
                            a 100,100 0 1 1 -200,0
                            M 30 100 a 70,70 0 1 1 140,0
                            a 70,70 0 1 1 -140,0</Geometry>
                    </ControlTemplate.Resources>
                    <Grid>
                        <!-- Base Spinner -->
                        <Path Fill="{TemplateBinding Background}"
                              Data="{StaticResource round}"/>
                        <Path Stroke="{TemplateBinding BorderBrush}" StrokeThickness="3"
                              Data="{StaticResource round}" Panel.ZIndex="2"/>

                        <!-- Loader Spinner "M 0 100 a 100,100 0 0 1 100,-100 v 30 a 70,70 0 0 0 -70,70" -->

                        <Path Fill="Gold">
                            <Path.Data>
                                <MultiBinding Converter="{rpb:ProgressBarToGeometry}">
                                    <Binding RelativeSource="{RelativeSource AncestorType=rpb:RoundProgressBar}" Path="Minimum"/>
                                    <Binding RelativeSource="{RelativeSource AncestorType=rpb:RoundProgressBar}" Path="Maximum"/>
                                    <Binding RelativeSource="{RelativeSource AncestorType=rpb:RoundProgressBar}" Path="Value"/>
                                </MultiBinding>
                            </Path.Data>
                        </Path>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

带有 Value 属性的使用和动画示例的窗口:

<Window x:Class="Core2023.RoundProgressBar.RoundProgressBarWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Core2023.RoundProgressBar"
        mc:Ignorable="d"
        Title="RoundProgressBarWindow" Height="450" Width="800">
    <Window.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Duration="0:0:5"
                                     To="10"
                                     Storyboard.TargetName="rpb"
                                     Storyboard.TargetProperty="Value"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>
    <Grid>
        <local:RoundProgressBar x:Name="rpb"
                                Maximum="10" Minimum="-10" Value="-10"
                                BorderBrush="Aqua"/>
    </Grid>
</Window>

Value 属性可以绑定到 ViewModel 或以任何其他方式设置。进度将正确显示。

P.S. 根据 Clements 的评论,我提供了使用 StreamGeometry 的转换器的改进版本。

    [ValueConversion(typeof(ProgressBar), typeof(Geometry))]
    public class ProgressBarToGeometryConverter : IMultiValueConverter
    {
        private static readonly double valPI = 2 * PI;
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double min = (double)values[0];
            double max = (double)values[1];
            double value = (double)values[2];

            if (value == max)
            {

            }

            double angle = (value - min) / (max - min);
            angle *= valPI;
            if (!double.IsNormal(angle))
                return DependencyProperty.UnsetValue;

            double cos = Cos(angle);
            double sin = Sin(angle);
            double x1 = 100 * cos + 100;
            double y1 = 100 * sin + 100;
            double x2 = 70 * cos + 100;
            double y2 = 70 * sin + 100;

            StreamGeometry sg = new();
            sg.FillRule = FillRule.EvenOdd;

            using StreamGeometryContext sgc = sg.Open();
            if (value >= max)
            {
                sgc.BeginFigure(new Point(200, 100), true, true);
                sgc.ArcTo(new Point(0, 100), new Size(100, 100), 0, false, SweepDirection.Clockwise, true, false);
                sgc.ArcTo(new Point(200, 100), new Size(100, 100), 0, false, SweepDirection.Clockwise, true, false);
                sgc.LineTo(new Point(170, 100), true, false);
                sgc.ArcTo(new Point(30, 100), new Size(070, 070), 0, false, SweepDirection.Counterclockwise, true, false);
                sgc.ArcTo(new Point(170, 100), new Size(070, 070), 0, false, SweepDirection.Counterclockwise, true, false);
            }
            else
            if (angle < PI)
            {
                sgc.BeginFigure(new Point(200, 100), true, true);
                sgc.ArcTo(new Point(x1, y1), new Size(100, 100), 0, false, SweepDirection.Clockwise, true, false);
                sgc.LineTo(new Point(x2, y2), true, false);
                sgc.ArcTo(new Point(170, 100), new Size(070, 070), 0, false, SweepDirection.Counterclockwise, true, false);
            }
            else
            {
                sgc.BeginFigure(new Point(200, 100), true, true);
                sgc.ArcTo(new Point(x1, y1), new Size(100, 100), 0, true, SweepDirection.Clockwise, true, false);
                sgc.LineTo(new Point(x2, y2), true, false);
                sgc.ArcTo(new Point(170, 100), new Size(070, 070), 0, true, SweepDirection.Counterclockwise, true, false);
            }
            return sg;
        }

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

        private ProgressBarToGeometryConverter() { }
        public static ProgressBarToGeometryConverter Instance { get; } = new();
    }
© www.soinside.com 2019 - 2024. All rights reserved.