WPF customize rotated wheel relentlessly via custom control

//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

 

posted @ 2026-08-22 21:40  FredGrit  阅读(3)  评论(0)    收藏  举报