WPF circle progress bar - wpf

I want to replace the regular ProgressBar with circle one and after shot search here in the forum i found what i want.
CircularProgressBar.XAML
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}"
StrokeThickness="{Binding StrokeThickness, ElementName=userControl}" HorizontalAlignment="Left" VerticalAlignment="Top">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
CircularProgressBar.cs:
public partial class CircularProgressBar : UserControl
{
public CircularProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PercentageProperty =
DependencyProperty.Register("Percentage", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(65d, new PropertyChangedCallback(OnPercentageChanged)));
// Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(5, new PropertyChangedCallback(OnThicknessChanged)));
// Using a DependencyProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SegmentColorProperty =
DependencyProperty.Register("SegmentColor", typeof(Brush), typeof(CircularProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red), new PropertyChangedCallback(OnColorChanged)));
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(25, new PropertyChangedCallback(OnPropertyChanged)));
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(120d, new PropertyChangedCallback(OnPropertyChanged)));
private static void OnColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.set_Color((SolidColorBrush)args.NewValue);
}
private static void OnThicknessChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.set_tick((int)args.NewValue);
}
private static void OnPercentageChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
if (circle.Percentage > 100) circle.Percentage = 100;
circle.Angle = (circle.Percentage * 360) / 100;
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.RenderArc();
}
public void set_tick(int n)
{
pathRoot.StrokeThickness = n;
}
public void set_Color(SolidColorBrush n)
{
pathRoot.Stroke = n;
}
public void RenderArc()
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(Angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.Width = Radius * 2 + StrokeThickness;
pathRoot.Height = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
MainWindow.xaml:
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF878889" StrokeThickness="25" Percentage="100" />
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="{Binding Value, ElementName=slider}" SegmentColor="#026873" StrokeThickness="25" />
</Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<Slider x:Name="slider" Grid.Row="1" Maximum="100" Value="60" />
</Grid>
And the result:
http://s21.postimg.org/xymj8k4pz/image.png
So after copy paste the same code into my solution and running all i can see is only the Slider withou the Circle Bar, this is my code:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="530,303,114,303">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF878889" StrokeThickness="8" Percentage="100" />
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="{Binding Value, ElementName=slider}" SegmentColor="#026873" StrokeThickness="8" />
</Grid>
</StackPanel>
<Slider x:Name="slider" Maximum="100" Value="20" Width="200" Margin="597,185,227,495" />
Am i doing something wrong ?

You probably missed x:Name="userControl" in the UserControl definition:
<UserControl x:Name="userControl" x:Class="DesignInControl.CircularProgressBar"
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">
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}" StrokeThickness="{Binding StrokeThickness, ElementName=userControl}" HorizontalAlignment="Left" VerticalAlignment="Top">
<Path.Data>
...

