Reputation: 876
I've created custom slider and thumb templates for my app. It's working as expected except for the PART_SelectionRange Rectangle, which begins slightly behind the thumb and by the time the maximum value is reached is noticeably far behind it.
I have no clue how this could be fixed since it looks like the problem is in the behaviour of whatever class is controlling the size of the PART_SelectionRange Rectangle.
(Sorry, I don't know how to inline it).
<ControlTemplate x:Key="SliderThumbHorizontalDefault" TargetType="{x:Type Thumb}">
<Grid>
<!-- SliderThumb -->
<Path x:Name="grip" Data="M-0.4,6.4 C1.2,1 9,1 10.5,6.5 12,11.787571 5.0267407,18.175417 5.0267407,18.175417 5.0267407,18.175417 -2,11.8 -0.4,6.4 z"
Fill="White" Stretch="Fill" SnapsToDevicePixels="True"
Stroke="Black" StrokeThickness="1" UseLayoutRounding="True" />
<ContentPresenter Margin="0 4 0 0" Content="{Binding Value, RelativeSource={RelativeSource AncestorType={x:Type Slider}}}" TextBlock.Foreground="Black" TextBlock.FontSize="20" TextBlock.TextAlignment="Center" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="SliderHorizontal" TargetType="{x:Type Slider}">
<ControlTemplate.Resources>
<vc:TickConverter x:Key="TickConverter" />
</ControlTemplate.Resources>
<Grid Height="{StaticResource SliderHeight}">
<Border x:Name="TrackBackground" BorderBrush="{StaticResource SliderThumb.Track.Border}" BorderThickness="0"
Background="White"
CornerRadius="{Binding ActualHeight, Converter={StaticResource HalvingConverter}, ElementName=TrackBackground}">
<!-- This rectangle is what lags behind! -->
<Rectangle x:Name="PART_SelectionRange" Fill="SlateBlue"
HorizontalAlignment="Left" RadiusY="3" RadiusX="3" />
</Border>
<Track x:Name="PART_Track" >
<Track.Thumb>
<Thumb x:Name="Thumb" Focusable="False" Width="35" Height="47" OverridesDefaultStyle="True"
Template="{StaticResource SliderThumbHorizontalDefault}" Margin="-14.65,-55,-16,12"/>
</Track.Thumb>
</Track>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelectionRangeEnabled" Value="true">
<Setter Property="Visibility" TargetName="PART_SelectionRange" Value="Visible" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="Foreground" TargetName="Thumb" Value="Blue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type Slider}">
<Setter Property="MinHeight" Value="{StaticResource SliderHeight}" />
<Setter Property="MaxHeight" Value="{StaticResource SliderHeight}" />
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false" />
<Setter Property="IsSelectionRangeEnabled" Value="True" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Foreground" Value="{StaticResource SliderThumb.Static.Foreground}" />
<Setter Property="SelectionStart" Value="{Binding Minimum, RelativeSource={RelativeSource Self}}" />
<Setter Property="SelectionEnd" Value="{Binding Value, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template" Value="{StaticResource SliderHorizontal}" />
</Style>
At this point I don't know what I should be trying to modify. I believe I've tried changing every property that made sense from xaml, so I think the problem is in the Slider class, but can't fathom where.
Upvotes: 4
Views: 1409
Reputation: 876
Following @dymanoid 's answer, I implemented the behaviour manually (after failing to create a new slider).
I removed the PART_SelectionRange control (had to make it transparent or it wouldn't compile) replacing it by this other rectangle.
<Border x:Name="TrackBackground" BorderBrush="{StaticResource SliderThumb.Track.Border}" BorderThickness="0"
Background="{StaticResource SliderThumb.Track.Background}"
CornerRadius="{Binding ActualHeight, Converter={StaticResource HalvingConverter}, ElementName=TrackBackground}">
<Rectangle x:Name="SelectionRange" Fill="{StaticResource SliderThumb.Track.BackgroundSelected}"
HorizontalAlignment="Left" RadiusY="3" RadiusX="3">
<Rectangle.Width>
<MultiBinding Converter="{StaticResource SliderToSelectionWidthConverter}">
<Binding Path="Value" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="Maximum" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="Minimum" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Rectangle.Width>
</Rectangle>
</Border>
The relevant thing is that its width is bound to the Slider's Value, Minimum, Maximum and ActualWidth properties. Using this converter:
class SliderToSelectionWidthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var Value = (double) values[0];
var Minimum = (double) values[1];
var Maximum = (double) values[2];
var ActualWidth = (double) values[3];
return (1 - (Value - Minimum) / (Maximum - Minimum)) * ActualWidth;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
those values can be used to compute the real width of the SelectionRange rectangle. Why this is not done in the default Slider is beyond me.
Upvotes: 1
Reputation: 15227
Unfortunately, you cannot change this behavior of the Slider
just using custom Style
s and ControlTemplate
s.
The reason is that the Slider
control adjusts the width of the selection range according to the width of the Thumb
. This causes the selection range bar to "hang back" while you're moving the slider position towards its Maximum
value. If you'd like the selection range to exactly follow the Value
, you'd need to set the Thumb
width to 0. But then, obviously, you won't be able to move the thumb since there will be no hit testing anymore.
This is the current implementation of the corresponding selection range size and position adjustments:
if (DoubleUtil.AreClose(range, 0d) || (DoubleUtil.AreClose(trackSize.Width, thumbSize.Width)))
{
valueToSize = 0d;
}
else
{
valueToSize = Math.Max(0.0, (trackSize.Width - thumbSize.Width) / range);
}
rangeElement.Width = ((SelectionEnd - SelectionStart) * valueToSize);
if (IsDirectionReversed)
{
Canvas.SetLeft(rangeElement, (thumbSize.Width * 0.5) + Math.Max(Maximum - SelectionEnd, 0) * valueToSize);
}
else
{
Canvas.SetLeft(rangeElement, (thumbSize.Width * 0.5) + Math.Max(SelectionStart - Minimum, 0) * valueToSize);
}
You have two options now:
Thumb
width"PART_SelectionRange"
name from the selection rectangle in your ControlTemplate
and use Binding
s with IValueConverter
s (or Behavior
s, or attached properties) to control the appearance manuallyUpvotes: 1