WinForms Interop, перетаскивание из WinForms -> WPF
Я пытаюсь перетащить данные из части Winforms моего приложения на элементы управления WPF, которые содержатся внутри "ElementHost". И он падает, когда я пытаюсь это сделать.
Попытка сделать то же самое, но из 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.GetDataIntoOleStruC (FORM) formatetc, STGMEDIUM& medium) в System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(FORMATETC& formatetc, STGMEDIUM& medium) в System.Windows.Forms.DataObjectSerjectSject.Sject.GetData(FORMATETC& formatetc, STGMEDIUM& medium) в System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC& formatetc, STGMEDIUM& medium) в системном..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.Form1.textBox_PreviewDragEnter(Отправитель объекта, DragEventArgs e) в WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs: строка 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;
}
}
4 ответа
@Pedery & jmayor: Спасибо за предложения, ребята! (см. мои выводы ниже)
После нескольких экспериментов, проб и ошибок и небольшого "рефлектора" мне удалось выяснить, почему именно я получал загадочное сообщение об ошибке "Ошибка HRESULT E_FAIL возвращена после вызова COM-компонента".
Это было связано с тем, что при перетаскивании данных WPF <-> Winforms в одном приложении эти данные должны быть сериализуемыми!
Я проверил, насколько сложно было бы преобразовать все наши классы в "Сериализуемый", и я бы испытал настоящую боль по нескольким причинам: во-первых, нам нужно было бы практически сделать все классы сериализуемыми, и два некоторые из этих классов имеют ссылки на элементы управления! И элементы управления не сериализуемы. Таким образом, основной рефакторинг был бы необходим.
Итак... поскольку мы хотели передать любой объект любого класса для перетаскивания из / в WPF внутри одного и того же приложения, я решил создать класс-оболочку с атрибутом Serializable и реализовать ISerializable. У меня был бы 1 конструктор с 1 параметром типа "объект", который был бы фактическими данными перетаскивания. Эта оболочка, при сериализации / десериализации, будет сериализовать не сам объект... а скорее IntPtr к объекту (что мы можем сделать, поскольку мы хотим, чтобы эта функциональность была только в нашем приложении только с одним экземпляром). См. Пример кода ниже:
[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
У меня была "похожая" проблема некоторое время назад, поэтому я могу, по крайней мере, рассказать вам, что я узнал.
Кажется,.Net прибегает к удаленному удаленному взаимодействию, когда операции перетаскивания выполняются, но в простейших случаях. По какой-то причине GetDataPresent в этих случаях будет успешным, а GetData завершится ошибкой. Кроме того, это загадывается тем фактом, что в инфраструктуре.Net существует несколько версий объекта IDataObject.
По умолчанию в Windows Forms используется System.Windows.Forms.IDataObject. Тем не менее, в вашем случае вы можете попытаться дать System.Runtime.InteropServices.ComTypes.IDataObject вместо этого выстрел. Вы также можете проверить мое обсуждение здесь.
Надеюсь это поможет.
Кажется удивительным с первого взгляда. Я попробовал это, но получил некоторые ошибки на реализациях. Я начал исправлять некоторые ошибки, когда решил поискать что-то попроще, без указателей (хм, мне это не нравится, особенно в отношении сбора мусора, но я понятия не имею, может ли это оказать реальное влияние) и которые не используют Interop.
Я придумаю это. Это работает для меня, и я надеюсь, что это будет работать для всех остальных. Он предназначен только для локального перетаскивания (внутри того же приложения).
Как использовать для перетаскивания:
DragDrop.DoDragDrop(listBoxOfAvailableScopes, new DragDropLocal(GetSelectedSimulResultScopes()),
DragDropEffects.Copy);
Как использовать, чтобы уронить (получить):
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);
}
}
}
Может быть, события в обратном направлении. PreviewDragEnter должно быть связано с WPFTextBox. Также обратите внимание на класс DragEventArgs. Один в System.Windows.Form (версия Windows Form) и один в System.Windows(для версии WPF).