One of possible solutions. It uses two converters to calculate if it is possible to scroll. Template is based on the standard ScrollViewer
template, but with two additional text blocks to display information ("arrows").
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
Title="Window1" Height="300" Width="300"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1">
<Window.Resources>
<ResourceDictionary>
<WpfApplication1:ScrollViewerCanScrollUpConverter x:Key="ScrollViewerCanScrollUpConverter" />
<WpfApplication1:ScrollViewerCanScrollDownConverter x:Key="ScrollViewerCanScrollDownConverter" />
</ResourceDictionary>
</Window.Resources>
<Grid>
<ScrollViewer Background="Transparent">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid x:Name="Grid" Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="Corner" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Column="1" Grid.Row="1"/>
<ScrollContentPresenter Margin="{TemplateBinding Padding}" x:Name="PART_ScrollContentPresenter" Grid.Column="0" Grid.Row="0" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False"/>
<ScrollBar Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Cursor="Arrow" x:Name="PART_VerticalScrollBar" ViewportSize="{TemplateBinding ViewportHeight}" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Value="{Binding Path=VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Grid.Column="1" Grid.Row="0" AutomationProperties.AutomationId="VerticalScrollBar"/>
<ScrollBar Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Cursor="Arrow" x:Name="PART_HorizontalScrollBar" Orientation="Horizontal" ViewportSize="{TemplateBinding ViewportWidth}" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Value="{Binding Path=HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Grid.Column="0" Grid.Row="1" AutomationProperties.AutomationId="HorizontalScrollBar"/>
<TextBlock x:Name="PART_UpTextBlock" VerticalAlignment="Top" Text="Can scroll up" Visibility="Collapsed" />
<TextBlock x:Name="PART_DownTextBlock" VerticalAlignment="Bottom" Text="Can scroll down" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Value="True" Binding="{Binding Path=VerticalOffset, RelativeSource={RelativeSource Self}, Converter={StaticResource ScrollViewerCanScrollUpConverter}}">
<Setter TargetName="PART_UpTextBlock" Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ScrollViewerCanScrollDownConverter}">
<Binding Path="VerticalOffset" RelativeSource="{RelativeSource Self}" />
<Binding Path="ExtentHeight" RelativeSource="{RelativeSource Self}" />
<Binding Path="ViewportHeight" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="PART_DownTextBlock" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ScrollViewer.Template>
<Border Margin="10" Height="400" Background="Yellow">
<TextBlock Text="Content" />
</Border>
</ScrollViewer>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Globalization;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class Window1
{
public const double Epsilon = 0.001;
public Window1()
{
InitializeComponent();
}
}
public class ScrollViewerCanScrollUpConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Math.Abs((double)value) > Window1.Epsilon;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
public class ScrollViewerCanScrollDownConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double verticalOffset = (double)values[0];
double extentHeight = (double)values[1];
double viewportHeight = (double)values[2];
double maxOffset = Math.Max(0.0, extentHeight - viewportHeight);
return verticalOffset < maxOffset - Window1.Epsilon;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}