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

我有一个需要以特定方式处理拖动事件的 winforms 控件。此控件包含许多子控件 - 其中一些是嵌入在 ElementHost 对象中的 WPF。它是最顶层的 winforms 控件,需要处理拖动事件。

当鼠标悬停在父级的 winforms 组件上时,我可以轻松地使拖动行为起作用。没有什么特别需要我做的。但是,WPF 控件是黑洞 - 它们似乎吞噬了拖动事件,从而阻止了主 winforms 控件正确处理它们。

我可以通过以下方式解决这个问题:

  1. 在WPF控件中设置AllowDrop = true
  2. 覆盖 OnDragEnter WPF 控件
  3. ElementHost 开始递归地遍历 Control.Parent 引用,直到找到我需要的顶级控件
  4. 在找到的顶级控件上调用 IDropTarget.OnDragEnter

如您所想,这很丑陋:

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);
}

OnDragDrop 也需要做类似的工作,但为了简洁起见,我省略了它

当然,我可以注入(inject)一个辅助对象,顶级控件和 WPF 控件都引用了该对象,这样我就可以消除控件遍历并稍微清理一下。

但是,如果我可以避免的话,我真的不想处理 WPF 控件中的拖动事件 - 我宁愿顶级控件负责并处理它在我拖动它的 winforms 时所做的事情 child 控制。有办法实现吗?

最佳答案

您所描述的是完全正常的。以极快的速度:当您将 AllowDrop 属性设置为 True 时,Winforms 和 WPF 都会调用 RegisterDragDrop()通讯功能。您将识别 IDropTarget 接口(interface)的方法,它们直接映射到等效的 .NET 事件。请注意 HWND 参数,这是此行为开始的地方。

在 Winforms 中,每个 Control 都有自己的 Handle 属性。所以没有什么特别的事情发生,事件是在悬停/放下的控件上引发的。这在 WPF 中不起作用,控件没有句柄,因此它们借用外部容器的句柄。通常是 Window 对象的 HWndSource。然后必须将事件定向到适当的控件,这就是路由事件所做的。关键区别在于事件似乎从内部控件“冒泡”到外部控件。在 Winforms 或 COM 管道中没有这样的冒泡。因此,如果嵌入式 WPF 控件没有用于拖动的用途,那么这就是降压停止的地方,用户将获得“无拖动”鼠标光标。就像在任何 Winforms 应用程序中发生的一样。

Winforms 程序员通常通过给 UI 一个明显的拖动目标来处理这个问题。类似于“放在这里”字形的东西。这并没有完全损害可用性,拖放功能往往很难发现。如果这不是您想要的,那么您唯一的选择就是自己冒泡事件。就像您一直在做的那样。

您需要的大部分内容已经存在,只是您没有以最佳方式执行此操作。最明显的方法是在 ElementHost 中进行这些冒泡。所有这些控件的共同特征。这很容易做到,95% 的 Winforms 问题都是通过从 .NET 类派生您自己的类来解决的。一些可玩的代码:

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...
}

我没有发布 Child_Xxxx 事件处理程序,您已经有了该代码。编译并从工具箱顶部放置新控件,替换现有的 ElementHosts。或者,在代码中,创建一个 ElementHostEx 而不是 ElementHost。

https://stackoverflow.com/questions/41349364/

相关文章:

unity3d - 切换场景时网格会重新加载吗?

android - 使用 DownloadManager.Request 从 url 下载文件时下载

C - do/while 循环和 switch(int)。输入 char 值会导致 inf。环形

haskell - 如何使 Pipe 与 Haskell 的 Pipe 库并发?

apache-spark - 自定义 log4j 类不适用于 spark 2.0 EMR

Ansible - 使用用户输入选择变量

hdf5 - dask 和并行 hdf5 写入

cherrypy - 更改 Content-Type 后返回的内容未自动编码

sprite-kit - SpriteKit : calculate distance betwee

compiler-construction - 这个产生式规则是否左递归?