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

bq9c1y66  于 2023-11-14  发布在  C#
关注(0)|答案(2)|浏览(153)

我创建了一个自定义的圆形进度条,如下所示:
x1c 0d1x的数据
使用以下代码:

<UserControl x:Class="WpfApp1.SpinnerProgressBar"
             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" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
            <Viewbox>
            <Canvas Width="100" Height="100">
                <!-- Base Spinner -->
            <Path Stroke="LightGray" StrokeThickness="3" Fill="LightGray"
                      Data="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" RenderTransformOrigin="0.5,0.5" />

            <!-- 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" 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>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform Angle="0"/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Path.RenderTransform>
            </Path>
        </Canvas>
    </Viewbox>

</UserControl>

字符串
对于Mimicing Marquee行为,我只需要绑定到<RotateTransform Angle="0"/>即可(如果我没有弄错的话).然而,我被困在如何创建一个完整的进度条的一部分时,有一个情况下说,像成功连接到数据库和与字幕时,仍然试图连接.我怎么能做完整的进度条的一部分?如果有一个教程/视频,将解释如何做到这一点,我会很感激。

  • 编辑1:* 我已经尝试了以下代码感谢@EldHasp在那里我做了以下事情:
    SpinnerProgressBar.xaml
<UserControl x:Class="SpinnerProgress.SpinnerProgressBar"
             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" 
             xmlns:local="clr-namespace:SpinnerProgress"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             xmlns:rpb="clr-namespace:SpinnerProgress.spinnerprogressbar">

    <!-- Spinner Design -->
        <!-- Use the text document about the path commands in Data. -->
        <Viewbox>
            <Grid>
                <!-- Arc Movement requirements: (Size, (RotatingAngle, isLargeArc, SweepDirection), Coordinates[End-points]) -->
                <Canvas Width="100" Height="100">
                    <Path Stroke="LightGray" StrokeThickness="3" Fill="LightGray" Panel.ZIndex="2"
                          Data="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" RenderTransformOrigin="0.5,0.5" />
                    <!-- Arc Movement requirements: (Size, (RotatingAngle, isLargeArc, SweepDirection), Coordinates[End-points]) -->
                    <!-- Loader Spinner "M 0 100 a 100,100 0 0 1 100,-100 v 30 a 70,70 0 0 0 -70,70" -->
                    <Path x:Name="progressPath" Fill="Gold" RenderTransformOrigin="1,1">
                        <Path.Data>
                        <MultiBinding Converter="{x:Static local:ProgressBarToGeometryConverter.Instance}">
                                <Binding Path="Value" RelativeSource="{RelativeSource AncestorType=local:SpinnerProgressBar}"/>
                                <Binding Path="Maximum" RelativeSource="{RelativeSource AncestorType=local:SpinnerProgressBar}"/>
                                <Binding Path="Minimum" RelativeSource="{RelativeSource AncestorType=local:SpinnerProgressBar}"/>
                            </MultiBinding>
                        </Path.Data>
                    </Path>
                </Canvas>
            </Grid>
        </Viewbox>
    
</UserControl>

SpinnerProgressBar背后的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SpinnerProgress
{
    /// <summary>
    /// Interaction logic for SpinnerProgressBar.xaml
    /// </summary>
    public partial class SpinnerProgressBar : UserControl
    {
        public SpinnerProgressBar()
        {
            InitializeComponent();
        }

        private void StartMarqueeAnimation()
        {
            // Create a double animation to rotate the Path (marquee effect)
            var rotationAnimation = new DoubleAnimation
            {
                To = 360, // Rotate a full circle (360 degrees)
                Duration = TimeSpan.FromSeconds(1), // Adjust the duration as needed
                RepeatBehavior = RepeatBehavior.Forever // Keep repeating the animation
            };

            progressPath.RenderTransform = new RotateTransform(); // Add a RotateTransform
            progressPath.RenderTransformOrigin = new Point(1, 1); // Set the rotation origin to the center

            // Begin the rotation animation
            progressPath.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, rotationAnimation);
        }
    }
}

MainWindow.xaml

<Window x:Class="SpinnerProgress.MainWindow"
        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:SpinnerProgress"
        xmlns:spb="clr-namespace:SpinnerProgress.spinnerprogressbar"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Duration="0:0:5"
                                     To="10"
                                     Storyboard.TargetName="Spinner"
                                     Storyboard.TargetProperty="Value"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>

    <Grid>
        <spb:SpinnerProgressBar x:Name="Spinner" Maximum="10" Minimum="-10" Value="-10" Height="100" Width="100" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
    
</Window>

Converter.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 SpinnerProgress.spinnerprogressbar
{
    public class SpinnerProgressBar : RangeBase
    {
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(SpinnerProgressBar), new PropertyMetadata(0.0));
        public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(SpinnerProgressBar), new PropertyMetadata(0.0));
        public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(SpinnerProgressBar), new PropertyMetadata(0.0));

        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        public double Minimum
        {
            get { return (double)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

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

    [ValueConversion(typeof(SpinnerProgressBar), 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;

            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();
        }

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

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


好像不行。我在想把字幕加到代码里。

0s7z1bwu

0s7z1bwu1#

圆形ProgressBar的示例。
自定义控件和转换器:

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.**这是使用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];

            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();
    }

“编辑1”错误

1.“class SpinnerProgressBar:UserControl”-这个类是多余的。删除它。
1.在类“class SpinnerProgressBar:RangeBase”中,不需要声明Minimum、Maxmimum和Value属性。这些属性已经在基类“RangeBase”中声明。
1.类“SpinnerProgressBar:RangeBase”是一个自定义控件,您需要为其声明一个主题。默认主题是文件“Themes/Generic.xaml”。
您可以从这里下载完整的工作项目:https://drive.google.com/file/d/1lUpKDHz0QQ_NoFEXeXeWBcBMVgj8WZhW/view?usp=drive_link

ddrv8njm

ddrv8njm2#

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控件派生和风格,如你所愿。
首先创建一个 * 标准 * 的ProgressBar,但基于来自MSDN https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/progressbar-styles-and-templates?view=netframeworkdesktop-4.8的示例进行样式化,这是一个好的开始

相关问题