This demonstrates how to animate the circle.
When AnimateProgressCircle is true, the "busy" circle will be rotating, else it will be invisible.
<!-- "I'm Busy" Animation circle. -->
<StackPanel Height="20" Width="20">
<Viewbox>
<!-- All this does is display the circle. -->
<local:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="0" SegmentColor="#726873" StrokeThickness="10">
<!-- All this does is continuously animate circle angle from 0 - 100% in response to "IfAnimateProgressCircle". -->
<local:CircularProgressBar.Style>
<Style TargetType="local:CircularProgressBar">
<Style.Triggers>
<DataTrigger Binding="{Binding IfAnimateProgressCircle}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Percentage"
From="0"
To="100"
Duration="0:0:1"
AutoReverse="True"
RepeatBehavior = "Forever"
/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Percentage" To="0.0" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</local:CircularProgressBar.Style>
</local:CircularProgressBar>
</Viewbox>
</StackPanel>
Advantages
As it's wrapped in a <Viewbox>, it will always be perfectly sized to the parent container.
Unlike other simplistic solutions, it does not consume resources when it is not in use because the animation is stopped.
Testing
Tested on:
WPF
.NET 4.5 + .NET 4.6.1
Win7 x64
Visual Studio 2015 + Update 2
To test, set DataContext="{Binding RelativeSource={RelativeSource Self}}" in the <Window> tag, then use this code behind.
You should see the circle pause for 2 seconds, then animate for 4 seconds, then stop.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _ifAnimateProgressCircle;
public MainWindow()
{
InitializeComponent();
Task.Run(
async () =>
{
// Animates circle for 4 seconds.
IfAnimateProgressCircle = false;
await Task.Delay(TimeSpan.FromMilliseconds(2000));
IfAnimateProgressCircle = true;
await Task.Delay(TimeSpan.FromMilliseconds(6000));
IfAnimateProgressCircle = false;
});
}
public bool IfAnimateProgressCircle
{
get { return _ifAnimateProgressCircle; }
set { _ifAnimateProgressCircle = value; OnPropertyChanged(); }
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Links that helped with this solution
WPF. How to stop data trigger animation through binding?
Animate UserControl in WPF?
Binding objects defined in code-behind
Triggers collection members must be of type EventTrigger

Related

WPF and MVVM: Use RotateTransform and DoubleAnimation to move an object along circular path

I'm relatively new to WPF animations. I would like to have an object (a simple circle for example) move around a circular path in 10 degree increments. The following example is tantalizingly close: WPF circular moving object See the answer by Clemens who uses a RotateTransform in XAML and a DoubleAnimation in code-behind. I am however, using MVVM and it's not clear to me how to accomplish this. I believe I would need to access the RotateTransform which is in the View from my ViewModel, so I can call BeginAnimation, but how?
Any examples or ideas? I have searched without luck.
Thanks
UPDATE: I can now be more specific in my question by showing what I've already tried. Based on the above mentioned reference AND Twoway-bind view's DependencyProperty to viewmodel's property? (answer by #Michael Schnerring), I have the following simple code (below). My ellipse is not rotating. No binding errors, or any other errors, just no rotation. And my methods are hit (I debugged into it) I'm guessing my PerformAnimation function is incorrect, specifically the SetTargetProperty part. I did try to play with it by adding two animations (one for Rotation, one for Transform) but without luck.
Can someone give me an idea what I'm doing wrong?
XAML:
<Canvas Grid.Row="0" Grid.Column="0">
<Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
Canvas.Left="200" Canvas.Top="200"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-100"/>
<RotateTransform />
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
<Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var nameOfPropertyInVm = "AngleProperty";
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(AngleProperty, binding);
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));
private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainWindow control = (MainWindow)sender;
control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
}
private void PerformAnimation(double oldValue, double newValue)
{
Storyboard s = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.From = oldValue;
animation.To = newValue;
animation.Duration = new Duration(TimeSpan.FromSeconds(1));
s.Children.Add(animation);
Storyboard.SetTarget(animation, MyEllipse);
Storyboard.SetTargetProperty(animation, new PropertyPath("(Ellipse.RenderTransform).(RotateTransform.Angle)"));
s.Begin();
}
And ViewModel
public class MyViewModel : ViewModelBase
{
private ICommand _rotateCommand = null;
private double _angleProperty = 10;
public MyViewModel()
{
}
public double AngleProperty
{
get
{
return _angleProperty;
}
set
{
_angleProperty = value;
OnPropertyChanged("AngleProperty");
}
}
public ICommand RotateCommand
{
get
{
if (_rotateCommand == null)
{
_rotateCommand = new RelayCommand(param => RotateCommandImplementation());
}
return _rotateCommand;
}
}
private void RotateCommandImplementation()
{
AngleProperty = AngleProperty + 10;
}
}
Here's my solution, based on a lot of help from #Clemens (see comments and WPF circular moving object)
VIEW
<Window x:Class="AnimationBindingPlay.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:AnimationBindingPlay"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Canvas Grid.Row="0" Grid.Column="0">
<Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
Canvas.Left="200" Canvas.Top="200"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-100"/>
<RotateTransform x:Name="rotateTransform"/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
<Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var nameOfPropertyInVm = "AngleProperty";
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(AngleProperty, binding);
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));
private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainWindow control = (MainWindow)sender;
control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
}
private void PerformAnimation(double oldValue, double newValue)
{
var rotationAnimation = new DoubleAnimation(oldValue, newValue, TimeSpan.FromSeconds(1));
rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotationAnimation);
}
}
ViewModel
Same as in question!

