New UWP Community Toolkit - RangeSelector
概述
前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 RangeSelector 相关功能。
RangeSelector 是一种范围选择控件,有两个滑块控件,允许用户在控件的取值范围内选择一个子区间范围。在实际应用开发中 RangeSelector 也有着非常广泛的应用,例如筛选时的价格区间选择等等。我们来看一下官方示例中的展示:

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/rangeselector
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
开发过程
代码分析
先来看看 RangeSelector 的结构组成:
- RangeChangedEventArgs.cs - 范围改变处理事件传入的参数类,包含了 oldValue,newValue 和 ChangedRangeProperty(标志 min 和 max 两个区间值是否改变)
- RangeSelector.cs - RangeSelector 的控件定义和事件处理类
- RangeSelector.xaml - RangeSelector 的样式文件

下面来看一下几个主要类中的主要代码实现,因为篇幅关系,我们只摘录部分关键代码实现:
1. RangeSelector.xaml
RangeSelector.xaml 是 RangeSelector 控件的样式文件,我们看到 Template 部分,由一个背景 Border OutOfRangeContentContainer,两个选择滑块 MinThumb 和 MaxThumb,以及显示当前范围的矩形 ActiveRectangle 组成;再看 VisualStateManager,我们截取了一部分,在 MinPressed 发生时,MinThumb 被高亮显示,同理其他状态发生时也会有对应的视觉状态发生。
<Style TargetType="controls:RangeSelector">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:RangeSelector">
<Grid x:Name="ControlGrid" Height="24" >
<Grid.Resources>
<Style x:Key="SliderThumbStyle" TargetType="Thumb"> ... </Style>
</Grid.Resources>
<Border x:Name="OutOfRangeContentContainer" Background="Transparent"> ... </Border>
<Canvas x:Name="ContainerCanvas"
Margin="0,0,8,0"
Background="Transparent">
<Rectangle x:Name="ActiveRectangle" Height="2" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" />
<Thumb x:Name="MinThumb" AutomationProperties.Name="Min thumb" IsTabStop="True" Style="{StaticResource SliderThumbStyle}" TabIndex="0" />
<Thumb x:Name="MaxThumb" AutomationProperties.Name="Max thumb" IsTabStop="True" Style="{StaticResource SliderThumbStyle}" TabIndex="1" />
</Canvas>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MinPressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MinThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightChromeHighBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- other visual state -->
...
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
2. RangeSelector.cs
我们先看看 RangeSelector 类的组成:

