RangeSlider控件用于在一个指定上下限的范围中选择一个数值范围,因此该控件的Maximum和Minimum属性用于指定上下限;而SelectionStart和SelectionEnd用于指定选择的范围,还有一个Change属性用于指定SelectionStart和SelectionEnd的最小变化值。运行效果如下图所示。默认样式很难看,不过定制一个漂亮的样式很简单。
以下是控件的默认样式:
<Style TargetType="{x:Type local:RangeSlider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:RangeSlider}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<Grid Name="PART_GridContainerName">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<RepeatButton Grid.Column="0" Command="{x:Static local:RangeSlider.MoveBackward}"/>
<RepeatButton Grid.Column="2" Command="{x:Static local:RangeSlider.MoveForward}"/>
<Grid Grid.Column="1">
<Thumb Name="PART_LeftThumb" IsTabStop="False" Cursor="SizeWE" Width="5" Margin="-5 0 00" HorizontalAlignment="Left"/>
<Thumb Name="PART_CenterThumb" IsTabStop="False" Cursor="ScrollAll" MinWidth="5"/>
<Thumb Name="PART_RightThumb" IsTabStop="False" Cursor="SizeWE" Width="5" Margin="0 0 -50" HorizontalAlignment="Right"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
从控件的样式中可以看出,三个Thumb分别支持拖动改变开始值,结束值和同时改变两个值。两个RepeatButton支持按最小变化值同时改变开始值和结束值;按下Ctl键和左右箭头可达到相同的目的。
RangeSlider控件还实现了SelectedRangeChanged路由事件,这样就可以在SelectionStart和SelectionEnd改变的时候发出事件通知。
RangeSlider控件还支持命令;也就是说它实现了ICommandSource接口,这样就可以和MVVM模式很好的结合起来。剩下也没什么好说的了,代码贴上:
using System;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Windows.Controls;
usingSystem.Windows;
usingSystem.Windows.Input;
usingSystem.Windows.Controls.Primitives;
usingSystem.ComponentModel;
namespace MySolution.Controls
{
[TemplatePart(Name = GridContainerName, Type = typeof(Grid))]
[TemplatePart(Name = LeftThumbName, Type = typeof(Thumb))]
[TemplatePart(Name = CenterThumbName, Type = typeof(Thumb))]
[TemplatePart(Name = RightThumbName, Type = typeof(Thumb))]
public class RangeSlider : Control,ICommandSource
{
static RangeSlider()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(typeof(RangeSlider)));
}
#region PartNames
private conststring LeftThumbName = "PART_LeftThumb";
private conststring CenterThumbName = "PART_CenterThumb";
private conststring RightThumbName = "PART_RightThumb";
private conststring GridContainerName = "PART_GridContainerName";
#endregion
#region Minimum
public long Minimum
{
get { return(long)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public staticreadonly DependencyPropertyMinimumProperty =
DependencyProperty.Register("Minimum", typeof(long), typeof(RangeSlider),
new FrameworkPropertyMetadata(0L,OnMinimumChanged));
private staticvoid OnMinimumChanged(DependencyObjectsender, DependencyPropertyChangedEventArgse)
{
RangeSlider slider = sender as RangeSlider;
slider.CoerceValue(MaximumProperty);
slider.CoerceValue(SelectionStartProperty);
slider.CoerceValue(SelectionEndProperty);
slider.Relayout();
}
#endregion
#region Maximum
public longMaximum
{
get { return(long)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public staticreadonly DependencyPropertyMaximumProperty =
DependencyProperty.Register("Maximum", typeof(long), typeof(RangeSlider),
new FrameworkPropertyMetadata(100L,OnMaximumChanged, CoerceMaximum));
private staticvoid OnMaximumChanged(DependencyObjectsender, DependencyPropertyChangedEventArgse)
{
RangeSlider slider = sender as RangeSlider;
slider.CoerceValue(SelectionStartProperty);
slider.CoerceValue(SelectionEndProperty);
slider.Relayout();
}
private staticobject CoerceMaximum(DependencyObjectsender, object baseValue)
{
RangeSlider slider = sender as RangeSlider;
long value = (long)baseValue;
if (value < slider.Minimum + 1)
{
returnslider.Minimum + 1;
}
return baseValue;
}
#endregion
#regionSelectionStart
public longSelectionStart
{
get { return(long)GetValue(SelectionStartProperty); }
set { SetValue(SelectionStartProperty, value); }
}
public staticreadonly DependencyPropertySelectionStartProperty =
DependencyProperty.Register("SelectionStart", typeof(long), typeof(RangeSlider),
new FrameworkPropertyMetadata(0L,OnSelectionStartChanged, CoerceSelectionStart));
private staticvoid OnSelectionStartChanged(DependencyObject sender, DependencyPropertyChangedEventArgse)
{
RangeSlider slider = sender as RangeSlider;
slider.CoerceValue(SelectionEndProperty);
slider.Relayout();
}
private staticobject CoerceSelectionStart(DependencyObject sender, objectbaseValue)
{
RangeSlider slider = sender as RangeSlider;
long value = (long)baseValue;
if (slider.SelectionEnd >=slider.Minimum && slider.SelectionEnd <= slider.Maximum)
{
if(value > slider.SelectionEnd)
{
returnslider.SelectionEnd;
}
}
if (value < slider.Minimum)
{
returnslider.Minimum;
}
if (value > slider.Maximum)
{
returnslider.Maximum;
}
return baseValue;
}
#endregion
#regionSelectionEnd
public longSelectionEnd
{
get { return(long)GetValue(SelectionEndProperty); }
set { SetValue(SelectionEndProperty, value); }
}
public staticreadonly DependencyPropertySelectionEndProperty =
DependencyProperty.Register("SelectionEnd", typeof(long), typeof(RangeSlider),
new FrameworkPropertyMetadata(10L,OnSelectionEndChanged, CoerceSelectionEnd));
private staticvoid OnSelectionEndChanged(DependencyObject sender, DependencyPropertyChangedEventArgse)
{
RangeSlider slider = sender as RangeSlider;
slider.CoerceValue(SelectionStartProperty);
slider.Relayout();
}
private staticobject CoerceSelectionEnd(DependencyObject sender, objectbaseValue)
{
RangeSlider slider = sender as RangeSlider;
long value = (long)baseValue;
if (slider.SelectionStart >=slider.Minimum && slider.SelectionStart <= slider.Maximum)
{
if(value < slider.SelectionStart)
{
returnslider.SelectionStart;
}
}
if (value < slider.Minimum)
{
returnslider.Minimum;
}
if (value > slider.Maximum)
{
returnslider.Maximum;
}
returnbaseValue;
}
#endregion
#region Change
public longChange
{
get { return(long)GetValue(ChangeProperty); }
set { SetValue(ChangeProperty, value); }
}
public staticreadonly DependencyPropertyChangeProperty =
DependencyProperty.Register("Change", typeof(long), typeof(RangeSlider),
new FrameworkPropertyMetadata(1L),ValidateChange);
private staticbool ValidateChange(objectvalue)
{
return (long)value!= 0;
}
#endregion
#region RoutedEvents
///<summary>
/// Eventraised whenever the selected range is changed
///</summary>
public staticreadonly RoutedEventSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"SelectionChangedEvent",
RoutingStrategy.Bubble,
typeof(EventHandler<RangeSelectionChangedEventArgs>),
typeof(RangeSlider));
///<summary>
/// Eventraised whenever the selected range is changed
///</summary>
public eventEventHandler<RangeSelectionChangedEventArgs>SelectedRangeChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove {RemoveHandler(SelectionChangedEvent, value); }
}
private voidOnSelectedRangeChanged(RangeSelectionChangedEventArgse)
{
e.RoutedEvent = SelectionChangedEvent;
RaiseEvent(e);
ExecuteCommand();
}
private voidExecuteCommand()
{
if (Command != null)
{
RoutedCommandcommand = Command as RoutedCommand;
if(command != null)
{
command.Execute(CommandParameter,CommandTarget);
}
else
{
Command.Execute(CommandParameter);
}
}
}
#endregion
#regionCommands
#region Commanddeclaration
///<summary>
/// Command tomove back the selection
///</summary>
public staticRoutedUICommand MoveBackward =
new RoutedUICommand("Move Backward", "MoveBackward", typeof(RangeSlider),
new InputGestureCollection(new InputGesture[]{ new KeyGesture(Key.Left, ModifierKeys.Control)}));
///<summary>
/// Command tomove forward the selection
///</summary>
public staticRoutedUICommand MoveForward =
new RoutedUICommand("Move Forward", "MoveForward",typeof(RangeSlider),
new InputGestureCollection(new InputGesture[]{ new KeyGesture(Key.Right, ModifierKeys.Control)}));
#endregion
#region CommandHandlers
private voidMoveBackwardHandler(object sender, ExecutedRoutedEventArgs e)
{
MoveSelection(-Change);
}
private voidMoveForwardHandler(object sender, ExecutedRoutedEventArgs e)
{
MoveSelection(Change);
}
#endregion
#endregion
#regionConstructor
public RangeSlider()
{
CommandBindings.Add(new CommandBinding(MoveBackward,MoveBackwardHandler));
CommandBindings.Add(new CommandBinding(MoveForward, MoveForwardHandler));
}
#endregion
#region Fields
private Thumb_centerThumb;
private Thumb_leftThumb;
private Thumb_rightThumb;
private Grid_gridContainer;
#endregion
public overridevoid OnApplyTemplate()
{
base.OnApplyTemplate();
_centerThumb = Template.FindName(CenterThumbName, this) as Thumb;
_leftThumb = Template.FindName(LeftThumbName, this)as Thumb;
_rightThumb = Template.FindName(RightThumbName, this)as Thumb;
_gridContainer = Template.FindName(GridContainerName, this) as Grid;
_leftThumb.DragDelta += DragSelectionStart;
_centerThumb.DragDelta += DragSelection;
_rightThumb.DragDelta += DragSelectionEnd;
Relayout();
}
private voidRelayout()
{
if (_gridContainer == null || SelectionEnd - SelectionStart < 0)
{
return;
}
double colWidth1 = SelectionStart -Minimum;
double colWidth2 = SelectionEnd -SelectionStart;
double colWidth3 = Maximum -SelectionEnd;
_gridContainer.ColumnDefinitions[0].Width = newGridLength(colWidth1, GridUnitType.Star);
_gridContainer.ColumnDefinitions[1].Width = newGridLength(colWidth2, GridUnitType.Star);
_gridContainer.ColumnDefinitions[2].Width = newGridLength(colWidth3, GridUnitType.Star);
OnSelectedRangeChanged(new RangeSelectionChangedEventArgs(SelectionStart,SelectionEnd));
}
#regionDragHandlers
private voidDragSelection(object sender, DragDeltaEventArgs e)
{
long offsetValue =GetNormalizedValue(e.HorizontalChange);
MoveSelection(offsetValue);
}
private voidMoveSelection(long offsetValue)
{
if (offsetValue < 0)
{
if(SelectionStart == Minimum)
{
return;
}
longrange = SelectionEnd - SelectionStart;
longnewValue = SelectionStart + offsetValue;
SelectionStart = Math.Max(Minimum, newValue);
SelectionEnd = SelectionStart +range;
}
else if(offsetValue > 0)
{
if(SelectionEnd == Maximum)
{
return;
}
longrange = SelectionEnd - SelectionStart;
longnewValue = SelectionEnd + offsetValue;
SelectionEnd = Math.Min(Maximum, newValue);
SelectionStart = SelectionEnd -range;
}
}
private voidMoveSelectionStart(long offsetValue)
{
if (offsetValue < 0)
{
if(SelectionStart == Minimum)
{
return;
}
longnewValue = SelectionStart + offsetValue;
SelectionStart = Math.Max(Minimum, newValue);
}
else if(offsetValue > 0)
{
longmax = SelectionEnd - Change;
if(SelectionStart == max)
{
return;
}
longnewValue = SelectionStart + offsetValue;
SelectionStart = Math.Min(max, newValue);
}
}
private voidMoveSelectionEnd(long offsetValue)
{
if (offsetValue < 0)
{
longmin = SelectionStart + Change;
if(SelectionEnd == min)
{
return;
}
longnewValue = SelectionEnd + offsetValue;
SelectionEnd = Math.Max(min, newValue);
}
else if(offsetValue > 0)
{
if(SelectionEnd == Maximum)
{
return;
}
longnewValue = SelectionEnd + offsetValue;
SelectionEnd = Math.Min(Maximum, newValue);
}
}
private void DragSelectionEnd(objectsender, DragDeltaEventArgs e)
{
long offsetValue =GetNormalizedValue(e.HorizontalChange);
MoveSelectionEnd(offsetValue);
}
private voidDragSelectionStart(object sender, DragDeltaEventArgs e)
{
long offsetValue =GetNormalizedValue(e.HorizontalChange);
MoveSelectionStart(offsetValue);
}
#endregion
#region HelperMethod
private longGetNormalizedValue(double pixelLength)
{
long offsetValue = (long)((Maximum - Minimum) * (pixelLength /RenderSize.Width));
long mod = offsetValue % Change;
if (Math.Abs(mod)< Change / 2)
{
offsetValue = Math.Sign(offsetValue) * ((long)(Math.Abs(offsetValue) /Change)) * Change;
}
else
{
offsetValue = Math.Sign(offsetValue) * ((long)(Math.Abs(offsetValue) /Change) + 1) * Change;
}
return offsetValue;
}
#endregion
#regionICommandSource Members
public staticreadonly DependencyPropertyCommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(RangeSlider), new PropertyMetadata(null,OnCommandChanged));
public staticreadonly DependencyPropertyCommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(RangeSlider),new FrameworkPropertyMetadata(null));
public staticreadonly DependencyPropertyCommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(RangeSlider),new FrameworkPropertyMetadata(null));
public objectCommandParameter
{
get { return(object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty,value); }
}
public ICommandCommand
{
get { return(ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public IInputElementCommandTarget
{
get { return(IInputElement)GetValue(CommandTargetProperty);}
set { SetValue(CommandTargetProperty, value); }
}
private staticvoid OnCommandChanged(DependencyObjectsender, DependencyPropertyChangedEventArgse)
{
RangeSlider slider = sender as RangeSlider;
slider.OnCommandChanged(e.OldValue as ICommand, e.NewValue asICommand);
}
private voidOnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -=CanExecuteChanged;
}
if (newCommand != null)
{
newCommand.CanExecuteChanged +=CanExecuteChanged;
}
}
private voidCanExecuteChanged(object sender, EventArgs e)
{
if (Command != null)
{
RoutedCommandcommand = Command as RoutedCommand;
if(command != null)
{
IsEnabled = command.CanExecute(CommandParameter,CommandTarget);
}
else
{
IsEnabled =Command.CanExecute(CommandParameter);
}
}
}
#endregion
}
}