Why ScrollBar's ValueChanged EventTrigger is binding animation to previous value

I have a strange behavior in my WPF application.
I am trying to launch an thickness animation on ScrollBar.ValueChanged event for animating an element margin, but every time the scroll bar value changed, the animation is binded to the OLD value of the scroll bar.
Resulting in a gap between the scroll bar value and the margin element.
<UserControl.Resources>
<Storyboard x:Key="AnimationScrollTest">
<ThicknessAnimation Storyboard.TargetName="ElementTest" Storyboard.TargetProperty="Margin" Duration="0:0:0:0.3"
To="{Binding ElementName=ScrollBarTest, Path=Value, Converter={StaticResource MyVerticalScrollBarValueToMarginConverter}}" />
</Storyboard>
<UserControl.Resources>
<Grid>
<ScrollBar x:Name="ScrollBarTest" Grid.RowSpan="3" Grid.ColumnSpan="2" Orientation="Vertical" Right" VerticalAlignment="Stretch" SmallChange="10" LargeChange="100" Value="0" Maximum="2000" >
<ScrollBar.Triggers>
<EventTrigger RoutedEvent="ScrollBar.ValueChanged" >
<BeginStoryboard Storyboard="{StaticResource AnimationScrollTest}" />
</EventTrigger>
</ScrollBar.Triggers>
</ScrollBar>
<Border x:Name="ElementTest" Grid.RowSpan="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Top" Width="50" Height="50" Background="Red"></Border>
</Grid>
public class VerticalScrollBarValueToMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new Thickness(0, (Double)value, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
When the EventTrigger is linked to ScrollBar.Scroll event, it works well.
When a breakpoint is added in the converter, the value object in the Convert method have the correct value
If the ElementTest margin is directly binded to the ScrollBar value (without animation), it works well too.
Any idea??
Thanks a lot
PS: sorry for the bad english, I'm french!
I attempted to solve the previous value problem with attached properties
xaml
<Grid xmlns:l="clr-namespace:CSharpWPF">
<ScrollBar x:Name="ScrollBarTest"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Orientation="Vertical"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
SmallChange="10"
LargeChange="100"
Value="0"
Maximum="2000">
</ScrollBar>
<Border x:Name="ElementTest"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Width="50"
Height="50"
Background="Red"
l:ScrollBarToMarginAnimator.ScrollBar="{Binding ElementName=ScrollBarTest}"></Border>
</Grid>
I have removed the storyboard from here and added an attached property l:ScrollBarToMarginAnimator.ScrollBar to the target element and binded to the scrollbar ScrollBarTest
class for the attached property
namespace CSharpWPF
{
public class ScrollBarToMarginAnimator : DependencyObject
{
public static ScrollBar GetScrollBar(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(ScrollBarProperty);
}
public static void SetScrollBar(DependencyObject obj, ScrollBar value)
{
obj.SetValue(ScrollBarProperty, value);
}
// Using a DependencyProperty as the backing store for ScrollBar. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScrollBarProperty =
DependencyProperty.RegisterAttached("ScrollBar", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator), new PropertyMetadata(null, OnScrollBarChanged));
private static void OnScrollBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollBar sb = e.NewValue as ScrollBar;
if (sb != null)
sb.Scroll += (ss, ee) =>
{
ThicknessAnimation ta = new ThicknessAnimation(new Thickness(0, sb.Value, 0, 0), TimeSpan.FromMilliseconds(300));
(d as FrameworkElement).BeginAnimation(FrameworkElement.MarginProperty, ta);
};
}
}
}
in this class I am listening to the Scroll event of the binded scrollbar and initiating a ThicknessAnimation on the attached element's Margin property.
above solution will listen any change in scroll bar and will react accordingly by animating margin of the border.
Multi Scrollbar
xaml
<Grid xmlns:l="clr-namespace:CSharpWPF">
<ScrollBar x:Name="vertical"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Orientation="Vertical"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
SmallChange="10"
LargeChange="100"
Value="0"
Maximum="2000"/>
<ScrollBar x:Name="horizontal"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SmallChange="10"
LargeChange="100"
Value="0"
Maximum="2000"/>
<Border x:Name="ElementTest"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Width="50"
Height="50"
Background="Red"
l:ScrollBarToMarginAnimator.Vertical="{Binding ElementName=vertical}"
l:ScrollBarToMarginAnimator.Horizontal="{Binding ElementName=horizontal}"/>
</Grid>
I have added another scrollbar and attached two properties to the border.
cs
namespace CSharpWPF
{
public class ScrollBarToMarginAnimator : DependencyObject
{
public static ScrollBar GetVertical(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(VerticalProperty);
}
public static void SetVertical(DependencyObject obj, ScrollBar value)
{
obj.SetValue(VerticalProperty, value);
}
// Using a DependencyProperty as the backing store for Vertical. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalProperty =
DependencyProperty.RegisterAttached("Vertical", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator),
new PropertyMetadata(null, (d, e) => AttachAnimation(d, e, true)));
public static ScrollBar GetHorizontal(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(HorizontalProperty);
}
public static void SetHorizontal(DependencyObject obj, ScrollBar value)
{
obj.SetValue(HorizontalProperty, value);
}
// Using a DependencyProperty as the backing store for Horizontal. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalProperty =
DependencyProperty.RegisterAttached("Horizontal", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator),
new PropertyMetadata(null, (d, e) => AttachAnimation(d, e, false)));
private static void AttachAnimation(DependencyObject d, DependencyPropertyChangedEventArgs e, bool isVertical)
{
ScrollBar sb = e.NewValue as ScrollBar;
if (sb != null)
sb.Scroll += (ss, ee) =>
{
FrameworkElement fw = d as FrameworkElement;
Thickness newMargin = fw.Margin;
if (isVertical)
newMargin.Top = sb.Value;
else
newMargin.Left = sb.Value;
ThicknessAnimation ta = new ThicknessAnimation(newMargin, TimeSpan.FromMilliseconds(300));
fw.BeginAnimation(FrameworkElement.MarginProperty, ta);
};
}
}
}
I have created two properties and attached the animation using a flag

