Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Scrollable Friction Canvas For Silverlight

0.00/5 (No votes)
9 Apr 2009 1  
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.

  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:

 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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here