RadialControls - 普通控件,BenT





5.00/5 (7投票s)
关于 RadialControls 的教程 - 一个适用于 Windows 8 应用的圆形控件库。
引言
在 WPF 中绘制圆形很困难。更具体地说,当我设计一个 Windows 8 天气应用程序时,我很快意识到我需要自己制作控件才能获得我想要的外观,因此 RadialControls 应运而生。
- GitHub: https://github.com/benthorner/radial_controls
- NuGet: https://nuget.net.cn/packages/RadialControls/
背景
RadialControls 包含两部分:一组简化了构建更复杂控件的原始组件,以及一些展示它们如何协同工作的示例。本文将介绍其中一个示例:ArcSlider 控件...
图片中的时间选择器使用 ArcSlider 控件来显示时钟的时针和分针。下一节我将概述构成滑块和时间选择器的控件,然后重点介绍滑块。
让光明降临...
RadialControls 有两个基本组件:一个 光晕 (halo) 和一个 圆环 (ring)。还有其他组件,例如 滑块 (sliders) 和 扇形 (slices),可以替代圆环组件。再看看图片中的时间选择器...
时间选择器由 3 个逻辑圆环组成,它们在 光晕 内排列成 带 (bands)。每个圆环都有一个 厚度 (thickness),因此光晕的运作方式有点像圆形的 StackPanel
(任何剩余空间都分配给内圈)。
<control:Halo>
<control:HaloDisc Fill="DeepSkyBlue" control:Halo.Band="2"/>
<local:ArcSlider x:Name="Minutes" control:Halo.Band="2"/>
<control:HaloDisc Fill="RoyalBlue" control:Halo.Band="1"/>
<local:ArcSlider x:Name="Hours" control:Halo.Band="1"/>
<control:HaloDisc Fill="Black"/>
<TextBlock x:Name="Display" FontSize="30" FontWeight="SemiBold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</control:Halo>
此时,概述所有控件是很有意义的...
Halo
- 用于将圆环排列成同心圆或带HaloRing
- 使用变换将其每个子项排列成圆形HaloChain
- 类似于HaloRing
,但所有子项都组合在一起HaloDisc
- 创建一个扩展的、填充的圆,适用于背景HaloArc
/Slice
- 在指定角度绘制圆形弧线/圆形Slider
- 一个基本的圆形滑块,我们将在下一节进行探讨
时间选择器摘录中包含其中一些。您也可以查看 RingLabel
示例,了解 HaloChain
的用法。继续按照计划,我现在将重点介绍用于创建 ArcSlider 的控件。
倾斜的斜坡...
ArcSlider 示例使用 HaloArc
来替换基本 Slider
控件自带的普通圆形,并添加一些动画使其表现得像一个按钮。忽略花哨的部分,该控件看起来像这样。
<control:Slider>
<control:Slider.Template>
<ControlTemplate>
<control:HaloArc x:Name="Arc" Tension="0.5" Spread="20"
Angle="{Binding Angle, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Offset="{Binding Offset, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"/>
</ControlTemplate>
</control:Slider.Template>
</control:Slider>
增加(或减少)角度将使滑块在圆形上移动。偏移量 (offset) 属性决定了圆上的起始点,默认为 0 度,即滑块从圆的顶部开始。
弧线使用数据绑定连接到基本滑块控件,后者负责计算用户滑动滑块时的角度(相对于偏移量)。我将在下面更详细地介绍滑块。
我稍后也会介绍弧线控件。现在,我将提及张力 (tension) 属性,它用于对齐;例如,张力为 0.5 意味着弧线围绕其当前角度居中,如图所示。现在开始滑块的乐趣!
转来转去...
将外观与行为分离总是有益的。尽管 ArcSlider 完全覆盖了它,但滑块控件确实有一个默认模板,该模板在 Generic 主题文件中定义。但我更想专注于行为...
protected override void OnApplyTemplate()
{
AddHandler(PointerPressedEvent, new PointerEventHandler(StealPointer), true);
AddHandler(PointerReleasedEvent, new PointerEventHandler(ReleasePointer), true);
AddHandler(PointerCanceledEvent, new PointerEventHandler(ReleasePointer), true);
AddHandler(PointerCaptureLostEvent, new PointerEventHandler(ReleasePointer), true);
AddHandler(PointerMovedEvent, new PointerEventHandler(UpdateValue), true);
}
StealPointer
和 ReleasePointer
事件处理程序用于捕获和释放指针。捕获指针意味着当按下指针时,所有指针事件都会被重定向到滑块。现在是棘手的部分...
给定一个 PointerMovedEvent
,控件需要计算滑块的新角度,然后根据偏移量进行调整(例如,对于 30 的偏移量,180 度角变为 150 度)。要计算角度...
private double SliderAngle(PointerRoutedEventArgs e)
{
var centre = new Point(
ActualWidth / 2, ActualHeight / 2
);
var thumb = e.GetCurrentPoint(parent)
.Position.RelativeTo(centre);
var vertical = new Vector(0, -1);
return thumb.AngleTo(vertical);
}
此方法首先使用 `Point` 上的 `RelativeTo` 扩展方法创建一个 Vector
。可以将其想象成一个从圆心指向指针的箭头,如下图所示。
一旦有了指向指针的向量,该方法就会计算该向量相对于直接向上的向量的角度。背后的数学有点棘手;如果您想了解更多信息,请在此处查看 这里。
因此,滑块控件使用事件处理程序来捕获、跟踪和释放指针,在此过程中更新角度。剩下要做的就是让某些东西绑定到角度属性 - 使角度可见。
<Style TargetType="control:Slider">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="control:Slider">
<control:HaloRing>
<Grid control:HaloRing.Angle="{Binding Angle, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
control:HaloRing.Offset="{Binding Offset, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}">
<ContentControl Template="{TemplateBinding Thumb}"/>
</Grid>
</control:HaloRing>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
您看到的是默认滑块模板的简化版本。模板并不重要(请记住 ArcSlider 完全覆盖了它),但它引入了另一个我想介绍的控件... HaloRing
。
生命之圆...
我创建 RadialControls 是因为我想要一种简单的方法来处理圆形。圆的一个问题是如何排列小控件,例如按钮或椭圆(如默认滑块模板中所示)。
HaloRing
解决了这个问题。它定义了两个附加属性:偏移量 (offset) 和角度 (angle),当应用于子控件时,会导致子控件出现在一个圆中,以及圆环中的任何其他子控件。
它的工作方式类似于面板:圆环控件重写了 Panel
类的布局方法,以指定其子控件的排列方式。我不会深入细节,但这是要点...
protected override Size ArrangeOverride(Size finalSize)
{
var thickness = RingThickness();
var size = RingSize(finalSize);
var radius = (
Math.Min(size.Width, size.Height) - thickness
)
foreach(var child in Children)
{
ArrangeChid(child, size);
TransformChild(child, radius);
}
return size;
}
ArrangeOverride
接收一个大小,即圆环可用的空间。然后它获取圆环的厚度(基于其子控件的最大期望大小),并将指定的大小向上推,使其至少等于厚度。
现在是重点:控件将在其区域的中心排列每个子项,然后将子项变换到其在圆上的位置,该位置使用偏移量和角度属性指定。
变换分为两部分:首先使用三角学计算圆上的点,将子控件平移到偏移量的位置。然后将子控件绕圆旋转到指定角度。
关于圆环控件还有很多可以说的,但我跑题了!到目前为止,我们已经介绍了滑块控件如何用作 ArcSlider 示例的基础。我现在将介绍使 ArcSlider 拥有其出色的外观的组件。
谁建造了弧线...
如果您不知道,弧线是圆周围边界的一部分。HaloArc
通过扩展 Path
类来绘制弧线,并设置如下所示的路径 Data
属性。
private PathFigure figure = new PathFigure();
private ArcSegment segment = new ArcSegment();
private PathGeometry path = new PathGeometry();
public HaloArc()
{
segment.SweepDirection = SweepDirection.Clockwise;
figure.Segments = new PathSegmentCollection { segment };
path.Figures = new PathFigureCollection { figure };
Data = path;
}
Data
属性设置为 PathGeometry
,它由一个 PathFigure
组成,该 PathFigure
又由一个 ArcSegment
(实际绘制内容的部分!)组成。现在来处理排列...
protected override Size ArrangeOverride(Size finalSize)
{
var circle = new Circle(finalSize);
circle.Radius -= StrokeThickness / 2;
ArrangePath(circle);
return finalSize;
}
Circle 是我用来建模(三角形)圆形的辅助工具。我减去一半的路径厚度,以便弧的笔画位于 finalSize
指定的区域内。然后我使用由此产生的圆来设置路径的元素...
private void ArrangePath(Circle circle)
{
var tension = Tension % 1;
var angle = Angle + Offset;
var startAngle = angle - tension * Spread;
var endAngle = angle + (1 - tension) * Spread;
figure.StartPoint = circle.PointAt(startAngle);
segment.Point = circle.PointAt(endAngle);
segment.Size = circle.Size();
segment.IsLargeArc = (Spread > 180);
}
前 4 行只是计算弧线开始和结束的角度。开始角度设置为图形上的一个点(微软,为什么?!),结束角度设置为弧段的(结束)点。
就是这样!PointAt
方法使用了一些三角学,但它应该是不言自明的(查看 Utilities/Extensions)。这样我们就完成了本次游览:感谢您的阅读,希望您觉得有所帮助!
结论
该吃饭了。