How to render (redrawn) usercontrol when binding property source changed in windows phone

I build a control in Windows Phone (same WPF) draw a vertical chart by timeline.
In Xaml:
<UserControl x:Class="MiO2.CustomControls.VerticalChart"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480"
xmlns:blockPivot="clr-namespace:MiO2.Framework"
x:Name="uc">
<UserControl.Resources>
<Storyboard x:Name="animateOpacityPointer">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="pointer">
<EasingDoubleKeyFrame KeyTime="0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<CircleEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" blockPivot:BlocksPan.IsEnabled="True" Background="{Binding Background, ElementName=uc}">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid x:Name="renderLayer" Grid.Row="1">
<Canvas x:Name="canvasRender">
</Canvas>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="10" x:Name="touchLayer" Visibility="Visible">
<Canvas x:Name="pointerLayer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid Width="52" x:Name="pointer" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Top="0" Canvas.Left="0">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tbTime" Height="40" Margin="0,0,0,0" Foreground="Black" />
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top">
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Ellipse Fill="#FF8BE0FB" Height="40" Width="40" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Ellipse Fill="#FF01ADF5" Height="20" Width="20" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<Line x:Name="line" Stroke="Black" StrokeDashArray="4 2" Y2="0" VerticalAlignment="Top" HorizontalAlignment="Center"/>
</StackPanel>
</StackPanel>
</Grid>
</Canvas>
<TextBlock x:Name="tbUnit" Text="1" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="Black" />
<Slider blockPivot:BlocksPan.IsEnabled="True" ManipulationCompleted="touchLayer_ManipulationCompleted" ManipulationStarted="touchLayer_ManipulationStarted" Background="Transparent" ManipulationDelta="touchLayer_ManipulationDelta" Foreground="Transparent" Style="{StaticResource SliderStyle1}">
</Slider>
</Grid>
</Grid>
In code behide:
public partial class VerticalChart : UserControl
{
public VerticalChart()
{
InitializeComponent();
this.Loaded += VerticalChart_Loaded;
}
void VerticalChart_Loaded(object sender, RoutedEventArgs e)
{
InitializeChart();
}
public static readonly DependencyProperty ItemSourceProperty =
DependencyProperty.Register("ItemSource", typeof(IEnumerable<ITimelineData>), typeof(VerticalChart),
new PropertyMetadata(null, ItemSourcePropertyChangedCallback));
private static void ItemSourcePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public IEnumerable<ITimelineData> ItemSource
{
get { return base.GetValue(ItemSourceProperty) as IEnumerable<ITimelineData>; }
set { base.SetValue(ItemSourceProperty, value); }
}
Random ran = new Random();
double unit = 0;
double leftStart = 0;
public void InitializeChart()
{
if (ItemSource.Count() == 0) return;
TimeSpan period = ItemSource.Last().TimeEnd - ItemSource.First().TimeStart;
double width = this.ActualWidth - pointer.ActualWidth / 2;
double height = renderLayer.ActualHeight;
double minutes = period.TotalMinutes;
leftStart = pointer.ActualWidth / 2;
double divide = (width / minutes);
if (divide >= 2) unit = 2;
else if (divide >= 1) unit = 1;
else if (divide >= 0.5) unit = 0.5;
else if (divide >= 0.25) unit = 0.25;
else unit = 0.1;
tbUnit.Text = unit.ToString();
foreach (ITimelineData used in ItemSource)
{
double min = (used.TimeEnd - used.TimeStart).TotalMinutes;
int borderWidth = (int)(unit * min);
double left = (used.TimeStart - ItemSource.First().TimeStart).TotalMinutes * unit + leftStart;
Border border = new Border();
border.Width = borderWidth;
border.Height = ran.Next(100, 250);
Binding binding = new Binding("Foreground");
binding.Source = this;
border.SetBinding(Border.BackgroundProperty, binding);
double top = height - border.Height;
Canvas.SetLeft(border, left);
Canvas.SetTop(border, top);
canvasRender.Children.Add(border);
}
}
}
And use in Mainpage.xaml
<myControl:VerticalChart ItemSource="{Binding ActivitiesToday}" />
And when property ActivitiesToday changed means ItemSource property updated. And I want, when ItemSource updated new or change number collection Cavas: renderLayer redrawn number of Border inside. How to do that? I want to redrawn Border in Canvas when ItemSource updated.
For code draw list Border:
Border border = new Border();
border.Width = borderWidth;
border.Height = ran.Next(100, 250);
Binding binding = new Binding("Foreground");
binding.Source = this;
border.SetBinding(Border.BackgroundProperty, binding);
double top = height - border.Height;
Canvas.SetLeft(border, left);
Canvas.SetTop(border, top);
canvasRender.Children.Add(border);
How to do that? Thanks!
Create a custom event in your usercontrol
private event ItemsSourceChanged;
Add all the required code to support event change
Now trigger the event on property change.
public IEnumerable<ITimelineData> ItemSource
{
get { return base.GetValue(ItemSourceProperty) as IEnumerable<ITimelineData>; }
set { base.SetValue(ItemSourceProperty, value); OnItemSourcePropertyChange(); }
}
In xaml, map the event to code-behind and do your changes.
<myControl:VerticalChart ItemSource="{Binding ActivitiesToday}" ItemSourceChanged="doSomething" />

