Attached Behavior for auto scrolling containers while doing Drag & Drop

This is a very common problem in WPF, if you support Drag & Drop within your Items control or across the controls like ListView, TreeView etc. and your List contains hundreds of items then it’s very hard to drag an item at the bottom to the top. Various solutions are available on net for this -

Scrolling Scrollviewer on Mouse Drag at the boundaries

Drag-to-Scroll in WPF

Wpf Drag & Drop Behavior

But I was not satisfied with any of the above and finally created following behavior based on answer of this SO question:

namespace WpfExtensions
{
    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
 
    /// <summary>
    /// Provides extended support for drag drop operation
    /// </summary>
    public static class DragDropExtension
    {
        #region ScrollOnDragDropProperty
 
        public static read-only DependencyProperty ScrollOnDragDropProperty =
            DependencyProperty.RegisterAttached("ScrollOnDragDrop",
                typeof(bool),
                typeof(DragDropExtension),
                new PropertyMetadata(false, HandleScrollOnDragDropChanged));
 
        public static bool GetScrollOnDragDrop(DependencyObject element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
 
            return (bool)element.GetValue(ScrollOnDragDropProperty);
        }
 
        public static void SetScrollOnDragDrop(DependencyObject element, bool value)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
 
            element.SetValue(ScrollOnDragDropProperty, value);
        }
 
        private static void HandleScrollOnDragDropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement container = d as FrameworkElement;
 
            if (d == null)
            {
                Debug.Fail("Invalid type!");
                return;
            }
 
            Unsubscribe(container);
 
            if (true.Equals(e.NewValue))
            {
                Subscribe(container);
            }
        }
 
        private static void Subscribe(FrameworkElement container)
        {
            container.PreviewDragOver += OnContainerPreviewDragOver;
        }
 
        private static void OnContainerPreviewDragOver(object sender, DragEventArgs e)
        {
            FrameworkElement container = sender as FrameworkElement;
 
            if (container == null)
            {
                return;
            }
 
            ScrollViewer scrollViewer = GetFirstVisualChild<ScrollViewer>(container);
 
            if (scrollViewer == null)
            {
                return;
            }
 
            double tolerance = 60;
            double verticalPos = e.GetPosition(container).Y;
            double offset = 20;
 
            if (verticalPos < tolerance) // Top of visible list? 
            {
                scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset); //Scroll up. 
            }
            else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list? 
            {
                scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset); //Scroll down.     
            }
        }
 
        private static void Unsubscribe(FrameworkElement container)
        {
            container.PreviewDragOver -= OnContainerPreviewDragOver;
        }
 
        public static T GetFirstVisualChild<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        return (T)child;
                    }
 
                    T childItem = GetFirstVisualChild<T>(child);
                    if (childItem != null)
                    {
                        return childItem;
                    }
                }
            }
 
            return null;
        }
 
        #endregion
    }
}

which can easily be used like this -

 <ListView
      xmlns:WpfExtensions="clr-namespace:WpfExtensions" 
      WpfExtensions:DragDropExtension.ScrollOnDragDrop="True"/> 

- akjoshi

Download

12 Comments

Add a Comment

As it will appear on the website

Not displayed

Your website