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>
Pingback: Dew Drop – September 15, 2009 | Alvin Ashcraft's Morning Dew
Pingback: Arjan’s World » LINKBLOG for Sep 15, 2009
Thanks. Great work.
I have just used the ScrollViewerUtilities class as it is in my main form. I have controlled movement of a scrollviewer from another one by the following.
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewerUtilities.SetVerticalOffset(scrollVert, cnvScroll.ContentVerticalOffset);
ScrollViewerUtilities.SetHorizontalOffset(scrollHori, cnvScroll.ContentHorizontalOffset);
}
Nice frickin’ work. Just dropped it into my project, changed the binding and BAM – perfect.
Halp!
I tried this, to bind one ScrollViewer’s horizontal offset to another’s.
And I get the following:
The property ‘ScrollViewer.HorizontalOffset’ cannot be set because it does not have an accessible set accessor.
‘HorizontalOffset’ property was registered as read-only and cannot be modified without an authorization key.
Any ideas?
Awesome, thank you!
Wtf the lines in the code example are huge! it makes it a pain to read the code..
What a terrible format. What could be useful is not because any attempt to copy the code is impossible