I need to create a rectangular button with rounded corners in C# 6.0 WPF. This button should have a progress bar instead of a frame that fills clockwise (starting from the middle of the top border).
I tried many ways to do this and even made a slightly workable version using Path, but without the rounded corners, which I need.
Please tell me how this can be done using xaml markup and how to manage progress.
Here is a regular button with rounded corners:
<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>
Initially, the frame should not be visible (the size of the button should not change with or without the frame); when clicked, the progress bar begins to fill starting from the middle of the upper border clockwise. After filling 100%, the button should look like in the picture:
In addition, the filling of the progress bar should be smooth even on rounded corners.
I have no idea how to do this at all. Help me please.
Update: I probably didn't explain my problem well, I'll try to clarify. I need to make the border itself as a progress bar (as an element or animation, it doesn’t matter), it should fill for the specified time approximately as in the picture: load_button
You may create a derived Border
control that draws a stroked Geometry on top of its border. The length of the stroke can be controlled by the dash array of a Pen
that is used to draw the border Geometry.
The control declares two additional properties, ProgressBrush
and ProgressValue
, a double value in the range 0 to 1.
The example below uses only the Left
component of the BorderThickness
and the TopLeft
component of the CornerRadius
, so it won't support irregular border thicknesses or corner radii.
public class ProgressBorder : Border
{
public static readonly DependencyProperty ProgressBrushProperty = DependencyProperty.Register(
nameof(ProgressBrush), typeof(Brush), typeof(ProgressBorder),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty ProgressValueProperty = DependencyProperty.Register(
nameof(ProgressValue), typeof(double), typeof(ProgressBorder),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
public Brush ProgressBrush
{
get => (Brush)GetValue(ProgressBrushProperty);
set => SetValue(ProgressBrushProperty, value);
}
public double ProgressValue
{
get => (double)GetValue(ProgressValueProperty);
set => SetValue(ProgressValueProperty, value);
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var w = RenderSize.Width;
var h = RenderSize.Height;
var t = BorderThickness.Left;
var d = t / 2;
var r = Math.Max(0, Math.Min(CornerRadius.TopLeft, Math.Min(w / 2 - t, h / 2 - t)));
var geometry = new StreamGeometry();
using (var dc = geometry.Open())
{
dc.BeginFigure(new Point(w / 2, d), true, true);
dc.LineTo(new Point(w - d - r, d), true, true);
dc.ArcTo(new Point(w - d, d + r), new Size(r, r), 0, false, SweepDirection.Clockwise, true, true);
dc.LineTo(new Point(w - d, h - d - r), true, true);
dc.ArcTo(new Point(w - d - r, h - d), new Size(r, r), 0, false, SweepDirection.Clockwise, true, true);
dc.LineTo(new Point(d + r, h - d), true, true);
dc.ArcTo(new Point(d, h - d - r), new Size(r, r), 0, false, SweepDirection.Clockwise, true, true);
dc.LineTo(new Point(d, d + r), true, true);
dc.ArcTo(new Point(d + r, d), new Size(r, r), 0, false, SweepDirection.Clockwise, true, true);
dc.LineTo(new Point(w / 2, d), true, true);
}
var length = (2 * w + 2 * h + 4 * ((0.5 * Math.PI - 2) * r - t)) / t;
var dashes = new double[] { ProgressValue * length, (1 - ProgressValue) * length };
var pen = new Pen
{
Brush = ProgressBrush,
Thickness = t,
StartLineCap = PenLineCap.Flat,
EndLineCap = PenLineCap.Flat,
DashCap = PenLineCap.Flat,
DashStyle = new DashStyle(dashes, 0),
LineJoin = PenLineJoin.Round
};
drawingContext.DrawGeometry(null, pen, geometry);
}
}
An example usage in XAML:
<local:ProgressBorder
Background="AliceBlue"
BorderBrush="LightGray"
ProgressBrush="Red"
BorderThickness="10"
CornerRadius="10,10,10,10">
<TextBlock Text="Hello" Margin="10"/>
<local:ProgressBorder.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="ProgressValue"
To="1.0" Duration="0:0:3"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</local:ProgressBorder.Triggers>
</local:ProgressBorder>