WPF: Command and CommandParameter for UserControl

I am creating a custom UserControl and would like the UC to have a Command just like the Button. I am new to WPF.
This is what I have tried so far without any luck:
WelcomeView.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
<Controls:SignedButton Grid.Row="1" VerticalAlignment="Top" Width="245" Height="45" Foreground="#FFFFFF"
LeftSign="+" Text="Add an account" TextSize="20"
ButtonBackground="#3A5795" HoverBackground="#C41AD7" HoverOpacity="1"
Command="{x:Static Infrastructure:ApplicationCommands.NavigateCommand}"
CommandParameter="{x:Type Views:AddAccountView}"/>
</Grid>
SignedButton.xaml.cs
public partial class SignedButton : UserControl
{
public static DependencyProperty ButtonBackgroundProperty =
DependencyProperty.Register("ButtonBackground", typeof (string), typeof (SignedButton));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof (object), typeof (SignedButton));
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof (ICommand), typeof (SignedButton));
public static DependencyProperty HoverBackgroundProperty =
DependencyProperty.Register("HoverBackground", typeof (string), typeof (SignedButton));
public static DependencyProperty HoverOpacityProperty =
DependencyProperty.Register("HoverOpacity", typeof (double), typeof (SignedButton));
public static DependencyProperty LeftSignProperty =
DependencyProperty.Register("LeftSign", typeof (string), typeof (SignedButton));
public static DependencyProperty RightSignProperty =
DependencyProperty.Register("RightSign", typeof (string), typeof (SignedButton));
public static DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (string), typeof (SignedButton));
public static DependencyProperty TextSizeProperty =
DependencyProperty.Register("TextSize", typeof (double), typeof (SignedButton));
public SignedButton()
{
InitializeComponent();
}
public string ButtonBackground
{
get { return (string) GetValue(ButtonBackgroundProperty); }
set { SetValue(ButtonBackgroundProperty, value); }
}
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public string HoverBackground
{
get { return (string) GetValue(HoverBackgroundProperty); }
set { SetValue(HoverBackgroundProperty, value); }
}
public double HoverOpacity
{
get { return (double) GetValue(HoverOpacityProperty); }
set { SetValue(HoverOpacityProperty, value); }
}
public string LeftSign
{
get { return (string) GetValue(LeftSignProperty); }
set { SetValue(LeftSignProperty, value); }
}
public string RightSign
{
get { return (string) GetValue(RightSignProperty); }
set { SetValue(RightSignProperty, value); }
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public double TextSize
{
get { return (double) GetValue(TextSizeProperty); }
set { SetValue(TextSizeProperty, value); }
}
}
SignedButton.xaml
<UserControl x:Class="Client.Infrastructure.Controls.SignedButton"
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"
mc:Ignorable="d"
d:DesignHeight="45" d:DesignWidth="245"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<FontFamily x:Key="OpenSans">. . .</FontFamily>
<Storyboard x:Key="OnMouseEnter">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="grid">
. . .
</DoubleAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="grid">
. . .
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnMouseLeave">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="grid">
. . .
</DoubleAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="grid">
. . .
</ColorAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
. . .
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
. . .
</EventTrigger>
</UserControl.Triggers>
<Grid x:Name="grid" Cursor="Hand" Background="{Binding ButtonBackground}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding LeftSign}"
FontFamily="{DynamicResource OpenSans}" FontSize="{Binding TextSize}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Text}"
FontFamily="{DynamicResource OpenSans}" FontSize="{Binding TextSize}"
HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1"/>
<TextBlock Text="{Binding RightSign}"
FontFamily="{DynamicResource OpenSans}" FontSize="{Binding TextSize}"
HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="2"/>
</Grid>
</UserControl>
It looks like the one step you have left is to connect the ICommand interface to the UI of the control. Start by creating a mouse-click event listener.
public SignedButton()
{
InitializeComponent();
this.MouseUp += new MouseButtonEventHandler(MyClassContent_MouseUp);
}
void MyClassContent_MouseUp(object sender, MouseButtonEventArgs e)
{
Command.Execute(CommandParameter);
}
You'll also want to listen to the CanExecuteChanged event on the Command instance, to enable/disable the clickable indicator on the UI.
Footnote From the scope of the question, it might be advisable to extend the WPF Button control rather than re-invent its various features. It's possible to customize its appearance and behavior in various ways, while still making use of core features like Visual States (pressed, active, disabled, etc) and the ICommand integration.

