How to set WPF ScrollViewer VerticalOffset and Horizontal offset

Today I needed to bind to the ScrolViewer VerticalOffset and Horizontal offset to do an animation for them. Yet unfortunately you cannot do this. In order to set these properties you need to use the ScrollToVerticalOffset or ScrollToHorizontalOffset methods :S This is not very good because it means you cannot do DataBinding or animate these properties….

Well as usual I resorted to  Attached Behaviour. Basically I created 2 attached properties one for HorizontalOffset and one for VerticalOffset. These properties just call the methods ScrollToVerticalOffset or ScrollToHorizontalOffset inside the property changed handlers… yep it’s as simple as that… Here is the attached behaviour class

   1: public class ScrollViewerUtilities

   2: {

   3:     #region HorizontalOffset

   4:  

   5:     /// <summary>

   6:     /// HorizontalOffset Attached Dependency Property

   7:     /// </summary>

   8:     public static readonly DependencyProperty HorizontalOffsetProperty =

   9:         DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(ScrollViewerUtilities),

  10:             new FrameworkPropertyMetadata((double)0.0,

  11:                 new PropertyChangedCallback(OnHorizontalOffsetChanged)));

  12:  

  13:     /// <summary>

  14:     /// Gets the HorizontalOffset property.  This dependency property 

  15:     /// indicates ....

  16:     /// </summary>

  17:     public static double GetHorizontalOffset(DependencyObject d)

  18:     {

  19:         return (double)d.GetValue(HorizontalOffsetProperty);

  20:     }

  21:  

  22:     /// <summary>

  23:     /// Sets the HorizontalOffset property.  This dependency property 

  24:     /// indicates ....

  25:     /// </summary>

  26:     public static void SetHorizontalOffset(DependencyObject d, double value)

  27:     {

  28:         d.SetValue(HorizontalOffsetProperty, value);

  29:     }

  30:  

  31:     /// <summary>

  32:     /// Handles changes to the HorizontalOffset property.

  33:     /// </summary>

  34:     private static void OnHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

  35:     {

  36:         var viewer = (ScrollViewer)d;

  37:         viewer.ScrollToHorizontalOffset((double)e.NewValue);

  38:     }

  39:  

  40:     #endregion

  41:  

  42:     #region VerticalOffset

  43:  

  44:     /// <summary>

  45:     /// VerticalOffset Attached Dependency Property

  46:     /// </summary>

  47:     public static readonly DependencyProperty VerticalOffsetProperty =

  48:         DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerUtilities),

  49:             new FrameworkPropertyMetadata((double)0.0,

  50:                 new PropertyChangedCallback(OnVerticalOffsetChanged)));

  51:  

  52:     /// <summary>

  53:     /// Gets the VerticalOffset property.  This dependency property 

  54:     /// indicates ....

  55:     /// </summary>

  56:     public static double GetVerticalOffset(DependencyObject d)

  57:     {

  58:         return (double)d.GetValue(VerticalOffsetProperty);

  59:     }

  60:  

  61:     /// <summary>

  62:     /// Sets the VerticalOffset property.  This dependency property 

  63:     /// indicates ....

  64:     /// </summary>

  65:     public static void SetVerticalOffset(DependencyObject d, double value)

  66:     {

  67:         d.SetValue(VerticalOffsetProperty, value);

  68:     }

  69:  

  70:     /// <summary>

  71:     /// Handles changes to the VerticalOffset property.

  72:     /// </summary>

  73:     private static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

  74:     {

  75:         var viewer = (ScrollViewer)d;

  76:         viewer.ScrollToVerticalOffset((double)e.NewValue);

  77:     }

  78:  

  79:     #endregion

  80:  

  81: }

and here is XAMl of how I use this

   1: <Grid>

   2:     <Grid.RowDefinitions>

   3:         <RowDefinition />

   4:         <RowDefinition Height="100"/>

   5:     </Grid.RowDefinitions>

   6:     <ScrollViewer x:Name="scrollViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible"  >

   7:         <Border Width="9000" Height="9000"/> 

   8:     </ScrollViewer>

   9:     

  10:     <StackPanel Grid.Row="1">

  11:         <WrapPanel>

  12:             <TextBlock Text="Slider for horizontal: Value:" Margin="0,0,10,0"/>    

  13:             <TextBlock Text="{Binding ElementName=scrollViewer, Path=HorizontalOffset}"/>

  14:         </WrapPanel>

  15:         <Slider Value="{Binding ElementName=scrollViewer, Path=(local:ScrollViewerUtilities.HorizontalOffset)}" 

  16:                 Minimum="0" Maximum="9000"/>

  17:         <WrapPanel>

  18:             <TextBlock Text="Slider for vertical: Value:" Margin="0,0,10,0"/>

  19:             <TextBlock Text="{Binding ElementName=scrollViewer, Path=VerticalOffset}"/>

  20:         </WrapPanel>

  21:         <Slider Value="{Binding ElementName=scrollViewer, Path=(local:ScrollViewerUtilities.VerticalOffset)}" 

  22:                 Minimum="0" Maximum="9000"/>

  23:         

  24:         <WrapPanel>

  25:             <TextBlock Text="Animate scroll" Margin="0,0,10,0"/>

  26:             <Button Content="Do it">

  27:                 <Button.Triggers>

  28:                     <EventTrigger RoutedEvent="Button.Click">

  29:                         <BeginStoryboard>

  30:                             <Storyboard>

  31:                                 <DoubleAnimation Storyboard.TargetName="scrollViewer" 

  32:                                          Storyboard.TargetProperty="(local:ScrollViewerUtilities.VerticalOffset)"

  33:                                          Duration="0:0:1" DecelerationRatio="1.0" To="8000"/>

  34:                             </Storyboard>

  35:                         </BeginStoryboard>

  36:                     </EventTrigger>

  37:                 </Button.Triggers>

  38:             </Button>

  39:         </WrapPanel>

  40:     </StackPanel>

  41: </Grid>

Download the sample app I built for this…