Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / C#

Scrollable Friction Canvas For Silverlight

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
9 Apr 2009CPOL 22.1K   9   2
Scrollable friction Canvas for Silverlight

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 DisciplesJeremiah 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.

C#
  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:                  // Determine the new amount to scroll.
 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:                  // Scroll to the new position.
 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:

XML
 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:

XML
 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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralHorizontal scrolling Pin
MasterX_Group19-Nov-10 21:48
MasterX_Group19-Nov-10 21:48 
GeneralRe: Horizontal scrolling Pin
Sacha Barber19-Nov-10 22:15
Sacha Barber19-Nov-10 22:15 
To be honest I had very little to do with the port to Silverlight, my original post was : http://sachabarber.net/?p=225 which was fine with inertia up down left and right. Jerimiah ported that code to Silverlight, which I just posted on my blog, have a look at my original code perhaps.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2010
  • Codeproject MVP 2008-2010
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.