我有一个 WPF 应用程序有一个
ListBox
。拖动机制已经实现了,但是当列表太长,我想把一个项目移动到不可见的位置时,我做不到。
例如屏幕显示10个项目。我有 20 件物品。如果我想将最后一个项目拖到第一个位置,我必须拖到顶部然后放下。向上滚动并再次拖动。
如何使
ListBox
自动滚动?
知道了。使用
DragOver
的事件 ListBox
,使用 here 中的函数来获取列表框的 scrollviewer
,之后它只是与 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;
}
基于此,我创建了一个 Attached Behavior 可以像这样轻松使用 -
<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;
}
}
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;