New UWP Community Toolkit - RadialGauge
概述
New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解 RadialGauge 的实现。
RadialGauge 是一种径向仪表盘控件,使用圆盘面上的指针来显示一定范围的值,这种显示和交互方式,让数据可视化的表现力和吸引力都有很大提高。在实际应用中也有很广泛的使用,如时钟显示,数据展示,仪表盘模拟等等。我们来看一下官方的介绍和官网示例中的展示:

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/radialgauge
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
开发过程
代码分析
先来看看 RadialGauge 的结构组成:
- RadialGauge.cs - RadialGauge 的控件定义和事件处理类
- RadialGauge.xaml - RadialGauge 的样式文件

1. RadialGauge.xaml
RadialGauge 控件的样式文件,结合上面官方示例的显示图,我们看 Template 部分;主要由以下几个部分组成:
- PART_Container - 底层容器,包含了下面三个控件部分
- PART_Scale - 比例尺控件
- PART_Trail - 仪表盘实际值显示控件
- Value and Unit - 实际值文本和单位显示控件
<Style TargetType="local:RadialGauge">
<Setter Property="UseSystemFocusVisuals" Value="True"></Setter>
<Setter Property="Foreground" Value="{ThemeResource RadialGaugeForegroundBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:RadialGauge">
<Viewbox>
<Grid x:Name="PART_Container"
Width="200"
Height="200"
Background="Transparent">
<!-- Scale -->
<Path Name="PART_Scale"
Stroke="{TemplateBinding ScaleBrush}"
StrokeThickness="{TemplateBinding ScaleWidth}" />
<!-- Trail -->
<Path Name="PART_Trail"
Stroke="{TemplateBinding TrailBrush}"
StrokeThickness="{TemplateBinding ScaleWidth}" />
<!-- Value and Unit -->
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<TextBlock Name="PART_ValueText"
Margin="0,0,0,2"
FontSize="20"
FontWeight="SemiBold"
Foreground="{TemplateBinding Foreground}"
Text="{TemplateBinding Value}"
TextAlignment="Center" />
<TextBlock Margin="0"
FontSize="16"
Foreground="{ThemeResource RadialGaugeAccentBrush}"
Text="{TemplateBinding Unit}"
TextAlignment="Center" />
</StackPanel>
</Grid>
</Viewbox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
2. RadialGauge.cs
我们先看看 RadialGauge 类的组成:

