DragDrop - DragEnter/DragLeave 事件持续触发
Posted
技术标签:
【中文标题】DragDrop - DragEnter/DragLeave 事件持续触发【英文标题】:DragDrop - DragEnter/DragLeave Events keep firing 【发布时间】:2011-02-07 15:16:35 【问题描述】:我在 Canvas 上有一个拖放操作,当一个对象被拖入和拖出它时,它应该做一些事情。我的问题是 DragEnter/DragLeave 事件在鼠标将对象移到它上面时不断触发,而不仅仅是在进入/退出时。鼠标移动得越快,事件触发的频率就越高。
Canvas DragOver 事件移动了 DragedObject 的 Canvas.Top/Left,我认为这可能是我的问题,但我不确定如何解决这个问题。
【问题讨论】:
你可能是对的,如果你告诉我们为什么你想这样做,也许我们可以建议一个替代方案。 我有一张画布,上面有一堆面板。可以拖动面板重新排列它们,或者可以通过将新面板从列表框中拖动到画布上来添加新面板。它们也可以通过将面板拖出画布并返回到列表框来删除。 【参考方案1】:这是事件的顺序:
-
您的鼠标从您在面板上单击它的位置移动。没有事件触发。
您的鼠标到达面板边缘并进入画布。
DragEnter
事件在 Canvas 上触发。
DragEnter
处理程序移动面板,使其现在位于鼠标下方。由于鼠标不再位于画布上(它位于面板上),因此会触发 DragLeave
事件。
您的鼠标进一步移动,再次到达面板边缘。重复第 2 步和第 3 步。
您移动鼠标的速度越快,您收到的事件就越多。
您的问题的本质是拖放使用命中测试,并且通过移动您的面板,您正在破坏命中测试查看面板“后面”以了解它被拖放到哪个容器中的能力。
解决方案是使用自己的代码进行拖放处理,这真的一点都不难。 WPF 的命中测试引擎足够强大,可以在当前对象后面进行命中测试,但拖放没有使用此功能。但是,您可以通过使用 HitTestFilterCallback
和 HitTestResultCallback
的 VisualTreeHelper.HitTest
重载直接使用它。只需向它传递一个过滤器,该过滤器会忽略正在拖动的面板中的任何点击。
我发现对于像您描述的场景,通过处理鼠标事件进行拖放实际上比使用内置的 DoDragDrop 更容易,因为您不必处理复杂性(DataObject、DragDropEffects、QueryContinueDrag 等)。这种额外的复杂性对于启用应用程序和进程之间的拖动场景非常重要,但对您正在做的事情没有帮助。
这是一个简单的解决方案:
-
在 MouseDown 上,左键向下,记录位置(在 MouseLeave 上擦除位置)
在 MouseMove 上,左键按下,位置已记录,当前鼠标位置相差超过 delta 设置一个标志,表示正在进行拖动操作并捕获鼠标
在正在进行拖动操作的 MouseMove 上,使用命中测试来确定您的面板应该在哪里(忽略面板本身)并相应地调整其父级和位置。
在正在进行拖动操作的 MouseUp 上,释放鼠标捕获并清除“正在进行拖动操作”标志
享受吧。
【讨论】:
谢谢,这正是我要找的东西!我将我的拖放属性转换为使用 MouseEvents 而不是 DragEvents,这让我得到了我正在寻找的结果。我最初有一些担心这样做,因为我正在拖动数据绑定对象和自定义类,但它们都顺利通过。 很好的解释。但是,通过将 myDraggedControl.IsHitTestVisible 设置为 false 并在完成后将其重置为 true,我找到了一个侵入性较小的解决方案(至少对于我的场景而言)。【参考方案2】:这有点旧,但我遇到了同样的问题。我正在使用装饰器来指示拖动的东西被拖动到哪里。我会在DragEnter
中启用装饰器,然后立即注册DragLeave
并禁用装饰器,然后是DragEnter
...
IsEnabled = false
对我不起作用,但 IsHitTestVisible = false
对我有用。我把它放在我的装饰器的构造函数中,现在一切正常:
public DragDropAdorner(UIElement adornedElement) : base(adornedElement)
_layer = AdornerLayer.GetAdornerLayer(AdornedElement);
_layer.IsEnabled = false;
_layer.IsHitTestVisible = false;
【讨论】:
这个简单的修复实际上对我有用,并且适合我的需要。谢谢。【参考方案3】:我发现有必要为正在发生的事情添加更准确的答案,我也会在与此问题相关的其他问题中添加相同的答案。
好的,这就是发生的事情:
-
您创建拖放操作。
在拖放时,您希望在鼠标上显示一些视觉效果。
该可视元素在 Drag-Enter 事件中被添加到网格或面板中,并在 Drag-Leave 事件中被移除。
当它被放置在鼠标下时,您将收到一个拖放事件,因为您绘制的元素实际上窃取了拖放操作..
解决方案:
确保IsEnabled=false
是您在鼠标下绘制的元素。
这样它就不会捕捉到你的拖放操作,你也不会得到闪烁的效果。
【讨论】:
IsEnabled 对我不起作用,必须像其他答案一样执行 IsHitTestVisible。【参考方案4】:我有一个类似的问题,解决它的魔法是 e.Handled = true;
当某物被拖过时,我想选择一个 TabItem。这是我的解决方案。
在资源字典中定义:
<Style TargetType="TabItem">
<Setter Property="AllowDrop" Value="True" />
<EventSetter Event="DragEnter" Handler="tabDragEnter" />
<EventSetter Event="DragOver" Handler="tabDragOver" />
</Style>
然后是 tabDragEnter 处理程序:
private void tabDragEnter(object sender, DragEventArgs e)
TabItem m_TabItem = sender as TabItem;
m_TabItem.IsSelected = true;
最后是 tabDragOver 处理程序:
private void tabDragOver(object sender, DragEventArgs e)
e.Effects = DragDropEffects.None;
e.Handled = true;
【讨论】:
【参考方案5】:我最近遇到了类似的问题,虽然它是在 Angular 6 框架中,使用响应式表单。这就是我针对我的情况解决它的方法:
基本上,我在拖动时关闭了对该组件的更改检测。
-
导入 ChangeDetectorRef:
import ChangeDetectorRef from '@angular/core';
-
将其注入构造函数:
constructor(private chngDetRef: ChangeDetectorRef) //...
-
在 dragStart 上将其分离:
private onDragStart(event, dragSource, dragIndex)
// ...
this.chngDetRef.detach();
// ...
-
在拖放和拖动时重新附加它:
private onDrop(event, dragSource, dragIndex)
// ...
this.chngDetRef.reattach();
// ...
private onDragEnd(event, dragIndex)
// ...
this.chngDetRef.reattach();
// ...
如果您有很多父组件或分层组件,您可能还必须对它们的更改检测做一些事情,才能看到实质性的改进。
祝你好运!
【讨论】:
以上是关于DragDrop - DragEnter/DragLeave 事件持续触发的主要内容,如果未能解决你的问题,请参考以下文章
DragDrop - DragEnter/DragLeave 事件持续触发