Rotate also resizes?

I have this xaml
<Image Width="240" Height="240">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<RotateTransform Angle="-15" CenterX="120" CenterY="120" />
<TranslateTransform Y="-20" />
</TransformGroup>
</DrawingGroup.Transform>
<ImageDrawing ImageSource="Images\pNxVK.png" Rect="0,0,240,240" />
</DrawingGroup>
<DrawingGroup.ClipGeometry>
<EllipseGeometry Center="120,120" RadiusX="60" RadiusY="60" />
</DrawingGroup.ClipGeometry>
</DrawingGroup>
<DrawingGroup>
<DrawingGroup>
<!--<DrawingGroup.Transform>
<RotateTransform Angle="-15" CenterX="120" CenterY="120" />
</DrawingGroup.Transform>-->
<ImageDrawing ImageSource="Images\zUr8D.png" Rect="0,0,240,240" />
</DrawingGroup>
<ImageDrawing ImageSource="Images\XPZW9.png" Rect="0,0,240,240" />
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
The result of that xaml is (Correct size)
If I uncomment the rotate transform in the xaml above i get this (Wrong size)
The drawings are rectangles. And a rotated rectangle has bigger bounding box than a non-rotated one, so it has to be scaled to fit the original boundaries.
You can resolve this by specifying ClipGeometry of the outermost DrawingGroup - just clip it to the original bounds.
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0 0 240 240" />
</DrawingGroup.ClipGeometry>
If its not working when we do it in xaml, maybe it will work from code:
Xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="Rotate:" />
<Slider Minimum="-360" Maximum="360" Value="{Binding ElementName=CrossHair, Path=Rotate, Mode=TwoWay}" />
<TextBlock Text="TranslateX:" />
<Slider Minimum="-200" Maximum="200" Value="{Binding ElementName=CrossHair, Path=TranslateX, Mode=TwoWay}" />
<TextBlock Text="TranslateY:" />
<Slider Minimum="-200" Maximum="200" Value="{Binding ElementName=CrossHair, Path=TranslateY, Mode=TwoWay}" />
<local:CrossHair Width="240" Height="240" x:Name="CrossHair" />
</StackPanel>
</Window>
Code-behind:
namespace Test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class CrossHair : FrameworkElement
{
public double TranslateX
{
get { return (double)GetValue(TranslateXProperty); }
set { SetValue(TranslateXProperty, value); }
}
public static readonly DependencyProperty TranslateXProperty = DependencyProperty.Register("TranslateX", typeof(double), typeof(CrossHair), new UIPropertyMetadata(0.0, PropertyChangedCallback));
public double TranslateY
{
get { return (double)GetValue(TranslateYProperty); }
set { SetValue(TranslateYProperty, value); }
}
public static readonly DependencyProperty TranslateYProperty = DependencyProperty.Register("TranslateY", typeof(double), typeof(CrossHair), new UIPropertyMetadata(-20.0, PropertyChangedCallback));
public double Rotate
{
get { return (double)GetValue(RotateProperty); }
set { SetValue(RotateProperty, value); }
}
// This will result in an OnRender call.
public static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = d as FrameworkElement;
if (element != null)
element.InvalidateVisual();
}
public static readonly DependencyProperty RotateProperty = DependencyProperty.Register("Rotate", typeof(double), typeof(CrossHair), new UIPropertyMetadata(-15.0, PropertyChangedCallback));
protected override void OnRender(DrawingContext ctx)
{
base.OnRender(ctx);
double renderWidht = this.ActualWidth;
double renderHeight = this.ActualHeight;
//Debug Rectangle, you should comment it.
//ctx.DrawRectangle(Brushes.Black, new Pen(Brushes.Black, 1), new Rect(0, 0, renderWidht, renderHeight));
// First Layer: clipped background.
ctx.PushClip(new EllipseGeometry(new Point(renderWidht / 2.0, renderHeight / 2.0), renderWidht / 4.0, renderHeight / 4.0));
ctx.PushTransform(new TransformGroup()
{
Children = new TransformCollection(2)
{
new TranslateTransform(TranslateX, TranslateY),
new RotateTransform(Rotate, renderWidht / 2.0, renderHeight / 2.0)
}
});
ctx.DrawImage(new BitmapImage(new Uri("pack://application:,,,/Images/pNxVK.png")), new Rect(0, 0, renderWidht, renderHeight));
ctx.Pop();// Pop the clipping
ctx.Pop();// Pop the translate
// 2nd Layer:
ctx.DrawImage(new BitmapImage(new Uri("pack://application:,,,/Images/XPZW9.png")), new Rect(0, 0, renderWidht, renderHeight));
// 3rd Layer:
ctx.DrawImage(new BitmapImage(new Uri("pack://application:,,,/Images/zUr8D.png")), new Rect(0, 0, renderWidht, renderHeight));
}
}
}

Resources