WPF ListBox拖拽时自动滚动

31

我有一个WPF应用程序,其中包含一个ListBox。拖动机制已经实现,但是当列表太长并且我想将项目移动到不可见的位置时,我就无法操作。

例如,屏幕显示10个项目。我有20个项目。如果我想将最后一个项目拖到第一个位置,我必须拖到顶部并释放。然后再向上滚动并再次拖动。

如何使ListBox自动滚动?

3个回答

34

懂了。使用了 ListBoxDragOver 事件,使用在这里找到的函数获取了列表框的 scrollviewer,然后就是对位置进行一些调整。

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;
}

1
我尝试了你的方法,它有效。但是,在将对象拖动到同一列表中时,放下后,它会返回到原始对象,而我希望看到已放置的项目。你有这个问题吗?你解决了吗? - David Brunelle
@DavidBrunelle 我不记得了,抱歉。 - Artur Carvalho
+1 很棒的答案,尽管它是深度优先搜索,而不是广度优先搜索。 - Cameron
我的“FindVisualChild”从未返回ScrollViewer,但我在XAML中将ListBox放在ScrollViewer中并使用“FindVisualParent”代替,这样就解决了问题。 - miriyo
@miriyo你好。我在尝试类似的东西。你能发一下代码吗? - Juan Pablo Gomez
嗨,我刚刚将这个从原始建议更改了:(抱歉它是在VB中)`Dim lListBoxItem As DragDropListBoxItem = FindVisualParent(Of DragDropListBoxItem)((DirectCast(e.OriginalSource, DependencyObject)))如果(lListBoxItem不是空的)Then     DragDrop.DoDragDrop(lListBoxItem,lListBoxItem.DataContext,DragDropEffects.Move) End If` - miriyo

19

基于此,我已经创建了一个附加行为,可以像这样轻松使用 -

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

以下是所附行为的代码 -
/// <summary>
/// Provides extended support for drag drop operation
/// </summary>
public static class DragDropExtension
{
    public static readonly 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;
    }

    private 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;
    }
}

2
非常好的解决方案。如果您想要平滑滚动,不要忘记在您的ListBox/ListView上添加“ScrollViewer.CanContentScroll="False"”。 - Pak
2
@Pak 值得一提的是,当您将 CanContentScroll 设置为 false 时,同时也会禁用虚拟化。 - pjrki

1

Artur的回答很好,让我找到了正确的方向,但我发现了一些问题。

首先,代码在这里有一个错误:

ScrollViewer sv = FindVisualChild<ScrollViewer>(ItemsList);

应该传入li

此外,上面的代码没有正确考虑ListBox是否有水平滚动条。我已经进行了修改,并进行了两个修复,如下所示:

private void MainTreeView_DragOver(object sender, DragEventArgs e)
{
    ListBox li = sender as ListBox;
    ScrollViewer sv = FindVisualChild<ScrollViewer>(li);

    double tolerance = 24;
    double verticalPos = e.GetPosition(li).Y;

    double topMargin = tolerance;
    var bottomMargin = li.ActualHeight - tolerance;
    if(sv.ComputedHorizontalScrollBarVisibility == Visibility.Visible)
    {
        var horizontalScrollBar = sv.Template.FindName("PART_HorizontalScrollBar", sv) as System.Windows.Controls.Primitives.ScrollBar;

        if(horizontalScrollBar != null)
        {
            bottomMargin -= horizontalScrollBar.ActualHeight;
        }
    }

    double distanceToScroll = 3;
    if (verticalPos < topMargin) // Top of visible list?
    {
        sv.ScrollToVerticalOffset(sv.VerticalOffset - distanceToScroll); //Scroll up.
    }
    else if (verticalPos > bottomMargin) //Bottom of visible list?
    {
        sv.ScrollToVerticalOffset(sv.VerticalOffset + distanceToScroll); //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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接