WPF Circle panel with ticks distributed evenly and one line point to the specified tick with specific number

<Window x:Class="WpfApp185.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:WpfApp185"
        mc:Ignorable="d"
        WindowState="Maximized"
        KeyDown="Window_KeyDown"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Path x:Name="circlePath"
              Stroke="Black"
              StrokeThickness="5"
              Panel.ZIndex="-1">
            <Path.Data>
                <EllipseGeometry
                    Center="{Binding ElpCenter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                    RadiusX="{Binding ElpRadius,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                    RadiusY="{Binding ElpRadius,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </Path.Data>
        </Path>

        <ItemsControl x:Name="ticksItemsControl"
                      Panel.ZIndex="1">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Line X1="{Binding TickStartX}"
                          Y1="{Binding TickStartY}"
                          X2="{Binding TickEndX}"
                          Y2="{Binding TickEndY}"                         
                          Stroke="{Binding TickStroke}"
                          StrokeThickness="{Binding TickStrokeThickness}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <Ellipse Width="50"
                 Height="50"
                 VerticalAlignment="Center"
                 HorizontalAlignment="Center"
                 Stroke="Red"
                 StrokeThickness="5"
                 Panel.ZIndex="2"/>

        <Line Panel.ZIndex="3"
              StrokeStartLineCap="Round"
              StrokeEndLineCap="Triangle"
              X1="{Binding CenterX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              Y1="{Binding CenterY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              X2="{Binding LineEndX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              Y2="{Binding LineEndY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
              Stroke="Red"
              StrokeThickness="5">
            <Line.RenderTransform>
                <RotateTransform x:Name="rotater"
                                 CenterX="{Binding CenterX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                 CenterY="{Binding CenterY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </Line.RenderTransform>
        </Line>
    </Grid>
</Window>


//cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
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 WpfApp185
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
            this.Loaded += MainWindow_Loaded;
            this.SizeChanged += MainWindow_SizeChanged;
        }

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

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

        private void Init()
        {
            var fe = this.Content as FrameworkElement;
            if (fe != null)
            {
                CenterX = fe.ActualWidth / 2;
                CenterY = fe.ActualHeight / 2;
                ElpCenter = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
                ElpRadius = fe.ActualHeight / 2 - 50;
                LineEndX = fe.ActualWidth / 2;
                LineEndY = 60;
                
                InitTicks();
                InitLineTickTimer();
            }
        }

        private System.Timers.Timer lineTmr { get; set; }
        Random lineRnd { get; set; }
        private void InitLineTickTimer()
        {
            if (lineTmr != null)
            {
                return;
            }
            lineTmr = new System.Timers.Timer();
            lineTmr.Elapsed += LineTmr_Elapsed;
            lineTmr.Interval = 1000;
            lineTmr.Start();            
        }

        private void LineTmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (isPaused)
            {
                return;
            }

            try
            {
                lineRnd = new Random();
                Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
                {
                    double lineAngle = lineRnd.Next(0, 360);
                    rotater.Angle = lineAngle;
                    this.Title = rotater.Angle.ToString();
                }));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\n" + ex.StackTrace.ToString());
            }            
        }

        private void InitTicks()
        {
            List<TickClass> ticksList = new List<TickClass>();
            double angleStep = 360 / 360;
            double tickLength = 0;
            double tickTickness = 0;
            Brush tickStroke = Brushes.Black;
            for (int i = 0; i < 360; i++)
            {
                tickLength = i % 5 == 0 ? 30 : 15;
                tickTickness = i % 5 == 0 ? 2 : 1;
                tickStroke = i % 5 == 0 ? Brushes.Red : Brushes.Black;
                double angleRadian = (i * angleStep - 90) * Math.PI / 180;
                ticksList.Add(new TickClass()
                {
                    TickStartX = CenterX + ElpRadius * Math.Cos(angleRadian),
                    TickStartY = CenterY + ElpRadius * Math.Sin(angleRadian),
                    TickEndX = CenterX + (ElpRadius - tickLength) * Math.Cos(angleRadian),
                    TickEndY = CenterY + (ElpRadius - tickLength) * Math.Sin(angleRadian),
                    TickStroke = tickStroke,
                    TickThickness = tickTickness
                });
            }
            ticksItemsControl.ItemsSource = ticksList;
        }

        private double lineEndX;
        public double LineEndX
        {
            get
            {
                return lineEndX;
            }
            set
            {
                if (value != lineEndX)
                {
                    lineEndX = value;
                    OnPropertyChanged(nameof(LineEndX));
                }
            }
        }

        private double lineEndY;
        public double LineEndY
        {
            get
            {
                return lineEndY;
            }
            set
            {
                if (value != lineEndY)
                {
                    lineEndY = value;
                    OnPropertyChanged(nameof(LineEndY));
                }
            }
        }

        private double centerX;
        public double CenterX
        {
            get
            {
                return centerX;
            }
            set
            {
                if (value != centerX)
                {
                    centerX = value;
                    OnPropertyChanged(nameof(CenterX));
                }
            }
        }


        private double centerY;
        public double CenterY
        {
            get
            {
                return centerY;
            }
            set
            {
                if (value != centerY)
                {
                    centerY = value;
                    OnPropertyChanged(nameof(CenterY));
                }
            }
        }


        private double elpRadius;
        public double ElpRadius
        {
            get
            {
                return elpRadius;
            }
            set
            {
                if (value != elpRadius)
                {
                    elpRadius = value;
                    OnPropertyChanged(nameof(ElpRadius));
                }
            }
        }


        private Point elpCenter;
        public Point ElpCenter
        {
            get
            {
                return elpCenter;
            }
            set
            {
                if (value != elpCenter)
                {
                    elpCenter = value;
                    OnPropertyChanged(nameof(ElpCenter));
                }
            }
        }

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

        bool isPaused = false;
        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            isPaused = !isPaused;
        }
    }

    public class TickClass
    {
        public double TickStartX { get; set; }
        public double TickStartY { get; set; }
        public double TickEndX { get; set; }
        public double TickEndY { get; set; }
        public Brush TickStroke { get; set; }
        public double TickThickness { get; set; }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2025-03-29 19:56  FredGrit  阅读(11)  评论(0)    收藏  举报