WinForms 互操作,从 WinForms 拖放 -> WPF
Posted
技术标签:
【中文标题】WinForms 互操作,从 WinForms 拖放 -> WPF【英文标题】:WinForms Interop, Drag & Drop from WinForms -> WPF 【发布时间】:2010-11-15 19:58:05 【问题描述】:我正在尝试将数据从应用程序的 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(字符串格式) 在 WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 48 中的 WindowsFormsApplication1.Form1.textBox_PreviewDragEnter(Object sender, DragEventArgs e)这里有一些代码:
// -- 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;
【问题讨论】:
【参考方案1】:@Pedery & jmayor:谢谢大家的建议! (见下面我的发现)
经过相当多的实验、试验和错误,以及一些“反射”,我设法弄清楚为什么我会收到神秘的错误消息“错误 HRESULT E_FAIL 已从对 COM 的调用中返回组件”。
这是因为当在同一个应用程序中拖动数据WPF Winforms时,该数据必须可序列化!
我已经检查了将我们所有的类转换为“可序列化”有多么困难,出于几个原因,我会感到非常痛苦......第一,我们实际上需要让所有类可序列化和两个,其中一些类引用了控件!并且控件不可序列化。因此需要进行一次重大重构。
所以...因为我们想要传递任何类的 any 对象以在同一应用程序中从 WPF 拖动/拖动到 WPF,所以我决定创建一个包装类,具有 Serializable 属性并实现 ISerializable .我将有 1 个带有 1 个“对象”类型参数的构造函数,这将是实际的拖动数据。该包装器在序列化/反序列化时不会序列化对象本身......而是将 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
为了保持 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);
再次感谢您的回复!你们给了我很好的想法,在哪里寻找可能的解决方案!
-奥利
【讨论】:
感谢您发布此内容,它帮助我解决了代码中的类似问题。我已将 [Serializable] 添加到我的 IDataObject 实现中,突然神秘的 E_FAIL:s 停止了。【参考方案2】:前段时间我遇到了一个“类似”的问题,所以我至少可以告诉你我发现了什么。
似乎 .Net 在执行拖放操作时诉诸 OLE 远程处理,但这是最简单的情况。由于某种原因,GetDataPresent 在这些情况下会成功,而 GetData 会失败。 .Net 框架中有多个版本的 IDataObject,这进一步使这一点变得神秘。
Windows 窗体默认为 System.Windows.Forms.IDataObject。但是,在您的情况下,您可以尝试给 System.Runtime.InteropServices.ComTypes.IDataObject 一个镜头。你也可以看看我的讨论here。
希望这会有所帮助。
【讨论】:
【参考方案3】:乍一看似乎很棒。我试过了,但在实现上遇到了一些错误。 当我决定寻找更简单的东西时,我开始纠正一些错误,没有指针(嗯,我不喜欢这样,特别是在收集碳水化合物时,但我不知道它是否会产生真正的影响)并且不使用互操作。
我想出了这个。它适用于我,我希望它适用于其他任何人。它仅用于本地拖放(在同一应用内)。
如何使用拖拽:
DragDrop.DoDragDrop(listBoxOfAvailableScopes, new DragDropLocal(GetSelectedSimulResultScopes()),
DragDropEffects.Copy);
如何使用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);
【讨论】:
【参考方案4】:也许事情正好相反。 PreviewDragEnter 应该与 WPFTextBox 相关。还要注意 DragEventArgs 类。 System.Windows.Form(Windows 窗体版本)中有一个,System.Windows 下有一个(WPF 版本)。
【讨论】:
以上是关于WinForms 互操作,从 WinForms 拖放 -> WPF的主要内容,如果未能解决你的问题,请参考以下文章