WPF draw circular numbers and ticks via itemscontrol ItemsPanelTemplate and ItemsControl.ItemTemplate

//uc.xaml
<UserControl x:Class="WpfApp178.UCElp"
             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:WpfApp178"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="450">
    <Grid>
        <Ellipse
                HorizontalAlignment="Left"
                Width="{Binding UCElpWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                Height="{Binding UCElpHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                Stroke="Black"
                StrokeThickness="10"/>

        <ItemsControl x:Name="ucNumbersItemsControl"
                      HorizontalAlignment="Left"
                      >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding NumStr}"
                               FontSize="30"
                               Foreground="Black">
                        <TextBlock.RenderTransform>
                            <TranslateTransform
                                X="{Binding NumX}"
                                Y="{Binding NumY}"/>
                        </TextBlock.RenderTransform>
                    </TextBlock>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <ItemsControl x:Name="ticksItemsControl"
                     HorizontalAlignment="Left"
                     >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Line X1="{Binding LineStartX}"
                          Y1="{Binding LineStartY}"
                          X2="{Binding LineEndX}"
                          Y2="{Binding LineEndY}"
                          Stroke="{Binding LineStroke}"
                          StrokeThickness="{Binding LineStrokeThickness}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        
    </Grid>
</UserControl>


//uc.xaml.cs
using System;
using System.Collections.Generic;
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 WpfApp178
{
    /// <summary>
    /// Interaction logic for UCElp.xaml
    /// </summary>
    public partial class UCElp : UserControl
    {
        public UCElp()
        {
            InitializeComponent();
            this.DataContext = this;
            this.SizeChanged += UCElp_SizeChanged;
        }

        private void UCElp_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UCElpWidth = Math.Min(this.ActualWidth, this.ActualHeight);
            UCElpHeight = Math.Min(this.ActualHeight, this.ActualWidth);
            UCCenterX = Math.Min(this.ActualWidth, this.ActualHeight) / 2;
            UCCenterY = Math.Min(this.ActualWidth, this.ActualHeight) / 2;
            UCNumsList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
            UCNumRadius = Math.Min(this.ActualWidth, this.ActualHeight) / 2 - 100;
            UCTickRadius = Math.Min(this.ActualWidth, this.ActualHeight) / 2 - 20;

            InitCircleNumbers();
            InitCircleTicks();
        }

        private void InitCircleTicks()
        {
            if (UCTicksCount <= 0)
            {
                return;
            }
            double tickAngleStep = 360 / UCTicksCount;
            List<UCTick> ticksList = new List<UCTick>();
            for (int i = 0; i < UCTicksCount; i++)
            {
                double angleRadian = i * tickAngleStep * Math.PI / 180;
                double startX = UCCenterX + UCTickRadius * Math.Cos(angleRadian);
                double startY = UCCenterY + UCTickRadius * Math.Sin(angleRadian);

                double endX = UCCenterX + (UCTickRadius - UCShortTickLength) * Math.Cos(angleRadian);
                double endY = UCCenterY + (UCTickRadius - UCShortTickLength) * Math.Sin(angleRadian);
                if (i % 5 == 0)
                {
                    endX = UCCenterX + (UCTickRadius - UCLongTickLength) * Math.Cos(angleRadian);
                    endY = UCCenterY + (UCTickRadius - UCLongTickLength) * Math.Sin(angleRadian);
                }

                double tickThickness = i % 5 == 0 ? UCLongTickThickness : UCShortTickThickness;
                ticksList.Add(new UCTick()
                {
                    LineStartX = startX,
                    LineStartY = startY,
                    LineEndX = endX,
                    LineEndY = endY,
                    LineStroke = Brushes.Black,
                    LineStrokeThickness = tickThickness
                });
            }
            ticksItemsControl.ItemsSource = ticksList;
        }

        private void InitCircleNumbers()
        {
            int numsCount = UCNumsList.Count;
            double angelStep = 360.0d / numsCount;
            List<UCNumText> numTextList = new List<UCNumText>();
            for (int i = 0; i < numsCount; i++)
            {
                double angleRadian = ((i + 1) * angelStep - 90) * Math.PI / 180;
                numTextList.Add(new UCNumText()
                {
                    NumStr = UCNumsList[i].ToString(),
                    NumX = UCCenterX + (UCNumRadius) * Math.Cos(angleRadian),
                    NumY = UCCenterY + (UCNumRadius) * Math.Sin(angleRadian)
                });
            }
            ucNumbersItemsControl.ItemsSource = numTextList;
        }

        public double UCCenterX
        {
            get { return (double)GetValue(UCCenterXProperty); }
            set { SetValue(UCCenterXProperty, value); }
        }

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





        public double UCCenterY
        {
            get { return (double)GetValue(UCCenterYProperty); }
            set { SetValue(UCCenterYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCCenterY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCCenterYProperty =
            DependencyProperty.Register("UCCenterY", typeof(double),
                typeof(UCElp), 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(UCElp), new PropertyMetadata(0.0d));






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

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




        public List<int> UCNumsList
        {
            get { return (List<int>)GetValue(UCNumsListProperty); }
            set { SetValue(UCNumsListProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCNumsList.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCNumsListProperty =
            DependencyProperty.Register("UCNumsList", typeof(List<int>),
                typeof(UCElp), new PropertyMetadata(new List<int>()));




        public double UCNumRadius
        {
            get { return (double)GetValue(UCNumRadiusProperty); }
            set { SetValue(UCNumRadiusProperty, value); }
        }

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







        public int UCTicksCount
        {
            get { return (int)GetValue(UCTicksCountProperty); }
            set { SetValue(UCTicksCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCTicksCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCTicksCountProperty =
            DependencyProperty.Register("UCTicksCount", typeof(int),
                typeof(UCElp), new PropertyMetadata(60));




        public double UCShortTickLength
        {
            get { return (double)GetValue(UCShortTickLengthProperty); }
            set { SetValue(UCShortTickLengthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCShortTickLength.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCShortTickLengthProperty =
            DependencyProperty.Register("UCShortTickLength", typeof(double),
                typeof(UCElp), new PropertyMetadata(10.0d));






        public double UCShortTickThickness
        {
            get { return (double)GetValue(UCShortTickThicknessProperty); }
            set { SetValue(UCShortTickThicknessProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCShortTickThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCShortTickThicknessProperty =
            DependencyProperty.Register("UCShortTickThickness", typeof(double),
                typeof(UCElp), new PropertyMetadata(2.0d));








        public double UCLongTickThickness
        {
            get { return (double)GetValue(UCLongTickThicknessProperty); }
            set { SetValue(UCLongTickThicknessProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCLongTickThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCLongTickThicknessProperty =
            DependencyProperty.Register("UCLongTickThickness", typeof(double),
                typeof(UCElp), new PropertyMetadata(5.0d));




        public double UCLongTickLength
        {
            get { return (double)GetValue(UCLongTickLengthProperty); }
            set { SetValue(UCLongTickLengthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UCLongTickLength.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UCLongTickLengthProperty =
            DependencyProperty.Register("UCLongTickLength", typeof(double),
                typeof(UCElp), new PropertyMetadata(20.0d));






        public double UCTickRadius
        {
            get { return (double)GetValue(UCTickRadiusProperty); }
            set { SetValue(UCTickRadiusProperty, value); }
        }

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



    }

    public class UCNumText
    {
        public string NumStr { get; set; }
        public double NumX { get; set; }
        public double NumY { get; set; }
    }

    public class UCTick
    {
        public double LineStartX { get; set; }
        public double LineStartY { get; set; }
        public double LineEndX { get; set; }
        public double LineEndY { get; set; }
        public Brush LineStroke { get; set; }
        public double LineStrokeThickness { get; set; }
    }
}

 

 

/main.xaml
<Window x:Class="WpfApp178.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:WpfApp178"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="450">
    <Grid>
        <local:UCElp/>
    </Grid>
</Window>

 

 

 

 

 

posted @ 2025-03-16 17:36  FredGrit  阅读(9)  评论(0)    收藏  举报