New UWP Community Toolkit 6.0 - RadialGauge

网友投稿 509 2022-05-28

概述

New UWP Community Toolkit  V6.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解  RadialGauge 的实现。

RadialGauge 是一种径向仪表盘控件,使用圆盘面上的指针来显示一定范围的值,这种显示和交互方式,让数据可视化的表现力和吸引力都有很大提高。在实际应用中也有很广泛的使用,如时钟显示,数据展示,仪表盘模拟等等。我们来看一下官方的介绍和官网示例中的展示:

Source: https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/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 - 实际值文本和单位显示控件

                                                                                                                                                                                                                                                                                                                                                                                    

2. RadialGauge.cs

我们先看看 RadialGauge 类的组成:

从上面第一张图中,我们可以看到 RadialGauge 注册了很多依赖属性,不一一列举了,大致分为几个类型:取值和角度属性,显示画刷属性,单位相关属性;属性也对应了修改时的回调事件,下面我们找出几个重点的事件处理方法来讲解:

① OnValueChanged(d)

New UWP Community Toolkit 6.0 - RadialGauge

在数值变化后,触发 OnValueChanged(d) 事件的方法;首先根据设置的取舍值,矫正当前的 Value,计算出对应的角度;给仪表盘的指针赋值,让指针指向当前角度;然后是给显示当前值区间的弧形赋值,如果当前角度值为 360,则整个填充仪表盘,否则根据角度计算出填充的区域,给 ArcSegment,PathFigure,PathGeometry 赋值;最后给仪表盘的数值文本控件赋值;

OnScaleChanged(d) 在刻度修改时触发,本质上讲,数值修改和刻度修改是相通的,所以处理方式也类似,这里不做赘述;

private static void OnValueChanged(DependencyObject d) {     RadialGauge radialGauge = (RadialGauge)d;    if (!double.IsNaN(radialGauge.Value))     {        if (radialGauge.StepSize != 0)         {             radialGauge.Value = radialGauge.RoundToMultiple(radialGauge.Value, radialGauge.StepSize);         }        var middleOfScale = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2);        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;                if (radialGauge.ValueAngle - radialGauge.NormalizedMinAngle == 360)                 {                    // Draw full circle.                     var eg = new EllipseGeometry();                     eg.Center = new Point(100, 100);                     eg.RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2);                     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 > (180 + 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 = new Vector3(100 - ((float)radialGauge.TickWidth / 2), 0.0f, 0);         tick.CenterPoint = new Vector3((float)radialGauge.TickWidth / 2, 100.0f, 0);         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 = new Vector3(100 - ((float)radialGauge.ScaleTickWidth / 2), (float)radialGauge.ScalePadding, 0);         tick.CenterPoint = new Vector3((float)radialGauge.ScaleTickWidth / 2, 100 - (float)radialGauge.ScalePadding, 0);         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 = new Vector3((float)radialGauge.NeedleWidth / 2, (float)radialGauge.NeedleLength, 0);     radialGauge._needle.Offset = new Vector3(100 - ((float)radialGauge.NeedleWidth / 2), 100 - (float)radialGauge.NeedleLength, 0);     radialGauge._root.Children.InsertAtTop(radialGauge._needle);     OnValueChanged(radialGauge); }

下面来看一下 RadialGauge 的鼠标点击和触摸手势交互事件处理方法,主要处理逻辑在 SetGaugeValueFromPoint(point) 方法中:

首先计算出当前点击或触摸点相对比仪表盘圆心的坐标,根据坐标计算出角度;再根据最大角度和最小角度的值,计算出可变化的实际区间;最后用当前角度与最小角度的差值,与实际区间做一个比例换算,得到当前角度对应在仪表盘里的数值;

private void SetGaugeValueFromPoint(Point p) {    var pt = new Point(p.X - (ActualWidth / 2), -p.Y + (ActualHeight / 2));    var angle = Math.Atan2(pt.X, pt.Y) / Degrees2Radians;    var divider = Mod(NormalizedMaxAngle - NormalizedMinAngle, 360);    if (divider == 0)     {         divider = 360;     }    var value = Minimum + ((Maximum - Minimum) * Mod(angle - NormalizedMinAngle, 360) / 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;以及每个部分的颜色设置,可以从示例运行图中看出:

总结

到这里我们就把 UWP Community Toolkit 中的 RadialGauge 控件的源代码实现过程和简单的调用示例讲解完成了,希望能对大家更好的理解和使用这个控件有所帮助。欢迎大家多多交流,谢谢!

最后,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通过微博关注最新动态。

计算

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:虚拟存储涉及到的相关基础知识总结 1
下一篇:elasticsearch面试必考(亲身经历的问题)
相关文章