1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > WPF自定义控件(教程含源码)-圆盘菜单

WPF自定义控件(教程含源码)-圆盘菜单

时间:2019-11-24 04:24:46

相关推荐

WPF自定义控件(教程含源码)-圆盘菜单

控件需求

圆盘菜单控件样式如下图所示

圆盘按钮

满足的功能需求

1.圆盘内的按钮,根据个数自动调整大小。

2.圆盘可以设置内径。

3.扇形按钮可以自定义“描边颜色”、“描边大小”、“填充颜色”

难点

WPF可以使用Path来绘制图形,Path.Data 存放绘制图形的路径。先来思考一下,如何在一个正方形区域内绘制一个扇形?

只需知道图上四个绿色原点的坐标位置即可。从左上角开始,顺时针命名点P1、P2、P3、P4。那么从P1到P2画圆弧,从P2到P3画直线,从P3到P4画圆弧,从P3到P1画直线,就绘制完成全部的路径。

那么,如何知道P1、P2、P3、P4的坐标呢?

借助正弦、余弦就可计算得出。假设P1、P4所在直线的角度为θ₁,P2、P3所在直线的角度为 θ₂,外圈半径为 r₁,内圈半径为r2。那么,4个点的坐标位置如下:

P1:( r₁*cosθ₁, r₁*sinθ₁)

P2:( r₁*cosθ₂, r₁*sinθ₂)

P3:( r₂*cosθ₂, r₂*sinθ₂)

P4:( r₂*cosθ₁, r₂*sinθ₁)

那点解决了,剩下就是将各个元素拼装组合。

扇形按钮控件

扇形按钮 xaml 模板代码如下:

<!--Button 按钮扇形转换--><local:RDiskButtonsButtonContainerConverter x:Key="RDiskButtonsButtonContainerConverter" /><!--Button 按钮内文字旋转角度--><local:RDiskButtonRotateAngleConverter x:Key="RDiskButtonRotateAngleConverter" /><!--Button 按钮在 RDiskPanel 内的样式--><Style TargetType="local:RDiskButton"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="local:RDiskButton"><Grid RenderTransformOrigin=".5 .5"><Path Stroke="{TemplateBinding Stroke}" StrokeThickness="{TemplateBinding StrokeThickness}" SnapsToDevicePixels="True" Fill="{TemplateBinding Fill}" ><Path.Data><MultiBinding Converter="{StaticResource RDiskButtonsButtonContainerConverter}"><Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskButton}"/><Binding Path="ActualHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskButton}"/><Binding Path="StrokeThickness" RelativeSource="{RelativeSource Mode=Self}"/><Binding Path="Index" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskButton}"/><Binding Path="Items.Count" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskPanel}"/><Binding Path="Radius" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskPanel}"/></MultiBinding></Path.Data></Path><Grid RenderTransformOrigin=".5 .5"><ContentPresenter VerticalAlignment="Top" HorizontalAlignment="Center" Margin="{TemplateBinding Padding}" /><Grid.RenderTransform><RotateTransform ><RotateTransform.Angle><MultiBinding Converter="{StaticResource RDiskButtonRotateAngleConverter}"><Binding Path="Index" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskButton}"/><Binding Path="Items.Count" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:RDiskPanel}"/></MultiBinding></RotateTransform.Angle></RotateTransform></Grid.RenderTransform></Grid><Grid.RenderTransform><ScaleTransform ScaleX="1" ScaleY="1" x:Name="scale"/></Grid.RenderTransform></Grid><ControlTemplate.Triggers><Trigger Property="IsMouseOver" Value="true"><Setter Property="Cursor" Value="Hand" /></Trigger><MultiTrigger><MultiTrigger.Conditions><Condition Property="IsPressed" Value="true"></Condition></MultiTrigger.Conditions><MultiTrigger.EnterActions><BeginStoryboard><Storyboard><DoubleAnimation Storyboard.TargetName="scale" Storyboard.TargetProperty="ScaleX" To="0.9" Duration="0:0:0.1" /><DoubleAnimation Storyboard.TargetName="scale" Storyboard.TargetProperty="ScaleY" To="0.9" Duration="0:0:0.1" /></Storyboard></BeginStoryboard></MultiTrigger.EnterActions><MultiTrigger.ExitActions><BeginStoryboard><Storyboard><DoubleAnimation Storyboard.TargetName="scale" Storyboard.TargetProperty="ScaleX" To="1" Duration="0:0:0.1" /><DoubleAnimation Storyboard.TargetName="scale" Storyboard.TargetProperty="ScaleY" To="1" Duration="0:0:0.1" /></Storyboard></BeginStoryboard></MultiTrigger.ExitActions></MultiTrigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style>

