You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WPF带圆角的轮廓进度条矩形按钮实现方案问询

WPF带圆角边框进度按钮实现方案

核心思路

通过叠加背景圆角Border和进度Path实现需求:用Path绘制带圆角的闭合边框,通过StrokeDashArray和StrokeDashOffset的动态调整,实现从顶部中点顺时针的平滑填充;点击按钮后启动进度动画,完成后切换样式。

XAML实现代码

<UserControl x:Class="Example.ProgressButton"
             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:Example"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:BoundsToStrokeDashArrayConverter x:Key="BoundsToStrokeDashArrayConverter"/>
        <local:ProgressToOffsetConverter x:Key="ProgressToOffsetConverter"/>
    </UserControl.Resources>
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
        <!-- 按钮背景容器 -->
        <Border x:Name="ButtonBackground" CornerRadius="10" Background="LightGray" Width="150" Height="60">
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="Click me"/>
        </Border>
        <!-- 进度边框路径 -->
        <Path x:Name="ProgressBorder" 
              Stroke="Transparent" 
              StrokeThickness="2" 
              StrokeLineJoin="Round"
              Data="M 75,0 L 140,0 A 10,10 0 0 1 150,10 L 150,50 A 10,10 0 0 1 140,60 L 10,60 A 10,10 0 0 1 0,50 L 0,10 A 10,10 0 0 1 10,0 L 75,0 Z">
            <Path.StrokeDashArray>
                <Binding ElementName="ProgressBorder" Path="Data.Geometry.Bounds" Converter="{StaticResource BoundsToStrokeDashArrayConverter}"/>
            </Path.StrokeDashArray>
            <Path.StrokeDashOffset>
                <MultiBinding Converter="{StaticResource ProgressToOffsetConverter}">
                    <Binding ElementName="ProgressBorder" Path="Data.Geometry.Bounds"/>
                    <Binding Path="Progress" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
                </MultiBinding>
            </Path.StrokeDashOffset>
        </Path>
    </Grid>
</UserControl>

后台逻辑与进度控制

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace Example
{
    public partial class ProgressButton : UserControl
    {
        // 进度依赖属性,用于绑定动画
        public static readonly DependencyProperty ProgressProperty =
            DependencyProperty.Register("Progress", typeof(double), typeof(ProgressButton), new PropertyMetadata(0.0));

        public double Progress
        {
            get => (double)GetValue(ProgressProperty);
            set => SetValue(ProgressProperty, value);
        }

        public ProgressButton()
        {
            InitializeComponent();
            ButtonBackground.MouseLeftButtonDown += Button_Click;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // 初始显示边框
            ProgressBorder.Stroke = Brushes.Green;
            // 创建进度动画(3秒从0到1)
            var progressAnimation = new DoubleAnimation
            {
                From = 0,
                To = 1,
                Duration = TimeSpan.FromSeconds(3),
                EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
            };
            // 动画完成后切换样式
            progressAnimation.Completed += (s, args) =>
            {
                ButtonBackground.Background = Brushes.LightGreen;
                ProgressBorder.Stroke = Brushes.DarkGreen;
            };
            // 启动动画
            BeginAnimation(ProgressProperty, progressAnimation);
        }
    }

    // 转换路径边界为StrokeDashArray(设置为路径总周长)
    public class BoundsToStrokeDashArrayConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Rect bounds)
            {
                const double cornerRadius = 10;
                // 计算圆角矩形周长:直边长度 + 圆角弧长总和
                double perimeter = 2 * (bounds.Width + bounds.Height - 4 * cornerRadius) + 2 * Math.PI * cornerRadius;
                return new DoubleCollection { perimeter, perimeter };
            }
            return new DoubleCollection { 0, 0 };
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
    }

    // 根据进度值计算StrokeDashOffset,实现顺时针填充
    public class ProgressToOffsetConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values[0] is Rect bounds && values[1] is double progress)
            {
                const double cornerRadius = 10;
                double perimeter = 2 * (bounds.Width + bounds.Height - 4 * cornerRadius) + 2 * Math.PI * cornerRadius;
                // 初始偏移为周长的一半(对应顶部中点),进度从0到1时,偏移逐步减少,实现顺时针填充
                return perimeter / 2 - progress * perimeter;
            }
            return 0;
        }

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

关键细节说明

  • 路径起点设置:Path的Data手动定义为起点在按钮顶部中点(75,0)的闭合圆角矩形,确保进度填充的起始位置正确。
  • 进度填充逻辑:StrokeDashArray设为[周长, 周长],使路径只有一段可见的"虚线";通过StrokeDashOffset的动态调整,让这段虚线从顶部中点开始,顺时针逐步覆盖整个边框。
  • 初始状态控制:初始时ProgressBorder的Stroke为Transparent,点击按钮后切换为可见颜色并启动动画。
  • 平滑过渡:添加CubicEase缓动函数,让进度填充更自然流畅。

内容的提问来源于stack exchange,提问作者Bagomot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:55:24