简体   繁体   English

拖动托管WPF控件在其winforms父级中吞噬的事件

[英]Drag events swallowed by a hosted WPF control in its winforms parent

I have a winforms control which needs to handle drag events in a particular way. 我有一个winforms控件,需要以特定方式处理拖动事件。 This control contains a number of child controls - some of which are WPF embedded within ElementHost objects. 此控件包含许多子控件 - 其中一些是嵌入在ElementHost对象中的WPF。 It is the top-most winforms control that needs to handle the drag events. 它是需要处理拖动事件的最顶级的winforms控件。

I can easily get the drag behavior to work while the mouse is over winforms components within the parent. 当鼠标悬停在父级中的winforms组件上时,我可以轻松地使拖动行为起作用。 There is nothing special I need to do. 我没有什么特别需要做的。 However, the WPF controls are black holes - they seem to swallow the drag events, preventing the main winforms control from handling them properly. 但是,WPF控件是黑洞 - 它们似乎吞下了拖动事件,阻止了主winforms控件正确处理它们。

I can hack around this with the following: 我可以通过以下方式解决这个问题:

  1. set AllowDrop = true in the WPF control 在WPF控件中设置AllowDrop = true
  2. override OnDragEnter WPF control 覆盖OnDragEnter WPF控件
  3. walk up the Control.Parent references recursively starting from the ElementHost until I find the top-level control I need ElementHost开始递归地向上走Control.Parent引用,直到找到我需要的顶级控件
  4. call IDropTarget.OnDragEnter on the top-level control that was found 在找到的顶级控件上调用IDropTarget.OnDragEnter

As you may imagine, this is rather ugly: 你可能想象,这是相当丑陋的:

public MyWPFControl()
{
    InitializeComponent();
    _host = new Lazy<System.Windows.Forms.Control>(BuildHostControl);
    AllowDrop = true;
}

private System.Windows.Forms.Control BuildHostControl()
{
    return new System.Windows.Forms.Integration.ElementHost
    {
        Dock = System.Windows.Forms.DockStyle.Fill,
        Child = this,
    };
}

protected override void OnDragEnter(DragEventArgs e)
{
    base.OnDragEnter(e);
    if (e.Data.GetDataPresent(typeof(MyDragObject)))
    {
        var args = ConvertDragArgs(e);
        var control = GetControl(_host.Value)
        ((System.Windows.Forms.IDropTarget)control).OnDragEnter(args);
    }
}

private System.Windows.Forms.DragEventArgs ConvertDragArgs(DragEventArgs e)
{
    var dragObject = e.Data.GetData(typeof(MyDragObject)) as MyDragObject;
    var position = e.GetPosition(this);
    var data = new System.Windows.Forms.DataObject(dragObject);
    return new System.Windows.Forms.DragEventArgs(data, (int)e.KeyStates, (int)position.X, (int)position.Y, System.Windows.Forms.DragDropEffects.Copy, System.Windows.Forms.DragDropEffects.Copy);
}

private MyTopLevelControl GetControl(System.Windows.Forms.Control control)
{
    var cell = control as MyTopLevelControl;
    if (cell != null)
        return cell;

    return GetControl(control.Parent);
}

similar effort would have to be taken for OnDragDrop, but for brevity, I have omitted it OnDragDrop必须采取类似的措施,但为了简洁起见,我省略了它

Certainly, I can inject a helper object that both the top-level control and the WPF control have references to so I can eliminate the control walking and clean it up somewhat. 当然,我可以注入一个顶级控件和WPF控件都有引用的辅助对象,这样我就可以消除控件行走并在某种程度上清理它。

However, I don't really want to handle the drag events in the WPF control if I can avoid it - I would rather the top-level control take charge and handle things like it does when I'm dragging over its winforms child controls. 但是,如果我可以避免它,我真的不想处理WPF控件中的拖动事件 - 我宁愿顶级控件负责并处理我拖动其winforms子控件时的操作。 Is there a way to accomplish this? 有没有办法实现这个目标?

