开发者社区> 杰克.陈> 正文

WPF范围选择控件(RangeSelector)

简介: 原文:WPF范围选择控件(RangeSelector) 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/78084886        在某些应用场景中,我们需要做可视化的范围选择。
+关注继续查看
原文:WPF范围选择控件(RangeSelector)

版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/78084886


       在某些应用场景中,我们需要做可视化的范围选择。例如,在进行录像剪辑的时候,我们希望在播放时间轴上通过拖动两个可移动的控件来确定两控件之间的时间轴为我们希望进行录像剪辑的时间范围。WPF中并没有这样的预定义控件,所以如果需要有这样的应用场景,则需要自定义这样的控件。本文便是简述定制这样一个控件的基本的思路。


       一 基本结构

      

       先来看一下这样一个控件的基本结构,如上图所示,总体可分为4个部分,1是整个可选择的范围,2是选中的范围,3是左右两个选择器,可在选择范围轴上移动,4是选择信息显示按钮。

       从控件构成来说,1、2都可以用Path来实现,3的上下两个部分也可以用Path实现,4则是一个TextBlock(之所以不选择Label,是希望能用到TextBlock的TextTrimming属性)。


       二 代码结构

       

       为了便于复用,我将此控件单独封装成了一个库(如有需要,也可以很方便的与其他自定义空间库合并),总体上代码的结构非常简单:一个RangeSelector类的cs代码文件RangeSelector.cs,用于控件的逻辑控制;一个控件默认模板的xaml代码文件RangeSelector.xaml;另外还有三个用于控件辅助控制的数据转换类(Converter)。

       

       三 默认模板

       RangeSelector.xaml定义了控件的默认外观,根据(一)里的基本结构,控件必须要包含以下几个命名部分:

       PART_Range:为Path控件,用于展示总的选择范围。

       PART_Canvas:为Canvas控件,用于承载其他绘制控件的容器,之所以选择Canvas,因为他可以通过SetLeft和SetTop方法方便的设置控件的绝对位置,为选择器的移动提供了方便。

       PART_SelectedRange:为Path控件,选中的范围。

       PART_RangeSelector1/PART_RangeSelector2:范围选择器,本文用两个Path组合的Grid来实现。

       PART_LowerMessageTextBlock/PART_UpperMessageTextBlock:为TextBlock控件,用于显示选择的范围信息。

       具体代码如下:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:RangeSelectors"
    xmlns:cvt="clr-namespace:RangeSelectors.Converter">

    <cvt:DoubleToGridLengthConverter x:Key="doubleToGridLengthConverter"/>
    <cvt:RangePathMarginConverter x:Key="rangePathMarginConverter"/>
    <cvt:SelectorUpShapeConverter x:Key="selectorUpShapeConverter"/>
    <cvt:SelectorDownShapeConverter x:Key="selectorDownShapeConverter"/>

    <Style TargetType="{x:Type local:RangeSelector}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:RangeSelector}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition Height="auto"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>

                            <Path x:Name="PART_Range" Grid.Row="1" Panel.ZIndex="0" 
                                  Fill="{TemplateBinding RangeColor}"
                                  HorizontalAlignment="Stretch" 
                                  VerticalAlignment="Stretch"
                                  Stretch="Fill">
                                <Path.Margin>
                                    <MultiBinding Converter="{StaticResource rangePathMarginConverter}">
                                        <Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                        <Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                    </MultiBinding>
                                </Path.Margin>
                            </Path>

                            <Canvas x:Name="PART_Canvas" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Top">

                                <Path x:Name="PART_SelectedRange" Grid.Row="1" Panel.ZIndex="1"
                                      Fill="{TemplateBinding SelectedRangeColor}" 
                                      HorizontalAlignment="Stretch" 
                                      VerticalAlignment="Stretch"
                                      Stretch="Fill">
                                    <Path.Margin>
                                        <MultiBinding Converter="{StaticResource rangePathMarginConverter}">
                                            <Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                            <Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                        </MultiBinding>
                                    </Path.Margin>
                                </Path>

                                <Grid x:Name="PART_RangeSelector1" Panel.ZIndex="0"
                                      Canvas.Left="0" Canvas.Top="0" Background="Transparent">
                                    <Grid.RowDefinitions>
                                        <RowDefinition/>
                                        <RowDefinition Height="{TemplateBinding RangeBarHeight, Converter={StaticResource doubleToGridLengthConverter}}"/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <Path x:Name="pathSelectorUp1" Grid.Row="0" 
                                          Fill="{TemplateBinding SelectorColor}" 
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Bottom">
                                        <Path.Data>
                                            <MultiBinding Converter="{StaticResource selectorUpShapeConverter}">
                                                <Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                                <Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                            </MultiBinding>
                                        </Path.Data>
                                    </Path>
                                    <Path x:Name="pathSelectorDown1" Grid.Row="2"
                                          Fill="{TemplateBinding SelectorColor}" 
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Top">
                                        <Path.Data>
                                            <MultiBinding Converter="{StaticResource selectorDownShapeConverter}">
                                                <Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                                <Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                            </MultiBinding>
                                        </Path.Data>
                                    </Path>
                                </Grid>

                                <Grid x:Name="PART_RangeSelector2" Panel.ZIndex="0"
                                      Canvas.Left="0" Canvas.Top="0" Background="Transparent"
                                      Width="{TemplateBinding SelectorWidth, Converter={StaticResource doubleToGridLengthConverter}}">
                                    <Grid.RowDefinitions>
                                        <RowDefinition/>
                                        <RowDefinition Height="{TemplateBinding RangeBarHeight, Converter={StaticResource doubleToGridLengthConverter}}"/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>

                                    <Path x:Name="pathSelectorUp2" Grid.Row="0"
                                          Fill="{TemplateBinding SelectorColor}" 
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Bottom">
                                        <Path.Data>
                                            <MultiBinding Converter="{StaticResource selectorUpShapeConverter}">
                                                <Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                                <Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                            </MultiBinding>
                                        </Path.Data>
                                    </Path>
                                    <Path x:Name="pathSelectorDown2" Grid.Row="2"
                                          Fill="{TemplateBinding SelectorColor}" 
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Top">
                                        <Path.Data>
                                            <MultiBinding Converter="{StaticResource selectorDownShapeConverter}">
                                                <Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                                <Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
                                            </MultiBinding>
                                        </Path.Data>
                                    </Path>
                                </Grid>

                                <TextBlock x:Name="PART_LowerMessageTextBlock" TextTrimming="CharacterEllipsis"
                                           HorizontalAlignment="Center" VerticalAlignment="Center"
                                           Foreground="{TemplateBinding MessageForeground}"
                                           MaxWidth="{TemplateBinding MessageWidth}" Panel.ZIndex="2"/>

                                <TextBlock x:Name="PART_UpperMessageTextBlock" TextTrimming="CharacterEllipsis"
                                           HorizontalAlignment="Center" VerticalAlignment="Center"
                                           Foreground="{TemplateBinding MessageForeground}" 
                                           MaxWidth="{TemplateBinding MessageWidth}" Panel.ZIndex="2"/>

                            </Canvas>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

       四 逻辑控制

       1 控件的定义与查找

       在类的定义中,我们定义了需要操作所有控件,并在复写OnApplyTemplate方法的时候通过GetTemplateChild方法从模板中找到对应的控件,完成控件的初始化与事件方法注册。如下:

        private Canvas _canvas = null;
        private FrameworkElement _rangeElement = null;
        private FrameworkElement _rangeSelector1 = null;
        private FrameworkElement _rangeSelector2 = null;
        private FrameworkElement _selectedRangeElement = null;
        private TextBlock _ttbLowerMessage = null;
        private TextBlock _ttbUpperMessage = null;
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _canvas = GetTemplateChild("PART_Canvas") as Canvas;

            _rangeElement = GetTemplateChild("PART_Range") as FrameworkElement;
            if (_rangeElement != null)
            {
                _rangeElement.SizeChanged += Control_SizeChanged;
                string pathString = string.Format("M{0},0 {1},0 {2},{3} {4},{5}z", 0, 100, 100, RangeBarHeight, 0, RangeBarHeight);
                GeometryConverter gc = new GeometryConverter();
                (_rangeElement as Path).Data = (Geometry)gc.ConvertFromString(pathString);
            }

           _selectedRangeElement = GetTemplateChild("PART_SelectedRange") as FrameworkElement;

            _rangeSelector1 = GetTemplateChild("PART_RangeSelector1") as FrameworkElement;
            if (_rangeSelector1 != null)
            {
                _rangeSelector1.SizeChanged += Control_SizeChanged;
                _rangeSelector1.MouseLeftButtonDown += Selector_MouseLeftButtonDown;
                _rangeSelector1.MouseMove += Selector_MouseMove;
                _rangeSelector1.MouseLeftButtonUp += Selector_MouseLeftButtonUp;
            }

            _rangeSelector2 = GetTemplateChild("PART_RangeSelector2") as FrameworkElement;
            if (_rangeSelector2 != null)
            {
                _rangeSelector2.MouseLeftButtonDown += Selector_MouseLeftButtonDown;
                _rangeSelector2.MouseMove += Selector_MouseMove;
                _rangeSelector2.MouseLeftButtonUp += Selector_MouseLeftButtonUp;
            }

            _ttbUpperMessage = GetTemplateChild("PART_LowerMessageTextBlock") as TextBlock;
            if(_ttbUpperMessage != null)
            {
                Canvas.SetTop(_ttbUpperMessage, SelectorHeight);
            }

            _ttbLowerMessage = GetTemplateChild("PART_UpperMessageTextBlock") as TextBlock;
            if(_ttbLowerMessage != null)
            {
                Canvas.SetTop(_ttbLowerMessage, SelectorHeight);
            }

            InitData();
        }

       2 定义依赖属性和属性包装器

       这些依赖属性主要用于与选择器的选择信息(上下界)和展示信息(控件各部分画刷)相关的数据绑定。

        #region Dependency Properties

        public static readonly DependencyProperty UpperBoundaryValueProperty
            = DependencyProperty.Register("UpperBoundaryValue", typeof(double), typeof(RangeSelector), new PropertyMetadata(0.0, OnUpBoundaryPropertyChanged));
        public static readonly DependencyProperty LowerBoundaryValueProperty
            = DependencyProperty.Register("LowerBoundaryValue", typeof(double), typeof(RangeSelector), new PropertyMetadata(0.0, OnLowBoundaryPropertyChanged));
        public static readonly DependencyProperty RangeColorProperty
            = DependencyProperty.Register("RangeColor", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.Transparent), null));
        public static readonly DependencyProperty SelectedRangeColorProperty
            = DependencyProperty.Register("SelectedRangeColor", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.White), null));
        public static readonly DependencyProperty SelectorColorProperty
            = DependencyProperty.Register("SelectorColor", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.Blue), null));
        public static readonly DependencyProperty MessageForegroundProperty
            = DependencyProperty.Register("MessageForeground", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.Black), null));
        public static readonly DependencyProperty MessageWidthProperty
            = DependencyProperty.Register("MessageWidth", typeof(double), typeof(RangeSelector), new PropertyMetadata(100.0, null));
        public static readonly DependencyProperty RangeBarHeightProperty
            = DependencyProperty.Register("RangeBarHeight", typeof(double), typeof(RangeSelector), new PropertyMetadata(15.0, null));
        public static readonly DependencyProperty SelectorWidthProperty
            = DependencyProperty.Register("SelectorWidth", typeof(double), typeof(RangeSelector), new PropertyMetadata(14.0, null));
        public static readonly DependencyProperty SelectorHeightProperty
            = DependencyProperty.Register("SelectorHeight", typeof(double), typeof(RangeSelector), new PropertyMetadata(25.0, null));

        #endregion

        #region Wrappers

        public double UpperBoundaryValue
        {
            get { return (double)GetValue(UpperBoundaryValueProperty); }
            set { SetValue(UpperBoundaryValueProperty, value); }
        }

        public double LowerBoundaryValue
        {
            get { return (double)GetValue(LowerBoundaryValueProperty); }
            set { SetValue(LowerBoundaryValueProperty, value); }
        }

        public Brush RangeColor
        {
            get { return (Brush)GetValue(RangeColorProperty); }
            set { SetValue(RangeColorProperty, value); }
        }

        public Brush SelectedRangeColor
        {
            get { return (Brush)GetValue(SelectedRangeColorProperty); }
            set { SetValue(SelectedRangeColorProperty, value); }
        }

        public Brush SelectorColor
        {
            get { return (Brush)GetValue(SelectorColorProperty); }
            set { SetValue(SelectorColorProperty, value); }
        }

        public Brush MessageForeground
        {
            get { return (Brush)GetValue(MessageForegroundProperty); }
            set { SetValue(MessageForegroundProperty, value); }
        }

        public double MessageWidth
        {
            get { return (double)GetValue(MessageWidthProperty); }
            set { SetValue(MessageWidthProperty, value); }
        }

        public double RangeBarHeight
        {
            get { return (double)GetValue(RangeBarHeightProperty); }
            set { SetValue(RangeBarHeightProperty, value); }
        }

        public double SelectorWidth
        {
            get { return (double)GetValue(SelectorWidthProperty); }
            set { SetValue(SelectorWidthProperty, value); }
        }

        public double SelectorHeight
        {
            get { return (double)GetValue(SelectorHeightProperty); }
            set { SetValue(SelectorHeightProperty, value); }
        }

       3 定义鼠标拖动事件方法

       在鼠标拖动的过程中,除了要对选择器控件进行移动外,还要实时更新选择的范围数据以及选择的展示信息。具体的在UpdateSelectedRange方法以及UpdateShownMessage方法中执行。

        /// <summary>
        /// 鼠标按下
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Selector_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (_canvas == null)
            {
                return;
            }

            FrameworkElement element = sender as FrameworkElement;
            if (element == null)
            {
                return;
            }

            //创建鼠标捕获
            Mouse.Capture(element);
            _enableMove = true;
            _spanLeft = e.GetPosition(_canvas).X - Canvas.GetLeft(element);
        }

        /// <summary>
        /// 鼠标移动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Selector_MouseMove(object sender, MouseEventArgs e)
        {
            if (_canvas == null)
            {
                return;
            }

            FrameworkElement element = sender as FrameworkElement;
            if (element == null)
            {
                return;
            }

            if (_enableMove)
            {
                double cLeft = e.GetPosition(_canvas).X - _spanLeft;
                if (double.IsNaN(cLeft))
                {
                    cLeft = 0;
                }

                //边界限制
                if (cLeft > _upperBound)
                {
                    cLeft = _upperBound;
                }
                else if (cLeft < _lowerBound)
                {
                    cLeft = _lowerBound;
                }

                //设置元素的位置
                Canvas.SetLeft(element, cLeft);

                //更新选择图像
                UpdateSelectedRange();
                //更新提示信息
                UpdateShownMessage();
            }
        }

        /// <summary>
        /// 鼠标松开
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Selector_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            if (element == null)
            {
                return;
            }

            //释放鼠标捕获
            element.ReleaseMouseCapture();
            _enableMove = false;
            _spanLeft = 0;
        }
        /// <summary>
        /// 更新选择范围
        /// </summary>
        private void UpdateSelectedRange()
        {
            if (_selectedRangeElement == null || _range == 0)
            {
                return;
            }

            GetUpperLowerSelector(out FrameworkElement lowerSelector, out FrameworkElement upperSelector);
            if (lowerSelector == null || upperSelector == null)
            {
                return;
            }

            double lower = Canvas.GetLeft(lowerSelector);
            double upper = Canvas.GetLeft(upperSelector);
            if (double.IsNaN(lower) || double.IsNaN(upper))
            {
                return;
            }

            string pathString = string.Format("M{0},0 {1},0 {2},{3} {4},{5}z", lower, upper, upper, RangeBarHeight, lower, RangeBarHeight);
            GeometryConverter gc = new GeometryConverter();
            (_selectedRangeElement as Path).Data = (Geometry)gc.ConvertFromString(pathString);
            Canvas.SetLeft(_selectedRangeElement, lower);

            UpperBoundaryValue = upper / _range;
            LowerBoundaryValue = lower / _range;

            if (_ttbLowerMessage != null)
            {
                Canvas.SetLeft(_ttbLowerMessage, lower - SelectorWidth / 2 - _ttbLowerMessage.ActualWidth);
            }

            if (_ttbUpperMessage != null)
            {
                Canvas.SetLeft(_ttbUpperMessage, upper + SelectorWidth * 3 / 2);
            }
        }

        /// <summary>
        /// 更新选择器的显示信息
        /// </summary>
        private void UpdateShownMessage()
        {
            if (ConvertRangeToMessage == null || _range == 0
                || _ttbUpperMessage == null || _ttbLowerMessage == null)
            {
                return;
            }

            GetUpperLowerSelector(out FrameworkElement lowerSelector, out FrameworkElement upperSelector);
            if (lowerSelector == null || upperSelector == null)
            {
                return;
            }

            double lowerSelectorCanvasLeft = Canvas.GetLeft(lowerSelector);
            double upperSelectorCanvasLeft = Canvas.GetLeft(upperSelector);
            if (double.IsNaN(lowerSelectorCanvasLeft) || double.IsNaN(upperSelectorCanvasLeft))
            {
                return;
            }

            double upperValue = upperSelectorCanvasLeft / _range;
            double lowerValue = lowerSelectorCanvasLeft / _range;

            string upperMessage = ConvertRangeToMessage(upperValue);
            string lowerMessage = ConvertRangeToMessage(lowerValue);

            _ttbUpperMessage.Text = upperMessage;
            _ttbUpperMessage.ToolTip = upperMessage;
            _ttbLowerMessage.Text = lowerMessage;
            _ttbLowerMessage.ToolTip = lowerMessage;
        }

       另外,在鼠标拖动选择器的过程中,并不限制某个选择器会在左边还是右边,因此会用下面的方法实时的分辨左右选择器。

        /// <summary>
        /// 判断两个选择器中哪一个是上界选择器,哪一个是下界选择器
        /// </summary>
        /// <param name="lowerSelector"></param>
        /// <param name="upperSelector"></param>
        private void GetUpperLowerSelector(out FrameworkElement lowerSelector, out FrameworkElement upperSelector)
        {
            if (_rangeSelector1 == null || _rangeSelector2 == null)
            {
                lowerSelector = null;
                upperSelector = null;
                return;
            }

            if (Canvas.GetLeft(_rangeSelector1) < Canvas.GetLeft(_rangeSelector2))
            {
                lowerSelector = _rangeSelector1;
                upperSelector = _rangeSelector2;
            }
            else
            {
                lowerSelector = _rangeSelector2;
                upperSelector = _rangeSelector1;
            }
        }


       4 选择信息的计算与展示

       在移动选择器的过程中,控件会根据上下界选择器在整个选择范围的位置计算其归一化的值,并赋值给依赖属性,以便将此选择范围暴露给使用者。但是,显示选择信息的时候,我们可以从外界传递一个委托方法,将选择器的范围值转化成格式化的字符串。以便在TextBlock上显示。


       五 控件的应用

       在使用控件的地方,为控件绑定好相关的属性就能获取到控件选择范围的上下界了。同时,因为控件内部的一些颜色属性通过依赖属性暴露了出来,所以可以在使用的地方灵活的更改控件各部分的颜色,以便得到想要的效果。

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        xmlns:rs="clr-namespace:RangeSelectors;assembly=RangeSelectors"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" Background="#ffffff">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="3*"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <rs:RangeSelector x:Name="rsTest" Margin="20"
                          Background="Transparent" 
                          SelectedRangeColor="#ff0000"
                          MessageForeground ="#ffffff"
                          BorderBrush="Red"
                          BorderThickness="0.5"
                          RangeBarHeight="15"
                          SelectorHeight="25"
                          SelectorWidth="12" RenderTransformOrigin="0.5,0.5">
            <rs:RangeSelector.RangeColor>
                <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                    <GradientStop Color="#ffffff" Offset="0.0" />
                    <GradientStop Color="#00ff00" Offset="0.8" />
                    <GradientStop Color="#009900" Offset="1" />
                </LinearGradientBrush>
            </rs:RangeSelector.RangeColor>

        </rs:RangeSelector>

        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <TextBox Text="{Binding LowerBoundaryValue, ElementName=rsTest, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Margin="20"/>
            <TextBox Text="{Binding UpperBoundaryValue, ElementName=rsTest, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Margin="20"/>
        </StackPanel>
    </Grid>
