65.9K
CodeProject is changing. Read more.
Home

Scrollable Friction Canvas For Silverlight

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3 votes)

Apr 9, 2009

CPOL
viewsIcon

22591

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!