views:

6

answers:

1

I am wanting to show a half opaque arrow at the top and bottom of my ScrollViewer if it can scroll up or down respectively. I'm thinking the best option is a DataTrigger, but I'm not sure what I can tie it too. I'm trying to avoid subclassing ScrollViewer, but if I absolutely have to, I will. Any ideas?

I'm using the .Net Framework 3.5 (man I wish I could upgrade!).

Thanks. :)

+1  A: 

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();
        }
    }
}
Athari
Not what I ended up doing, but very nice! I ended up creating properties in the code behind that returned boolean values as to whether the scroll bar could scroll up or down. I named my user control and accessed them through that. :)
Jordan