概述

前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 RangeSelector 相关功能。

RangeSelector 是一种范围选择控件,有两个滑块控件,允许用户在控件的取值范围内选择一个子区间范围。在实际应用开发中 RangeSelector 也有着非常广泛的应用,例如筛选时的价格区间选择等等。我们来看一下官方示例中的展示:

Source: https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/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的更多相关文章

  1. New UWP Community Toolkit

    概述 UWP Community Toolkit 是一个 UWP App 自定义控件.应用服务和帮助方法的集合,能够很大程度的简化和指引开发者的开发工作,相信广大 UWPer 并不陌生. 下面是截取自 ...

  2. New UWP Community Toolkit - XAML Brushes

    概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...

  3. New UWP Community Toolkit - Markdown

    概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 MarkdownTextBlock 和 MarkdownDoc ...

  4. New UWP Community Toolkit - Staggered panel

    概述 前面 New UWP Community Toolkit 文章中,我们对 2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解  St ...

  5. New UWP Community Toolkit - Carousel

    概述 New UWP Community Toolkit  V2.2.0 的版本发布日志中提到了 Carousel 的调整,本篇我们结合代码详细讲解  Carousel 的实现. Carousel 是 ...

  6. New UWP Community Toolkit - RadialProgressBar

    概述 UWP Community Toolkit  中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解  RadialProgressBar 的实现. Radi ...

  7. New UWP Community Toolkit - RadialGauge

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

  8. New UWP Community Toolkit - ImageEx

    概述 UWP Community Toolkit  中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解  ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...

  9. New UWP Community Toolkit - AdaptiveGridView

    概述 UWP Community Toolkit  中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解  AdaptiveGridView 的实现 ...

随机推荐

  1. 洛谷P3459 [POI2007]MEG-Megalopolis(树链剖分,Splay)

    洛谷题目传送门 正解是树状数组维护dfn序上的前缀和,这样的思路真是又玄学又令我惊叹( 我太弱啦,根本想不到)Orz各路Dalao 今天考了这道题,数据范围还比洛谷的小,只有\(10^5\)(害我复制 ...

  2. angular编写表单验证

    angular编写表单验证 一.整体概述 表单内容如下图,包括常用的用户名.密码.确认密码.手机.邮箱等 整体js代码很少,就一个指令用于写确认密码和密码是否相等.其他 验证都是使用angular自带 ...

  3. checkBox半选中状态

    checkbox 可以半选中,这个特性,很多浏览器都支持 // 用 input.indeterminate 这个属性来获取或者设置半选中状态,必须要用 js 添加属性,才有效果. input.inde ...

  4. 微信小程序-weui实例代码提取

    搜索框 对应代码如下: wxss文件 <view class="page"> <view class="page__hd"> <v ...

  5. 快速开发基于 HTML5 网络拓扑图应用之 DataBinding 数据绑定篇

    前言 发现大家对于我从 json 文件中直接操作节点属性来控制界面的动态变化感到比较好奇,所以这篇就针对数据绑定以及如何使用这些绑定的数据做一篇说明,我写了一个简单的例子,基于机房工控的服务器上设备的 ...

  6. 01背包问题(Java实现)

    关于背包问题,百度文库上有崔添翼大神的<背包九讲>,不明的请移步查看.这里仅介绍最基本的01背包问题的实现. public class Knapsack { private final i ...

  7. VirboxLM许可管理平台,一站式软件保护解决方案

    安全,易用,灵活 轻松解决开发者软件版权保护难题 Virbox LM为企业提供安全易用的软件保护管理平台,实现高安全强度的软件防护,防止盗版及逆向工程.实现便捷.安全的软件授权,包括创建灵活的许可模式 ...

  8. python 对模块的应用你还得练点这些

    1.有如下字符串:n = "路飞学城"(编程题) - 将字符串转换成utf-8的字符编码的字节,再将转换的字节重新转换为utf-8的字符编码的字符串 - 将字符串转换成gbk的字符 ...

  9. WebPack的安装

    一.前提因为webpack是一个基于node的项目,所以首先需要确保你的电脑里面已经安装了node.js,以及npm.在这里我使用的版本是:node:v5.8.0 ,npm:3.7.3,若是版本问题, ...

  10. oracle 11g数据库 DMP还原数据库

    -------------------------- jd :表空间 -------------------------- --本地登陆 cmd下直接执行 sqlplus/as sysdba; --修 ...