WPF polygon rotate around circle and always point to the circle center

//usercontrol.xaml
<UserControl x:Class="WpfApp195.UCElpRotateFivePolygon"
             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:WpfApp195"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <!--<Ellipse Width="{Binding UCElpWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                 Height="{Binding UCElpHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                 Stroke="Black"
                 StrokeThickness="3"
                 HorizontalAlignment="Center"
                 VerticalAlignment="Center"/>-->
        <Path Stroke="Black"
              StrokeThickness="3">
            <Path.Data>
                <EllipseGeometry Center="{Binding UCCenterPt,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                 RadiusX="{Binding UCRadiusX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                 RadiusY="{Binding UCRadiusY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </Path.Data>
        </Path>

        <Ellipse Width="20"
                 Height="20"
                 StrokeThickness="5"
                 Stroke="Black"
                 HorizontalAlignment="Center"
                 VerticalAlignment="Center"/>

        <ItemsControl x:Name="ticksItemsControl"
                      >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Line X1="{Binding UCStartX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                          Y1="{Binding UCStartY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                          X2="{Binding UCEndX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                          Y2="{Binding UCEndY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                          Stroke="{Binding UCTickStroke,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                          StrokeThickness="{Binding UCTickStrokeThickness,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <Path Fill="Blue">
            <Path.Data>
                <PathGeometry>
                    <PathFigure StartPoint="0,5" IsClosed="True">
                        <LineSegment Point="50,5"/>
                        <LineSegment Point="50,25"/>
                        <LineSegment Point="25,50"/>
                        <LineSegment Point="0,25"/>
                        <LineSegment Point="0,5"/>
                    </PathFigure>
                </PathGeometry>
            </Path.Data>
            <Path.RenderTransform>
                <TransformGroup>
                    <TranslateTransform X="{Binding UCTX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                        Y="{Binding UCTY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    <RotateTransform x:Name="polygonRotater"
                                     CenterX="{Binding UCRotaterX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                     CenterY="{Binding UCRotaterY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                </TransformGroup>
            </Path.RenderTransform>
        </Path>

        <Line 
              Stroke="Red"
              StrokeThickness="5"
              StrokeEndLineCap="Triangle"
              X1="{Binding UCLineStartX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              Y1="{Binding UCLineStartY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              X2="{Binding UCLineEndX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              Y2="{Binding UCLineEndY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
            <Line.RenderTransform>
                <RotateTransform x:Name="lineRotater"
                                 CenterX="{Binding UCRotaterX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                 CenterY="{Binding UCRotaterY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </Line.RenderTransform>
        </Line>
    </Grid>
</UserControl>



//usercontrol.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
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.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Reflection;
using System.Windows.Ink;

namespace WpfApp195
{
    /// <summary>
    /// Interaction logic for UCElpRotateFivePolygon.xaml
    /// </summary>
    public partial class UCElpRotateFivePolygon : UserControl
    {
        public UCElpRotateFivePolygon()
        {
            InitializeComponent();
            this.DataContext = this;
            this.Loaded += UCElpRotateFivePolygon_Loaded;
            this.SizeChanged += UCElpRotateFivePolygon_SizeChanged;
        }

        private void UCElpRotateFivePolygon_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Init();
        }

        private void UCElpRotateFivePolygon_Loaded(object sender, RoutedEventArgs e)
        {
            Init();
        }


        private System.Timers.Timer rotaterTmr;
        private void Init()
        {
            //var instance = Activator.CreateInstance(typeof(UCElpRotateFivePolygon)) as UCElpRotateFivePolygon;
            //if (instance != null)
            //{
            //    var content = instance.Content as FrameworkElement;
            //    if (content != null)
            //    {
            //        UCElpHeight = content.ActualHeight;
            //        UCElpWidth = content.ActualWidth;
            //    }
            //}

            var content = this.Content as FrameworkElement;
            if (content != null)
            {
                UCElpWidth = content.ActualHeight;
                UCElpHeight = content.ActualHeight;
                UCCenterPt = new Point(content.ActualWidth / 2, content.ActualHeight / 2);
                UCRadiusX = content.ActualHeight / 2 - 50;
                UCRadiusY = content.ActualHeight / 2 - 50;
                UCTX = content.ActualWidth / 2 - 25;
                UCRotaterX = content.ActualWidth / 2;
                UCRotaterY = content.ActualHeight / 2;
                UCLineStartX=content.ActualWidth / 2;
                UCLineStartY=content.ActualHeight / 2;
                UCLineEndX=content.ActualWidth / 2;
                UCLineEndY = 105;
                InitTickItemsControl();
                InitPolygonRotateTimer();
            }
        }

        private void InitPolygonRotateTimer()
        {
            if(rotaterTmr!=null)
            {
                return;
            }
            rotaterTmr=new System.Timers.Timer();
            rotaterTmr.Elapsed += RotaterTmr_Elapsed;
            rotaterTmr.Interval = 1000;
            rotaterTmr.Start();
        }

        private void RotaterTmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
            {
                polygonRotater.Angle += 10;
                if (polygonRotater.Angle >= 360)
                {
                    polygonRotater.Angle = 0;
                }

                lineRotater.Angle += 10;
                UCRotatedStr = $"lineRotater.Angle:{lineRotater.Angle},polygonRotater.Angle:{polygonRotater.Angle}";
            }));
        }

        private void InitTickItemsControl()
        {
            List<LineTick> ticksList = new List<LineTick>();
            double tickLength = 20;
            Brush lineStroke = Brushes.Black;
            double tickThickness = 0.0d;
            double angleStep = 360 / 360;
            double angleRadian = 0.0d;

            for (int i = 0; i < 360; i++)
            {
                tickLength = i % 10 == 0 ? 50 : 20;
                lineStroke = i % 10 == 0 ? Brushes.Red : Brushes.Black;
                tickThickness = i % 10 == 0 ? 3 : 1;
                angleRadian = (i * angleStep - 90) * Math.PI / 180;
                ticksList.Add(new LineTick()
                {
                    UCStartX = UCCenterPt.X + UCRadiusX * Math.Cos(angleRadian),
                    UCStartY = UCCenterPt.Y + UCRadiusY * Math.Sin(angleRadian),
                    UCEndX = UCCenterPt.X + (UCRadiusX - tickLength) * Math.Cos(angleRadian),
                    UCEndY = UCCenterPt.Y + (UCRadiusY - tickLength) * Math.Sin(angleRadian),
                    UCTickStroke = lineStroke,
                    UCTickStrokeThickness = tickThickness
                });
            }

            ticksItemsControl.ItemsSource = ticksList;
        }

        public Point UCCenterPt
        {
            get { return (Point)GetValue(UCCenterPtProperty); }
            set { SetValue(UCCenterPtProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCCenterPt.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCCenterPtProperty =
            DependencyProperty.Register("UCCenterPt", typeof(Point),
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(new Point(0, 0)));






        public double UCRadiusX
        {
            get { return (double)GetValue(UCRadiusXProperty); }
            set { SetValue(UCRadiusXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCRadiusX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCRadiusXProperty =
            DependencyProperty.Register("UCRadiusX", typeof(double),
                    typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));






        public double UCRadiusY
        {
            get { return (double)GetValue(UCRadiusYProperty); }
            set { SetValue(UCRadiusYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCRadiusY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCRadiusYProperty =
            DependencyProperty.Register("UCRadiusY", typeof(double),
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));





        public double UCElpWidth
        {
            get { return (double)GetValue(UCElpWidthProperty); }
            set { SetValue(UCElpWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCElpWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCElpWidthProperty =
            DependencyProperty.Register("UCElpWidth", typeof(double),
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(GetDefaultElpWidth()));

        private static double GetDefaultElpWidth()
        {
            var content = Assembly.GetExecutingAssembly().GetType().GetProperties();
            return 800.0d;
        }

        public double UCElpHeight
        {
            get { return (double)GetValue(ElpHeightProperty); }
            set { SetValue(ElpHeightProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ElpHeight.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ElpHeightProperty =
            DependencyProperty.Register("ElpHeight", typeof(double), typeof(UCElpRotateFivePolygon),
                new PropertyMetadata(GetDefaultElpHeight()));

        private static double GetDefaultElpHeight()
        {
            return 800.0d;
        }



        public double UCTX
        {
            get { return (double)GetValue(UCTXProperty); }
            set { SetValue(UCTXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCTX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCTXProperty =
            DependencyProperty.Register("UCTX", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));




        public double UCTY
        {
            get { return (double)GetValue(UCTYProperty); }
            set { SetValue(UCTYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCTY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCTYProperty =
            DependencyProperty.Register("UCTY", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));




        public double UCRotaterX
        {
            get { return (double)GetValue(UCRotaterXProperty); }
            set { SetValue(UCRotaterXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCRotaterX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCRotaterXProperty =
            DependencyProperty.Register("UCRotaterX", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));



        public double UCRotaterY
        {
            get { return (double)GetValue(UCRotaterYProperty); }
            set { SetValue(UCRotaterYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCRotaterY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCRotaterYProperty =
            DependencyProperty.Register("UCRotaterY", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));





        public double UCLineStartX
        {
            get { return (double)GetValue(UCLineStartXProperty); }
            set { SetValue(UCLineStartXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCLineStartX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCLineStartXProperty =
            DependencyProperty.Register("UCLineStartX", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));





        public double UCLineStartY
        {
            get { return (double)GetValue(UCLineStartYProperty); }
            set { SetValue(UCLineStartYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCLineStartY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCLineStartYProperty =
            DependencyProperty.Register("UCLineStartY", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));






        public double UCLineEndX
        {
            get { return (double)GetValue(UCLineEndXProperty); }
            set { SetValue(UCLineEndXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCLineEndX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCLineEndXProperty =
            DependencyProperty.Register("UCLineEndX", typeof(double), 
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));







        public double UCLineEndY
        {
            get { return (double)GetValue(UCLineEndYProperty); }
            set { SetValue(UCLineEndYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCLineEndY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCLineEndYProperty =
            DependencyProperty.Register("UCLineEndY", typeof(double),
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(0.0d));




        public string UCRotatedStr
        {
            get { return (string)GetValue(UCRotatedStrProperty); }
            set { SetValue(UCRotatedStrProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCRotatedStr.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCRotatedStrProperty =
            DependencyProperty.Register("UCRotatedStr", typeof(string),
                typeof(UCElpRotateFivePolygon), new PropertyMetadata(""));


    }


    public class LineTick
    {
        public double UCStartX { get; set; }
        public double UCStartY { get; set; }
        public double UCEndX { get; set; }
        public double UCEndY { get; set; }
        public Brush UCTickStroke { get; set; }
        public double UCTickStrokeThickness { get; set; }

    }
}



//window.xaml
<Window x:Class="WpfApp195.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:WpfApp195"
        WindowState="Maximized"
        mc:Ignorable="d"
        Title="{Binding DataContext.RotatedStr,Mode=TwoWay}" 
        Height="450" Width="800">
    <Grid>
        <local:UCElpRotateFivePolygon
            UCRotatedStr="{Binding DataContext.RotatedStr,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,
            RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"/>
    </Grid>
</Window>



//window.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
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.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp195
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window,INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        private string rotatedStr="";
        public string RotatedStr
        {
            get
            {
                return rotatedStr;
            }
            set
            {
                if(value != rotatedStr)
                {
                    rotatedStr = value;
                    OnPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propName="")
        {
            var handler = PropertyChanged;
            if(handler!=null)
            {
                handler?.Invoke(this, new PropertyChangedEventArgs(propName));
            }
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2025-04-06 15:10  FredGrit  阅读(12)  评论(0)    收藏  举报