</Window>

  public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            rsTest.ConvertRangeToMessage = new Func<double, string>((d) =>
            {
                DateTime recordStartTime = DateTime.Now;
                DateTime recordEndTime = DateTime.Now.AddDays(1);
                
                double recordLengthInSecond = (recordEndTime - recordStartTime).TotalSeconds;
                DateTime selectedTime = recordStartTime.AddSeconds(recordLengthInSecond * d);

                return selectedTime.ToString("HH:mm:ss");
            });
        }
    }

       效果图:

       


       源代码

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Spring Boot 入门
优点: 快速创建独立运行spring项目和主流框架集成 嵌入式servlet容器, 应用无需打成war包 starters自动依赖与版本控制 大量的自动配置, 简化开发, 也可修改默认值 无需配置xml, 无代码生成, 开箱即用 准生产环境的运行时应用监控 与云计算的天然集成 基本要求: JDK 1.8 maven IDEA 生成项目 在线生成Demo的网站 Spring Initializr https://start.spring.io/ Aliyun Java Initializr https://start.aliyun.com/ 基本结构 启动引导Spring ReadingLi
1 0
RobotFrameWork接口设计规范
RobotFrameWork接口设计规范
2 0
MySQL 总结
引擎类型 与其他 DBMS 一样,MySQL 有一个具体管理和处理数据的内部引擎。在你使用CREATE TABLE 语句时,该引擎具体创建表,而在你使用 SELECT 语句或进行其他数据库处理时,该引擎在内部处理你的请求。多数时候,此引擎都隐藏在 DBMS 内,不需要过多关注它。但 MySQL 与其他 DBMS 不一样,它具有多种引擎。它打包多个引擎,这些引擎都隐藏在MySQL服务器内,全都能执行 CREATE TABLE 和 SELECT 等命令。为什么要发行多种引擎呢?因为它们具有各自不同的功能和特性,为不同的任务选择正确的引擎能获得良好的功能和灵活性。 以下是几个需要知道的引擎: ❑
4 0
Python HTML和CSS 1:html文档结构和常用标签
Python HTML和CSS 1:html文档结构和常用标签
4 0
SQL 简易教程 上
本节包含SQL 介绍,增删查改语句知识。 什么是数据库 数据库(database)保存有组织的数据的容器(通常是一个文件或一组文件)。 表(table)某种特定类型数据的结构化清单。 SQL 是什么 SQL(发音为字母S-Q-L或sequel)是 Structured Query Language(结构化查询语言)的缩写。SQL 是一种专门用来与数据库沟通的语言。 SQL 的扩展 许多 DBMS 厂商通过增加语句或指令,对 SQL 进行了扩展。这种扩展的目的是提供执行特定操作的额外功能或简化方法。虽然这种扩展很有用,但一般都是针对个别 DBMS 的,很少有两个厂商同时支持这种扩展。标准
5 0
Python HTML和CSS 2:表格、传统布局以及表单 介绍
Python HTML和CSS 2:表格、传统布局以及表单 介绍
3 0
说说Python编码规范
说说Python编码规范
4 0
面向对象七大设计原则
面向对象七大设计原则
3 0
Python HTML和CSS 3:CSS基本语法以及样式引入的三种方式
Python HTML和CSS 3:CSS基本语法以及样式引入的三种方式
1 0
Python HTML和CSS 5:CSS盒子模型
Python HTML和CSS 5:CSS盒子模型
1 0
+关注
杰克.陈
一个安静的程序猿~
10427
文章
2
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载