拖放时滚动(WPF)

14

大家好,我一直在苦思冥想这个问题,花了好几个小时研究它的工作原理,但我还没有找到答案。如果你想看我的SRC,请随时询问,我会尽力帮忙。

基本上,我遇到的问题是在我的应用程序中有一个文件夹的TreeView,例如:

Catalog

  Brands
    Nike
    Adidas
    Lactose

  Styles
    Sandles
    Trainers
    Boots

我想解决的问题是,当我拖动文件夹时(这在我的 DragDropManager 类中处理),我无法上下滚动(只会显示一个漂亮的停止标志)。我也无法在TreeView中找到任何滚动条,所以我不确定它是如何生成的(这不是我自己的软件,我最近才开始为一家公司工作,所以我不熟悉代码,也没有人知道)。

如果我想将某个东西从顶部移动到底部,则会出现问题。

单独进行滚动时,滚动正常工作。

如果有人想看我的代码的任何部分,请随时询问,因为我不确定实际要向您展示什么。

我已经阅读了很多文章,但仍无法理解。

3个回答

30

我创建了一个附加属性来实现这种行为,可以看看我在这里发布的帖子 -

拖放时自动滚动容器的附加行为

主要逻辑大致如下 -

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? 
    {
        //Scroll up
        scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset);
    }
    else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list? 
    {
        //Scroll down
        scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset);     
    }
}

在 SO 上有类似的问题(虽然它们主要是针对 ListBox/ListView,但也适用于 TreeView):

WPF Listbox 拖拽时自动滚动

WPF ListView 数据绑定拖放自动滚动

WPF 拖拽滚动不正确


我一直在尝试在我的列表框中使用它,但是如果列表框中的项目无法被拖放,则无法正常工作。我的列表框中有混合项目,有些可以被拖放,有些则不能。如果靠近顶部(或底部)的项目无法接收拖放,则滚动不会激活。有什么解决方法吗? - Lutz Kretzschmar
@Lutz 这很有趣,我从未遇到过这种情况,所以不能说太多。我能想到的方法有:
  1. 在当前项之间添加一些虚拟项(高度较小等等,让它们看起来不奇怪或浪费空间),使它们可拖放,这样 PreviewDragOver 就会一直被调用。但是你需要处理拖放等操作。
  2. 使所有项都可以拖放,并在拖放后进行验证处理。
  3. 尝试查找是否有其他事件针对这些非可拖动项被触发。
- akjoshi
@akjoshi - 我已经按照您指定的方式添加了您的代码。但是,当我将“WpfExtensions:DragDropExtension.ScrollOnDragDrop =“True””这一行添加到我的ListView中时,它会给我一个智能感知错误:“命名空间'clr-namespace:WpfExtensions'中不存在DragDropExtension”。 - nikotromus
@akjoshi 我正在使用您的示例在多个视图上实现拖放,但是在扩展类中,当我执行GetFirstVisualChild时,一个视图返回类型为“System.Windows.Controls.Border”,而另一个返回类型为“System.Windows.Controls.Grid”。返回网格的视图不起作用,而返回边框的视图却起作用。我应该如何调整方法以使其始终返回边框依赖项? - tCoe
1
这个扩展程序也适用于ScrollViewers。但是你必须将ScrollViewers包装在一个Border中,并将扩展程序添加到Border而不是ScrollViewers中。 - jor
显示剩余3条评论

3

我知道这个问题很老了,但以下是作为附加属性的MVVM方式:

    using System.Windows;
    using System.Windows.Controls;

    namespace AndroidCtrlUI.XTools.Behaviors
    {
        ///<summary>
        /// TreeItemAttach 
        ///<para/> TreeViewItem
        ///</summary>
        public sealed class TreeItemAttach
        {
            #region BringIntoView

            ///<summary>
            /// DependencyProperty
            ///</summary>
            public static readonly DependencyProperty BringIntoViewProperty = DependencyProperty.RegisterAttached("BringIntoView", typeof(bool), typeof(TreeItemAttach), new UIPropertyMetadata(false, (s, e) =>
            {
                if ((bool)e.NewValue != (bool)e.OldValue && s is TreeViewItem t)
                {
                    if ((bool)e.NewValue)
                    {
                        t.Selected += BringIntoView;
                    }
                    else
                    {
                        t.Selected -= BringIntoView;
                    }
                }
            }));

            ///<summary>
            /// Get
            ///</summary>
            ///<param name="target">DependencyObject</param>
            ///<returns>ICommand</returns>
            public static bool GetBringIntoView(DependencyObject target)
            {
                return (bool)target.GetValue(BringIntoViewProperty);
            }

            ///<summary>
            /// Set
            ///</summary>
            ///<param name="target">DependencyObject</param>
            ///<param name="value">ICommand</param>
            public static void SetBringIntoView(DependencyObject target, bool value)
            {
                target.SetValue(BringIntoViewProperty, value);
            }

            private static void BringIntoView(object sender, RoutedEventArgs e)
            {
                if (e.Source is TreeViewItem s)
                {
                    double h = s.ActualHeight;
                    if (s.IsExpanded && s.Items.Count > 0)
                    {
                        h = s.ActualHeight / TreeWalker(s);
                    }
                    s.BringIntoView(new Rect(0, h * -1, s.ActualWidth, h * 2.5));
                }
            }

            private static long TreeWalker(TreeViewItem item)
            {
                long c = item.Items.Count;
                foreach (object i in item.Items)
                {
                    if (i != null && item.ItemContainerGenerator.ContainerFromItem(i) is TreeViewItem t && t.IsExpanded && t.Items.Count > 0)
                    {
                        c += TreeWalker(t);
                    }
                }
                return c;
            }
            #endregion
        }
    }

并且它可以用作:

<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
    <Setter Property="tool:TreeItemAttach.BringIntoView" Value="True"/>
</Style>

1
基于@akjoshi的答案,但我希望在这样掉落时速度可变: enter image description here 我的PreviewDragOver处理程序如下:
private static void container_PreviewDragOver(object sender, DragEventArgs e)
{
    if (!(sender is FrameworkElement container))
        return;

    var scrollViewer = findChildOfType<ScrollViewer>(container);
    if (scrollViewer == null)
        return;

    const double heightOfAutoScrollZone = 25;
    double mouseYRelativeToContainer = e.GetPosition(container).Y;

    if (mouseYRelativeToContainer < heightOfAutoScrollZone)
    {
        double offsetChange = heightOfAutoScrollZone - mouseYRelativeToContainer;
        scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offsetChange);
    }
    else if (mouseYRelativeToContainer > container.ActualHeight - heightOfAutoScrollZone)
    {
        double offsetChange = mouseYRelativeToContainer - (container.ActualHeight - heightOfAutoScrollZone);
        scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offsetChange);
    }
}

完整代码和示例请参见github


1
在我的数据网格中工作得很好! - RuudSieb

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