views:

2660

answers:

3

Is it possible to implement smooth scroll in a WPF listview like how it works in Firefox?
When the Firefox browser contained all listview items and you hold down the middle mouse button (but not release), and drag it, it should smoothly scroll the listview items. When you release it should stop.

It looks like this is not possible in winforms, but I am wondering if it is available in WPF?

+4  A: 

You can achieve smooth scrolling but you lose item virtualisation, so basically you should use this technique only if you have few elements in the list:

Info here: Smooth scrolling on listbox

Pop Catalin
Thanks Pop. What's virtualisation?
Joan Venge
Virtualization is where content inside a VirtualizingStackPanel (Which is the default ItemsPanel for ListBoxs and such) does not actually render the layout for the items until/unless the item is visible. So, with numerous or visually complex items it can offer a significant performance boost, since it's only generating the ui for a small percentage at a time.More info here:http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel.aspx
rmoore
Doing this is switching from logical to physical scrolling (assuming you are using a StackPanel which implements IScrollInfo). If you still want logical scrolling but to make it smooth, this does not help
Schneider
+1  A: 

Try setting the ScrollViewer.CanContentScroll attached property to false on the ListView. But like Pop Catalin said, you lose item virtualization, meaning all the items in the list get loaded and populated at once, not when a set of items are needed to be displayed - so if the list is huge, it could cause some memory and performance issues.

Eddie
Thanks. I see, yeah that would be fine. But when I set this property to false, how will I implement the scroll? Like if I middle click+drag down, I want it to scroll down by an amount smoothly. Or is this feature already built in but activated by this property?
Joan Venge
It's activated by the property automatically. I'm not sure about the middle button scroll feature, but the list should still scroll. The CanContentScroll name is a little misleading, imho :) I'll give it a try to double check it.
Eddie
Thanks Eddie. Yeah strange name for sure. So this scrolling when the property is enabled works with the scrollbar then, right?
Joan Venge
Yup, apparently so. I just did a tiny little app, and the attached property seems to only affect the way it scrolls when you actually grab the scroller and drag it. Clicking the arrows still makes it jump. And no built in middle button scrolling. I'm sure that can somehow be added in semi-manually. Still looking - I'd like the same functionality :)
Eddie
Look at this related question/answer - it looks like you'll have to do some manual work to handle the mouse how you want, but it also would give you control over how smooth/how many pixels you scroll by:http://stackoverflow.com/questions/1009036/how-can-i-programmatically-scroll-a-wpf-listview
Eddie
Thanks Eddie. Then to me this property seems like a different feature. I mean one can implement the middle scroll independent of virtualization, right? (whether it's dynamic or at once)
Joan Venge
That's what it looks like. The CanContentScroll property seems to be the quick way out, but has side-effects (no virtualization). But grabbing the actual ScrollViewer seems to give you a lot more power - a lot more code, but lets you do what you want. It might still have virtualization issues. I'm not sure - I have a hard time seeing how custom scrolling would still permit loading only the needed items into memory at once.Here's another great post: http://stackoverflow.com/questions/876994/in-wpf-how-do-i-adjust-the-scroll-increment-for-a-flowdocumentreader-with-viewin/973500#973500
Eddie
Thanks Eddie, I will try those out and see if it works.
Joan Venge
@Eddie: Using those methods won't interfere with virtualization, a good way to test this is to place a very wide item at the top and then scroll down, the HorizontalScrollBar will disappear. (Or if the orientation is flipped you can make the VerticalScrollBar disapear) @Joan Venge Because of that behavior with virtualization, I think it's better to disable virtualization if there's any expectation of the items having different heights and widths.
rmoore
Thanks rmoore. Items are same size, at least height for sure. Widths are very similar.
Joan Venge
+3  A: 

It is indeed possible to do what you're asking, though it will require a fair amount of custom code.

Normally in WPF a ScrollViewer uses what is known as Logical Scrolling, which means it's going to scroll item by item instead of by an offset amount. The other answers cover some of the ways you can change the Logical Scrolling behavior into that of Physical Scrolling. The other way is to make use of the ScrollToVertialOffset and ScrollToHorizontalOffset methods exposed by both ScrollViwer and IScrollInfo.

To implement the larger part, the scrolling when the mouse wheel is pressed, we will need to make use of the MouseDown and MouseMove events.

<ListView x:Name="uiListView"
    Mouse.MouseDown="OnListViewMouseDown"
    Mouse.MouseMove="OnListViewMouseMove"
    ScrollViewer.CanContentScroll="False">
    ....
</ListView>

In the MouseDown, we are going to record the current mouse position, which we will use as a relative point to determine which direction we scroll in. In the mouse move, we are going to get the ScrollViwer component of the ListView and then Scroll it accordingly.

private Point myMousePlacementPoint;

private void OnListViewMouseDown(object sender, MouseButtonEventArgs e)
{
 if (e.MiddleButton == MouseButtonState.Pressed)
 {
  myMousePlacementPoint = this.PointToScreen(Mouse.GetPosition(this));
 }
}

private void OnListViewMouseMove(object sender, MouseEventArgs e)
{
 ScrollViewer scrollViewer = ScrollHelper.GetScrollViewer(uiListView) as ScrollViewer;

 if (e.MiddleButton == MouseButtonState.Pressed)
 {
  var currentPoint = this.PointToScreen(Mouse.GetPosition(this));

  if (currentPoint.Y < myMousePlacementPoint.Y)
  {
   scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - 3);
  }
  else if (currentPoint.Y > myMousePlacementPoint.Y)
  {
   scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + 3);
  }

  if (currentPoint.X < myMousePlacementPoint.X)
  {
   scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 3);
  }
  else if (currentPoint.X > myMousePlacementPoint.X)
  {
   scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + 3);
  }
 }
}

public static DependencyObject GetScrollViewer(DependencyObject o)
{
 // Return the DependencyObject if it is a ScrollViewer
 if (o is ScrollViewer)
 { return o; }

 for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
 {
  var child = VisualTreeHelper.GetChild(o, i);

  var result = GetScrollViewer(child);
  if (result == null)
  {
   continue;
  }
  else
  {
   return result;
  }
 }
 return null;
}

There's some areas it's lacking as it's just a proof of concept but it should definitely get you started in the right direction. To have it constantly scroll once the mouse is moved away from the initial MouseDown point, the scrolling logic could go into a DispatcherTimer or something similar.

rmoore