WinForms Interop,从WinForms拖放 - > WPF

Sup*_*Oli 17 wpf interop drag-and-drop winforms

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

尝试相同的东西,但从Winforms到Winforms工作正常.(参见下面的示例代码)

我需要帮助才能完成这项工作......有什么线索我做错了什么?

谢谢!


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

情况1)工作正常但案例2)在尝试使用GetData()检索丢弃数据时崩溃.GetDataPresent("WindowsFormsApplication1.MyContainerClass")返回"true"所以从理论上讲,我应该能够像在Winforms中那样检索那种类型的drop数据.

这是崩溃的堆栈跟踪:

"Error HRESULT E_FAIL has been returned from a call to a COM component" with the following stack trace:
 at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
 at System.Windows.Forms.DataObject.GetDataIntoOleStructs(FORMATETC& formatetc, STGMEDIUM& medium)
 at System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(FORMATETC& formatetc, STGMEDIUM& medium)
 at System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetData(FORMATETC& formatetc, STGMEDIUM& medium)
 at System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC& formatetc, STGMEDIUM& medium)
 at System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(String format, DVASPECT aspect, Int32 index)
 at System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(String format, DVASPECT aspect, Int32 index)
 at System.Windows.DataObject.OleConverter.GetData(String format, Boolean autoConvert, DVASPECT aspect, Int32 index)
 at System.Windows.DataObject.OleConverter.GetData(String format, Boolean autoConvert)
 at System.Windows.DataObject.GetData(String format, Boolean autoConvert)
 at System.Windows.DataObject.GetData(String format)
 at WindowsFormsApplication1.Form1.textBox_PreviewDragEnter(Object sender, DragEventArgs e) in WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 48  

这是一些代码:

// -- 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Sup*_*Oli 16

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

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

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

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

所以...因为我们想要传递任何类的任何对象来从同一个应用程序中的WPF拖动,所以我决定创建一个包装类,使用Serializable属性并实现ISerializable.我将有1个带有1个"object"类型参数的构造函数,它将是实际的拖动数据.当序列化/反序列化时,该包装器不会序列化对象本身......而是将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
}
Run Code Online (Sandbox Code Playgroud)

为了保持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
}
Run Code Online (Sandbox Code Playgroud)

我们正在使用上面这样的类:

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

// in the drop event for example
e.Data.GetData(typeof(myNonSerializableClass));
Run Code Online (Sandbox Code Playgroud)

我知道我知道......它不是很漂亮 ......但它正在做我们想要的.我们还创建了一个dragdrop辅助类来掩盖DataObject的创建,并使用模板化的GetData函数来检索数据,而不需要任何转换......有点像:

myNonSerializableClass newObj = DragDropHelper.GetData<myNonSerializableClass>(e.Data);
Run Code Online (Sandbox Code Playgroud)

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

-OLi


Ped*_*ery 5

我前段时间遇到过"类似"的问题所以我至少可以告诉你我发现了什么.

似乎.Net在执行拖放操作时采用OLE远程处理,但最简单的情况.出于某种原因,GetDataPresent将在这些情况下成功并且GetData将失败.由于.Net框架中有多个版本的IDataObject,这更加神秘.

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

希望这可以帮助.