拖放 - DragEnter/DragLeave事件不断触发

16

我有一个在画布上的拖放操作,当对象被拖入和拖出时应该做某些事情。我的问题是DragEnter / DragLeave事件会随着鼠标将对象移动到其上而不仅仅是在进入/退出时触发。鼠标移动得越快,事件触发得就越频繁。

Canvas DragOver事件会移动DraggedObject的Canvas.Top / Left,我认为这可能是我的问题,但我不确定如何解决它。


如果您告诉我们为什么想这样做,您可能是正确的,也许我们可以建议一个替代方案。 - John Knoeller
我有一个画布,上面有一堆面板。这些面板可以拖动以重新排列它们,或者通过从列表框中拖动新面板到画布上添加新的面板。它们也可以通过将面板从画布拖回到列表框中来删除。 - Rachel
5个回答

21
以下是事件的顺序:
1. 您点击鼠标并将其移动到面板上。没有事件触发。 2. 您的鼠标到达面板边缘并进入画布。画布上的“DragEnter”事件被触发。 3. 您的“DragEnter”处理程序移动面板,使其现在在鼠标下方。由于鼠标不再位于画布上(而是在面板上),所以“DragLeave”事件被触发。 4. 您的鼠标继续移动,再次到达面板边缘。步骤2和3重复。
您移动鼠标的速度越快,就会收到越多的事件。
您遇到的问题实质上是拖放使用了命中测试,通过移动面板,您正在破坏命中测试查看后面的功能,以了解它被放置在哪个容器中。
解决方法是使用自己的代码来处理拖放操作,这实际上并不难。 WPF的命中测试引擎足够强大,可以对当前对象后面的命中测试进行测试,但拖放不使用此功能。但是,您可以直接使用它,只需使用带有HitTestFilterCallback和HitTestResultCallback的VisualTreeHelper.HitTest重载即可。只需传递一个过滤器,忽略在拖动的面板内部的任何命中。
我发现,对于您描述的场景,通过处理鼠标事件进行拖放实际上比使用内置的DoDragDrop更容易,因为您不必处理复杂性(DataObject、DragDropEffects、QueryContinueDrag等)。这种额外的复杂性非常重要,可以使拖放在应用程序和进程之间进行拖动,但对于您正在做的事情没有帮助。
以下是简单的解决方案:
1. 在按下鼠标左键时记录位置(在鼠标离开时擦除位置) 2. 在MouseMove中,如果{左键按下,记录了位置,并且当前鼠标位置与差异大于阈值},则设置一个标志表示拖动操作正在进行中并捕获鼠标。
  • 在拖放操作中的MouseMove事件中,使用命中测试确定面板应该在哪里(忽略面板本身),并相应地调整其父级和位置。
  • 在拖放操作中的MouseUp事件中,释放鼠标捕获并清除“拖动操作正在进行中”的标志。

  • 谢谢,这正是我在寻找的东西!我将我的拖放属性转换为使用MouseEvents而不是DragEvents,这让我得到了我想要的结果。一开始我有些担心,因为我正在拖动数据绑定对象和自定义类,但它们都很好地通过了。 - Rachel
    1
    很好的解释。然而,我发现了一个不那么侵入性的解决方案(至少对于我的情况),通过将myDraggedControl.IsHitTestVisible设置为false,并在完成后将其重置为true。 - sa.he

    2

    这有点老了,但我遇到了同样的问题。我正在使用装饰器来指示拖动物体被拖动到哪里。我会在DragEnter中启用装饰器,然后立即注册一个DragLeave并禁用装饰器,然后是DragEnter...

    IsEnabled = false对我不起作用,但IsHitTestVisible = false可以。我将其放在我的装饰器构造函数中,现在一切都运行得很好:

    public DragDropAdorner(UIElement adornedElement) : base(adornedElement)
    {
        _layer = AdornerLayer.GetAdornerLayer(AdornedElement);
        _layer.IsEnabled = false;
        _layer.IsHitTestVisible = false;
    }
    

    这个简单的修复方法对我很有效,也符合我的需求。谢谢。 - Patrick D'Souza

    1
    我发现有必要添加一个更准确的答案来解释正在发生的事情,我将在其他与此问题相关的问题中添加相同的答案。
    好的,这就是正在发生的事情:
    1. 您创建了一个拖放操作。 2. 当您进行拖放操作时,希望有一些视觉元素沿着鼠标显示。 3. 该视觉元素在 Drag-Enter 事件中添加到网格或面板上,并在 Drag-Leave 事件中删除。 4. 当它被放置在鼠标下方时,您将收到一个 Drag-Leave 事件,因为您绘制的元素实际上会窃取拖放操作。
    解决方案:确保您在鼠标下方绘制的元素的 IsEnabled=false。这样它就不会捕获您的拖放操作,您也就不会出现闪烁效果。

    IsEnabled 对我没用,不得不像其他答案中那样使用 IsHitTestVisible。 - Zarat

    0

    我曾经遇到过类似的问题,而解决之道就是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;
        }
    

    -1

    我最近遇到了类似的问题,尽管是在 Angular 6 框架下,使用响应式表单。这是我针对我的情况解决它的方法:

    基本上简单来说,当拖动正在进行时,我关闭了该组件上的变更检测。

    1. 导入 ChangeDetectorRef:
        import { ChangeDetectorRef } from '@angular/core';
    

    将其注入构造函数:
        constructor(private chngDetRef: ChangeDetectorRef) { //...
    
    1. 在 dragStart 时将其分离:
        private onDragStart(event, dragSource, dragIndex) {
            // ...
            this.chngDetRef.detach();
            // ...
    
    1. 在拖放结束后重新附加它:
        private onDrop(event, dragSource, dragIndex) {
            // ...
            this.chngDetRef.reattach();
            // ...
    
        private onDragEnd(event, dragIndex) {
            // ...
            this.chngDetRef.reattach();
            // ...
    

    如果您有很多父组件或分层组件,为了看到实质性的改进,您可能还需要对它们的变更检测进行一些处理。

    祝您好运!


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