WinForms 互操作,从 WinForms 拖放 -> WPF

Posted

技术标签:

【中文标题】WinForms 互操作,从 WinForms 拖放 -> WPF【英文标题】:WinForms Interop, Drag & Drop from WinForms -> WPF 【发布时间】:2010-11-15 19:58:05 【问题描述】:

我正在尝试将数据从应用程序的 Winforms 部分拖到包含在“ElementHost”中的 WPF 控件上。当我尝试这样做时它会崩溃。

尝试同样的事情,但从 Winforms 到 Winforms 工作正常。 (见下面的示例代码)

我需要帮助来完成这项工作...有任何线索我做错了什么吗?

谢谢!


示例: 在下面的示例代码中,我只是尝试拖动在 1) System.Windows.Forms.TextBox (Winforms) 和 2) System.Windows.TextBox (WPF) 上的标签控件上启动拖动时创建的自定义 MyContainerClass 对象, 添加到 ElementHost)。

案例 1) 工作正常,但案例 2) 在尝试使用 GetData() 检索放置数据时崩溃。 GetDataPresent("WindowsFormsApplication1.MyContainerClass") 返回“true”,所以理论上,我应该能够像在 Winforms 中那样检索该类型的放置数据。

这是崩溃的堆栈跟踪:

“对 COM 组件的调用已返回错误 HRESULT E_FAIL”,并带有以下堆栈跟踪: 在 System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 错误代码,IntPtr 错误信息) 在 System.Windows.Forms.DataObject.GetDataIntoOleStructs(FORMATETC& 格式等,STGMEDIUM& 中) 在 System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(FORMATETC& 格式等,STGMEDIUM& 中) 在 System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetData(FORMATETC& 格式等,STGMEDIUM& 介质) 在 System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC& 格式等,STGMEDIUM& 中) 在 System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(字符串格式,DVASPECT 方面,Int32 索引) 在 System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(字符串格式,DVASPECT 方面,Int32 索引) 在 System.Windows.DataObject.OleConverter.GetData(字符串格式,布尔自动转换,DVASPECT 方面,Int32 索引) 在 System.Windows.DataObject.OleConverter.GetData(字符串格式,布尔自动转换) 在 System.Windows.DataObject.GetData(字符串格式,布尔自动转换) 在 System.Windows.DataObject.GetData(字符串格式) 在 WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 48 中的 WindowsFormsApplication1.Form1.textBox_PreviewDragEnter(Object sender, DragEventArgs e)

这里有一些代码:

// -- Add an ElementHost to your form --
// -- Add a label to your form --

public partial class Form1 : Form

    public Form1()
    
        InitializeComponent();

        System.Windows.Controls.TextBox textBox = new System.Windows.Controls.TextBox();
        textBox.Text = "WPF TextBox";
        textBox.AllowDrop = true;
        elementHost2.Child = textBox;
        textBox.PreviewDragEnter += new System.Windows.DragEventHandler(textBox_PreviewDragEnter);

        System.Windows.Forms.TextBox wfTextBox = new System.Windows.Forms.TextBox();
        wfTextBox.Text = "Winforms TextBox";
        wfTextBox.AllowDrop = true;
        wfTextBox.DragEnter += new DragEventHandler(wfTextBox_DragEnter);
        Controls.Add(wfTextBox);
    

    void wfTextBox_DragEnter(object sender, DragEventArgs e)
    
        bool dataPresent = e.Data.GetDataPresent("WindowsFormsApplication1.MyContainerClass");

        // NO CRASH here!
        object data = e.Data.GetData("WindowsFormsApplication1.MyContainerClass");
    

    void textBox_PreviewDragEnter(object sender, System.Windows.DragEventArgs e)
    
        bool dataPresent = e.Data.GetDataPresent("WindowsFormsApplication1.MyContainerClass");

        // Crash appens here!!
        // "Error HRESULT E_FAIL has been returned from a call to a COM component."
        object data = e.Data.GetData("WindowsFormsApplication1.MyContainerClass");
    

    private void label1_MouseDown(object sender, MouseEventArgs e)
    
        label1.DoDragDrop(new MyContainerClass(label1.Text), DragDropEffects.Copy);
    


public class MyContainerClass

    public object Data  get; set; 

    public MyContainerClass(object data)
    
        Data = data;
    

【问题讨论】:

【参考方案1】:

@Pedery & jmayor:谢谢大家的建议! (见下面我的发现)

经过相当多的实验、试验和错误,以及一些“反射”,我设法弄清楚为什么我会收到神秘的错误消息“错误 HRESULT E_FAIL 已从对 COM 的调用中返回组件”。

这是因为当在同一个应用程序中拖动数据WPF Winforms时,该数据必须可序列化!

我已经检查了将我们所有的类转换为“可序列化”有多么困难,出于几个原因,我会感到非常痛苦......第一,我们实际上需要让所有类可序列化和两个,其中一些类引用了控件!并且控件不可序列化。因此需要进行一次重大重构。

所以...因为我们想要传递任何类的 any 对象以在同一应用程序中从 WPF 拖动/拖动到 WPF,所以我决定创建一个包装类,具有 Serializable 属性并实现 ISerializable .我将有 1 个带有 1 个“对象”类型参数的构造函数,这将是实际的拖动数据。该包装器在序列化/反序列化时不会序列化对象本身......而是将 IntPtr 序列化到对象(我们可以这样做,因为我们只希望在我们的 1 个实例应用程序中实现该功能。)请参见下面的代码示例:

[Serializable]
public class DataContainer : ISerializable

public object Data  get; set; 

public DataContainer(object data)

    Data = data;


