WPF usercontrol customize triangle rotated around bigger circle and invoked in window
//usercontrol.xaml <UserControl x:Class="WpfApp187.UCRotateTriangle" 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:WpfApp187" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Path Panel.ZIndex="-1" Stroke="Black" StrokeThickness="5"> <Path.Data> <EllipseGeometry Center="{Binding UCCenter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" RadiusX="{Binding UCCircleRadius,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" RadiusY="{Binding UCCircleRadius,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> </Path.Data> <Path.Fill> <ImageBrush ImageSource="{Binding UCImgSource,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> </Path.Fill> </Path> <ItemsControl x:Name="ticksItemsControl" Panel.ZIndex="0"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Line X1="{Binding StartX}" Y1="{Binding StartY}" X2="{Binding EndX}" Y2="{Binding EndY}" Stroke="{Binding LineStroke}" StrokeThickness="{Binding LineThickness}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Path Panel.ZIndex="1" Fill="Red"> <Path.Data> <PathGeometry> <PathFigure StartPoint="0,0" IsClosed="True"> <LineSegment Point="30,0"/> <LineSegment Point="15,50"/> </PathFigure> </PathGeometry> </Path.Data> <Path.RenderTransform> <TransformGroup> <TranslateTransform X="{Binding UCTriangleX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Y="{Binding UCTriangleY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <RotateTransform x:Name="triangleRotater" CenterX="{Binding UCTriangleCenterX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" CenterY="{Binding UCTriangleCenterY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Angle="{Binding UCTriangleAngle,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" /> </TransformGroup> </Path.RenderTransform> </Path> </Grid> </UserControl> //usercontrol.xaml.cs using System; using System.Collections.Generic; 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 WpfApp187 { /// <summary> /// Interaction logic for UCRotateTriangle.xaml /// </summary> public partial class UCRotateTriangle : UserControl { public UCRotateTriangle() { InitializeComponent(); this.Loaded += UCRotateTriangle_Loaded; this.SizeChanged += UCRotateTriangle_SizeChanged; this.DataContext = this; } private void UCRotateTriangle_SizeChanged(object sender, SizeChangedEventArgs e) { Init(); } private void UCRotateTriangle_Loaded(object sender, RoutedEventArgs e) { Init(); } private void Init() { if(UCCenter==default) { UCCenter = new Point(this.ActualWidth / 2, this.ActualHeight / 2); } UCCircleRadius = Math.Min(this.ActualWidth, this.ActualHeight) / 2 - 50; UCTriangleX = this.ActualWidth / 2 - 15; UCTriangleY = 0; UCTriangleCenterX = this.ActualWidth / 2; UCTriangleCenterY = this.ActualHeight / 2; UCTriangleAngle = 0; InitTicks(); } private System.Timers.Timer tmr { get; set; } private void InitTicks() { double tickLength = 0; Brush tickStroke = Brushes.Black; double angleStep = 360 / 60; double angleRadian = 0.0d; double tickRadius = this.ActualHeight / 2 - 50; double tickThickness = 0; List<LineClass> linesList = new List<LineClass>(); for (int i = 0; i < 60; i++) { tickLength = i % 5 == 0 ? 50 : 20; tickStroke = i % 5 == 0 ? Brushes.Red : Brushes.Black; angleRadian = angleStep * i * Math.PI / 180; tickThickness = i % 5 == 0 ? 5 : 1; linesList.Add(new LineClass() { StartX = UCTriangleCenterX + (tickRadius) * Math.Cos(angleRadian), StartY = UCTriangleCenterY + tickRadius * Math.Sin(angleRadian), EndX = UCTriangleCenterX + (tickRadius - tickLength) * Math.Cos(angleRadian), EndY = UCTriangleCenterY + (tickRadius - tickLength) * Math.Sin(angleRadian), LineStroke = tickStroke, LineThickness = tickThickness }); } ticksItemsControl.ItemsSource = linesList; } public Point UCCenter { get { return (Point)GetValue(UCCenterProperty); } set { SetValue(UCCenterProperty, value); } } // Using a DependencyProperty as the backing store for UCCenter. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCCenterProperty = DependencyProperty.Register("UCCenter", typeof(Point), typeof(UCRotateTriangle), new PropertyMetadata(new Point(0, 0))); public double UCCircleRadius { get { return (double)GetValue(UCCircleRadiusProperty); } set { SetValue(UCCircleRadiusProperty, value); } } // Using a DependencyProperty as the backing store for UCCircleRadius. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCCircleRadiusProperty = DependencyProperty.Register("UCCircleRadius", typeof(double), typeof(UCRotateTriangle), new PropertyMetadata(0.0d)); public ImageSource UCImgSource { get { return (ImageSource)GetValue(UCImgSourceProperty); } set { SetValue(UCImgSourceProperty, value); } } // Using a DependencyProperty as the backing store for UCImgSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCImgSourceProperty = DependencyProperty.Register("UCImgSource", typeof(ImageSource), typeof(UCRotateTriangle), new PropertyMetadata(null)); public double UCTriangleX { get { return (double)GetValue(UCTriangleXProperty); } set { SetValue(UCTriangleXProperty, value); } } // Using a DependencyProperty as the backing store for UCTriangleX. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCTriangleXProperty = DependencyProperty.Register("UCTriangleX", typeof(double), typeof(UCRotateTriangle), new PropertyMetadata(0.0d)); public double UCTriangleY { get { return (double)GetValue(UCTriangleYProperty); } set { SetValue(UCTriangleYProperty, value); } } // Using a DependencyProperty as the backing store for UCTriangleY. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCTriangleYProperty = DependencyProperty.Register("UCTriangleY", typeof(double), typeof(UCRotateTriangle), new PropertyMetadata(0.0d)); public double UCTriangleCenterX { get { return (double)GetValue(UCTriangleCenterXProperty); } set { SetValue(UCTriangleCenterXProperty, value); } } // Using a DependencyProperty as the backing store for UCTriangleCenterX. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCTriangleCenterXProperty = DependencyProperty.Register("UCTriangleCenterX", typeof(double), typeof(UCRotateTriangle), new PropertyMetadata(0.0d)); public double UCTriangleCenterY { get { return (double)GetValue(UCTriangleCenterYProperty); } set { SetValue(UCTriangleCenterYProperty, value); } } // Using a DependencyProperty as the backing store for UCTriangleCenterY. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCTriangleCenterYProperty = DependencyProperty.Register("UCTriangleCenterY", typeof(double), typeof(UCRotateTriangle), new PropertyMetadata(0.0d)); public double UCTriangleAngle { get { return (double)GetValue(UCTriangleAngleProperty); } set { SetValue(UCTriangleAngleProperty, value); } } // Using a DependencyProperty as the backing store for UCTriangleAngle. This enables animation, styling, binding, etc... public static readonly DependencyProperty UCTriangleAngleProperty = DependencyProperty.Register("UCTriangleAngle", typeof(double), typeof(UCRotateTriangle), new PropertyMetadata(0.0d)); } public class LineClass { public double StartX { get; set; } public double StartY { get; set; } public double EndX { get; set; } public double EndY { get; set; } public Brush LineStroke { get; set; } public double LineThickness { get; set; } } } //window.xaml <Window x:Class="WpfApp187.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:WpfApp187" mc:Ignorable="d" WindowState="Maximized" Title="MainWindow" Height="450" Width="800"> <Grid> <local:UCRotateTriangle UCTriangleAngle="{Binding DataContext.TriangleAngle,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 WpfApp187 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var vm = new MainVM(this); this.DataContext = vm; } } public class MainVM : INotifyPropertyChanged { private Window win { get; set; } public MainVM(Window winValue) { win = winValue; win.Loaded += Win_Loaded; win.SizeChanged += Win_SizeChanged; } private void Win_SizeChanged(object sender, SizeChangedEventArgs e) { Init(); } private void Win_Loaded(object sender, RoutedEventArgs e) { Init(); } private System.Timers.Timer tmr { get; set; } private void Init() { var fe = win.Content as FrameworkElement; if(fe!=null) { ElpCenter=new Point(fe.ActualWidth/4, fe.ActualHeight/4); if(tmr!=null) { return; } tmr=new System.Timers.Timer(); tmr.Elapsed += Tmr_Elapsed; tmr.Interval = 1000; tmr.Start(); } } private void Tmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { try { TriangleAngle += 30; if (TriangleAngle >= 360) { TriangleAngle = 0; } } catch (Exception ex) { MessageBox.Show(ex.Message); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) { handler?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } private double triangleAngle; public double TriangleAngle { get { return triangleAngle; } set { if(triangleAngle!=value) { triangleAngle = value; OnPropertyChanged(); OnTriangleAngleChanged(); } } } private void OnTriangleAngleChanged() { Application.Current?.Dispatcher.BeginInvoke(new Action(() => { win.Title=TriangleAngle.ToString(); })); } private Point elpCenter; public Point ElpCenter { get { return elpCenter; } set { if (value != elpCenter) { elpCenter = value; OnPropertyChanged(); } } } } }