WinForms 互操作、拖动和从 WinForms 中删除 ->WPF [英] WinForms Interop, Drag & Drop from WinForms -> WPF

查看:15
本文介绍了WinForms 互操作、拖动和从 WinForms 中删除 ->WPF的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将数据从应用程序的 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(字符串格式)在 WindowsFormsApplication1WindowsFormsApplication1Form1.cs:line 48 中的 WindowsFormsApplication1.Form1.textBox_PreviewDragEnter(Object sender, DragEventArgs e)

这里有一些代码:

//-- 在表单中添加一个 ElementHost --//-- 在表单中添加标签 --公共部分类Form1:表格{公共表格1(){初始化组件();System.Windows.Controls.TextBox textBox = new System.Windows.Controls.TextBox();textBox.Text = "WPF 文本框";文本框.AllowDrop = true;elementHost2.Child = 文本框;textBox.PreviewDragEnter += new System.Windows.DragEventHandler(textBox_PreviewDragEnter);System.Windows.Forms.TextBox wfTextBox = new System.Windows.Forms.TextBox();wfTextBox.Text = "Winforms 文本框";wfTextBox.AllowDrop = true;wfTextBox.DragEnter += new DragEventHandler(wfTextBox_DragEnter);Controls.Add(wfTextBox);}void wfTextBox_DragEnter(对象发送者,DragEventArgs e){bool dataPresent = e.Data.GetDataPresent("WindowsFormsApplication1.MyContainerClass");//这里没有崩溃!对象数据 = e.Data.GetData("WindowsFormsApplication1.MyContainerClass");}void textBox_PreviewDragEnter(对象发送者,System.Windows.DragEventArgs e){bool dataPresent = e.Data.GetDataPresent("WindowsFormsApplication1.MyContainerClass");//崩溃出现在这里!!//{"错误 HRESULT E_FAIL 已从对 COM 组件的调用中返回."}对象数据 = e.Data.GetData("WindowsFormsApplication1.MyContainerClass");}私人无效标签1_MouseDown(对象发送者,MouseEventArgs e){label1.DoDragDrop(new MyContainerClass(label1.Text), DragDropEffects.Copy);}}公共类 MyContainerClass{公共对象数据 { 获取;放;}公共 MyContainerClass(对象数据){数据=数据;}}

解决方案

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

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

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

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

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

[可序列化]公共类 DataContainer : ISerializable{公共对象数据 { 获取;放;}公共数据容器(对象数据){数据=数据;}//反序列化构造函数受保护的 DataContainer(SerializationInfo 信息,StreamingContext 上下文){IntPtr 地址 = (IntPtr)info.GetValue("dataAddress", typeof(IntPtr));GCHandle 句柄 = GCHandle.FromIntPtr(地址);数据=句柄.目标;处理.Free();}#region ISerializable 成员public void GetObjectData(SerializationInfo 信息,StreamingContext 上下文){GCHandle 句柄 = GCHandle.Alloc(Data);IntPtr 地址 = GCHandle.ToIntPtr(handle);info.AddValue("数据地址", 地址);}#endregion}

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

公共类 DataObject : IDataObject{System.Collections.Hashtable _Data = new System.Collections.Hashtable();公共数据对象(){}公共数据对象(对象数据){设置数据(数据);}public DataObject(字符串格式,对象数据){设置数据(格式,数据);}#region IDataObject 成员公共对象GetData(类型格式){返回_Data[格式.FullName];}public bool GetDataPresent(类型格式){返回 _Data.ContainsKey(format.FullName);}公共字符串[] GetFormats(){字符串[] strArray = 新字符串[_Data.Keys.Count];_Data.Keys.CopyTo(strArray, 0);返回字符串数组;}公共字符串[] GetFormats(bool autoConvert){返回 GetFormats();}private void SetData(对象数据,字符串格式){对象 obj = new DataContainer(data);如果(字符串.IsNullOrEmpty(格式)){//创建一个虚拟 DataObject 对象以检索所有可能的格式.//例如:对于 System.String 类型,GetFormats 返回 3 种格式://System.String"、UnicodeText"和Text"System.Windows.Forms.DataObject dataObject = new System.Windows.Forms.DataObject(data);foreach(dataObject.GetFormats() 中的字符串 fmt){_Data[fmt] = 对象;}}别的{_Data[格式] = obj;}}公共无效SetData(对象数据){设置数据(数据,空);}#endregion}

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

myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject));//例如在 drop 事件中e.Data.GetData(typeof(myNonSerializableClass));

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

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

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

-奥利

I'm trying to drag data from the Winforms portion of my application on a WPF controls that's contained inside an "ElementHost". And it crashes when I try doing so.

Trying the same thing but from Winforms to Winforms works fine. (See example code below)

I need help on making this work... have any clues what I'm doing wrong?

Thanks!


Example:
In the sample code below, I'm just trying to drag a custom MyContainerClass object created when initating the drag on the label control on a 1) System.Windows.Forms.TextBox (Winforms) and 2) System.Windows.TextBox (WPF, added to an ElementHost).

Case 1) works fine but case 2) is crashing when trying to retrieve the drop data using GetData(). GetDataPresent("WindowsFormsApplication1.MyContainerClass") returns "true" so In theory, I should be able to retrive my drop data of that type like in Winforms.

Here is the stack trace of the crash:

"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 WindowsFormsApplication1WindowsFormsApplication1Form1.cs:line 48  

Here is some code:

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

解决方案

@Pedery & jmayor: Thanks for the suggestions guys! (see my findings below)

After quite a few experimentation, trials and errors, and a bit of "Reflector'ing", I managed to figure out exactly why I was receiving the cryptic error message "Error HRESULT E_FAIL has been returned from a call to a COM component".

It was due to the fact that when dragging data WPF <-> Winforms in a same app, that data has to be Serializable!

I've checked how difficult it would be to transform all of our classes to "Serializable" and I would have a been a real pain for a couple of reasons... one, we would need to practically make all of classes serializable and two, some of these classes have references to Controls! And Controls aren't serializable. So a major refactoring would have been needed.

So... since we wanted to pass any object of any class to drag from/to WPF inside the same application, I decided to create a wrapper class, with the Serializable attribute and implementing ISerializable. I would have 1 contructor with 1 parameter of type "object" which would be the actual drag data. That wrapper, when serializing/de-serializing, would serialize not the object itself... but rather the IntPtr to the object (which we can do since we only want that functionnality inside our 1 instance only application.) See code sample below:

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

To keep the IDataObject functionnality, I created the following DataObject wrapper:

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
}

And we are using the above classes like this:

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

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

I know I know... it's not very pretty... but it's doing what we wanted. We also created a dragdrop helper class which masks the DataObject creation and has templated GetData functions to retrieve the data without any cast... a bit like:

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

So thanks again for the replies! You guys gave me good ideas where to look at for possible solutions!

-Oli

这篇关于WinForms 互操作、拖动和从 WinForms 中删除 ->WPF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