从上面第一张图中,我们可以看到 RadialGauge 注册了很多依赖属性,不一一列举了,大致分为几个类型:取值和角度属性,显示画刷属性,单位相关属性;属性也对应了修改时的回调事件,下面我们找出几个重点的事件处理方法来讲解:
① OnValueChanged(d)
在数值变化后,触发 OnValueChanged(d) 事件的方法;首先根据设置的取舍值,矫正当前的 Value,计算出对应的角度;给仪表盘的指针赋值,让指针指向当前角度;然后是给显示当前值区间的弧形赋值,如果当前角度值为 360,则整个填充仪表盘,否则根据角度计算出填充的区域,给 ArcSegment,PathFigure,PathGeometry 赋值;最后给仪表盘的数值文本控件赋值;
OnScaleChanged(d) 在刻度修改时触发,本质上讲,数值修改和刻度修改是相通的,所以处理方式也类似,这里不做赘述;
private static void OnValueChanged(DependencyObject d)
{
RadialGauge radialGauge = (RadialGauge)d;
if (!double.IsNaN(radialGauge.Value))
{
)
{
radialGauge.Value = radialGauge.RoundToMultiple(radialGauge.Value, radialGauge.StepSize);
}
- radialGauge.ScalePadding - (radialGauge.ScaleWidth / );
var valueText = radialGauge.GetTemplateChild(ValueTextPartName) as TextBlock;
radialGauge.ValueAngle = radialGauge.ValueToAngle(radialGauge.Value);
// Needle
if (radialGauge._needle != null)
{
radialGauge._needle.RotationAngleInDegrees = (float)radialGauge.ValueAngle;
}
// Trail
var trail = radialGauge.GetTemplateChild(TrailPartName) as Path;
if (trail != null)
{
if (radialGauge.ValueAngle > radialGauge.NormalizedMinAngle)
{
trail.Visibility = Visibility.Visible;
)
{
// Draw full circle.
var eg = new EllipseGeometry();
eg.Center = , );
eg.RadiusX = - radialGauge.ScalePadding - (radialGauge.ScaleWidth / );
eg.RadiusY = eg.RadiusX;
trail.Data = eg;
}
else
{
// Draw arc.
var pg = new PathGeometry();
var pf = new PathFigure();
pf.IsClosed = false;
pf.StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale);
var seg = new ArcSegment();
seg.SweepDirection = SweepDirection.Clockwise;
seg.IsLargeArc = radialGauge.ValueAngle > ( + radialGauge.NormalizedMinAngle);
seg.Size = new Size(middleOfScale, middleOfScale);
seg.Point = radialGauge.ScalePoint(Math.Min(radialGauge.ValueAngle, radialGauge.NormalizedMaxAngle), middleOfScale); // On overflow, stop trail at MaxAngle.
pf.Segments.Add(seg);
pg.Figures.Add(pf);
trail.Data = pg;
}
}
else
{
trail.Visibility = Visibility.Collapsed;
}
}
// Value Text
if (valueText != null)
{
valueText.Text = radialGauge.Value.ToString(radialGauge.ValueStringFormat);
}
}
}
② OnFaceChanged(d)
任何外观有变化,或刻度值有变化时就会触发,控件整体的 UI 重绘;首先是 Ticks 重绘,然后是 Scale 重绘,后面是 Needle 的重绘,可以看到三种重绘的实现都很类似;最后是执行处理数值变化的方法;
private static void OnFaceChanged(DependencyObject d)
{
RadialGauge radialGauge = (RadialGauge)d;
var container = radialGauge.GetTemplateChild(ContainerPartName) as Grid;
if (container == null || DesignTimeHelpers.IsRunningInLegacyDesignerMode)
{
// Bad template.
return;
}
radialGauge._root = container.GetVisual();
radialGauge._root.Children.RemoveAll();
radialGauge._compositor = radialGauge._root.Compositor;
// Ticks.
SpriteVisual tick;
for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing)
{
tick = radialGauge._compositor.CreateSpriteVisual();
tick.Size = new Vector2((float)radialGauge.TickWidth, (float)radialGauge.TickLength);
tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.TickBrush.Color);
tick.Offset = - ((), );
tick.CenterPoint = , );
tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i);
radialGauge._root.Children.InsertAtTop(tick);
}
// Scale Ticks.
for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing)
{
tick = radialGauge._compositor.CreateSpriteVisual();
tick.Size = new Vector2((float)radialGauge.ScaleTickWidth, (float)radialGauge.ScaleWidth);
tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.ScaleTickBrush.Color);
tick.Offset = - ((), ();
tick.CenterPoint = , - ();
tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i);
radialGauge._root.Children.InsertAtTop(tick);
}
// Needle.
radialGauge._needle = radialGauge._compositor.CreateSpriteVisual();
radialGauge._needle.Size = new Vector2((float)radialGauge.NeedleWidth, (float)radialGauge.NeedleLength);
radialGauge._needle.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.NeedleBrush.Color);
radialGauge._needle.CenterPoint = , ();
radialGauge._needle.Offset = - ((), - ();
radialGauge._root.Children.InsertAtTop(radialGauge._needle);
OnValueChanged(radialGauge);
}
下面来看一下 RadialGauge 的鼠标点击和触摸手势交互事件处理方法,主要处理逻辑在 SetGaugeValueFromPoint(point) 方法中:
首先计算出当前点击或触摸点相对比仪表盘圆心的坐标,根据坐标计算出角度;再根据最大角度和最小角度的值,计算出可变化的实际区间;最后用当前角度与最小角度的差值,与实际区间做一个比例换算,得到当前角度对应在仪表盘里的数值;
private void SetGaugeValueFromPoint(Point p)
{
), -p.Y + (ActualHeight / ));
var angle = Math.Atan2(pt.X, pt.Y) / Degrees2Radians;
);
)
{
divider = ;
}
) / divider);
if (value < Minimum || value > Maximum)
{
// Ignore positions outside the scale angle.
return;
}
Value = value;
}
另外,RadialGauge 控件还支持键盘快捷键操作,当按下 Ctrl 键时,数值变化的幅度是正常变化的 5 倍;而当按下 Left 或 Right 键时,数值会变为最小值或最大值。
调用示例
我们给 RadialGauge 控件设置的范围是 0~180,当前值是 116;最小角度是 210,最大角度是 150;以及每个部分的颜色设置,可以从示例运行图中看出:
<controls:RadialGauge
x:Name="RadialGauge"
Grid.Column="1"
Value="116"
Minimum="0"
Maximum="180"
StepSize="1"
IsInteractive="True"
TickSpacing="18"
ScaleWidth="8"
MinAngle="210"
MaxAngle="150"
Unit="Units"
TickBrush="LightGreen"
ScaleTickBrush="LightBlue"
ValueBrush="ForestGreen"
NeedleBrush="ForestGreen"
NeedleWidth="5"
TickLength="18" />