先来看看类中的依赖属性:
- Minimum - 控件允许选择范围的最小值,默认是 0.0,修改时触发 MinimumChangedCallback
- Maximum - 控件允许选择范围的最大值,默认是 1.0,修改时触发 MaximumChangedCallback
- RangeMin - 控件实际选择范围的最小值,默认是 0.0,修改时触发 RangeMinChangedCallback
- RangeMax - 控件实际选择范围的最大值,默认是 1.0,修改时触发 RangeMaxChangedCallback
- IsTouchOptimized - 触摸优化的标志,默认是 false,修改时触发 IsTouchOptimizedChangedCallback
- StepFrequency - 每次调整范围时的步长,默认是 1.0
我们在其中挑出有代表性的方法详细看一下:
① MinimumChangedCallback(d, e)
允许范围最小值调整的回调方法,最大值对应的方法功能类似;当最小值调整后的 newValue 大于等于旧的最大值时,对最大值重新设置为 newValue + 0.01;当 newValue 大于等于实际范围最小值时,把实际最小值设置为 newValue,当 newValue 大于等于实际范围最大值时,把实际最大值也设置为 newValue;最后如果 newValue 小于 oldValue 时,需要同步滑块的位置;
private static void MinimumChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rangeSelector = d as RangeSelector;
if (rangeSelector == null || !rangeSelector._valuesAssigned)
{
return;
}
var newValue = (double)e.NewValue;
var oldValue = (double)e.OldValue;
if (rangeSelector.Maximum < newValue)
{
rangeSelector.Maximum = newValue + Epsilon;
}
if (rangeSelector.RangeMin < newValue)
{
rangeSelector.RangeMin = newValue;
}
if (rangeSelector.RangeMax < newValue)
{
rangeSelector.RangeMax = newValue;
}
if (newValue < oldValue)
{
rangeSelector.SyncThumbs();
}
}
② RangeMinChangedCallback(d, e)
实际范围最小值调整的回调方法,最大值对应的方法功能类似;根据步长来对 newValue 做矫正,比如 oldValue = 0.0,newValue = 0.11,步长 0.1,那么 newValue 会调整为 0.1;然后是对 newValue 超出允许选择范围时的边界处理;最后实际选择范围修改时,需要同步调整显示实际范围的矩形控件的状态;
private static void RangeMinChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rangeSelector = d as RangeSelector;
if (rangeSelector == null)
{
return;
}
rangeSelector._minSet = true;
if (!rangeSelector._valuesAssigned)
{
return;
}
var newValue = (double)e.NewValue;
rangeSelector.RangeMinToStepFrequency();
if (rangeSelector._valuesAssigned)
{
if (newValue < rangeSelector.Minimum)
{
rangeSelector.RangeMin = rangeSelector.Minimum;
return;
}
if (newValue > rangeSelector.Maximum)
{
rangeSelector.RangeMin = rangeSelector.Maximum;
return;
}
rangeSelector.SyncActiveRectangle();
if (newValue > rangeSelector.RangeMax)
{
rangeSelector.RangeMax = newValue;
}
}
else
{
rangeSelector.SyncActiveRectangle();
}
}
③ IsTouchOptimizedChangedCallback(d, e)
当触摸优化变化时,控件也会做出变化,实际处理方法是 ArrangeForTouch();我们看到,在触摸优化后,滑块的宽高被设置为 44,对应的范围显示也会变大;而在非触摸优化时,控件整体会变小,变为鼠标点击时的样式;因为实现了触摸优化,所以我们可以根据当前设备是否是平板模式,来决定控件的显示状态,非常有用。
private void ArrangeForTouch()
{
if (_containerCanvas == null)
{
return;
}
if (IsTouchOptimized)
{
...
if (_minThumb != null)
{
_minThumb.Width = _minThumb.Height = ;
_minThumb.Margin = , , , );
}
...
}
else
{
...
if (_minThumb != null)
{
_minThumb.Width = ;
_minThumb.Height = ;
_minThumb.Margin = , , , );
}
...
}
}
下面的几个方法,主要处理的是 rangeMin 和 rangeMax 两个滑块的拖拽事件,我们还是只看 min 对应的处理,max 处理类似:
根据当前滑块拖拽后的位置,来修改实际范围最小值;计算方式就是根据允许范围区间,控件实际宽度,以及当前位置距离最小值的距离,来计算出比例;
private void MinThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
_absolutePosition += e.HorizontalChange;
RangeMin = DragThumb(_minThumb, , Canvas.GetLeft(_maxThumb), _absolutePosition);
}
private double DragThumb(Thumb thumb, double min, double max, double nextPos)
{
nextPos = Math.Max(min, nextPos);
nextPos = Math.Min(max, nextPos);
Canvas.SetLeft(thumb, nextPos);
return Minimum + ((nextPos / _containerCanvas.ActualWidth) * (Maximum - Minimum));
}
而在滑块拖拽开始和结束时,以及可用状态变化时,也会触发对应的 VisualStateManager 的 state 来调整控件视觉显示状态;
调用示例
我们定义了一个 RangeSelector 控件,在左右两侧显示当前选择范围的最小值和最大值,而控件的可选范围区间是 0~100,可以看到示例运行图的显示:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="Black"
Text="{Binding RangeMin, ElementName=RangeSelectorControl}" />
<controls:RangeSelector Grid.Column="1"
x:Name="RangeSelectorControl"
Minimum="0"
Maximum="100"
StepFrequency="1"/>
<TextBlock Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="Black"
Text="{Binding RangeMax, ElementName=RangeSelectorControl}" />
</Grid>