// Deserialization constructor
protected DataContainer(SerializationInfo info, StreamingContext context)

    IntPtr address = (IntPtr)info.GetValue("dataAddress", typeof(IntPtr));
    GCHandle handle = GCHandle.FromIntPtr(address);
    Data = handle.Target;
    handle.Free();


#region ISerializable Members

public void GetObjectData(SerializationInfo info, StreamingContext context)

    GCHandle handle = GCHandle.Alloc(Data);
    IntPtr address = GCHandle.ToIntPtr(handle);
    info.AddValue("dataAddress", address);


#endregion

为了保持 IDataObject 功能,我创建了以下 DataObject 包装器:

public class DataObject : IDataObject

System.Collections.Hashtable _Data = new System.Collections.Hashtable();

public DataObject()  

public DataObject(object data)

    SetData(data);


public DataObject(string format, object data)

    SetData(format, data);


#region IDataObject Members

public object GetData(Type format)

    return _Data[format.FullName];


public bool GetDataPresent(Type format)

    return _Data.ContainsKey(format.FullName);


public string[] GetFormats()

    string[] strArray = new string[_Data.Keys.Count];
    _Data.Keys.CopyTo(strArray, 0);
    return strArray;


public string[] GetFormats(bool autoConvert)

    return GetFormats();


private void SetData(object data, string format)

    object obj = new DataContainer(data);

    if (string.IsNullOrEmpty(format))
    
        // Create a dummy DataObject object to retrieve all possible formats.
        // Ex.: For a System.String type, GetFormats returns 3 formats:
        // "System.String", "UnicodeText" and "Text"
        System.Windows.Forms.DataObject dataObject = new System.Windows.Forms.DataObject(data);
        foreach (string fmt in dataObject.GetFormats())
        
            _Data[fmt] = obj;
        
    
    else
    
        _Data[format] = obj;
    


public void SetData(object data)

    SetData(data, null);


#endregion

我们使用上面的类是这样的:

myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject));

// in the drop event for example
e.Data.GetData(typeof(myNonSerializableClass));

我知道我知道......这不是很漂亮......但它正在做我们想要的。我们还创建了一个拖放帮助器类,它掩盖了 DataObject 的创建,并具有模板化的 GetData 函数来检索数据而无需任何强制转换......有点像:

myNonSerializableClass newObj = DragDropHelper.GetData<myNonSerializableClass>(e.Data);

再次感谢您的回复!你们给了我很好的想法,在哪里寻找可能的解决方案!

-奥利

【讨论】:

感谢您发布此内容,它帮助我解决了代码中的类似问题。我已将 [Serializable] 添加到我的 IDataObject 实现中,突然神秘的 E_FAIL:s 停止了。【参考方案2】:

前段时间我遇到了一个“类似”的问题,所以我至少可以告诉你我发现了什么。

似乎 .Net 在执行拖放操作时诉诸 OLE 远程处理,但这是最简单的情况。由于某种原因,GetDataPresent 在这些情况下会成功,而 GetData 会失败。 .Net 框架中有多个版本的 IDataObject,这进一步使这一点变得神秘。

Windows 窗体默认为 System.Windows.Forms.IDataObject。但是,在您的情况下,您可以尝试给 System.Runtime.InteropServices.ComTypes.IDataObject 一个镜头。你也可以看看我的讨论here。

希望这会有所帮助。

【讨论】:

【参考方案3】:

乍一看似乎很棒。我试过了,但在实现上遇到了一些错误。 当我决定寻找更简单的东西时,我开始纠正一些错误,没有指针(嗯,我不喜欢这样,特别是在收集碳水化合物时,但我不知道它是否会产生真正的影响)并且不使用互操作。

我想出了这个。它适用于我,我希望它适用于其他任何人。它仅用于本地拖放(在同一应用内)。

如何使用拖拽:

DragDrop.DoDragDrop(listBoxOfAvailableScopes, new DragDropLocal(GetSelectedSimulResultScopes()),
                                                DragDropEffects.Copy);

如何使用drop(get):

DragDropLocal dragDropLocal = (DragDropLocal)e.Data.GetData(typeof(DragDropLocal));
            SimulResultScopes simulResultScopes = (SimulResultScopes)dragDropLocal.GetObject();

代码:

namespace Util

    [Serializable]
    public class DragDropLocal
    
        private static readonly Dictionary<Guid, object> _dictOfDragDropLocalKeyToDragDropSource = new Dictionary<Guid, object>();

        private Guid _guid = Guid.NewGuid();

        public DragDropLocal(object objToDrag)
        
            _dictOfDragDropLocalKeyToDragDropSource.Add(_guid, objToDrag);
        

        public object GetObject()
        
            object obj;
            _dictOfDragDropLocalKeyToDragDropSource.TryGetValue(_guid, out obj);
            return obj;
        

        ~DragDropLocal()
        
            _dictOfDragDropLocalKeyToDragDropSource.Remove(_guid);
        
    

【讨论】:

【参考方案4】:

也许事情正好相反。 PreviewDragEnter 应该与 WPFTextBox 相关。还要注意 DragEventArgs 类。 System.Windows.Form(Windows 窗体版本)中有一个,System.Windows 下有一个(WPF 版本)。

【讨论】:

以上是关于WinForms 互操作,从 WinForms 拖放 -> WPF的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Spotify 拖放到 Winforms 应用程序

Winforms -> 可视化拖放项目

拖放在 C# Winforms 应用程序中不起作用

Winforms中如何支持拖放文件夹?

使用拖放重新排序winforms列表框?

C# WinForms 用鼠标拖动控件