WinForms交互,从WinForms拖放到WPF

18

我正在尝试从Winforms应用程序中的一个包含在"ElementHost"中的WPF控件上拖动数据。 但是在尝试执行此操作时,它会崩溃。

在Winforms到Winforms的情况下,尝试相同的操作可以正常工作。(请参见下面的示例代码)

我需要帮助使其正常工作...你有任何线索吗?我做错了什么吗?

谢谢!


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

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

以下是崩溃的堆栈跟踪:

使用以下堆栈跟踪返回“Error HRESULT E_FAIL has been returned from a call to a COM component”:
 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;
    }
}
4个回答

17

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

经过多次尝试、试错和一些“反编译”,我终于弄清楚了为什么会收到神秘的错误消息“调用 COM 组件返回了 HRESULT E_FAIL 错误”。

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

我检查了将所有类转换为“可序列化”所需的难度,这将是一项真正的痛苦,原因有几个……首先,我们实际上需要使所有类都可序列化;其次,其中一些类引用了控件!而控件是不可序列化的。因此,需要进行大量的重构。

因此……既然我们想要在同一应用程序内传递任何类的任何对象以进行拖放操作,我决定创建一个包装器类,具有 Serializable 属性并实现 ISerializable 接口。我将有一个接受类型为“object”的参数的构造函数,它将是实际的拖动数据。当进行序列化/反序列化时,该包装器将不会序列化对象本身……而是对象的 IntPtr(我们之所以可以这样做,是因为我们只需要在我们的单个实例应用程序中使用该功能)。请参见下面的代码示例:

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

非常感谢你们的回复!你们给了我很好的思路,让我知道了可能的解决方法!

-Oli


谢谢您发布这篇文章,它帮助我解决了代码中的一个类似问题。我已经在我的IDataObject实现中添加了[Serializable],突然间神秘的E_FAIL错误不再出现了。 - John Källén

5

我曾经遇到了一个“类似”的问题,因此我至少可以告诉你我的发现。

似乎在执行简单的拖放操作时,.Net会采用OLE远程处理。由于某种原因,在这些情况下GetDataPresent将成功,而GetData将失败。此外,.Net框架中有几个版本的IDataObject,这更加使问题复杂化。

Windows窗体默认使用System.Windows.Forms.IDataObject。但在您的情况下,您可以尝试使用System.Runtime.InteropServices.ComTypes.IDataObject。您还可以查看我的讨论(此处)

希望这有所帮助。


3
乍一看似乎很不错。我试了试,但是在实现过程中遇到了一些错误。 当我决定寻找更简单的东西时,我开始纠正一些错误,希望能够不使用指针(呃,我不喜欢那个,特别是在垃圾收集方面,但我不知道它是否会产生真实影响)和不使用Interop。
这是我的发现。它适合我并且我希望它也适用于其他人。仅适用于本地拖放(在同一个应用程序内部)。
如何使用拖放:
DragDrop.DoDragDrop(listBoxOfAvailableScopes, new DragDropLocal(GetSelectedSimulResultScopes()),
                                                DragDropEffects.Copy);

How to use to 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);
        }
    }
}

0
也许事件的顺序是相反的。PreviewDragEnter 应该与 WPFTextBox 相关联。同时要注意 DragEventArgs 类的版本。在 System.Windows.Form( Windows Form 版本)中有一个,而在 System.Windows(WPF 版本)下也有一个。

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