问题描述
我正在尝试将数据从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中检索这种类型的丢弃数据。
这是崩溃的堆栈跟踪:
错误HRESULT E_FAIL已经通过以下堆栈跟踪从调用COM组件返回:
在System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode,IntPtr errorInfo)
在System.Windows.Forms.DataObject .GetDataIntoOleStructs(FORMATETC&formatetc,STGMEDIUM&medium)
在System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(FORMATETC&formatetc,STGMEDIUM&medium)
在System.Windows.Forms .DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetData(FORMATETC&formatetc,STGMEDIUM&medium)
在System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC&formatetc,STGMEDIUM&medium)
在System.Windows .DataObject.OleConverter.GetDataFromOleHGLOBAL(String format,DVASPECT aspect,Int32 index)
在System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(String forma t,DVASPECT方面,Int32索引)
在System.Windows.DataObject.OleConverter.GetData(String格式,Boolean autoConvert,DVASPECT方面,Int32索引)
在System.Windows.DataObject.OleConverter.GetData(字符串格式,Boolean autoConvert)
在System.Windows.DataObject.GetData(String format,Boolean autoConvert)
在System.Windows.DataObject.GetData(字符串格式)
在WindowsFormsApplication1.Form1。 WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs中的textBox_PreviewDragEnter(Object sender,DragEventArgs e)$ 48
$ / pre
这是一些代码: p>
// - 向您的表单添加ElementHost -
// - 在表单中添加标签 -
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!
// {HRESULT E_FAIL已从对COM组件的调用返回。
对象数据= 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;组;
public MyContainerClass(object data)
{
Data = data;
}
}
解决方案Pedery& jmayor:谢谢你们的建议! (见下面的发现)
经过不少试验,试验和错误,还有一点反射器,我设法弄清楚为什么我是接收到隐含错误消息错误HRESULT E_FAIL已从对COM组件的调用返回。
这是因为当拖动数据WPF时 - > Winforms在同一个应用程序中,数据必须可序列化!
我已经检查了将我们所有的类到可序列化,我会有一个真正的痛苦有几个原因...一个,我们需要实际上使所有的类可序列化和两个,其中一些类有引用控件!控件不可序列化。因此,需要重构 。
所以...因为我们想通过任何对象任何类在同一个应用程序中拖动到WPF,我决定用Serializable属性创建一个包装类,并实现ISerializable。我将有1个参数,其中1个参数类型为object,这将是实际的拖动数据。这个包装器,当序列化/解串行化时,不会将对象本身序列化,而是将对象的IntPtr序列化(我们可以做的只是在我们的1个实例应用程序中只需要该功能)。请参见下面的代码示例:
[Serializable]
public class DataContainer:ISerializable
{
public object Data {get ;组;
public DataContainer(object data)
{
Data = data;
}
//反序列化构造函数
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成员
public void GetObjectData(SerializationInfo info,StreamingContext context)
{
GCHandle handle = GCHandle.Alloc (数据);
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(字符串格式,对象数据)
{
SetData(format,data);
}
#region IDataObject成员
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 obj = new DataContainer(data);
if(string.IsNullOrEmpty(format))
{
//创建一个虚拟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] = obj;
}
}
else
{
_Data [format] = obj;
}
}
public void SetData(object data)
{
SetData(data,null);
}
#endregion
}
我们正在使用以下类:
myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject));
//在drop事件中,例如
e.Data.GetData(typeof(myNonSerializableClass));
我知道我知道...这不是很好的 但它正在做我们想要的。我们还创建了一个dragdrop帮助器类,它掩盖了DataObject的创建,并具有模板化的GetData函数来检索数据,而没有任何转换...有点像:
code> myNonSerializableClass newObj = DragDropHelper.GetData< myNonSerializableClass>(e.Data);
所以再次感谢回复!你们给了我很好的想法,看看可能的解决方案!
-Oli
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 WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 48Here 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 Interop,Drag&从WinForms中删除 - > WPF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!