我需要在 C# 6.0 WPF 中创建一个带圆角的矩形按钮。该按钮应该有一个进度条,而不是一个顺时针填充的框架(从顶部边框的中间开始)。
我尝试了很多方法来做到这一点,甚至使用 Path 制作了一个稍微可行的版本,但没有我需要的圆角。
告诉我如何使用 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">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="button" Width="150" Height="60" Content="Click me" Background="LightGray" BorderBrush="Transparent">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="10" Background="{TemplateBinding Background}" BorderBrush="Green" BorderThickness="2">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
</UserControl>
最初,框架不应该是可见的(无论有没有框架,按钮的大小都不应改变);单击时,进度条从上边框中间开始顺时针填充。填充 100% 后,按钮应如下图所示:
此外,进度条的填充即使是圆角也应该平滑。
我根本不知道该怎么做。请帮助我。
它是几个问题的组合,包括:
我很快将一些东西打包在一起以帮助您开始。还有工作要做;)
Xaml:
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<local:AngleToPointConverter x:Key="angleToPointConverter" />
<local:AngleToIsLargeConverter x:Key="angleToIsLargeConverter" />
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Viewbox Stretch="Fill"
ClipToBounds="True">
<Viewbox.Clip>
<RectangleGeometry RadiusX="10"
RadiusY="10"
Rect="0,0,200,100" />
</Viewbox.Clip>
<Path Stroke="LightGray"
StrokeThickness="100"
Width="100"
Height="100">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="50,0">
<ArcSegment RotationAngle="0"
SweepDirection="Clockwise"
Size="50,50"
Point="{Binding DataContext.Angle, Converter={StaticResource angleToPointConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType=Button}}"
IsLargeArc="{Binding DataContext.Angle, Converter={StaticResource angleToIsLargeConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType=Button}}">
</ArcSegment>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Viewbox>
<Border CornerRadius="10"
Background="Transparent"
BorderBrush="Green"
BorderThickness="2">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Progress}"
VerticalAlignment="Top" />
<Button Width="200"
Height="100"
Content="Click me"
Command="{Binding ClickCommand}" />
</Grid>
</Window>
铯:
namespace WpfApp3
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public DelegateCommand ClickCommand { get; }
public double Progress { get; set; }
public double Angle => Progress / 100 * 360;
public ViewModel()
{
var busy = false;
ClickCommand = new(async o =>
{
busy = true;
ClickCommand!.Update();
for (int i = 1; i <= 100; i++)
{
SetProgress(i);
await Task.Delay(20);
}
SetProgress(0);
busy = false;
ClickCommand.Update();
}, o => !busy);
}
void SetProgress(double value)
{
Progress = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Progress)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Angle)));
}
}
public class DelegateCommand : ICommand
{
public event EventHandler? CanExecuteChanged;
readonly Action<object?> execute;
readonly Predicate<object?> canExecute;
public DelegateCommand(Action<object?> execute, Predicate<object?> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public void Update() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public bool CanExecute(object? parameter) => canExecute(parameter);
public void Execute(object? parameter) => execute(parameter);
}
class AngleToPointConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double angle = (double)value;
double radius = 50;
double piang = angle * Math.PI / 180;
double px = Math.Sin(piang) * radius + radius;
double py = -Math.Cos(piang) * radius + radius;
return new Point(px, py);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
class AngleToIsLargeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double angle = (double)value;
return angle > 180;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}