WinForms 互操作、拖动和从 WinForms 中删除 ->WPF [英] WinForms Interop, Drag & Drop from 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屋!