form wpf winforms interop drag-and-drop

wpf in windows forms



WinForms Interop, arrastrar y soltar desde WinForms-> WPF (4)

@Pedery & jmayor: ¡Gracias por las sugerencias, chicos! (ver mis hallazgos a continuación)

Después de varios experimentos, pruebas y errores, y un poco de "Reflector''ing", logré averiguar exactamente por qué recibía el mensaje de error críptico "Error HRESULT E_FAIL ha sido devuelto desde una llamada a un componente COM".

Fue debido al hecho de que al arrastrar datos WPF <-> Winforms en una misma aplicación, ¡esos datos deben ser serializables!

He comprobado lo difícil que sería transformar todas nuestras clases a "Serializable" y habría sido un verdadero dolor por un par de razones ... una, tendríamos que hacer prácticamente todas las clases serializables y dos , algunas de estas clases tienen referencias a los controles! Y los controles no son serializables. Así que habría sido necesaria una refactorización importante .

Entonces, dado que queríamos pasar cualquier objeto de cualquier clase para arrastrar desde / a WPF dentro de la misma aplicación, decidí crear una clase de envoltorio, con el atributo Serializable e implementando ISerializable. Tendría 1 contructor con 1 parámetro de tipo "objeto", que serían los datos reales de arrastre. Esa envoltura, al serializar / des-serializar, no serializaría el objeto en sí ... sino el IntPtr al objeto (lo que podemos hacer ya que solo queremos esa funcionalidad dentro de nuestra aplicación de 1 instancia solamente). Vea el ejemplo de código a continuación:

[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 }

Para mantener la funcionalidad de IDataObject, creé el siguiente contenedor de 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 }

Y estamos usando las clases anteriores como esta:

myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject)); // in the drop event for example e.Data.GetData(typeof(myNonSerializableClass));

Sé que lo sé ... no es muy bonito ... pero está haciendo lo que queríamos. También creamos una clase de ayudante de arrastrar y arrastrar que enmascara la creación de DataObject y tiene funciones GetData para recuperar los datos sin ningún tipo de conversión ... un poco como:

myNonSerializableClass newObj = DragDropHelper.GetData<myNonSerializableClass>(e.Data);

Así que gracias de nuevo por las respuestas! ¡Ustedes me dieron buenas ideas donde buscar posibles soluciones!

-Oli

Estoy tratando de arrastrar datos de la parte de Winforms de mi aplicación a un control WPF que está contenido dentro de un "ElementHost". Y se estrella cuando intento hacerlo.

Intentar lo mismo pero de Winforms a Winforms funciona bien. (Ver código de ejemplo a continuación)

Necesito ayuda para hacer que esto funcione ... ¿Tienes alguna pista de lo que estoy haciendo mal?

¡Gracias!

Ejemplo:
En el código de ejemplo a continuación, solo intento arrastrar un objeto MyContainerClass personalizado cuando se inicia el arrastre del control de etiqueta en 1) System.Windows.Forms.TextBox (Winforms) y 2) System.Windows.TextBox (WPF , añadido a un ElementHost).

El caso 1) funciona bien, pero el caso 2 se bloquea cuando se intenta recuperar los datos soltados utilizando GetData (). GetDataPresent ("WindowsFormsApplication1.MyContainerClass") devuelve "true", por lo que, en teoría, debería poder recuperar mis datos de ese tipo, como en Winforms.

Aquí está el rastro de la pila de la caída:

"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

Aquí hay un código:

// -- 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; } }


Hace un tiempo tuve un problema "similar", así que al menos puedo decirle lo que descubrí.

Parece que .Net está recurriendo al control remoto OLE cuando se realizan operaciones de arrastrar y soltar, pero en los casos más simples. Por alguna razón, GetDataPresent tendrá éxito en estos casos y GetData fallará. Esto se ve además desconcertado por el hecho de que hay varias versiones de IDataObject en el marco .Net.

De forma predeterminada, Windows Forms es System.Windows.Forms.IDataObject. Sin embargo, en su caso, podría intentar darle un disparo a System.Runtime.InteropServices.ComTypes.IDataObject. También puedes ver mi discusión here .

Espero que esto ayude.


Parece maravilloso a primera vista. Lo intenté pero conseguí algunos errores en implementaciones. Comencé a corregir algunos errores cuando decidí buscar algo un poco más simple, que no tiene punteros (humm no me gusta eso, particularmente con la colección de carbage, pero no tengo idea si podría tener un impacto real) y que no utilicen Interop.

Se me ocurre eso. Funciona para mí y espero que funcione para cualquier otra persona. Solo está diseñado para ser usado para arrastrar localmente (dentro de la misma aplicación).

Cómo utilizar para arrastrar:

DragDrop.DoDragDrop(listBoxOfAvailableScopes, new DragDropLocal(GetSelectedSimulResultScopes()), DragDropEffects.Copy);

Cómo usar para soltar (obtener):

DragDropLocal dragDropLocal = (DragDropLocal)e.Data.GetData(typeof(DragDropLocal)); SimulResultScopes simulResultScopes = (SimulResultScopes)dragDropLocal.GetObject();

Código:

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); } } }


Tal vez los eventos son de la manera opuesta. El PreviewDragEnter debe estar relacionado con el WPFTextBox. También ten cuidado con la clase DragEventArgs. Hay uno en System.Windows.Form (versión Windows Form) y el que se encuentra en System.Windows (para la versión WPF).