我正在尝试将我的应用程序的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中那样检索那种类型的drop数据。
以下是崩溃的堆栈跟踪:
"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 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;
}
}
答案 0 :(得分:16)
@Pedery& jmayor:谢谢你的建议! (见下面我的发现)
经过相当多的实验,试验和错误,以及一些“反射器”,我设法弄明白为什么我收到了神秘的错误消息“错误HRESULT E_FAIL已从调用COM返回部件”。
这是因为当拖动数据时WPF< - > Winforms在同一个应用中,数据必须可序列化!
我已经检查过将所有类转换为“Serializable”是多么困难,我会因为几个原因而感到非常痛苦......其中一个,我们需要实际完成所有类serializable和two,其中一些类引用了Controls!并且控件不可序列化。因此需要进行主要的重构。
所以......因为我们想要传递任何类的任何对象来从同一个应用程序中的WPF拖动,所以我决定使用Serializable属性创建一个包装类,并实现ISerializable 。我将有1个带有1个“object”类型参数的构造函数,它将是实际的拖动数据。当序列化/反序列化时,该包装器不会序列化对象本身......而是将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));
我知道我知道......它不是漂亮 ......但它正在做我们想要的事情。我们还创建了一个dragdrop辅助类来掩盖DataObject的创建,并使用模板化的GetData函数来检索数据而不需要任何强制转换......有点像:
myNonSerializableClass newObj = DragDropHelper.GetData<myNonSerializableClass>(e.Data);
再次感谢回复!你们给了我很好的想法,在哪里寻找可能的解决方案!
-OLi
答案 1 :(得分:5)
前段时间我遇到了“类似”的问题所以我至少可以告诉你我发现了什么。
似乎.Net在执行拖放操作时采用OLE远程处理,但最简单的情况。出于某种原因,GetDataPresent将在这些情况下成功并且GetData将失败。在.Net框架中有多个版本的IDataObject这一事实使这更加神秘。
Windows窗体默认为System.Windows.Forms.IDataObject。但是,在您的情况下,您可以尝试给System.Runtime.InteropServices.ComTypes.IDataObject一个镜头。您还可以查看我的讨论here。
希望这有帮助。
答案 2 :(得分:2)
一见钟情似乎很棒。我尝试了但是在实现上遇到了一些错误。 我开始纠正一些错误,当我决定寻找一些更简单的东西,没有指针(哼哼我不喜欢,特别是对于垃圾收集,但我不知道它是否会产生真正的影响)并且不使用Interop。
我想到了。它适用于我,我希望它适用于任何其他人。它仅用于本地拖放(在同一个应用程序内)。
如何使用拖动:
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);
}
}
}
答案 3 :(得分:0)
也许事件正好相反。 PreviewDragEnter应与WPFTextBox相关联。还要注意DragEventArgs类。 System.Windows.Form(Windows窗体版)中有一个,System.Windows下有一个(适用于WPF版)。