Reputation: 591
I would like to customise a progressbar for loading in C# WPF. Instead of a rectangle, it should have some small sharpness at the end, looking something like this:
When it has completed loading, the sharpness should disappear.
Currently, this is what I have done.
How can I achieve that customised loading bar?
This is my code, XAML
<Window x:Class="loadingbarSolution.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="{x:Type ProgressBar}"
TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border BorderBrush="#D9DCE1" BorderThickness="0" Background="#FF0C0B0B" CornerRadius="0" Padding="0">
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#FF2BA9FF" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ProgressBar x:Name="IMSIProgressBar"
HorizontalAlignment="Left"
Height="20" Margin="82,136,0,0"
VerticalAlignment="Top"
Width="200"
BorderThickness="1" Background="#FF0C0B0B"/>
</Grid>
</Window>
What should I do?
Upvotes: 1
Views: 8446
Reputation: 141
A third approach is using Path
with its Data
being designed as an anti-clockwise skew transformation and its RenderTransform
being a clockwise SkewTransform
. This way the moving indicator indeed reaches 100 % completely.
<ControlTemplate x:Key="ProgressBarPath" TargetType="ProgressBar">
<Viewbox Stretch="Fill">
<Grid HorizontalAlignment="Left" Margin="-5 0">
<Path Stretch="None" x:Name="PART_Track" Fill="#0C0B0B" RenderTransformOrigin="0,0.5" StrokeMiterLimit="1" Data="M 0,0 l 150,0 10,10 -150,0 z">
<Path.RenderTransform>
<SkewTransform AngleX="-45" />
</Path.RenderTransform>
</Path>
<Path Stretch="None" x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#29AAE1" RenderTransformOrigin="0,0.5" StrokeMiterLimit="1" Data="M 0,0 l 150,0 10,10 -150,0 z">
<Path.RenderTransform>
<SkewTransform AngleX="-45" />
</Path.RenderTransform>
</Path>
</Grid>
</Viewbox>
</ControlTemplate>
Comparison of the two methods from 2018 and this one:
Entire code for above APNG:
MainWindow.xaml:
<Window x:Class="StackOverFlowTest.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:StackOverFlowTest"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="550">
<Window.Resources>
<!--
ProgressBar templates with skewed Indicator:
Reference: "How do I make a custom shape of progressbar in wpf?"
https://stackoverflow.com/questions/52250531/how-do-i-make-a-custom-shape-of-progressbar-in-wpf
-->
<!--
ProgressBar with skewed Rectangle
Advantages: - Moving Indicator supports transparent background
- Moving Indicator shows no artifacts
Disadvantage: - Moving Indicator doesn't completely reach 100%
Code from "Mr. Squirrel.Downy": https://stackoverflow.com/a/52252590
-->
<ControlTemplate x:Key="ProgressBarRectangle" TargetType="ProgressBar">
<Border BorderBrush="#D9DCE1" BorderThickness="0" Background="#FF0C0B0B" CornerRadius="0" Padding="0" ClipToBounds="True">
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#2BA9FF" RenderTransformOrigin="0,0">
<Rectangle.RenderTransform>
<TransformGroup>
<SkewTransform AngleX="-45"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Border>
</ControlTemplate>
<!--
ProgressBar with right-aligned Polygon
Disadvantages: - Moving Indicator doesn't completely reach 100%
- Moving Indicator doesn't support transparent background (Polygon must hide Indicator)
- Moving Indicator shows artifacts when ProgressBar is set to large width (Polygon isn't able to hide Indicator accurately)
- <ControlTemplate> has to be adjusted if height of ProgressBar exceeds height of Polygon
Code from "mm8": https://stackoverflow.com/a/52279788
-->
<ControlTemplate x:Key="ProgressBarPolygon" TargetType="ProgressBar">
<Border BorderBrush="#D9DCE1" BorderThickness="0" Background="#FF0C0B0B" CornerRadius="0" Padding="0">
<Grid x:Name="PART_Track">
<Grid x:Name="PART_Indicator" HorizontalAlignment="Left" Background="#FF2BA9FF">
<Polygon Points="0,32 32,0 32,32" Stroke="#FF0C0B0B" Fill="#FF0C0B0B" HorizontalAlignment="Right" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
<!--
ProgressBar with skewed Path
Advantages: - Moving Indicator completely reaches 100%
- Moving Indicator supports transparent background
- Moving Indicator shows no artifacts
Disadvantage: - <ControlTemplate> has to be adjusted
if proportion (width-height-ratio) of ProgressBar differs from
the two ones in PathGeometry, otherwise the moving Indicator alters its angle.
How2: Create PathGeometry ( <Path Data="..."> ) contrary to planed skew angle to skew PART_Indicator to desired angle.
Example: By default "PART_Indicator" is always vertical. To solely give the Indicator a 45° clockwise 'rotation' ("/"),
design your Path as a 45° anti-clockwise skewed Path.
For a simple Path like in these ProgressBars, you can quite easily do mental arithmetic.
For more complex Path Data, you can use calculation methods in CS code...
-->
<ControlTemplate x:Key="ProgressBarPath" TargetType="ProgressBar">
<Viewbox Stretch="Fill">
<Grid HorizontalAlignment="Left" Margin="-5 0">
<Path Stretch="None" x:Name="PART_Track" Fill="#0C0B0B" RenderTransformOrigin="0,0.5" StrokeMiterLimit="1" Data="M 0,0 l 150,0 10,10 -150,0 z">
<Path.RenderTransform>
<SkewTransform AngleX="-45" />
</Path.RenderTransform>
</Path>
<Path Stretch="None" x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#29AAE1" RenderTransformOrigin="0,0.5" StrokeMiterLimit="1" Data="M 0,0 l 150,0 10,10 -150,0 z">
<Path.RenderTransform>
<SkewTransform AngleX="-45" />
</Path.RenderTransform>
</Path>
</Grid>
</Viewbox>
</ControlTemplate>
</Window.Resources>
<StackPanel Orientation="Vertical" Background="#464646">
<!--ProgressBar with Rectangle, "Mr. Squirrel.Downy":-->
<Grid HorizontalAlignment="Center" Margin="0 27 0 0">
<ProgressBar Template="{StaticResource ProgressBarRectangle}" Width="480" Height="32" Value="{Binding ElementName=Progress, Path=Value}" />
<Label Content="<Rectangle> + <SkewTansform> (by Mr. Squirrel Downy)" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="#FFFFFF" />
<Label Content="{Binding ElementName=Progress, Path=Value}" ContentStringFormat="{}{0} %" Padding="0" HorizontalAlignment="Right" VerticalAlignment="Center" FontStyle="Italic" Margin="0 0 10 0" Foreground="#808080" />
</Grid>
<!--ProgressBar with Polygon, "mm8":-->
<Grid HorizontalAlignment="Center" Margin="0 4 0 0">
<ProgressBar Template="{StaticResource ProgressBarPolygon}" Width="480" Height="32" Value="{Binding ElementName=Progress, Path=Value}" />
<Label Content="<Polygon HorizontalAlignment="Right" /> (by mm8)" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="#FFFFFF" />
<Label Content="{Binding ElementName=Progress, Path=Value}" ContentStringFormat="{}{0} %" Padding="0" HorizontalAlignment="Right" VerticalAlignment="Center" FontStyle="Italic" Margin="0 0 10 0" Foreground="#808080" />
</Grid>
<!--ProgressBar with Path:-->
<Grid HorizontalAlignment="Center" Margin="0 4 0 0">
<ProgressBar Template="{StaticResource ProgressBarPath}" Width="480" Height="32" Value="{Binding ElementName=Progress, Path=Value}" />
<Label Content="<Path> + <SkewTransform>" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="#FFFFFF" />
<Label Content="{Binding ElementName=Progress, Path=Value}" ContentStringFormat="{}{0} %" Padding="0" HorizontalAlignment="Right" VerticalAlignment="Center" FontStyle="Italic" Margin="0 0 10 0" Foreground="#808080" />
</Grid>
<Slider Name="Progress" Margin="0 35 0 0" Minimum="0" Maximum="100" Width="480" IsSnapToTickEnabled="True" TickFrequency="1" />
</StackPanel>
</Window>
For the sake of completeness an example for calculating complex Path Data in code behind (not necessary for above XAML):
MainWindow.xaml.cs:
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace StackOverFlowTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Instantiate and initialize variable for normal Path Data without transformation:
Geometry geo = Geometry.Parse("M 0,0 l 150,0 0,10 -150,0 z");
// Instantiate and initialize variable for desired shearing/transvection
// (use opposite transformation to the one in the ControlTemplate):
SkewTransform skewT = new SkewTransform(45, 0);
// In case of additional transformations:
// Instantiate and initialize variable for desired translation:
//TranslateTransform transT = new TranslateTransform(-31.89, 0);
// Instantiate variable for all transformations, as you have to apply all transformation at once:
//TransformGroup tG = new TransformGroup();
//tG.Children.Add(skewT);
//tG.Children.Add(transT);
// Create a clone of of your Geometry object,
// since in order to apply a transform, geometry must not be readonly:
Geometry geoClone = geo.Clone();
// Apply transformation:
geoClone.Transform = skewT;
// For multiple transformations:
//geoClone.Transform = tG;
// Calculate new Path Data:
string result = geoClone.GetFlattenedPathGeometry(0.001, ToleranceType.Relative).ToString(CultureInfo.InvariantCulture);
//var result = geoClone.GetFlattenedPathGeometry(0.001, ToleranceType.Relative).ToString().Replace(",", ".").Replace(";", ",");
// Return new Path Data:
Trace.WriteLine(this + ": " + result);
// Returns: M0,0L150,0 160,10 10,10 0,0z
// Note that returned values are absolute values.
// Identical Path Data in relative coordinates (meaning offset values to respective previous point):
// M 0,0 l 150,0 10,10 -150,0 z
}
}
}
Upvotes: 2
Reputation: 1167
Back Code:
class MyCustomConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new Thickness(0, 0, -(double)value, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Template:
<ControlTemplate TargetType="ProgressBar">
<ControlTemplate.Resources>
<local:MyCustomConverter x:Key="sttc"/>
</ControlTemplate.Resources>
<Border BorderBrush="#D9DCE1" BorderThickness="0" Background="#FF0C0B0B" CornerRadius="0" Padding="0" ClipToBounds="True">
<Grid x:Name="PART_Track" Margin="{TemplateBinding Height ,Converter={StaticResource sttc}}">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#FF2BA9FF" RenderTransformOrigin="0,0">
<Rectangle.RenderTransform>
<TransformGroup>
<SkewTransform AngleX="-45"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Border>
</ControlTemplate>
Notice: If the height is fixed, the converter is unnecessary, set margin to a fixed thickness. Converter is just work for auto sizing.
Upvotes: 3
Reputation: 169150
You could use a Polygon
in the template:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border BorderBrush="#D9DCE1" BorderThickness="0" Background="#FF0C0B0B" CornerRadius="0" Padding="0">
<Grid x:Name="PART_Track">
<Grid x:Name="PART_Indicator" HorizontalAlignment="Left" Background="#FF2BA9FF">
<Polygon Points="0,20 20,0 20,20" Stroke="#FF0C0B0B" Fill="#FF0C0B0B" HorizontalAlignment="Right" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
You will have to hide it whenever Value == Maximum
, for example using a converter.
Upvotes: 1