Click here to Skip to main content
Click here to Skip to main content

Tagged as

Friction Scrolling Now An WPF Attached Behaviour Too

, 29 Dec 2009
Rate this:
Please Sign up or sign in to vote.
A while ago I wrote about how to create a scrollable design surface in WPF, and how you could also add friction into the mix.My original post was called “Creating A Scrollable Control Surface In WPF” which can be found at  the following url:http://sachabarber.net/?p=225This original blog post p

A while ago I wrote about how to create a scrollable design surface in WPF, and how you could also add friction into the mix.

My original post was called “Creating A Scrollable Control Surface In WPF” which can be found at  the following url:

http://sachabarber.net/?p=225

This original blog post proved to be quite popular and one of my fellow WPF Disciples my homeboy Jeremiah Morrill took it upon himself to rewrite my little control to be a content control for Silverlight, which you can get to at  “Scrollable Friction Canvas For Silverlight” which can be found at  the following url:

http://sachabarber.net/?p=481

I have been asked for my original code a lot, and another of my friends, and founder of the WPF Disciples, Marlon Grech took my code and has further improved it for WPF users, by making it an attached behaviour so all you have to do is hook up one property on your ScrollViewer and bingo its a Friction enabled surface. Neato I say.

Here is Marlons attached behaviour code:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Input;
   8:  using System.Windows.Threading;
   9:   
  10:  namespace ScrollableArea
  11:  {
  12:      public class KineticBehaviour
  13:      {
  14:          #region Friction
  15:   
  16:          /// <span class="code-SummaryComment"><summary>
</span>  17:          /// Friction Attached Dependency Property
  18:          /// <span class="code-SummaryComment"></summary>
</span>  19:          public static readonly DependencyProperty FrictionProperty =
  20:              DependencyProperty.RegisterAttached("Friction", 
  21:              typeof(double), typeof(KineticBehaviour),
  22:                  new FrameworkPropertyMetadata((double)0.95));
  23:   
  24:          /// <span class="code-SummaryComment"><summary>
</span>  25:          /// Gets the Friction property.  This dependency property 
  26:          /// indicates ....
  27:          /// <span class="code-SummaryComment"></summary>
</span>  28:          public static double GetFriction(DependencyObject d)
  29:          {
  30:              return (double)d.GetValue(FrictionProperty);
  31:          }
  32:   
  33:          /// <span class="code-SummaryComment"><summary>
</span>  34:          /// Sets the Friction property.  
  35:          /// <span class="code-SummaryComment"></summary>
</span>  36:          public static void SetFriction(DependencyObject d, double value)
  37:          {
  38:              d.SetValue(FrictionProperty, value);
  39:          }
  40:   
  41:          #endregion
  42:   
  43:          #region ScrollStartPoint
  44:   
  45:          /// <span class="code-SummaryComment"><summary>
</span>  46:          /// ScrollStartPoint Attached Dependency Property
  47:          /// <span class="code-SummaryComment"></summary>
</span>  48:          private static readonly DependencyProperty ScrollStartPointProperty =
  49:              DependencyProperty.RegisterAttached("ScrollStartPoint", 
  50:              typeof(Point), typeof(KineticBehaviour),
  51:                  new FrameworkPropertyMetadata((Point)new Point()));
  52:   
  53:          /// <span class="code-SummaryComment"><summary>
</span>  54:          /// Gets the ScrollStartPoint property.  
  55:          /// <span class="code-SummaryComment"></summary>
</span>  56:          private static Point GetScrollStartPoint(DependencyObject d)
  57:          {
  58:              return (Point)d.GetValue(ScrollStartPointProperty);
  59:          }
  60:   
  61:          /// <span class="code-SummaryComment"><summary>
</span>  62:          /// Sets the ScrollStartPoint property.  
  63:          /// <span class="code-SummaryComment"></summary>
</span>  64:          private static void SetScrollStartPoint(DependencyObject d, 
  65:              Point value)
  66:          {
  67:              d.SetValue(ScrollStartPointProperty, value);
  68:          }
  69:   
  70:          #endregion
  71:   
  72:          #region ScrollStartOffset
  73:   
  74:          /// <span class="code-SummaryComment"><summary>
</span>  75:          /// ScrollStartOffset Attached Dependency Property
  76:          /// <span class="code-SummaryComment"></summary>
</span>  77:          private static readonly DependencyProperty ScrollStartOffsetProperty =
  78:              DependencyProperty.RegisterAttached("ScrollStartOffset", 
  79:              typeof(Point), typeof(KineticBehaviour),
  80:                  new FrameworkPropertyMetadata((Point)new Point()));
  81:   
  82:          /// <span class="code-SummaryComment"><summary>
</span>  83:          /// Gets the ScrollStartOffset property.  
  84:          /// <span class="code-SummaryComment"></summary>
</span>  85:          private static Point GetScrollStartOffset(DependencyObject d)
  86:          {
  87:              return (Point)d.GetValue(ScrollStartOffsetProperty);
  88:          }
  89:   
  90:          /// <span class="code-SummaryComment"><summary>
</span>  91:          /// Sets the ScrollStartOffset property.  
  92:          /// <span class="code-SummaryComment"></summary>
</span>  93:          private static void SetScrollStartOffset(DependencyObject d, 
  94:              Point value)
  95:          {
  96:              d.SetValue(ScrollStartOffsetProperty, value);
  97:          }
  98:   
  99:          #endregion
 100:   
 101:          #region InertiaProcessor
 102:   
 103:          /// <span class="code-SummaryComment"><summary>
</span> 104:          /// InertiaProcessor Attached Dependency Property
 105:          /// <span class="code-SummaryComment"></summary>
</span> 106:          private static readonly DependencyProperty InertiaProcessorProperty =
 107:              DependencyProperty.RegisterAttached("InertiaProcessor", 
 108:              typeof(InertiaHandler), typeof(KineticBehaviour),
 109:                  new FrameworkPropertyMetadata((InertiaHandler)null));
 110:   
 111:          /// <span class="code-SummaryComment"><summary>
</span> 112:          /// Gets the InertiaProcessor property.  
 113:          /// <span class="code-SummaryComment"></summary>
</span> 114:          private static InertiaHandler GetInertiaProcessor(DependencyObject d)
 115:          {
 116:              return (InertiaHandler)d.GetValue(InertiaProcessorProperty);
 117:          }
 118:   
 119:          /// <span class="code-SummaryComment"><summary>
</span> 120:          /// Sets the InertiaProcessor property.  
 121:          /// <span class="code-SummaryComment"></summary>
</span> 122:          private static void SetInertiaProcessor(DependencyObject d, 
 123:              InertiaHandler value)
 124:          {
 125:              d.SetValue(InertiaProcessorProperty, value);
 126:          }
 127:   
 128:          #endregion
 129:   
 130:          #region HandleKineticScrolling
 131:   
 132:          /// <span class="code-SummaryComment"><summary>
</span> 133:          /// HandleKineticScrolling Attached Dependency Property
 134:          /// <span class="code-SummaryComment"></summary>
</span> 135:          public static readonly DependencyProperty 
 136:              HandleKineticScrollingProperty =
 137:              DependencyProperty.RegisterAttached("HandleKineticScrolling", 
 138:              typeof(bool), typeof(KineticBehaviour),
 139:                  new FrameworkPropertyMetadata((bool)false,
 140:                      new PropertyChangedCallback(
 141:                          OnHandleKineticScrollingChanged)));
 142:   
 143:          /// <span class="code-SummaryComment"><summary>
</span> 144:          /// Gets the HandleKineticScrolling property.  
 145:          /// <span class="code-SummaryComment"></summary>
</span> 146:          public static bool GetHandleKineticScrolling(DependencyObject d)
 147:          {
 148:              return (bool)d.GetValue(HandleKineticScrollingProperty);
 149:          }
 150:   
 151:          /// <span class="code-SummaryComment"><summary>
</span> 152:          /// Sets the HandleKineticScrolling property.  
 153:          /// <span class="code-SummaryComment"></summary>
</span> 154:          public static void SetHandleKineticScrolling(DependencyObject d, 
 155:              bool value)
 156:          {
 157:              d.SetValue(HandleKineticScrollingProperty, value);
 158:          }
 159:   
 160:          /// <span class="code-SummaryComment"><summary>
</span> 161:          /// Handles changes to the HandleKineticScrolling property.
 162:          /// <span class="code-SummaryComment"></summary>
</span> 163:          private static void OnHandleKineticScrollingChanged(DependencyObject d, 
 164:              DependencyPropertyChangedEventArgs e)
 165:          {
 166:              ScrollViewer scoller = d as ScrollViewer;
 167:              if ((bool)e.NewValue)
 168:              {
 169:                  scoller.PreviewMouseDown += OnPreviewMouseDown;
 170:                  scoller.PreviewMouseMove += OnPreviewMouseMove;
 171:                  scoller.PreviewMouseUp += OnPreviewMouseUp;
 172:                  SetInertiaProcessor(scoller, new InertiaHandler(scoller));
 173:              }
 174:              else
 175:              {
 176:                  scoller.PreviewMouseDown -= OnPreviewMouseDown;
 177:                  scoller.PreviewMouseMove -= OnPreviewMouseMove;
 178:                  scoller.PreviewMouseUp -= OnPreviewMouseUp;
 179:                  var inertia = GetInertiaProcessor(scoller);
 180:                  if (inertia != null)
 181:                      inertia.Dispose();
 182:              }
 183:              
 184:          }
 185:   
 186:          #endregion
 187:   
 188:          #region Mouse Events
 189:          private static void OnPreviewMouseDown(object sender, 
 190:              MouseButtonEventArgs e)
 191:          {
 192:              var scrollViewer = (ScrollViewer)sender;
 193:              if (scrollViewer.IsMouseOver)
 194:              {
 195:                  // Save starting point, used later when 
 196:                  //determining how much to scroll.
 197:                  SetScrollStartPoint(scrollViewer, 
 198:                      e.GetPosition(scrollViewer));
 199:                  SetScrollStartOffset(scrollViewer, 
 200:                      new Point(scrollViewer.HorizontalOffset, 
 201:                          scrollViewer.VerticalOffset));
 202:                  scrollViewer.CaptureMouse();
 203:              }
 204:          }
 205:   
 206:   
 207:          private static void OnPreviewMouseMove(object sender, MouseEventArgs e)
 208:          {
 209:              var scrollViewer = (ScrollViewer)sender;
 210:              if (scrollViewer.IsMouseCaptured)
 211:              {
 212:                  Point currentPoint = e.GetPosition(scrollViewer);
 213:   
 214:                  var scrollStartPoint = GetScrollStartPoint(scrollViewer);
 215:                  // Determine the new amount to scroll.
 216:                  Point delta = new Point(scrollStartPoint.X - 
 217:                      currentPoint.X, scrollStartPoint.Y - currentPoint.Y);
 218:   
 219:                  var scrollStartOffset = GetScrollStartOffset(scrollViewer);
 220:                  Point scrollTarget = new Point(scrollStartOffset.X + 
 221:                      delta.X, scrollStartOffset.Y + delta.Y);
 222:   
 223:                  var inertiaProcessor = GetInertiaProcessor(scrollViewer);
 224:                  if (inertiaProcessor != null)
 225:                      inertiaProcessor.ScrollTarget = scrollTarget;
 226:                  
 227:                  // Scroll to the new position.
 228:                  scrollViewer.ScrollToHorizontalOffset(scrollTarget.X);
 229:                  scrollViewer.ScrollToVerticalOffset(scrollTarget.Y);
 230:              }
 231:          }
 232:   
 233:          private static void OnPreviewMouseUp(object sender, 
 234:              MouseButtonEventArgs e)
 235:          {
 236:              var scrollViewer = (ScrollViewer)sender;
 237:              if (scrollViewer.IsMouseCaptured)
 238:              {
 239:                  scrollViewer.ReleaseMouseCapture();
 240:              }
 241:          }
 242:          #endregion
 243:   
 244:          #region Inertia Stuff
 245:   
 246:          /// <span class="code-SummaryComment"><summary>
</span> 247:          /// Handles the inertia 
 248:          /// <span class="code-SummaryComment"></summary>
</span> 249:          class InertiaHandler : IDisposable
 250:          {
 251:              private Point previousPoint;
 252:              private Vector velocity;
 253:              ScrollViewer scroller;
 254:              DispatcherTimer animationTimer;
 255:   
 256:              private Point scrollTarget;
 257:              public Point ScrollTarget 
 258:              {   
 259:                  get { return scrollTarget; } 
 260:                  set { scrollTarget = value; } 
 261:              }
 262:   
 263:              public InertiaHandler(ScrollViewer scroller)
 264:              {
 265:                  this.scroller = scroller;
 266:                  animationTimer = new DispatcherTimer();
 267:                  animationTimer.Interval = 
 268:                      new TimeSpan(0, 0, 0, 0, 20);
 269:                  animationTimer.Tick += 
 270:                      new EventHandler(HandleWorldTimerTick);
 271:                  animationTimer.Start();
 272:              }
 273:   
 274:              private void HandleWorldTimerTick(object sender, 
 275:                  EventArgs e)
 276:              {
 277:                  if (scroller.IsMouseCaptured)
 278:                  {
 279:                      Point currentPoint = Mouse.GetPosition(scroller);
 280:                      velocity = previousPoint - currentPoint;
 281:                      previousPoint = currentPoint;
 282:                  }
 283:                  else
 284:                  {
 285:                      if (velocity.Length > 1)
 286:                      {
 287:                          scroller.ScrollToHorizontalOffset(
 288:                              ScrollTarget.X);
 289:                          scroller.ScrollToVerticalOffset(
 290:                              ScrollTarget.Y);
 291:                          scrollTarget.X += velocity.X;
 292:                          scrollTarget.Y += velocity.Y;
 293:                          velocity *= 
 294:                              KineticBehaviour.GetFriction(scroller);
 295:                      }
 296:                  }
 297:              }
 298:   
 299:              #region IDisposable Members
 300:   
 301:              public void Dispose()
 302:              {
 303:                  animationTimer.Stop();
 304:              }
 305:   
 306:              #endregion
 307:          }
 308:   
 309:          #endregion
 310:      }
 311:  }