总结
到这里我们就把 UWP Community Toolkit 中的 RangeSelector 控件的源代码实现过程和简单的调用示例讲解完成了,希望能对大家更好的理解和使用这个控件有所帮助,大家也可以在实际应用中,编写更丰富的控件样式,或者更特殊的范围选择,比如环形等。欢迎大家多多交流,谢谢!
最后,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通过微博关注最新动态。
衷心感谢 UWPCommunityToolkit 的作者们杰出的工作,Thank you so much, UWPCommunityToolkit authors!!!
New UWP Community Toolkit - RangeSelector的更多相关文章
- 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 - RadialGauge
概述 New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解 RadialGauge 的实现. Radi ...
- New UWP Community Toolkit - ImageEx
概述 UWP Community Toolkit 中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解 ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...
- New UWP Community Toolkit - AdaptiveGridView
概述 UWP Community Toolkit 中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解 AdaptiveGridView 的实现 ...
随机推荐
- 【Luogu3804】【模板】后缀自动机(后缀自动机)
[Luogu3804][模板]后缀自动机(后缀自动机) 题面 洛谷 题解 一个串的出现次数等于\(right/endpos\)集合的大小 而这个集合的大小等于所有\(parent\)树上儿子的大小 这 ...
- 【BZOJ5020】【THUWC2017】在美妙的数学王国中畅游(Link-Cut Tree,组合数学)
[BZOJ5020][THUWC2017]在美妙的数学王国中畅游(Link-Cut Tree,组合数学) 题解 Description 数字和数学规律主宰着这个世界. 机器的运转, 生命的消长, 宇宙 ...
- 【Luogu1345】周游加拿大(动态规划)
[Luogu1345]周游加拿大(动态规划) 题面 题目描述 你赢得了一场航空公司举办的比赛,奖品是一张加拿大环游机票.旅行在这家航空公司开放的最西边的城市开始,然后一直自西向东旅行,直到你到达最东边 ...
- 【洛谷1541】【CJOJ1087】【NOIP2010】乌龟棋
题面 Description 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌 ...
- java基础:内存分配(上)
java执行中的内存分区: 1.代码域:存放代码 2.数据域:存放静态的数据 3.栈:存放局部变量 4.堆:存放成员变量 (ps:局部变量是类中方法体中申明的变量,只在这个方法中有效:成员变量是类中方 ...
- 如何降低90%Java垃圾回收时间?以阿里HBase的GC优化实践为例
过去的一年里,我们准备在Ali-HBase上突破这个被普遍认知的痛点,为此进行了深度分析及全面创新的工作,获得了一些比较好的效果.以蚂蚁风控场景为例,HBase的线上young GC时间从120ms减 ...
- 服务器 Disk full
General error: 1021 Disk full (/tmp/#sql_24a3_0.MAI); waiting for someone to free some space... (err ...
- git团队协作
hi,team,我们目前使用的是git做项目管理,它是非常优秀的版本控制工具,使用好可以极大提高我们团队开发效率.但是,出现不必要的冲突和代码丢失就要费时解决这些可避免的问题. git开发流程 这个流 ...
- python模板:自动化执行测试函数
#!/bin/python #example 1.1 #applay def function(a,b): print(a,b) def example1(): apply(function, (&q ...
- 炫丽的倒计时效果Canvas绘图与动画基础
前言 想要在自己做的网页中,加入canvas动画效果,但是发现模板各种调整不好,觉得还是要对canvas有所了解,才可以让自己的网页变得狂拽炫酷吊炸天! 一.绘制基础 1 <!DOCTYPE h ...