I am trying to create a floating slider. Because animations are freezable, besides that as soon as you drag the slider, its value changes and the thumb jumps to the position, it would be tricky to animate the actual thumb.
So what I did was to animate a fake thumb instead (I call it hooked thumb) and make the actual thumb transparent. that went pretty cool but there are few problems.
as you see in the following gif, Margin of hooked thumb is not responsive to width of track :
(Left margin is animated from codebehind.)
How can I make the margin responsive? (I don't want fix this problem from code behind)
This part is the template for style of slider.
<Grid Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}">
<Track x:Name="PART_Track">
<Track.DecreaseRepeatButton>
<RepeatButton x:Name="PART_SelectionRange"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Style="{StaticResource PlaybackScrollRepeatButtonStyle}"
Command="{x:Static Slider.DecreaseLarge}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Style="{StaticResource PlaybackScrollRepeatButtonStyle}"
Command="{x:Static Slider.IncreaseLarge}"/>
</Track.IncreaseRepeatButton>
<!-- Transparent thumb with width of 1px and auto height. -->
<Track.Thumb>
<Thumb x:Name="Thumb"
Style="{StaticResource PlaybackSliderHiddenThumbStyle}"/>
</Track.Thumb>
</Track>
<!-- This is the fake thumb. its margin is animated in codebehind. -->
<ContentControl x:Name="HookedThumb" HorizontalAlignment="Left"
Style="{StaticResource PlaybackSliderHookedThumbStyle}"
Background="{StaticResource Playback.Slider.Thumb.Brush}"
BorderBrush="{StaticResource Playback.Slider.Thumb.Border}"/>
</Grid>
I Know the reason why this is not responsive, because I'm using HorizontalAlignment="Left"
for the hooked thumb. If I use HorizontalAlignment="Stretch"
it is still not responsive. this is what you see :
(codebehind changed because margin is calculated in different way)
Now this is closer to the thumb but still not responsive.
I'm open with any solution. don't worry about animation. I will deal with that. what I want is to make hooked thumb be responsive like the main thumb is.
The other idea i had was to make border with thumb attached to it, and just animate the width of border but I cant get the right style. any help appreciated.
Here is the code behind in case you want to know (when HorizontalAlignment="Stretch"
)
// following will animate margin of hooked thumb to the location of thumb.
var cc = (ContentControl)PlaybackSlider.Template.FindName("HookedThumb", PlaybackSlider);
var track = (Track)PlaybackSlider.Template.FindName("PART_Track", PlaybackSlider);
var selection = (RepeatButton)PlaybackSlider.Template.FindName("PART_SelectionRange", PlaybackSlider);
var thumb = track.Thumb;
cc.Margin = new Thickness(-track.ActualWidth + cc.Width, 0, 0, 0); // starting margin
var keyframe = new SplineThicknessKeyFrame(); // holds the target keyframe.
var animator = new ThicknessAnimationUsingKeyFrames // this will animate margin
{
KeyFrames = new ThicknessKeyFrameCollection { keyframe },
BeginTime = TimeSpan.Zero,
Duration = TimeSpan.FromMilliseconds(200),
DecelerationRatio = 1
};
var storyboard = new Storyboard // we use storyboard so we can stop and begin animation at any time.
{
Children = new TimelineCollection { animator }
};
// setup storyboard with target property and dependency object.
Storyboard.SetTarget(animator, cc);
Storyboard.SetTargetProperty(animator, new PropertyPath(MarginProperty));
Action beginAnimation = () =>
{
storyboard.Stop(); // stop animation. change target, begin animation again.
var left = -track.ActualWidth + selection.Width*2; // calculate left margin
var correction = left; // set correction to the left margin
// prevent moving thumb out of range
if (correction < -track.ActualWidth + cc.Width) correction = -track.ActualWidth + cc.Width;
if (left + cc.Width > track.ActualWidth)
correction = track.ActualWidth - cc.Width;
// set new target frame
keyframe.Value = new Thickness(correction, 0, 0, 0);
storyboard.Begin();
};
// following are the handlers that begins the animation.
PlaybackSlider.ValueChanged += (o, e) =>
{
if(thumb.IsDragging) return;
beginAnimation();
};
thumb.DragStarted += (o, e) => beginAnimation();
thumb.DragDelta += (o, e) => beginAnimation();
thumb.DragCompleted += (o, e) => beginAnimation();
Here are the styles : Triggers excluded.
<!-- playback repeat button style -->
<Style x:Key="PlaybackScrollRepeatButtonStyle" TargetType="{x:Type RepeatButton}" BasedOn="{StaticResource {x:Type RepeatButton}}">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="Transparent">
<Rectangle Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding BorderBrush}"
VerticalAlignment="Center"
StrokeThickness="0.5"
Height="3"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- playback slider thumb-->
<Style x:Key="PlaybackSliderHiddenThumbStyle" TargetType="{x:Type Thumb}" BasedOn="{StaticResource {x:Type Thumb}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Canvas Background="Transparent" Width="1px"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- playback slider thumb decoy-->
<Style x:Key="PlaybackSliderHookedThumbStyle" TargetType="{x:Type ContentControl}" BasedOn="{StaticResource {x:Type ContentControl}}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="IsHitTestVisible" Value="False"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Width" Value="17"/>
<Setter Property="Height" Value="17"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Border Background="Transparent">
<Ellipse x:Name="Ellipse"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="1.5"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- playback slider style -->
<Style x:Key="PlaybackSliderStyle" TargetType="{x:Type Slider}" BasedOn="{StaticResource {x:Type Slider}}">
<Setter Property="Background" Value="{StaticResource Playback.Slider.Brush}"/>
<Setter Property="BorderBrush" Value="{StaticResource Playback.Slider.Border}"/>
<Setter Property="Foreground" Value="{StaticResource Playback.Slider.SelectionRange}"/>
<Setter Property="SelectionStart" Value="{Binding Minimum, RelativeSource={RelativeSource Self}}"/>
<Setter Property="SelectionEnd" Value="{Binding Value, RelativeSource={RelativeSource Self}}"/>
<Setter Property="IsSelectionRangeEnabled" Value="True"/>
<Setter Property="IsMoveToPointEnabled" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<Grid Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}">
<Track x:Name="PART_Track">
<Track.DecreaseRepeatButton>
<RepeatButton x:Name="PART_SelectionRange"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Style="{StaticResource PlaybackScrollRepeatButtonStyle}"
Command="{x:Static Slider.DecreaseLarge}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Style="{StaticResource PlaybackScrollRepeatButtonStyle}"
Command="{x:Static Slider.IncreaseLarge}"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="Thumb"
Style="{StaticResource PlaybackSliderHiddenThumbStyle}"/>
</Track.Thumb>
</Track>
<ContentControl x:Name="HookedThumb" HorizontalAlignment="Stretch"
Style="{StaticResource PlaybackSliderHookedThumbStyle}"
Background="{StaticResource Playback.Slider.Thumb.Brush}"
BorderBrush="{StaticResource Playback.Slider.Thumb.Border}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>