Introduction
Based on an answer from akjoshi
on Stackoverflow (http://stackoverflow.com/questions/1316251/wpf-listbox-auto-scroll-while-dragging ) this Attached-Behavior
brings you automatic scrolling when dragging over a control in WPF.
This is an adapted version; the short example on Stackoverflow does
not respect all facets of different controls and their scroll
behavior. Support for acceleration towards the edges was also added.
So here is an improved version. There is no
version for
System.Interactivity
. However it should be easy to port it and
make it a Behavior.
What is it?
This article provides you with an
attached property, that can be used within XAML code. It will add
auto-scroll capability to every control that has a ScrollViewer
. In
fact, this code does a deep search in the visual tree and grabs the
first ScrollViewer
it can get ahold of. Practically making any visual
tree with a ScrollViewer
to scroll automatically once a drag
operation is in progress. That means: Since the Control-Templates
of a ListView
, TreeView
or a ListBox
usually have got a ScrollViewer
contained, applying that attribute to the Control will enable the
auto-scroll functionality.
Look and behavior
The look is not existing. However if the
user is in a drag operation and he/she drags over such a prepared
control and approaches the upper or lower edges of the scrollable
control area, it magically starts scrolling. The auto scroll starts
at abut 40 WPF-pixel units or 25% of the height. Whatever is smaller
(respecting very small controls). These limits are hard coded and
respect different DIP-settings/scale factors. The speed of the auto
scrolling varies in position. The closer the user drags towards the
upper or lower edge, the faster it scrolls.
Using the code
Just use the code like any other
attribute, too: Add the namespace to your XAML file:
<Window x:Class="Wpf_Drag_Drop_Scroll.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dds="clr-namespace:Wpf_Drag_Drop_Scroll"
Title="Auto-Scrollon Drag - Demo" >
....
Then
set the ScrollOnDragDrop
property to true on the scrollable control in
question:
<ListBox Name="listBox1"
dds :DragDropExtension.ScrollOnDragDrop="True"
AllowDrop="True">
<Button>Blalba</Button>
<Button>Blalba</Button>
<Button>Blawesdgs</Button>
...
</ListBox>
Make
sure you also have the AllowDrop
property set to true
. Anyway, see
the demo project that provides and demonstrates the code.
Restrictions
It only works vertically. However it
should not be hard to create a second attached property, which would
do the same for horizontal. I’d suggest two properties for the
flexibility of use. Also there is no hook to circumvent scrolling
depending on the dragged object. So regardless of what is under your
mouse cursor, the scrolling works.
Lessons learned
The lesson one can learn off that
implementation is this: The ScrollViewer
behaves differently
depending on content and conditions. Namely the ScrollableHeight
and
the VerticalOffset
properties have got quite different values. You can
observe this, if you use a vanilla ListBox and for example a generic
ScrollViewer
around a Canvas
. The ScrollViewer
of the ListBox
will treat every line as 1 unit (thus ScrollableHeight
== #Elements), while the ScrollViewer
around the Canvas
treats every pixel as a
unit. This is because of the ScrollViewer.CanContentScrollProperty
property that each control supplies (tree-inherits) to the
ScrollViewer
. This property is affected by the
virtualization-capability of certain WPF-controls. See
ItemsContainer
=StackPanel
vs. VirtualizingStackPanel
. If content is
virtualized, it's size has not been determined. Yet, it can't be. Thus the
ScrollableHeight
is indefinite and as a resolution all Items are
considered to be the same height: 1 unit. If content however is not
virtualized, it is well available for measure etc. Thus the
ScrollViewer can use Pixel units (even fractions) to work with
it. This different behavior has been taken into account when this
code was written. This bit is one of the extensions compared with
the linked base code. As
a result, the code scrolls virtualized content 1..3 lines as of
different speeds, while scrollable content is speed up between 1 and
30 pixel depending on position of the mouse cursor.
Example
Program
Attached
is a test program to check out the behavior of the code. Use the
aquamarine colored ellipse to start dragging. Provided are different controls to test the code with their differently
behaving ScrollViewer
s. On the left, we have got a ListBox
with
virtualized content. Therefore scroll speeds vary digitally from
1..3. The second control is a Canvas
(arbirtrary image) inside a
ScrollViewer
. Here and in the next two controls content is scrollable
(ScrollViewer.CanContentScrollProperty=true
)
and the movement is pixel-wise. Speed varies 1..30. The last two
controls are: A WrapPanel
inside a ScrollViewer
and a TreeView
with
it's intrinsic ScrollViewer
.

Last words
Use
this code in many places in your application and make your users happier. And thanks to akjoshi for the nice base work!
History
First version.