2009-07-31 17 views
17

Estoy tratando de arrastrar los datos desde la parte de Winforms de mi aplicación en los controles de WPF que están dentro de un "ElementHost". Y se cuelga cuando trato de hacerlo.WinForms Interop, arrastrar y soltar desde WinForms -> WPF

Probando lo mismo pero de Winforms a Winforms funciona bien. (Vea el código de ejemplo a continuación)

Necesito ayuda para hacer este trabajo ... ¿tengo alguna pista de lo que estoy haciendo mal?

Gracias!


Ejemplo:
En el código de ejemplo siguiente, sólo estoy tratando de arrastrar un objeto MyContainerClass personalizado creado cuando initating la fricción en el control de etiqueta en una) System.Windows.Forms.TextBox 1 (Winforms) y 2) System.Windows.TextBox (WPF, agregado a ElementHost).

El caso 1) funciona bien, pero el caso 2) se bloquea cuando se intenta recuperar los datos de colocación utilizando GetData(). GetDataPresent ("WindowsFormsApplication1.MyContainerClass") devuelve "verdadero", así que en teoría, debería ser capaz de recuperar mis datos de caída de ese tipo, como en Winforms.

Aquí está el seguimiento de la pila del accidente:

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

Respuesta

16

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

Después de bastantes experimentos, pruebas y errores, y un poco de "Reflector'ing", me las arreglé para descubrir exactamente por qué estaba recibiendo el mensaje de error críptico "Error HRESULT E_FAIL ha sido devuelto de una llamada a un componente COM ".

Se debió al hecho de que al arrastrar datos WPF < -> Winforms en una misma aplicación, esos datos tienen que ser ser serializables.

He comprobado lo difícil que sería transformar todas nuestras clases en "Serializable" y hubiera sido un verdadero dolor por un par de razones ...uno, necesitaríamos hacer prácticamente todas las clases serializables y dos, ¡algunas de estas clases tienen referencias a Controles! Y los controles no son serializables. Por lo tanto, se habría necesitado una refactorización mayor.

Así que ... ya que queríamos pasar cualquier objeto de cualquier clase para arrastrar desde/hasta WPF dentro de la misma aplicación, he decidido crear una clase contenedora, con el atributo Serializable e implementar ISerializable. Tendría 1 contructor con 1 parámetro de tipo "objeto" que sería la información real de arrastre. Ese envoltorio, al serializar/deserializar, serializaría no el objeto en sí ... sino el IntPtr al objeto (lo cual podemos hacer ya que solo queremos esa funcionalidad dentro de nuestra aplicación de solo 1 instancia). Vea el siguiente ejemplo de código:

[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 IDataObject, creé la siguiente DataObject envoltorio:

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 utilizando las clases anteriores de esta manera:

myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject)); 

// in the drop event for example 
e.Data.GetData(typeof(myNonSerializableClass)); 

Sé que sé ... no es muy bonita ... pero está haciendo lo que queríamos. También hemos creado una clase de ayuda dragdrop que enmascara la creación DataObject y ha de plantilla funciones GetData para recuperar los datos, sin ningún dominante ... un poco como:

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

Así que gracias de nuevo por las respuestas! ¡Ustedes me dieron buenas ideas sobre dónde buscar posibles soluciones!

-Oli

+0

Gracias por publicar esto, me ayudó a resolver un problema similar en mi código. He agregado [Serializable] a mi implementación de IDataObject y de repente los misteriosos E_FAIL: s han cesado. –

0

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

5

Tuve un problema "similar" hace algún tiempo, así que al menos puedo contarte lo que descubrí.

Parece que .Net está recurriendo a la comunicación remota OLE cuando se realizan operaciones de arrastrar/soltar, pero en el más simple de los casos. Por algún motivo, GetDataPresent tendrá éxito y GetData fallará. Esto se desconcierta aún más por el hecho de que hay varias versiones de IDataObject en .Net framework.

Windows Forms se predetermina a System.Windows.Forms.IDataObject. Sin embargo, en su caso podría tratar de darle a System.Runtime.InteropServices.ComTypes.IDataObject un disparo en su lugar. También puede consultar mi discusión here.

Espero que esto ayude.

2

Parece maravilloso a primera vista. Lo intenté pero obtuve algunos errores en las implementaciones. Comencé a corregir algunos errores cuando decidí buscar algo un poco más simple, que no tuviera punteros (Humm, no me gusta, especialmente con la recolección de residuos, pero no tengo idea de si podría tener un impacto real) y que no usen Interop.

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

Cómo utilizar para arrastrar:

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

Cómo utilizar a caer (GET):

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