Pregunta

He creado una pequeña aplicación de prueba de formularios Windows Forms para probar un código de arrastrar / soltar. El formulario consta de tres PictureBoxes. Mi intención era tomar una imagen de un PictureBox, mostrarla como un cursor personalizado durante la operación de arrastre y luego soltarla en otro objetivo PictureBox.

Esto funciona bien de un PictureBox a otro siempre que estén en el mismo formulario .

Si abro dos instancias de la misma aplicación e intento arrastrar / soltar entre ellas, aparece el siguiente error críptico:

  

Este proxy remoto no tiene canal   hundirse, lo que significa que el servidor tiene   no hay canales de servidor registrados que sean   escuchando, o esta aplicación no tiene   canal de cliente adecuado para hablar con el   servidor.

Sin embargo, por alguna razón, funciona para arrastrar y soltar en Wordpad (pero no en MS Word o Paintbrush).

Los tres PictureBoxes obtienen sus eventos conectados de esta manera:

foreach (Control pbx in this.Controls) {
    if (pbx is PictureBox) {
        pbx.AllowDrop = true;
        pbx.MouseDown    += new MouseEventHandler(pictureBox_MouseDown);
        pbx.GiveFeedback += new GiveFeedbackEventHandler(pictureBox_GiveFeedback);
        pbx.DragEnter    += new DragEventHandler(pictureBox_DragEnter);
        pbx.DragDrop     += new DragEventHandler(pictureBox_DragDrop);
    }
}

Luego están los cuatro eventos como este:

void pictureBox_MouseDown(object sender, MouseEventArgs e) {
    int width = (sender as PictureBox).Image.Width;
    int height = (sender as PictureBox).Image.Height;

    Bitmap bmp = new Bitmap(width, height);
    Graphics g = Graphics.FromImage(bmp);
    g.DrawImage((sender as PictureBox).Image, 0, 0, width, height);
    g.Dispose();
    cursorCreatedFromControlBitmap = CustomCursors.CreateFormCursor(bmp, transparencyType);
    bmp.Dispose();

    Cursor.Current = this.cursorCreatedFromControlBitmap;

    (sender as PictureBox).DoDragDrop((sender as PictureBox).Image, DragDropEffects.All);
}

void pictureBox_GiveFeedback(object sender, GiveFeedbackEventArgs gfea) {
    gfea.UseDefaultCursors = false;
}

void pictureBox_DragEnter(object sender, DragEventArgs dea) {
    if ((dea.KeyState & 32) == 32) { // ALT is pressed
        dea.Effect = DragDropEffects.Link;
    }
    else if ((dea.KeyState & 8) == 8) { // CTRL is pressed
        dea.Effect = DragDropEffects.Copy;
    }
    else if ((dea.KeyState & 4) == 4) { // SHIFT is pressed
        dea.Effect = DragDropEffects.None;
    }
    else {
        dea.Effect = DragDropEffects.Move;
    }
}

void pictureBox_DragDrop(object sender, DragEventArgs dea) {
    if (((IDataObject)dea.Data).GetDataPresent(DataFormats.Bitmap))
        (sender as PictureBox).Image = (Image)((IDataObject)dea.Data).GetData(DataFormats.Bitmap);
}

¡Cualquier ayuda sería muy apreciada!

¿Fue útil?

Solución

Después de mucho crujir de dientes y tirar de cabello, pude encontrar una solución viable. Parece que hay algo de extrañeza indocumentada bajo las cubiertas con .NET y su soporte para arrastrar y soltar OLE. Parece estar intentando usar el control remoto .NET al realizar arrastrar y soltar entre aplicaciones .NET, pero ¿está documentado en alguna parte? No, no creo que lo sea.

Entonces, la solución que se me ocurrió implica una clase auxiliar para ayudar a calcular los datos de mapa de bits entre procesos. Primero, aquí está la clase.