Which to use you would simply do this :

   1:  <Window x:Class="ScrollableArea.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:local="clr-namespace:ScrollableArea"
   5:      Title="Window1" Height="300" Width="300">
   6:      <Window.Resources>
   7:   
   8:          <!-- scroll viewer -->
   9:          <Style x:Key="ScrollViewerStyle"
  10:                 TargetType="{x:Type ScrollViewer}">
  11:              <Setter Property="HorizontalScrollBarVisibility" 
  12:                      Value="Hidden" />
  13:              <Setter Property="VerticalScrollBarVisibility" 
  14:                      Value="Hidden" />
  15:          </Style>
  16:   
  17:      </Window.Resources>
  18:   
  19:      <Grid Margin="0">
  20:          <ScrollViewer x:Name="ScrollViewer" 
  21:              Style="{StaticResource ScrollViewerStyle}" 
  22:              local:KineticBehaviour.HandleKineticScrolling="True">
  23:              <ItemsControl x:Name="itemsControl" 
  24:                            VerticalAlignment="Center">
  25:   
  26:                  <ItemsControl.ItemsPanel>
  27:                      <ItemsPanelTemplate>
  28:                          <!-- Custom Panel-->
  29:                          <StackPanel Orientation="Vertical"/>
  30:                      </ItemsPanelTemplate>
  31:                  </ItemsControl.ItemsPanel>
  32:   
  33:   
  34:              </ItemsControl>
  35:          </ScrollViewer>
  36:      </Grid>
  37:   
  38:  </Window>

