A while back, I published a post about creating a friction enabled scrolling canvas in WPF (the old post can be found at http://sachabarber.net/?p=225), which I thought was way cool. It turns out that I was not the only one that thought this, and one of my WPF Buddies and fellow WPF Disciples, Jeremiah Morrill thought it was so cool that he turned it into a Silverlight Content control. Thoughtful old Jerimiah even sent me the code back, which I think is awesome of him.
As a result, I thought I would let you all have it. So here goes.
This is what Jer has done, here is the actual ContentControl
, which has my friction stuff in it.
1: using System;
2: using System.Windows;
3: using System.Windows.Controls;
4: using System.Windows.Input;
5: using System.Windows.Threading;
6:
7: namespace FlickScrollerApp
8: {
9: [TemplatePart(Name = "PART_ScrollViewer",
Type = typeof(ScrollViewer))]
10: public class FlickScrollView : ContentControl
11: {
12: private readonly DispatcherTimer m_animationTimer =
13: new DispatcherTimer();
14: private double m_friction;
15: private Point m_currentMousePos;
16: private Point m_previousPoint;
17: private Point m_scrollStartOffset = new Point();
18: private Point m_scrollStartPoint;
19: private Point m_scrollTarget = new Point();
20: private Vector m_velocity;
21: private ScrollViewer scrollViewer;
22:
23: public FlickScrollView()
24: {
25: DefaultStyleKey = typeof(FlickScrollView);
26:
27: m_friction = 0.98;
28:
29: m_animationTimer.Interval = new
TimeSpan(0, 0, 0, 0, 20);
30: m_animationTimer.Tick += HandleWorldTimerTick;
31: m_animationTimer.Start();
32:
33: MouseMove += MouseMoveHandler;
34: MouseLeftButtonUp += MouseLeftButtonUpHandler;
35: MouseLeftButtonDown += MouseLeftButtonDownHandler;
36: }
37:
38: private bool IsMouseCaptured { get; set; }
39:
40: public double Friction
41: {
42: get { return 1.0 - m_friction; }
43: set { m_friction =
Math.Min(Math.Max(1.0 - value, 0), 1.0); }
44: }
45:
46: public override void OnApplyTemplate()
47: {
48: base.OnApplyTemplate();
49: scrollViewer = GetTemplateChild("PART_ScrollViewer")
as ScrollViewer;
50: }
51:
52: private void MouseLeftButtonDownHandler(object sender,
53: MouseButtonEventArgs e)
54: {
55: if (scrollViewer == null)
56: return;
57:
58: m_scrollStartPoint = e.GetPosition(this);
59: m_scrollStartOffset.X = scrollViewer.HorizontalOffset;
60: m_scrollStartOffset.Y = scrollViewer.VerticalOffset;
61:
62: CaptureMouse();
63:
64: IsMouseCaptured = true;
65: }
66:
67: private void MouseLeftButtonUpHandler(object sender,
68: MouseButtonEventArgs e)
69: {
70: if (!IsMouseCaptured)
71: return;
72:
73: ReleaseMouseCapture();
74: IsMouseCaptured = false;
75: }
76:
77: private void MouseMoveHandler(object sender, MouseEventArgs e)
78: {
79: if (scrollViewer == null)
80: return;
81:
82: m_currentMousePos = e.GetPosition(this);
83:
84: if (IsMouseCaptured)
85: {
86: Point currentPoint = e.GetPosition(this);
87:
88:
89: var delta = new Point(m_scrollStartPoint.X -
90: currentPoint.X, m_scrollStartPoint.Y - currentPoint.Y);
91:
92: m_scrollTarget.X = m_scrollStartOffset.X + delta.X;
93: m_scrollTarget.Y = m_scrollStartOffset.Y + delta.Y;
94:
95:
96: scrollViewer.ScrollToHorizontalOffset(m_scrollTarget.X);
97: scrollViewer.ScrollToVerticalOffset(m_scrollTarget.Y);
98: }
99: }
100:
101: private void HandleWorldTimerTick(object sender, EventArgs e)
102: {
103: if (scrollViewer == null)
104: return;
105:
106: if (IsMouseCaptured)
107: {
108: Point currentPoint = m_currentMousePos;
109: m_velocity.X = m_previousPoint.X - currentPoint.X;
110: m_velocity.Y = m_previousPoint.Y - currentPoint.Y;
111: m_previousPoint = currentPoint;
112: }
113: else
114: {
115: if (m_velocity.Length > 1)
116: {
117: scrollViewer.ScrollToHorizontalOffset(m_scrollTarget.X);
118: scrollViewer.ScrollToVerticalOffset(m_scrollTarget.Y);
119: m_scrollTarget.X += m_velocity.X;
120: m_scrollTarget.Y += m_velocity.Y;
121: m_velocity *= m_friction;
122: }
123: }
124: }
125: }
126: }
And here is how you would use it in XAML:
1: <UserControl x:Class="FlickScrollerApp.MainPage"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:FlickScrollerApp="clr-namespace:FlickScrollerApp"
5: Width="400″
6: Height="300″>
7: <Grid x:Name="LayoutRoot"
8: Background="White">
9: <FlickScrollerApp:FlickScrollView>
10: <StackPanel>
11: <Rectangle Fill="Red"
12: Width="400″
13: Height="200″
14: IsHitTestVisible="False" />
15: <Rectangle Fill="Blue"
16: Width="400″
17: Height="200″
18: IsHitTestVisible="False" />
19: <Rectangle Fill="Red"
20: Width="400″
21: Height="200″
22: IsHitTestVisible="False" />
23: <Rectangle Fill="Blue"
24: Width="400″
25: Height="200″
26: IsHitTestVisible="False" />
27: <Rectangle Fill="Red"
28: Width="400″
29: Height="200″
30: IsHitTestVisible="False" />
31: <Rectangle Fill="Blue"
32: Width="400″
33: Height="200″
34: IsHitTestVisible="False" />
35: <Rectangle Fill="Red"
36: Width="400″
37: Height="200″
38: IsHitTestVisible="False" />
39: </StackPanel>
40: </FlickScrollerApp:FlickScrollView>
41: </Grid>
42: </UserControl>
And another nice thing Jer has done is provide a Theme:
1: <ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:FlickScrollerApp="clr-namespace:FlickScrollerApp">
4: <Style TargetType="FlickScrollerApp:FlickScrollView">
5: <Setter Property="IsEnabled"
6: Value="true" />
7: <Setter Property="HorizontalContentAlignment"
8: Value="Left" />
9: <Setter Property="VerticalContentAlignment"
10: Value="Top" />
11: <Setter Property="Cursor"
12: Value="Arrow" />
13: <Setter Property="Background"
14: Value="#00000000″ />
15: <Setter Property="Template">
16: <Setter.Value>
17: <ControlTemplate TargetType="FlickScrollerApp:FlickScrollView">
18: <Border Background="{TemplateBinding Background}"
19: BorderBrush="{TemplateBinding BorderBrush}"
20: BorderThickness="{TemplateBinding BorderThickness}"
21: CornerRadius="2″>
22: <ScrollViewer x:Name="PART_ScrollViewer">
23: <ContentControl Content="{TemplateBinding Content}"
24: ContentTemplate="{TemplateBinding ContentTemplate}"
25: Cursor="{TemplateBinding Cursor}"
26: HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
27: HorizontalContentAlignment=
28: "{TemplateBinding HorizontalContentAlignment}"
29: FontFamily="{TemplateBinding FontFamily}"
30: FontSize="{TemplateBinding FontSize}"
31: FontStretch="{TemplateBinding FontStretch}"
32: Foreground="{TemplateBinding Foreground}"
33: Margin="{TemplateBinding Padding}"
34: VerticalAlignment="{TemplateBinding VerticalAlignment}"
35: VerticalContentAlignment=
36: "{TemplateBinding VerticalContentAlignment}" />
37: </ScrollViewer>
38: </Border>
39: </ControlTemplate>
40: </Setter.Value>
41: </Setter>
42: </Style>
43: </ResourceDictionary>
Here is a small Silverlight 3 project that demonstrates this.
Cheers Jer, you rock!