//D:\C\WpfApp2\WpfApp2\RotateWheel.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WpfApp2
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfApp2"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfApp2;assembly=WpfApp2"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Browse to and select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:RotateWheel/>
///
/// </summary>
public class RotateWheel : Control
{
private DispatcherTimer tmr;
private double speedInterval=10.0d;
public RotateWheel()
{
tmr = new DispatcherTimer();
tmr.Interval = TimeSpan.FromSeconds(1);
tmr.Tick += Tmr_Tick;
tmr.Start();
}
private void Tmr_Tick(object? sender, EventArgs e)
{
double current = SpeedKmh;
if(SpeedKmh>=200)
{
speedInterval = -20;
}
else if(current<=10)
{
speedInterval = 20;
}
SpeedKmh = current + speedInterval;
}
static RotateWheel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RotateWheel), new FrameworkPropertyMetadata(typeof(RotateWheel)));
}
#region DependencyProperties
public bool IsClockWise
{
get { return (bool)GetValue(IsClockWiseProperty); }
set { SetValue(IsClockWiseProperty, value); }
}
// Using a DependencyProperty as the backing store for IsClockWise. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsClockWiseProperty =
DependencyProperty.Register(nameof(IsClockWise),
typeof(bool),
typeof(RotateWheel),
new PropertyMetadata(true,OnClockWiseChanged));
private static void OnClockWiseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((RotateWheel)d).UpdateAnimationState();
}
public double SpeedKmh
{
get { return (double)GetValue(SpeedKmhProperty); }
set { SetValue(SpeedKmhProperty, value); }
}
// Using a DependencyProperty as the backing store for SpeedKmh. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SpeedKmhProperty =
DependencyProperty.Register(nameof(SpeedKmh),
typeof(double),
typeof(RotateWheel),
new PropertyMetadata(10.0,OnSpeedChanged,CoerceSpeedChanged));
private static object CoerceSpeedChanged(DependencyObject d, object baseValue)
{
double v = (double)baseValue;
return Math.Clamp(v, 0, 200);
}
private static void OnSpeedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((RotateWheel)d).UpdateAnimationState();
}
#endregion
#region Template Children refs
private RotateTransform wheelRotateTransform;
private DoubleAnimation rotatedAnimation;
#endregion
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
wheelRotateTransform = GetTemplateChild("PART_WheelRotateTransform") as RotateTransform;
SetupAnimation();
UpdateAnimationState();
}
private void UpdateAnimationState()
{
if(wheelRotateTransform is null || rotatedAnimation is null)
{
return;
}
double speed = SpeedKmh;
if(speed<=double.Epsilon)
{
wheelRotateTransform.BeginAnimation(RotateTransform.AngleProperty, null);
return;
}
double secPerRad = 2.0 / speed;
double fullCircleRad = 2 * Math.PI;
double fullCircleSeconds = secPerRad * fullCircleRad;
rotatedAnimation.Duration = TimeSpan.FromSeconds(fullCircleSeconds);
//clockwise
rotatedAnimation.IsCumulative = true;
rotatedAnimation.From = 0;
rotatedAnimation.To = IsClockWise ? 360 : -360;
wheelRotateTransform.BeginAnimation(RotateTransform.AngleProperty, null);
wheelRotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotatedAnimation);
}
private void SetupAnimation()
{
if(wheelRotateTransform==null)
{
return;
}
rotatedAnimation = new DoubleAnimation
{
From = 0,
To = 360,
RepeatBehavior = RepeatBehavior.Forever,
FillBehavior = FillBehavior.HoldEnd,
};
wheelRotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotatedAnimation);
}
}
}
//D:\C\WpfApp2\WpfApp2\Themes\Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2">
<Style TargetType="{x:Type local:RotateWheel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:RotateWheel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="6*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
HorizontalAlignment="Center"
Height="500"
Width="500"
RenderTransformOrigin="0.5,0.5" >
<Grid.RenderTransform>
<RotateTransform x:Name="PART_WheelRotateTransform"
Angle="0"/>
</Grid.RenderTransform>
<Path Width="500" Height="500">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<EllipseGeometry RadiusX="250"
RadiusY="250"
Center="250,250"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry RadiusX="200"
RadiusY="200"
Center="250,250"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
<Path.Fill>
<LinearGradientBrush>
<GradientStop Color="LightPink" Offset="0.1"/>
<GradientStop Color="Red" Offset="0.2"/>
<GradientStop Color="Orange" Offset="0.3"/>
<GradientStop Color="DarkOrange" Offset="0.4"/>
<GradientStop Color="DarkBlue" Offset="0.5"/>
<GradientStop Color="Yellow" Offset="0.6"/>
<GradientStop Color="LightGreen" Offset="0.7"/>
<GradientStop Color="Green" Offset="0.8"/>
<GradientStop Color="Cyan" Offset="0.9"/>
<GradientStop Color="DarkCyan" Offset="1.0"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding IsClockWise,RelativeSource={RelativeSource TemplatedParent}}"
Content="Clockwise"
Margin="10"
HorizontalAlignment="Center"/>
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
Interval="10"
Value="{Binding SpeedKmh,RelativeSource={RelativeSource TemplatedParent}}"
Margin="5,5"
Width="300"
HorizontalAlignment="Center"/>
<TextBlock Grid.Column="2"
Text="{Binding SpeedKmh,
RelativeSource={RelativeSource TemplatedParent},
StringFormat='Speed:{0:F2} km/h'}"
HorizontalAlignment="Center"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
//D:\C\WpfApp2\WpfApp2\MainWindow.xaml
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" WindowState="Maximized">
<Grid>
<local:RotateWheel/>
</Grid>
</Window>
![image]()
![image]()