As always here is a small demo app:

http://dl.dropbox.com/u/2600965/ScrollableAreaAttachedBehaviour.zip

License

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

Share

About the Author

Sacha Barber
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 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

 
QuestionFlowDocumentScroll Viewer Pinmembersharathkumar112-Oct-12 23:19 
QuestionListBox. PinmemberBrummell224-Aug-12 12:10 
AnswerRe: ListBox. PinmvpSacha Barber24-Aug-12 16:52 
GeneralRe: ListBox. PinmemberBrummell224-Aug-12 17:35 
GeneralMy vote of 3 Pinmemberbuyong27-Sep-11 17:17 
GeneralMy vote of 3 Pinmembermheskol1-Feb-11 6:29 
GeneralRe: My vote of 3 PinmemberMmPizza5-Apr-12 6:33 
QuestionQuestion Pinmemberstefan++15-Feb-10 23:08 
AnswerRe: Question PinmvpSacha Barber16-Feb-10 0:25 
GeneralRe: Question Pinmemberstefan++16-Feb-10 3:16 
GeneralRe: Question PinmvpSacha Barber16-Feb-10 3:19 
GeneralControls in the scrollviewer Pinmemberbflosabre9129-Dec-09 9:15 
GeneralRe: Controls in the scrollviewer PinmvpSacha Barber29-Dec-09 20:21 
GeneralRe: Controls in the scrollviewer PinmvpSacha Barber29-Dec-09 20:31 
GeneralRe: Controls in the scrollviewer Pinmemberbflosabre917-Jan-10 4:55 
GeneralRe: Controls in the scrollviewer PinmvpSacha Barber7-Jan-10 5:01 
GeneralRe: Controls in the scrollviewer Pinmemberbflosabre917-Jan-10 11:15 
GeneralRe: Controls in the scrollviewer PinmvpSacha Barber7-Jan-10 23:11 
GeneralRe: Controls in the scrollviewer PinmemberJean-Francois Dionne8-Nov-10 0:12 
AnswerRe: Controls in the scrollviewer [modified] Pinmemberefrost27-May-10 20:53 
GeneralRe: Controls in the scrollviewer [modified] Pinmemberefrost27-May-10 22:42 
GeneralRe: Controls in the scrollviewer PinmvpSacha Barber28-May-10 0:00 
GeneralRe: Controls in the scrollviewer Pinmembershuixingzhiyi22-Apr-11 21:21 
GeneralRe: Controls in the scrollviewer [modified] Pinmembersharathkumar1110-Oct-12 22:19 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140902.1 | Last Updated 29 Dec 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid