Question

I have a WPF app that has a ListBox. The drag mechanism is already implemented, but when the list is too long and I want to move an item to a position not visible I can't.

For example, the screen shows 10 items. And I have 20 items. If I want to drag the last item to the first position I must drag to the top and drop. Scroll up and drag again.

How can I make the ListBox auto scroll?

Was it helpful?

Solution

Got it. Used the event DragOver of the ListBox, used the function found here to get the scrollviewer of the listbox and after that its just a bit of juggling with the Position.

private void ItemsList_DragOver(object sender, System.Windows.DragEventArgs e)
{
    ListBox li = sender as ListBox;
    ScrollViewer sv = FindVisualChild<ScrollViewer>(ItemsList);

    double tolerance = 10;
    double verticalPos = e.GetPosition(li).Y;
    double offset = 3;

    if (verticalPos < tolerance) // Top of visible list?
    {
        sv.ScrollToVerticalOffset(sv.VerticalOffset - offset); //Scroll up.
    }
    else if (verticalPos > li.ActualHeight - tolerance) //Bottom of visible list?
    {
        sv.ScrollToVerticalOffset(sv.VerticalOffset + offset); //Scroll down.    
    }
}

public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
    // Search immediate children first (breadth-first)
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);

        if (child != null && child is childItem)
            return (childItem)child;

        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);

            if (childOfChild != null)
                return childOfChild;
        }
    }

    return null;
}

OTHER TIPS

Based on this I have created an Attached Behavior which can easily be used like this -

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

Here is the code for attached behavior -

/// <summary>
/// Provides extended support for drag drop operation
/// </summary>
public static class DragDropExtension
{
    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;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top