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
相关产品推荐
相关产品推荐

