2009-07-29 8 views
16

Ho creato una piccola applicazione di test di Windows Form per provare un codice di trascinamento. Il modulo è composto da tre PictureBox. La mia intenzione era quella di catturare un'immagine da un PictureBox, visualizzarlo come un cursore personalizzato durante l'operazione di trascinamento, quindi rilasciarlo su un altro oggetto PictureBox.Trascinamento tra istanze della stessa applicazione Windows Form

Questo funziona bene da un PictureBox a un altro finché sono sullo stesso modulo.

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

This remoting proxy has no channel sink which means either the server has no registered server channels that are listening, or this application has no suitable client channel to talk to the server.

Per qualche ragione, però, funziona per trascinare/drop per Wordpad (ma non MS Word o Pennello).

I tre PictureBoxes ottenere i loro eventi agganciati 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); 
    } 
} 

Poi 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!

risposta

10

Dopo aver digrignato i denti e tirato i capelli, sono stato in grado di trovare una soluzione praticabile Sembra che ci sia qualche stranezza non documentata in corso sotto le copertine con .NET e il suo supporto per il drag and drop di OLE. Sembra che stia cercando di utilizzare il servizio remoto .NET durante l'esecuzione del trascinamento della selezione tra applicazioni .NET, ma questo è documentato ovunque? No, non penso che lo sia.

Quindi la soluzione che ho trovato coinvolge una classe helper per aiutare il marshalling dei dati bitmap tra i processi. Primo, ecco la classe.

[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 maniera tale da supportare sia NET e destinatari non gestiti della bitmap, una classe DataObject viene utilizzato per il trascinamento funzionamento che 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; 
} 

L'assegno per il cliente BitmapTransfer viene eseguita per prima in modo che ha la precedenza su l'esistenza di una bitmap regolare l'oggetto dati. La classe BitmapTransfer può essere collocata in una libreria condivisa per l'utilizzo con più applicazioni. Deve essere contrassegnato come serializzabile come mostrato per il trascinamento della selezione tra le applicazioni.L'ho provato con il drag and drop di bitmap all'interno di un'applicazione, tra applicazioni e da un'applicazione .NET a Wordpad.

Spero che questo ti aiuti.

+0

Ottima risposta. Molto bello +1 –

+0

Ciao Michael! Mi piace il tuo approccio. Grazie per la risposta! Questo mi ha infastidito per un bel po 'di tempo e la tua soluzione è una buona soluzione per un problema ricorrente. Tuttavia, ho trovato un'altra soluzione che potrebbe essere migliore (almeno più breve) nel caso di trasferimento di formati di appunti comuni. Quella soluzione è descritta sotto. In ogni caso voglio darti la "risposta accettata" in quanto la tua soluzione è probabilmente più adattabile al caso generale. Si prega di rivedere la mia soluzione di seguito per un modo diverso per risolvere questo problema. - Peder - – Pedery

+0

La tua ricerca è molto interessante ed è probabilmente la soluzione migliore per il tuo caso. Intendo completamente scavare in quell'articolo e sperimentare questa tecnica. È un po 'triste che sia stato necessario un tale sforzo da entrambe le parti per risolvere questo problema. Mi fa rabbrividire ogni volta che ho bisogno di interagire con la shell di .NET, in quanto il matrimonio è tipicamente un evento roccioso. Grazie per il credito e ancora di più per le informazioni aggiuntive su questo argomento. –

1

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

+0

Ho provato a creare una bitmap completamente separata e l'ho usata. Stesso risultato È possibile trascinare/rilasciare internamente e non lavorare con un'applicazione separata. Per rispondere alla tua domanda, sì, nell'evento DragDrop l'immagine viene visualizzata come una bitmap. È solo che l'applicazione si blocca con l'errore di cui sopra. – Pedery

6

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

Prima di tutto, una cosa che mi ha colpito è stata che Wordpad è stato in grado di ricevere le immagini di trascinamento/rilascio appena fuori dalla scatola. Quindi la confezione del file non era probabilmente il problema, ma forse c'era qualcosa di strano che stava succedendo.

E pesce c'era. Risulta che ci sono diversi tipi di IDataObjects che galleggiano sul framework .Net. Come ha sottolineato Michael, OLE trascina e rilascia i tentativi di supporto per utilizzare il .Net remoting durante l'interazione tra le applicazioni. Questo in realtà mette un System.Runtime.Remoting.Proxies .__ TransparentProxy dove dovrebbe essere l'immagine. Chiaramente questo non è (interamente) corretto.

Il seguente articolo mi ha dato alcune indicazioni 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 default System.Windows.Forms.IDataObject. Tuttavia, poiché abbiamo a che fare con diversi processi qui, ho deciso di dare a System.Runtime.InteropServices.ComTypes.IDataObject un colpo invece.

Nel caso dragdrop, il seguente codice 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 solamente lo stesso nome. Uno restituisce un oggetto, l'altro è definito per restituire nulla e invece passa le informazioni al STGMEDIUM fuori parametro:

(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 di chiamare la funzione ReleaseStgMedium OLE :

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 per in modo corretto con 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. Poiché non è stato fatto nulla con la parte della confezione, è comunque possibile trascinare un'immagine in Wordpad e, poiché accetta i formati bitmap, è possibile anche trascinare un'immagine da Word nell'applicazione.

Come nota a margine, trascinare e rilasciare un'immagine direttamente da IE non aumenta nemmeno l'evento DragDrop. Strano.

+0

<< Come nota a margine, trascinare e rilasciare un'immagine direttamente da IE non solleva nemmeno l'evento DragDrop. >> Quale sistema operativo? Su Vista +, è stato fatto del lavoro per impedire che il contenuto della zona Internet venga rilasciato a ignare applicazioni. È necessario registrare la propria applicazione nel registro per accettare le eliminazioni da IE. – EricLaw

7

Recentemente mi sono imbattuto in questo problema e stavo usando un formato personalizzato negli appunti, rendendo l'Interop un po 'più difficile. Ad ogni modo, con un po 'di riflessione della luce, sono riuscito ad arrivare al System.Windows.Forms.DataObject originale, quindi chiamare GetData e ottenere il mio elemento personalizzato come normale.

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

Interessante. Pensi che il tuo metodo sarebbe "sicuro", il che significa che non funzionerebbe su alcuni computer e crach sugli altri? – Pedery

+0

L'unico problema potenziale sarebbe ottenere il tipo OleConverter, che dovrebbe sempre essere presente con PresentationCore, in modo che possa essere reso più sicuro utilizzando un tipo che si sa sarà in PresentationCore per ottenere il nome assembly completo. Ma a parte questo, dovrebbe funzionare senza problemi. – dariusriggins

+0

si potrebbe provare a fare un cast semplice per avere Interop IDataObject: (System.Runtime.InteropServices.ComTypes.IDataObject) e.Data –

Problemi correlati