I created a Behavior<ScrollBar>
with a dependency property for the scroll amount. I applied the behavior to the ScrollBars inside the ScrollViewer control template, and bound the HorizontalOffset
/ VerticalOffset
properties to the behavior's DPs. Then, I added some visual states to the ScrollBar's control template. The behavior is then responsible for updating the visual state when the scroll offset changes.
This is the behavior (actually need 2 extra dependency properties to complete the calculation: size of the viewport, and extent of the scroll bar):
public enum ScrollBarDirection { Vertical, Horizontal } ;
public class HideScrollButtonsBehavior : Behavior<ScrollBar>
{
public ScrollBarDirection Direction { get; set; }
public double VerticalScrollAmount
{
get { return (double)GetValue(VerticalScrollAmountProperty); }
set { SetValue(VerticalScrollAmountProperty, value); }
}
public static readonly DependencyProperty VerticalScrollAmountProperty =
DependencyProperty.Register("VerticalScrollAmount", typeof(double), typeof(HideScrollButtonsBehavior), new PropertyMetadata(ScrollChanged));
public double ScrollExtent
{
get { return (double)GetValue(ScrollExtentProperty); }
set { SetValue(ScrollExtentProperty, value); }
}
public static readonly DependencyProperty ScrollExtentProperty =
DependencyProperty.Register("ScrollExtent", typeof(double), typeof(HideScrollButtonsBehavior), new PropertyMetadata(null));
public double ViewportExtent
{
get { return (double)GetValue(ViewportExtentProperty); }
set { SetValue(ViewportExtentProperty, value); }
}
public static readonly DependencyProperty ViewportExtentProperty =
DependencyProperty.Register("ViewportExtent", typeof(double), typeof(HideScrollButtonsBehavior), new PropertyMetadata(null));
public static void ScrollChanged(object sender, DependencyPropertyChangedEventArgs args)
{
if (args.NewValue as double? == null)
return;
var owner = (HideScrollButtonsBehavior)sender;
ScrollBar scrollBar = owner.AssociatedObject;
double scrollPosition = (double)args.NewValue;
if (scrollPosition <= 0)
VisualStateManager.GoToState(scrollBar, owner.Direction + "DownOnly", true);
else if (scrollPosition >= owner.ScrollExtent - owner.ViewportExtent)
VisualStateManager.GoToState(scrollBar, owner.Direction + "UpOnly", true);
else
VisualStateManager.GoToState(scrollBar, owner.Direction + "Both", true);
}
}
These are applied to the ScrollBar
instances inside the ScrollViewer
control template like this (DPs set using a RelativeSource
binding):
<ScrollBar x:Name="VerticalScrollBar"
IsTabStop="False"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Maximum="{TemplateBinding ScrollableHeight}"
Minimum="0"
Value="{TemplateBinding VerticalOffset}">
<i:Interaction.Behaviors>
<behavior:HideScrollButtonsBehavior
VerticalScrollAmount="{Binding RelativeSource={RelativeSource AncestorType=ScrollViewer},Path=VerticalOffset}"
ScrollExtent="{Binding RelativeSource={RelativeSource AncestorType=ScrollViewer},Path=ExtentHeight}"
ViewportExtent="{Binding RelativeSource={RelativeSource AncestorType=ScrollViewer},Path=ViewportHeight}"
Direction="Vertical"
/>
</i:Interaction.Behaviors>
</ScrollBar>
And finally, the visual states in the ScrollBar
control template just show/hide the increase/decrease buttons as needed:
<VisualStateGroup x:Name="VerticalScrollButtonVisibilityStates">
<VisualState x:Name="VerticalDownOnly">
<Storyboard Duration="0:0:0.1">
<DoubleAnimation Storyboard.TargetName="VerticalSmallDecrease" Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:0.1" />
<DoubleAnimation Storyboard.TargetName="VerticalSmallIncrease" Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
<VisualState x:Name="VerticalUpOnly">
<Storyboard Duration="0:0:0.1">
<DoubleAnimation Storyboard.TargetName="VerticalSmallDecrease" Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:0.1" />
<DoubleAnimation Storyboard.TargetName="VerticalSmallIncrease" Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
<VisualState x:Name="VerticalBoth">
<Storyboard Duration="0:0:0.1">
<DoubleAnimation Storyboard.TargetName="VerticalSmallDecrease" Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:0.1" />
<DoubleAnimation Storyboard.TargetName="VerticalSmallIncrease" Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
</VisualStateGroup>