What you describe is entirely normal. 你所描述的是完全正常的。 At breakneck speed: when you set the AllowDrop property to True then both Winforms and WPF call the RegisterDragDrop() COM function. 以极快的速度:当您将AllowDrop属性设置为True时,Winforms和WPF都会调用RegisterDragDrop() COM函数。 You'll recognize the methods of the IDropTarget interface, they map directly to equivalent .NET events. 您将识别IDropTarget接口的方法,它们直接映射到等效的.NET事件。 Do note the HWND argument, that's where this behavior starts. 请注意HWND参数,即此行为开始的位置。

In Winforms, every Control has its own Handle property. 在Winforms中,每个Control都有自己的Handle属性。 So nothing very special happens, the events are raised on the control that's being hovered/dropped. 所以没有什么特别的事情发生,事件是在被徘徊/掉落的控件上引发的。 That doesn't work in WPF, controls don't have a handle, so they borrow the handle of the outer container. 这在WPF中不起作用,控件没有句柄,所以他们借用外容器的句柄。 Typically the HWndSource of the Window object. 通常是Window对象的HWndSource。 The event must then be directed to the appropriate control, that's what routed events do. 然后必须将事件定向到适当的控件,这就是路由事件所做的事情。 Key difference is that the events appear to "bubble" from the inner to the outer control. 关键的区别在于事件似乎从内部控制到外部控制“起泡”。 No such bubbling in either Winforms or the COM plumbing. 在Winforms或COM管道中没有这样的冒泡。 So if the embedded WPF control doesn't have a use for the drag then that's where the buck stops, the user gets the "no drag" mouse cursor. 因此,如果嵌入式WPF控件没有用于拖动,那么降压停止的位置,用户将获得“无拖动”鼠标光标。 Like what happens in any Winforms app. 就像任何Winforms应用程序中发生的一样。

A Winforms programmer typically deals with this by giving the UI an obvious drag target. Winforms程序员通常通过为UI提供明显的拖动目标来处理此问题。 Something resembling a "drop here" glyph. 类似于“放在这里”字形的东西。 That doesn't exactly hurt usability, drag+drop functionality tends to be pretty hard to discover. 这并不会严重损害可用性,拖放功能往往很难发现。 If that's not what you want then the only alternative you have is to bubble the events yourself. 如果那不是你想要的,那么你唯一的选择就是自己冒泡。 Much like you've been doing. 就像你一直在做的一样。

Most of what you need is already there, you just didn't do this the optimal way. 您需要的大部分内容已经存在,您只是没有以最佳方式执行此操作。 The most obvious approach is to do these bubbling in the ElementHost. 最明显的方法是在ElementHost中进行这些冒泡。 The feature that all of these controls have in common. 所有这些控件都有共同点的功能。 That's easy to do, 95% of all Winforms problems are solved by deriving your own class from the .NET class. 这很容易做到,95%的Winforms问题都是通过从.NET类派生自己的类来解决的。 Some code to play with: 一些代码可以使用:

using System;
using System.Windows.Forms;
using System.Windows.Forms.Integration;

class ElementHostEx : ElementHost {
    public ElementHostEx() {
        this.ChildChanged += ElementHostEx_ChildChanged;
    }

    private void ElementHostEx_ChildChanged(object sender, ChildChangedEventArgs e) {
        var prev = e.PreviousChild as System.Windows.UIElement;
        if (prev != null) {
            prev.DragEnter -= Child_DragEnter;
            prev.Drop -= Child_Drop;

        }
        if (this.Child != null) {
            this.Child.DragEnter += Child_DragEnter;
            this.Child.Drop += Child_Drop;
        }
    }
    // etc...
}

I didn't post the Child_Xxxx event handlers, you already have that code. 我没有发布Child_Xxxx事件处理程序,您已经拥有该代码。 Compile and drop the new control from the top of the toolbox, replacing the existing ElementHosts. 从工具箱顶部编译并删除新控件,替换现有的ElementHosts。 Or, in code, create an ElementHostEx instead of an ElementHost. 或者,在代码中,创建ElementHostEx而不是ElementHost。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM