WPF: Scroll content of control when drag & drop is in progress

It's nice to be able to scroll through content of a control when items are being dragged over a WPF control. 

I have created an attached property called "IsScrollOnDragOverEnabled", which you can set on a control with ScrollViewer as shown below to scroll the content of the control when items are being dragged over the control.

Features:
1) Horizontal Scroll
2) Vertical Scroll
3) Smooth Scroll (Note: This feature scrolls content of the control in a smooth scroll fashion, to let the users see and interact with content being scrolled.

Here is how to use it:
<Window x:Class="DragAndDropFramework.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DragAndDropBehaviors="clr-namespace:DragAndDropBehaviors;assembly=DragAndDrop" Title="Drag and Drop Demo"
        Height="500" Width="500">
    <Grid>
        <ListBox AllowDrop="True" DragAndDropBehaviors:ScrollOnDragOverBehavior.IsScrollOnDragOverEnabled="True" />
    </Grid>
</Window>

Here is the attached property:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
 
namespace DragAndDropBehaviors
{
    public static class ScrollOnDragOverBehavior
    {
        public static bool GetIsScrollOnDragOverEnabled(DependencyObject element)
        {
            return (bool) element.GetValue(IsScrollOnDragOverEnabledProperty);
        }
 
        public static void SetIsScrollOnDragOverEnabled(DependencyObject element, bool value)
        {
            element.SetValue(IsScrollOnDragOverEnabledProperty, value);
        }
 
        public static readonly DependencyProperty IsScrollOnDragOverEnabledProperty =
            DependencyProperty.RegisterAttached("IsScrollOnDragOverEnabled"typeof (bool),
                                                typeof (ScrollOnDragOverBehavior),
                                                new FrameworkPropertyMetadata(false, OnIsScrollOnDragOverPropertyChanged));
 
        private static void OnIsScrollOnDragOverPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var dropTarget = d as FrameworkElement;
 
            if (dropTarget == nullreturn;
 
            if ((bool) e.OldValue)
            {
                dropTarget.PreviewDragOver -= OnPreviewDragOver;
            }
 
            if ((bool) e.NewValue)
            {
                dropTarget.PreviewDragOver += OnPreviewDragOver;
            }
        }
 
        private static DateTime _lastPreviewDragOverEvent = DateTime.MinValue;
 
        private static void OnPreviewDragOver(object sender, DragEventArgs e)
        {
            if (DateTime.Now.Subtract(_lastPreviewDragOverEvent).Milliseconds >= 250)
            {
                _lastPreviewDragOverEvent = DateTime.Now;
                var dropTarget = sender as FrameworkElement;
 
                if (dropTarget == null)
                {
                    return;
                }
 
                var scrollViewer = FindChild<ScrollViewer>(dropTarget);
 
                if (scrollViewer == null)
                {
                    return;
                }
 
                const double tolerance = 20;
 
                const double yOffset = 5;
                const double xOffset = 5;
 
                var yPos = e.GetPosition(dropTarget).Y;
                var xPos = e.GetPosition(dropTarget).X;
 
 
                if (yPos < tolerance)
                {
                    SmoothVerticalScroll(scrollViewer, -yOffset);
                }
                else if (yPos > dropTarget.ActualHeight - tolerance)
                {
                    SmoothVerticalScroll(scrollViewer, yOffset);
                }
 
                if (xPos < tolerance)
                {
                    SmoothHorizontalScroll(scrollViewer, -xOffset);
                }
                else if (xPos > dropTarget.ActualWidth - tolerance)
                {
                    SmoothHorizontalScroll(scrollViewer, xOffset);
                }
            }
        }
 
        private static void SmoothVerticalScroll(ScrollViewer scrollviewer, double offset)
        {
 
            if (offset > 0)
            {
                for (var i = 0; i < offset; i++)
                {
                    scrollviewer.ScrollToVerticalOffset(scrollviewer.VerticalOffset + 1);
                }
            }
            else
            {
                for (var i = 0; i < Math.Abs(offset); i++)
                {
                    scrollviewer.ScrollToVerticalOffset(scrollviewer.VerticalOffset - 1);
                }
            }
        }
 
        private static void SmoothHorizontalScroll(ScrollViewer scrollviewer, double offset)
        {
            if (offset > 0)
            {
                for (var i = 0; i < offset; i++)
                {
                    scrollviewer.ScrollToHorizontalOffset(scrollviewer.HorizontalOffset + 1);
                }
            }
            else
            {
                for (var i = 0; i < Math.Abs(offset); i++)
                {
                    scrollviewer.ScrollToHorizontalOffset(scrollviewer.HorizontalOffset - 1);
                }
            }
        }
 
        public static T FindChild(DependencyObject depObj) where T : DependencyObject
        {
            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                var child = VisualTreeHelper.GetChild(depObj, 0);
                if (child != null && child is T)
                {
                    return (T)child;
                }
 
                var childItem = FindChild(child);
                if (childItem != null)
                {
                    return childItem;
                }
            }
            return null;
        }
    }
}

Comments

Popular posts from this blog

WPF How to Dispose ViewModel when the associated UserControl (Not Window) closes?

C# How to unit test Dispatcher

WPF: How to Deep Copy WPF object (e.g. UIElement) ?