Domanda

Ho creato una piccola applicazione di test di Windows Form per provare un po 'di codice di trascinamento. Il modulo è composto da tre PictureBox. La mia intenzione era di catturare un'immagine da un PictureBox, visualizzarla come cursore personalizzato durante l'operazione di trascinamento, quindi rilasciarla su un altro target PictureBox.

Funziona bene da un PictureBox all'altro purché siano nella stessa forma .

Se apro due istanze della stessa applicazione e provo a trascinare / rilasciare tra di loro, ottengo il seguente errore criptico:

  

Questo proxy remoto non ha canali   sink che significa che ha il server   nessun canale server registrato che lo sia   ascolto, o questa applicazione non ha   canale client adatto per parlare con   server.

Per qualche motivo, tuttavia, funziona per trascinare / rilasciare su Wordpad (ma non su MS Word o Paintbrush).

I tre PictureBox ottengono i loro eventi collegati in questo modo:

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

Quindi ci sono i quattro eventi come questo:

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

Qualsiasi aiuto sarebbe molto apprezzato!

È stato utile?

Soluzione

Dopo molto digrignare i denti e tirare i capelli, sono stato in grado di trovare una soluzione praticabile. Sembra che ci sia una stranezza non documentata in corso sotto le coperte con .NET e il suo supporto drag and drop OLE. Sembra che stia tentando di utilizzare il telecomando .NET durante l'esecuzione del trascinamento tra le applicazioni .NET, ma questo è documentato ovunque? No, non penso che lo sia.

Quindi la soluzione che mi è venuta in mente prevede una classe di supporto per il marshalling dei dati bitmap tra i processi. Innanzitutto, ecco la lezione.

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

Per utilizzare la classe in un modo che supporterà sia i destinatari .NET sia quelli non gestiti della bitmap, per l'operazione di trascinamento della selezione viene utilizzata una classe DataObject come segue.

Per avviare l'operazione di trascinamento:

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

Per completare l'operazione:

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

Il controllo per il cliente BitmapTransfer viene eseguito per primo, quindi ha la precedenza sull'esistenza di una normale bitmap nell'oggetto dati. La classe BitmapTransfer potrebbe essere collocata in una libreria condivisa per l'uso con più applicazioni. Deve essere contrassegnato come serializzabile come mostrato per il trascinamento della selezione tra le applicazioni. L'ho provato con il trascinamento della selezione di bitmap all'interno di un'applicazione, tra applicazioni e da un'applicazione .NET a Wordpad.

Spero che questo ti aiuti.

Altri suggerimenti

Di recente ho riscontrato questo problema e stavo usando un formato personalizzato negli Appunti, rendendo Interop un po 'più difficile. Ad ogni modo, con un po 'di riflesso della luce sono stato in grado di raggiungere l'originale System.Windows.Forms.DataObject, quindi chiamare GetData e estrarre il mio oggetto personalizzato come al solito.

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

Dopo ore e ore di frustrazione per il vapore che mi usciva dalle orecchie, sono finalmente arrivato a una seconda soluzione a questo problema. La soluzione più elegante è probabilmente agli occhi di chi guarda. Spero che Michael e le mie soluzioni aiuteranno entrambi i programmatori frustrati e risparmieranno tempo quando intraprenderanno ricerche simili.

Prima di tutto, una cosa che mi ha colpito è che Wordpad è stato in grado di ricevere le immagini di trascinamento della selezione immediatamente. Pertanto, il pacchetto del file non era probabilmente il problema, ma c'era forse qualcosa di sospetto che stava accadendo alla fine della ricezione.

E di pesce c'era. Si scopre che esistono diversi tipi di IDataObjects che fluttuano nel framework .Net. Come ha sottolineato Michael, il supporto del trascinamento della selezione OLE tenta di utilizzare il telecomando .Net durante l'interazione tra le applicazioni. Questo effettivamente mette un System.Runtime.Remoting.Proxies .__ TransparentProxy dove dovrebbe essere l'immagine. Chiaramente questo non è (interamente) corretto.

Il seguente articolo mi ha dato alcuni suggerimenti nella giusta direzione:

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

Windows Form utilizza per impostazione predefinita System.Windows.Forms.IDataObject. Tuttavia, poiché qui abbiamo a che fare con diversi processi, ho deciso di dare una possibilità a System.Runtime.InteropServices.ComTypes.IDataObject.

Nell'evento dragdrop, il codice seguente risolve il 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;

Le due funzioni GetData condividono solo lo stesso nome. Uno restituisce un oggetto, l'altro è definito per restituire il vuoto e invece passa le informazioni nel parametro out di 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;

Infine, per evitare perdite di memoria, è probabilmente una buona idea chiamare la funzione OLE ReleaseStgMedium:

ReleaseStgMedium(ref stgMedium);

Tale funzione può essere inclusa come segue:

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

... e questo codice sembra funzionare perfettamente con le operazioni di trascinamento della selezione (di bitmap) tra due applicazioni. Il codice potrebbe essere facilmente esteso ad altri formati di appunti validi e probabilmente anche a formati di appunti personalizzati. Dal momento che non è stato fatto nulla con la parte del packaging, puoi comunque trascinare un'immagine su Wordpad e poiché accetta i formati bitmap, puoi anche trascinare un'immagine da Word nell'applicazione.

Come nota a margine, il trascinamento di un'immagine direttamente da IE non genera nemmeno l'evento DragDrop. Strano.

Appena per curiosità, nel metodo DragDrop, hai provato a verificare se è possibile estrarre l'immagine bitmap da DragEventArgs? Senza fare il cast del mittente? Mi chiedo se l'oggetto picturebox non sia serializzabile, il che causa il problema quando si tenta di utilizzare il mittente in un dominio di app diverso ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top