总结
到这里我们就把 UWP Community Toolkit 中的 RadialGauge 控件的源代码实现过程和简单的调用示例讲解完成了,希望能对大家更好的理解和使用这个控件有所帮助。欢迎大家多多交流,谢谢!
最后,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通过微博关注最新动态。
衷心感谢 UWPCommunityToolkit 的作者们杰出的工作,Thank you so much, UWPCommunityToolkit authors!!!
New UWP Community Toolkit - RadialGauge的更多相关文章
- New UWP Community Toolkit
概述 UWP Community Toolkit 是一个 UWP App 自定义控件.应用服务和帮助方法的集合,能够很大程度的简化和指引开发者的开发工作,相信广大 UWPer 并不陌生. 下面是截取自 ...
- New UWP Community Toolkit - XAML Brushes
概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...
- New UWP Community Toolkit - Markdown
概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 MarkdownTextBlock 和 MarkdownDoc ...
- New UWP Community Toolkit - Staggered panel
概述 前面 New UWP Community Toolkit 文章中,我们对 2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解 St ...
- New UWP Community Toolkit - Carousel
概述 New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 Carousel 的调整,本篇我们结合代码详细讲解 Carousel 的实现. Carousel 是 ...
- New UWP Community Toolkit - RadialProgressBar
概述 UWP Community Toolkit 中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解 RadialProgressBar 的实现. Radi ...
- New UWP Community Toolkit - RangeSelector
概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 Ra ...
- New UWP Community Toolkit - ImageEx
概述 UWP Community Toolkit 中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解 ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...
- New UWP Community Toolkit - AdaptiveGridView
概述 UWP Community Toolkit 中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解 AdaptiveGridView 的实现 ...
随机推荐
- Dictionary排序
有时候由于某些要求会对Dictionary排序,一般有两种方法. 1.使用SortedDictionary. 这种自动会对保存的值进行排序. static void Main(string[] arg ...
- intra调用order
response = orderMessageService.apply(mdpRequest[A1] ); public interface OrderMessageService[A2] { pu ...
- Python基础__字符串拼接、格式化输出与复制
上一节介绍了序列的一些基本操作类型,这一节针对字符串的拼接.格式化输出以及复制的等做做详细介绍.一. 字符串的拼接 a = 'I', b = 'love', c = 'Python'. 我们的目的是: ...
- Mac上使用虚拟机搭建Hadoop集群
一. mini安装一台centos到虚拟机上 安装过程参考这篇博客http://www.linuxdown.net/install/setup/2015/0906/4053.html 二. 修改网络配 ...
- 【BZOJ3992】序列统计(动态规划,NTT)
[BZOJ3992]序列统计(动态规划,NTT) 题面 BZOJ 题解 最裸的暴力 设\(f[i][j]\)表示前\(i\)个数,积在膜意义下是\(j\)的方案数 转移的话,每次枚举一个数,直接丢进去 ...
- CDQ 分治算法模板
CDQ分治 1.三维偏序问题:三维偏序(陌上花开) #include<bits/stdc++.h> #define RG register #define IL inline #defin ...
- 【learning】二分图最大匹配的König定理
[吐槽] 嗯好吧这个东西吧..其实是一开始做一道最小点覆盖的题的时候学到的奇妙深刻的东西 然后发现写了很长 然后就觉得不拎出来对不起自己呀哈哈哈哈 咳咳好的进入正题 [正题] 在这里码一下最小点覆盖的 ...
- TypeScript入门知识一(字符串特性)
一,TypeScript多行字符串 传统JavaScript字符串换行需要+进行拼接,而TypeScript不需要+拼接.看下面实例: javaScript: var content = " ...
- 进入PE后不显示硬盘的解决办法
其实我很早之前就知道这个方法了,我虽然不知道原因,不过我是一个一个试出来的,转过来备忘, 内容介绍:经常使用PE的朋友相信都遇到过这样的问题,一些新购买的电脑可以正常把PE系统安装到U盘中,也可以正常 ...
- 设计模式——原型模式(C++实现)
#include <iostream> #include <string> using namespace std; class CPrototype { public: CP ...