[Serializable]
public class BitmapTransfer
{
    private byte[] buffer;
    private PixelFormat pixelFormat;
    private Size size;
    private float dpiX;
    private float dpiY;

    public BitmapTransfer(Bitmap source)
    {
        this.pixelFormat = source.PixelFormat;
        this.size = source.Size;
        this.dpiX = source.HorizontalResolution;
        this.dpiY = source.VerticalResolution;
        BitmapData bitmapData = source.LockBits(
            new Rectangle(new Point(0, 0), source.Size),
            ImageLockMode.ReadOnly, 
            source.PixelFormat);
        IntPtr ptr = bitmapData.Scan0;
        int bufferSize = bitmapData.Stride * bitmapData.Height;
        this.buffer = new byte[bufferSize];
        System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, bufferSize);
        source.UnlockBits(bitmapData);
    }

    public Bitmap ToBitmap()
    {
        Bitmap bitmap = new Bitmap(
            this.size.Width,
            this.size.Height,
            this.pixelFormat);
        bitmap.SetResolution(this.dpiX, this.dpiY);
        BitmapData bitmapData = bitmap.LockBits(
            new Rectangle(new Point(0, 0), bitmap.Size),
            ImageLockMode.WriteOnly, bitmap.PixelFormat);
        IntPtr ptr = bitmapData.Scan0;
        int bufferSize = bitmapData.Stride * bitmapData.Height;
        System.Runtime.InteropServices.Marshal.Copy(this.buffer, 0, ptr, bufferSize);
        bitmap.UnlockBits(bitmapData);
        return bitmap;
    }
}

Para usar la clase de una manera que admita .NET y los destinatarios no administrados del mapa de bits, se usa una clase DataObject para la operación de arrastrar y soltar de la siguiente manera.

Para iniciar la operación de arrastre:

DataObject dataObject = new DataObject();
dataObject.SetData(typeof(BitmapTransfer), 
  new BitmapTransfer((sender as PictureBox).Image as Bitmap));
dataObject.SetData(DataFormats.Bitmap, 
  (sender as PictureBox).Image as Bitmap);
(sender as PictureBox).DoDragDrop(dataObject, DragDropEffects.All);

Para completar la operación:

if (dea.Data.GetDataPresent(typeof(BitmapTransfer)))
{
    BitmapTransfer bitmapTransfer = 
       (BitmapTransfer)dea.Data.GetData(typeof(BitmapTransfer));
    (sender as PictureBox).Image = bitmapTransfer.ToBitmap();
}
else if(dea.Data.GetDataPresent(DataFormats.Bitmap))
{
    Bitmap b = (Bitmap)dea.Data.GetData(DataFormats.Bitmap);
    (sender as PictureBox).Image = b;
}

La verificación del cliente BitmapTransfer se realiza primero, por lo que tiene prioridad sobre la existencia de un Bitmap regular en el objeto de datos. La clase BitmapTransfer podría ubicarse en una biblioteca compartida para su uso con múltiples aplicaciones. Debe marcarse como serializable como se muestra para arrastrar y soltar entre aplicaciones. Lo probé con arrastrar y soltar mapas de bits dentro de una aplicación, entre aplicaciones y desde una aplicación .NET a Wordpad.

Espero que esto te ayude.

Otros consejos

Recientemente me encontré con este problema y estaba usando un formato personalizado en el portapapeles, lo que dificulta un poco más la interoperabilidad. De todos modos, con un poco de reflexión de la luz pude llegar al System.Windows.Forms.DataObject original, y luego llamar al GetData y sacar mi artículo personalizado como de costumbre.