扇形按钮后台代码如下:

internal class RDiskButtonsButtonContainerConverter : IMultiValueConverter {const int spanAngle = 6; // 间隔角度const double startAngle = -90; // 起始角度public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {if (values.Length >= 3&& values[0] is double width&& values[1] is double height&& values[2] is double strokeWidth&& values[3] is int index&& values[4] is int count&& values[5] is double radius) {if (count == 0 || index <= -1) return "";var angle = 360 / count;var center = new Point(width * 0.5, height * 0.5);width -= strokeWidth;height -= strokeWidth;double a1 = startAngle - angle * 0.5 + spanAngle * 0.5 + angle * index;double a2 = startAngle + angle * 0.5 - spanAngle * 0.5 + angle * index;var r1 = Math.Min(width, height) * 0.5;if (radius > r1) radius = 0;var p1 = a1.AngleToPoint(r1, center, 0 * 0.5);var p2 = a2.AngleToPoint(r1, center, 0 * 0.5);var p3 = a2.AngleToPoint(radius, center, 0 * 0.5);var p4 = a1.AngleToPoint(radius, center, 0 * 0.5);var dataStr = $"M {p1.X},{p1.Y} A {r1},{r1} 0 {(Math.Abs(a1 - a2) >= 180 ? 1 : 0)} 1 {p2.X},{p2.Y} L {p3.X},{p3.Y} A {radius},{radius} 0 {(Math.Abs(a1 - a2) >= 180 ? 1 : 0)} 0 {p4.X},{p4.Y} L {p1.X},{p1.Y} z";var converter = TypeDescriptor.GetConverter(typeof(Geometry));return (Geometry)converter.ConvertFrom(dataStr);} else {return "";}}public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {throw new NotImplementedException();}}internal class RDiskButtonRotateAngleConverter : IMultiValueConverter {public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {if (values.Length == 2 && values[0] is int index && values[1] is int count) {if (count == 0 || index <= -1) return 0;return index * 360 / count * 1.0d;} else {return 0;}}public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {throw new NotImplementedException();}}public partial class RDiskButton: Button {static RDiskButton() {DefaultStyleKeyProperty.OverrideMetadata(typeof(RDiskButton), new FrameworkPropertyMetadata(typeof(RDiskButton)));}public static readonly DependencyProperty IndexProperty =DependencyProperty.Register("Index", typeof(int), typeof(RDiskButton), new PropertyMetadata(0));public int Index {get => (int)GetValue(IndexProperty);set => SetValue(IndexProperty, value);}#region Stroke 描边颜色public static readonly DependencyProperty StrokeProperty =DependencyProperty.Register("Stroke", typeof(Brush), typeof(RDiskButton), new PropertyMetadata(Brushes.Red));public Brush Stroke {get { return (Brush)GetValue(StrokeProperty); }set { SetValue(StrokeProperty, value); }}#endregion#region StrokeThickness 描边大小public static readonly DependencyProperty StrokeThicknessProperty =DependencyProperty.Register("StrokeThickness", typeof(double), typeof(RDiskButton), new PropertyMetadata(1d));public double StrokeThickness {get => (double)GetValue(StrokeThicknessProperty);set => SetValue(StrokeThicknessProperty, value);}#endregion#region Fill 填充颜色public static readonly DependencyProperty FillProperty =DependencyProperty.Register("Fill", typeof(Brush), typeof(RDiskButton), new PropertyMetadata(Brushes.Red));public Brush Fill {get { return (Brush)GetValue(FillProperty); }set { SetValue(FillProperty, value); }}#endregion}

圆盘Panel控件(RDiskPanel)

RDiskPanel继承 ItemsControl

xaml 代码如下:

<ControlTemplate TargetType="local:RDiskPanel" x:Key="RDiskButtons_Template"><Grid IsItemsHost="True" /></ControlTemplate><Style TargetType="local:RDiskPanel"><Setter Property="Template" Value="{StaticResource RDiskButtons_Template}" /></Style>

后台代码如下:

public partial class RDiskPanel : ItemsControl {static RDiskPanel() {DefaultStyleKeyProperty.OverrideMetadata(typeof(RDiskPanel), new FrameworkPropertyMetadata(typeof(RDiskPanel)));}#region Radius 内径大小public static readonly DependencyProperty RadiusProperty =DependencyProperty.Register("Radius", typeof(double), typeof(RDiskPanel), new PropertyMetadata(0d));public double Radius {get => (double)GetValue(RadiusProperty);set => SetValue(RadiusProperty, value);}#endregion}

以上就是控件全部的源码。有问题欢迎在评论去交流

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。