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 的实现 ...
随机推荐
- 一个简单的freemark输入输出的案例(二)
freemarker入门实例 1.设计思路 (1)新建Maven Project (2)生成freemarker模板 (3)写freemarker页面ftl文件 (4)写测试文件 2.新建Maven ...
- java io 节点流和处理流
JAVA IO操作总结:节点流和处理流 JAVA IO操作总结--节点流和处理流 按照流是否直接与特定的地方(如磁盘.内存.设备等)相连,分为节点流和处理流两类. 节点流:可以从或向一个特定的地方( ...
- 【BZOJ3172】单词(AC自动机)
[BZOJ3172]单词(AC自动机) 题面 Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input ...
- [AH/HNOI2017]单旋
这道题可以用LCT做,开set,LCT,二叉树 操作1:直接开set,找到它要插入的位置,一定是前驱,后缀中deep最大的(显然手玩) 操作2:set+LCT询问路径,直接手动提上去,因为树的形态不变 ...
- [BZOJ1880] [Sdoi2009] Elaxia的路线 (SPFA & 拓扑排序)
Description 最近,Elaxia和w**的关系特别好,他们很想整天在一起,但是大学的学习太紧张了,他们 必须合理地安排两个人在一起的时间.Elaxia和w**每天都要奔波于宿舍和实验室之间, ...
- 【noip模拟】Fantasia
Time Litmit: 1000ms Memory Limit: 256MB Description 给定一张 $N$ 个点.$M$ 条边的无向图 $G$ .每个点有个权值$W_i$. 我 ...
- OpenStack Paste.ini详解(二)
接着OpenStack Paste.ini详解(一),接下来就分析request被paste.ini处理的流程 WSGI server接收到URL形式的request时,这些request首先会被Pa ...
- ETL总结(扫盲版)
1.ETL名词解释 英文缩写 Extract-Transform-Load ,用来描述将数据从来源端经过抽取(extract).转换(transform).加载(load)至到目的端(一般指的是数 ...
- 在做APP前端开发时应注意的一些问题
在做APP前端开发时应注意的一些问题 在整个app开发流程中,app前端开发是一个必不可少的环节,也是一个在app开发过程中重量级的角色.说到这,那么在app应用的前端开发中,又要注意什么问题呢?一. ...
- Shiro入门这篇就够了【Shiro的基础知识、回顾URL拦截】
前言 本文主要讲解的知识点有以下: 权限管理的基础知识 模型 粗粒度和细粒度的概念 回顾URL拦截的实现 Shiro的介绍与简单入门 一.Shiro基础知识 在学习Shiro这个框架之前,首先我们要先 ...