var oleConverterType = Type.GetType("System.Windows.DataObject+OleConverter, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var oleConverter = typeof(System.Windows.DataObject).GetField("_innerData", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(e.Data);
var dataObject = (System.Windows.Forms.DataObject)oleConverterType.GetProperty("OleDataObject").GetValue(oleConverter, null);

var item = dataObject.GetData(this.Format);

Después de horas y horas de frustración con el vapor saliendo de mis oídos, finalmente llegué a una segunda solución a este problema. Exactamente qué solución es la más elegante es probablemente a los ojos del espectador. Espero que las soluciones de Michael y mis ayuden a los programadores frustrados y les ahorren tiempo cuando se embarcan en misiones similares.

Primero que nada, una cosa que me sorprendió fue que Wordpad pudo recibir las imágenes de arrastrar y soltar fuera de la caja. Por lo tanto, el empaquetado del archivo probablemente no fue el problema, pero quizás haya algo sospechoso en el extremo receptor.

Y a pescado había. Resulta que hay varios tipos de IDataObjects que flotan en el marco .Net. Como señaló Michael, el soporte de arrastrar y soltar OLE intenta utilizar el control remoto .Net al interactuar entre aplicaciones. Esto realmente pone un System.Runtime.Remoting.Proxies .__ TransparentProxy donde se supone que debe estar la imagen. Claramente esto no es (enteramente) correcto.

El siguiente artículo me dio algunos consejos en la dirección correcta:

http://blogs.msdn.com/adamroot/archive/2008/02/01/shell-style-drag-and-drop-in-net-wpf-and-winforms.aspx

Los formularios Windows Forms están predeterminados en System.Windows.Forms.IDataObject. Sin embargo, dado que estamos tratando con diferentes procesos aquí, decidí darle una oportunidad a System.Runtime.InteropServices.ComTypes.IDataObject.

En el evento dragdrop, el siguiente código resuelve el problema:

const int CF_BITMAP = 2;

System.Runtime.InteropServices.ComTypes.FORMATETC formatEtc;
System.Runtime.InteropServices.ComTypes.STGMEDIUM stgMedium;

formatEtc = new System.Runtime.InteropServices.ComTypes.FORMATETC();
formatEtc.cfFormat = CF_BITMAP;
formatEtc.dwAspect = System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT;
formatEtc.lindex = -1;
formatEtc.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_GDI;

Las dos funciones GetData solo comparten el mismo nombre. Uno devuelve un objeto, el otro se define para devolver un vacío y, en cambio, pasa la información al parámetro out de stgMedium:

(dea.Data as System.Runtime.InteropServices.ComTypes.IDataObject).GetData(ref formatEtc, out stgMedium);
Bitmap remotingImage = Bitmap.FromHbitmap(stgMedium.unionmember);

(sender as PictureBox).Image = remotingImage;

Finalmente, para evitar pérdidas de memoria, probablemente sea una buena idea llamar a la función OLE ReleaseStgMedium:

ReleaseStgMedium(ref stgMedium);

Esa función se puede incluir de la siguiente manera:

[DllImport("ole32.dll")]
public static extern void ReleaseStgMedium([In, MarshalAs(UnmanagedType.Struct)] ref System.Runtime.InteropServices.ComTypes.STGMEDIUM pmedium);

... y este código parece funcionar perfectamente con las operaciones de arrastrar y soltar (de mapas de bits) entre dos aplicaciones. El código podría extenderse fácilmente a otros formatos de portapapeles válidos y probablemente también a formatos de portapapeles personalizados. Como no se hizo nada con la parte del paquete, aún puede arrastrar una imagen a Wordpad, y dado que acepta formatos de mapa de bits, también puede arrastrar una imagen desde Word a la aplicación.

Como nota al margen, arrastrar y soltar una imagen directamente desde IE ni siquiera provoca el evento DragDrop. Extraño.

Solo por curiosidad, en el método DragDrop, ¿ha intentado probar si puede obtener la imagen de mapa de bits de DragEventArgs? Sin hacer el elenco remitente? Me pregunto si el objeto picturebox no es serializable, lo que causa el problema cuando intenta utilizar el remitente en un dominio de aplicación diferente ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top