介绍
本文介绍了一个具有可配置属性的自定义旋转拨号控件,包括标签、主要刻度和主要刻度增量。它包括一个简单的演示应用程序,其中包含使用中的旋转拨号控制示例:
前三个表盘使用圆形位置指示器。
左上角的刻度盘具有从 0 到 1 的连续值范围,每 0.2 个单位有一个主刻度,并带有黑色标签。表盘宽 150 个单位。
顶部中央表盘具有从 0 到 100 的整数值,每 20 个单位有一个主要刻度和黑色标签。表盘宽 200 个单位。
右上角的表盘具有从 0 到 50 的整数值,每 5 个单位有一个主要刻度,并带有黑色标签。表盘宽 100 个单位。
底部四个表盘使用指针指针,并有彩色弧线。
左下方的拨盘由两个拨盘控件构成。顶部表盘具有从 0 到 80 的连续值范围,每 20 个单位有一个主要刻度,并带有黑色标签。底部刻度盘具有从 0 到 1.0 的连续值范围,每 0.1 个单位有一个主刻度和黑色标签。
底部中央左右刻度盘具有从 0 到 100 的连续值范围,每 10 个单位有一个主要刻度和白色标签。每个表盘的宽度为 200 个单位。左侧中央刻度盘在刻度线外有标签,而右侧中央刻度盘在刻度线内有标签。
右下角的表盘跨越一个弧形而不是一个完整的圆圈。
背景
您将需要了解 C# 和 WPF 的基础知识。
设计转盘
我的设计目标是让旋转表盘尽可能地可定制,而又不会过于复杂。为了实现这一目标,旋转控件由一系列同心圆或刻度盘构成,其中一些是虚构的,用于布局标签和刻度线等组件。
标签放置在虚拟标签刻度盘的边缘,其半径由LabelDialRadius
依赖属性设置。字体大小由FontSize
依赖属性设置,字体颜色由Foreground
依赖属性设置。
主要刻度放置在虚构的主要标签刻度盘的边缘,其半径由MajorTickDialRadius
依赖属性设置。每个主要刻度的长度由MajorTickLength
依赖属性设置。
小刻度放置在虚构的小标签刻度盘的边缘,其半径由MinorTickDialRadius
依赖属性设置。每个次要刻度的长度由MinorTickLength
依赖属性设置。
表盘上第一个值的角度位置由StartAngleInDegrees
依赖属性设置。
刻度盘上最后一个值的角度位置由EndAngleInDegrees
依赖属性设置。
这两个角度都相对于 12 点钟位置,并且是顺时针方向。
有两种方法可以将彩色段或完整的圆圈添加到旋转控件。
添加彩色段的第一种方法是使用 Segments 依赖属性。这包含一个对象数组RotaryControlSegment
。每个都定义了一个具有相关颜色和角度的段。这些段是连续的,并从第一个值开始。所有线段都具有相同的外半径(由 设置SegmentRadius
)和相同的厚度(由 设置)SegmentRadius
。
添加彩色段的第二种更灵活的方法是使用Arcs
依赖属性。每条圆弧都有自己的半径、起始角度、圆弧角度和厚度。
有一个内表盘可以用来代表一个旋钮。它的半径由InnerDialRadius
依赖属性设置,颜色由InnerDialFill
依赖属性设置。
指针样式由PointerType
依赖属性设置。支持的值包括表示旋钮上的位置指示器的圆形指针和表示指针指针的箭头指针。对于所有的指针,除了圆形之外,可以设置长度、宽度和填充。
许多属性都有合理的默认值,不需要显式定义。
使用代码
创建一个带有中央旋钮和位置指示器的简单表盘很容易,如下所示:
以上是使用以下 XAML 代码创建的:
<view:RotaryControl Grid.Row="0" Grid.Column="3"
x:Name="_dialTemperature" Value="{Binding Temperature,
Mode=TwoWay}" FontBrush="Black" FontSize="20"
Foreground="Black" Background="Transparent">
<view:RotaryControl.MinimumValue>20</view:RotaryControl.MinimumValue>
<view:RotaryControl.NumberOfMajorTicks>9</view:RotaryControl.NumberOfMajorTicks>
<view:RotaryControl.MajorTickIncrement>10</view:RotaryControl.MajorTickIncrement>
<view:RotaryControl.MajorTickBrush>White</view:RotaryControl.MajorTickBrush>
<view:RotaryControl.NumberOfMinorTicks>4</view:RotaryControl.NumberOfMinorTicks>
<view:RotaryControl.MinorTickBrush>White</view:RotaryControl.MinorTickBrush>
<view:RotaryControl.OuterDialFill>SteelBlue</view:RotaryControl.OuterDialFill>
<view:RotaryControl.OuterDialBorder>Transparent</view:RotaryControl.OuterDialBorder>
<view:RotaryControl.OuterDialBorderThickness>1</view:RotaryControl.OuterDialBorderThickness>
<view:RotaryControl.InnerDialRadius>60</view:RotaryControl.InnerDialRadius>
<view:RotaryControl.PointerFill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="#DDDDDD" Offset="0"/>
<GradientStop Color="#BBBBBB" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.PointerFill>
</view:RotaryControl>
上面定义了一个带有灰色中央控制的旋转表盘,石板蓝色背景上的 9 个主要刻度,主要刻度增量为 10,标签的黑色 20 点字体和透明背景。Value
绑定到视图模型中的属性Temperature
。
默认情况下,控件的宽度为 200 个单位。
要调整控件的大小,请使用LayoutTransform
:
<view:RotaryControl.LayoutTransform>
<ScaleTransform ScaleX="2" ScaleY="2"/>
</view:RotaryControl.LayoutTransform>
当您调整控件大小时,您当然必须相应地调整字体大小。使用布局转换而不是在代码中实现缩放的优点是缩放是统一的,并且需要的后台代码要少得多。
要创建具有整数值的刻度盘,请将Value
依赖属性绑定到视图模型中的整数/长属性。对于连续值,绑定Value
到double
视图模型中的属性。
如果您喜欢使用指针指针创建控件,则可以如下所示:
以上是使用以下 XAML 代码创建的:
<view:RotaryControl Grid.Row="1" Grid.Column="5"
FontBrush="White" FontSize="10"
Foreground="Black" Background="Transparent" >
<view:RotaryControl.PointerFill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="#DDDDDD" Offset="0"/>
<GradientStop Color="#AAAAAA" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.PointerFill>
<view:RotaryControl.OuterDialFill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Gray" Offset="0.5"/>
<GradientStop Color="Black" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.OuterDialFill>
<view:RotaryControl.OuterDialBorder>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="Gray" Offset="0"/>
<GradientStop Color="White" Offset="0.5"/>
<GradientStop Color="Gray" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.OuterDialBorder>
<view:RotaryControl.OuterDialBorderThickness>3</view:RotaryControl.OuterDialBorderThickness>
<view:RotaryControl.InnerDialRadius>0</view:RotaryControl.InnerDialRadius>
<view:RotaryControl.InnerDialFill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="White" Offset="0.5"/>
<GradientStop Color="White" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.InnerDialFill>
<view:RotaryControl.LabelDialRadius>48</view:RotaryControl.LabelDialRadius>
<view:RotaryControl.MajorTickDialRadius>65.5</view:RotaryControl.MajorTickDialRadius>
<view:RotaryControl.MajorTickLength>6</view:RotaryControl.MajorTickLength>
<view:RotaryControl.NumberOfMajorTicks>11</view:RotaryControl.NumberOfMajorTicks>
<view:RotaryControl.MajorTickIncrement>10</view:RotaryControl.MajorTickIncrement>
<view:RotaryControl.MajorTickBrush>White</view:RotaryControl.MajorTickBrush>
<view:RotaryControl.NumberOfMinorTicks>4</view:RotaryControl.NumberOfMinorTicks>
<view:RotaryControl.MinorTickBrush>White</view:RotaryControl.MinorTickBrush>
<view:RotaryControl.StartAngleInDegrees>210</view:RotaryControl.StartAngleInDegrees>
<view:RotaryControl.EndAngleInDegrees>150</view:RotaryControl.EndAngleInDegrees>
<view:RotaryControl.PointerAxleFill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="Gray" Offset="0"/>
<GradientStop Color="White" Offset="0.5"/>
<GradientStop Color="Gray" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.PointerAxleFill>
<view:RotaryControl.PointerLength>45</view:RotaryControl.PointerLength>
<view:RotaryControl.PointerWidth>2</view:RotaryControl.PointerWidth>
<view:RotaryControl.PointerType>standard</view:RotaryControl.PointerType>
<view:RotaryControl.SegmentThickness>5</view:RotaryControl.SegmentThickness>
<view:RotaryControl.SegmentRadius>35</view:RotaryControl.SegmentRadius>
<view:RotaryControl.Segments>
<x:Array Type="{x:Type view:RotaryControlSegment}" >
<view:RotaryControlSegment Fill="YellowGreen" AngleInDegrees="210"/>
<view:RotaryControlSegment Fill="Gold" AngleInDegrees="30"/>
<view:RotaryControlSegment Fill="Orange" AngleInDegrees="30"/>
<view:RotaryControlSegment Fill="Crimson" AngleInDegrees="30"/>
</x:Array>
</view:RotaryControl.Segments>
</view:RotaryControl>
上面虽然比较啰嗦,但是应该比较容易理解。长度是由于允许高度定制的大量属性。彩色段由段依赖属性定义。这是一个包含四个对象的数组,RotaryControlSegment
每个对象定义一个彩色段。这些段共享相同的半径和厚度。
您还可以创建一个对着圆弧的刻度盘,如下所示:
以上是使用以下 XAML 代码创建的:
<view:RotaryControl Grid.Row="1" Grid.Column="7" FontBrush="Black"
FontSize="12" Foreground="Black" Background="Transparent"
Value="{Binding Pressure, Mode=TwoWay}" >
<view:RotaryControl.PointerType>rectangle</view:RotaryControl.PointerType>
<view:RotaryControl.PointerFill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#AAAAAA" Offset="1.0"/>
</LinearGradientBrush>
</view:RotaryControl.PointerFill>
<view:RotaryControl.PointerLength>50</view:RotaryControl.PointerLength>
<view:RotaryControl.PointerWidth>3</view:RotaryControl.PointerWidth>
<view:RotaryControl.PointerAxleFill>Black</view:RotaryControl.PointerAxleFill>
<view:RotaryControl.PointerAxleRadius>4</view:RotaryControl.PointerAxleRadius>
<view:RotaryControl.OuterDialFill>Transparent</view:RotaryControl.OuterDialFill>
<view:RotaryControl.OuterDialBorderThickness>0
</view:RotaryControl.OuterDialBorderThickness>
<view:RotaryControl.InnerDialRadius>10</view:RotaryControl.InnerDialRadius>
<view:RotaryControl.InnerDialFill>White</view:RotaryControl.InnerDialFill>
<view:RotaryControl.LabelDialRadius>77</view:RotaryControl.LabelDialRadius>
<view:RotaryControl.MinimumValue>0</view:RotaryControl.MinimumValue>
<view:RotaryControl.StartAngleInDegrees>210
</view:RotaryControl.StartAngleInDegrees>
<view:RotaryControl.EndAngleInDegrees>330</view:RotaryControl.EndAngleInDegrees>
<view:RotaryControl.MajorTickDialRadius>61
</view:RotaryControl.MajorTickDialRadius>
<view:RotaryControl.MajorTickLength>8</view:RotaryControl.MajorTickLength>
<view:RotaryControl.MajorTickWidth>1</view:RotaryControl.MajorTickWidth>
<view:RotaryControl.NumberOfMajorTicks>6</view:RotaryControl.NumberOfMajorTicks>
<view:RotaryControl.MajorTickIncrement>1</view:RotaryControl.MajorTickIncrement>
<view:RotaryControl.MajorTickBrush>Black</view:RotaryControl.MajorTickBrush>
<view:RotaryControl.MinorTickDialRadius>55
</view:RotaryControl.MinorTickDialRadius>
<view:RotaryControl.MinorTickLength>2</view:RotaryControl.MinorTickLength>
<view:RotaryControl.NumberOfMinorTicks>2</view:RotaryControl.NumberOfMinorTicks>
<view:RotaryControl.MinorTickBrush>Black</view:RotaryControl.MinorTickBrush>
<view:RotaryControl.SegmentThickness>15</view:RotaryControl.SegmentThickness>
<view:RotaryControl.SegmentRadius>67</view:RotaryControl.SegmentRadius>
<view:RotaryControl.Segments>
<x:Array Type="{x:Type view:RotaryControlSegment}" >
<view:RotaryControlSegment Fill="YellowGreen" AngleInDegrees="60"/>
<view:RotaryControlSegment Fill="Gold" AngleInDegrees="30"/>
<view:RotaryControlSegment Fill="Orange" AngleInDegrees="20"/>
<view:RotaryControlSegment Fill="Crimson" AngleInDegrees="10"/>
<view:RotaryControlSegment Fill="White" AngleInDegrees="10"/>
</x:Array>
</view:RotaryControl.Segments>
<view:RotaryControl.Arcs>
<x:Array Type="{x:Type view:RotaryControlArc}" >
<view:RotaryControlArc Fill="Black" StartAngleInDegrees="180"
AngleInDegrees="180" Radius="6" Thickness="1"
Stroke="Black" StrokeThickness="0"/>
<view:RotaryControlArc Fill="Black" StartAngleInDegrees="0"
AngleInDegrees="180" Radius="6" Thickness="1"
Stroke="Black" StrokeThickness="0"/>
<view:RotaryControlArc Fill="White" StartAngleInDegrees="200"
AngleInDegrees="10" Radius="67"
Thickness="35" StrokeThickness="0"/>
<view:RotaryControlArc Fill="White" StartAngleInDegrees="200"
AngleInDegrees="140" Radius="90" Thickness="23"
Stroke="Black" StrokeThickness="0"/>
<view:RotaryControlArc Fill="White" StartAngleInDegrees="200"
AngleInDegrees="140" Radius="52" Thickness="42"
Stroke="Black" StrokeThickness="0"/>
</x:Array>
</view:RotaryControl.Arcs>
</view:RotaryControl>
代码
表盘被实现为 WPF UserControl
:
<UserControl x:Class="WpfRotaryControlDemo.View.RotaryControl"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<LinearGradientBrush x:Key="InnerDialFillResource">
<LinearGradientBrush.StartPoint>0.5,1.0</LinearGradientBrush.StartPoint>
<LinearGradientBrush.EndPoint>0.5,0.0</LinearGradientBrush.EndPoint>
<GradientStop Color="#BBBBBB" Offset="0"/>
<GradientStop Color="#DDDDDD" Offset="1.0"/>
</LinearGradientBrush>
</ResourceDictionary>
</UserControl.Resources>
<Grid Name="_grid" Width="200"
Height="200" Background="Transparent">
<Ellipse x:Name="_ellipseOuterDial" Width="150"
Height="150" Stroke="Gainsboro"
StrokeThickness="4" Fill="SteelBlue" />
<Ellipse x:Name="_ellipseInnerDial"
Width="100" Height="100" Panel.ZIndex="98"/>
<Ellipse Name="_pointerCircle" Width="20"
Height="20" Stroke="Gainsboro" StrokeThickness="0" Panel.ZIndex="99">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="_markerTranslation" X="35" Y="0"/>
</TransformGroup>
</Ellipse.RenderTransform>
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="Red" Offset="0"/>
<GradientStop Color="DarkRed" Offset="1.0"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Path Name="_pointerStandard" Stroke="Red"
StrokeThickness="0" Fill="Red" Panel.ZIndex="100">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="100,100">
<LineSegment Point="100,98" x:Name="_pointerTopLeft"/>
<LineSegment Point="140,98" x:Name="_pointerTopRight"/>
<LineSegment Point="150,100" x:Name="_pointerTip"/>
<LineSegment Point="140,102" x:Name="_pointerBottomRight"/>
<LineSegment Point="100,102" x:Name="_pointerBottomLeft"/>
<LineSegment Point="100,100"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Name="_pointerArrow" Stroke="Red"
StrokeThickness="0" Fill="Red" Panel.ZIndex="100">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="100,100">
<LineSegment Point="100,98" x:Name="_pointerArrowTopLeft"/>
<LineSegment Point="150,100" x:Name="_pointerArrowTip"/>
<LineSegment Point="100,102" x:Name="_pointerArrowBottomLeft"/>
<LineSegment Point="100,100"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Name="_pointerRectangle" Stroke="Red"
StrokeThickness="0" Fill="Red" Panel.ZIndex="100">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="100,100">
<LineSegment Point="100,98" x:Name="_pointerRectangleTopLeft"/>
<LineSegment Point="150,98" x:Name="_pointerRectangleTopRight"/>
<LineSegment Point="150,102" x:Name="_pointerRectangleBottomRight"/>
<LineSegment Point="100,102" x:Name="_pointerRectangleBottomLeft"/>
<LineSegment Point="100,100"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Name="_pointerAxle" Stroke="Black"
StrokeThickness="0" Fill="Black" Panel.ZIndex="101">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="100,97" x:Name="_pointerPathFigure">
<ArcSegment Point="100,103" Size="3,3"
SweepDirection="Clockwise" IsLargeArc="True"
x:Name="_pointerAxleArc1"/>
<ArcSegment Point="100,97" Size="3,3"
SweepDirection="Clockwise" IsLargeArc="True"
x:Name="_pointerAxleArc2"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</UserControl>
上面的 XAML 从控制轮廓的外圆、旋钮的内圆和指针的各种形状创建基本的旋转控件。
刻度线和标签是在CreateControl
从构造函数调用的方法中创建的。据我所知,这不能在 XAML 中完成。每个刻度线都是用 来创建的Polyline
,每个注释都是用 来创建的Label
。
该控件有大量的依赖属性:
Value | 当前读数。如果要绑定到此值:
复制代码
请注意,Mode 设置为 TwoWay,因为默认情况下它是 OneWayToSource。 |
MinimumValue | 最小允许值 |
FontBrush | 用于在标签表盘周围绘制数字的刷子 |
StartAngleInDegrees | 第一个主要刻度相对于 12 点钟位置的角度 |
EndAngleInDegrees | 最后一个主要刻度相对于 12 点钟位置的角度 |
MajorTickDialRadius | 主刻度盘的半径 |
MajorTickLength | 每个主要刻度的长度 |
MajorTickWidth | 每个主要刻度的宽度 |
NumberOfMajorTicks | 主要刻度的数量(不包括零) |
MajorTickIncrement | 相邻主要刻度之间的数值增量 |
MajorTickBrush | 用于绘制主要刻度的画笔 |
MinorTickDialRadius | 小刻度盘的半径 |
MinorTickLength | 每个小刻度的长度 |
NumberOfMinorTicks | 每个主要刻度增量的次要刻度数 |
MinorTickBrush | 用于绘制小刻度的画笔 |
InnerDialRadius | 内表盘的半径。内表盘可用于绘制带有圆形位置指示器的旋钮。 |
InnerDialFill | 用于填充内表盘的刷子 |
OuterDialFill | 用于填充外表盘的刷子。外部表盘包含其他表盘、标签、刻度和指针。 |
OuterDialBorder | 用于填充外部表盘边框的画笔。 |
OuterDialBorderThickness | 表盘外边框的厚度。 |
SegmentThickness | 彩色段的宽度。 |
SegmentRadius | 段的半径。 |
Segments | 共享相同半径和厚度的连续彩色段的可选数组。 |
Arcs | 一个可选的彩色线段数组,每个线段都有自己的起始角度、弧角、半径和厚度。 |
PointerType | 指针的类型。允许值如下:
|
默认情况下,控件的宽度为 200 个单位。这是在 XAML 和相关代码中设置的。
注释
创建一个复合拨号控件并不难,在圆形边框内有两个或更多仪表。演示应用程序包含一个带有两个半圆形仪表的示例。
请注意,控件是矩形的,不可见区域会吸收鼠标和键盘事件。这可能会给半圆形表盘带来问题。解决方案是确保它们之间的良好分离。
假设您具有 C# 和 WPF 的基本知识,那么更改表盘的外观以适应您自己的需求非常简单。