Question

J'ai créé une petite application de test Windows Forms pour essayer du code de glisser-déposer. Le formulaire consiste en trois PictureBox. Mon intention était de saisir une image d'un PictureBox, de l'afficher sous forme de curseur personnalisé lors de l'opération de glissement, puis de la déposer sur une autre cible PictureBox.

Cela fonctionne correctement d'un PictureBox à un autre tant qu'ils se trouvent sur le même formulaire .

Si j'ouvre deux instances de la même application et tente de glisser-déposer entre elles, j'obtiens l'erreur cryptique suivante:

  

Ce proxy distant n'a pas de canal   puits qui signifie soit le serveur a   aucun canal de serveur enregistré qui est   écoute, ou cette application n'a pas   canal client approprié pour parler au   serveur.

Pour une raison quelconque, cependant, cela fonctionne de glisser / déposer dans Wordpad (mais pas avec MS Word ou Paintbrush).

Les trois PictureBox obtiennent leurs événements liés comme suit:

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

Ensuite, il y a les quatre événements comme celui-ci:

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

Toute aide serait grandement appréciée!

Était-ce utile?

La solution

Après avoir beaucoup grincé des dents et tiré les cheveux, j’ai été en mesure de proposer une solution viable. Il semble y avoir une certaine étrangeté non documentée sous les couvertures de .NET et de son support OLE drag and drop. Il semble que vous essayiez d'utiliser .NET Remoting lorsque vous effectuez un glisser-déposer entre des applications .NET, mais cela est-il documenté quelque part? Non, je ne pense pas que ce soit le cas.

La solution que j'ai proposée implique donc une classe d'assistance qui aide à marshaler les données bitmap entre les processus. Tout d’abord, voici 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;
    }
}

Pour utiliser la classe de manière à prendre en charge les destinataires .NET et non gérés du bitmap, une classe DataObject est utilisée comme suit pour l'opération de glisser-déposer.

Pour démarrer l'opération de glisser:

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

Pour terminer l'opération:

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 vérification du client BitmapTransfer est effectuée en premier. Elle a donc préséance sur l’existence d’un bitmap standard dans l’objet de données. La classe BitmapTransfer peut être placée dans une bibliothèque partagée pour être utilisée avec plusieurs applications. Il doit être marqué comme étant sérialisable comme indiqué pour le glisser-déposer entre les applications. Je l'ai testé par glisser-déposer de bitmaps au sein d'une application, entre applications et d'une application .NET vers Wordpad.

J'espère que cela vous aide.

Autres conseils

J'ai récemment rencontré ce problème et j'utilisais un format personnalisé dans le presse-papiers, ce qui rendait Interop un peu plus difficile. Quoi qu'il en soit, avec un peu de réflexion de la lumière, j'ai pu accéder à l'original System.Windows.Forms.DataObject, puis appeler GetData et obtenir mon élément personnalisé comme d'habitude.

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

Après des heures et des heures de frustration avec la vapeur qui me sortait des oreilles, je suis finalement arrivé à une deuxième solution à ce problème. La solution la plus élégante est probablement aux yeux du spectateur. J'espère que mes solutions et celles de Michael aideront les programmeurs frustrés et leur feront gagner du temps lorsqu'ils se lancent dans des quêtes similaires.

Tout d’abord, une chose qui m’a frappée est que Wordpad a été capable de recevoir les images glissées / déposées à la sortie de la boîte. L’emballage du fichier n’était donc probablement pas le problème, mais il y avait peut-être quelque chose de louche chez le destinataire.

Et il y avait du poisson. Il s'avère que plusieurs types d'IDataObject flottent autour du framework .Net. Comme Michael l'a souligné, le support OLE par glisser / déposer tente d'utiliser .Net Remoting lors de l'interaction entre des applications. Cela place en fait un System.Runtime.Remoting.Proxies .__ TransparentProxy à l'endroit où l'image est supposée être. Clairement, ce n’est pas (tout à fait) correct.

L'article suivant m'a donné quelques indications dans la bonne direction:

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

Par défaut, Windows Forms est System.Windows.Forms.IDataObject. Cependant, comme nous avons affaire à différents processus ici, j’ai décidé de donner à System.Runtime.InteropServices.ComTypes.IDataObject un coup à la place.

Dans l'événement dragdrop, le code suivant résout le problème:

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;

Les deux fonctions GetData partagent le même nom. L’un retourne un objet, l’autre est défini pour retourner void et passe l’information au paramètre stgMedium out :

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

(sender as PictureBox).Image = remotingImage;

Enfin, pour éviter les fuites de mémoire, appelez la fonction OLE ReleaseStgMedium:

ReleaseStgMedium(ref stgMedium);

Cette fonction peut être incluse comme suit:

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

... et ce code semble fonctionner parfaitement avec les opérations de glisser-déposer (d'images bitmap) entre deux applications. Le code pourrait facilement être étendu à d'autres formats de presse-papiers valides et probablement aussi à des formats de presse-papiers personnalisés. Comme rien n’a été fait avec la partie empaquetage, vous pouvez toujours faire glisser une image vers Wordpad et, comme elle accepte les formats bitmap, vous pouvez également faire glisser une image de Word dans l’application.

En note de bas de page, glisser-déposer une image directement depuis IE ne déclenche même pas l'événement DragDrop. Étrange.

Juste par curiosité, dans la méthode DragDrop, avez-vous essayé de vérifier si vous pouviez extraire l’image bitmap des DragEventArgs? Sans faire le casting de l'expéditeur? Je me demande si l'objet picturebox n'est pas sérialisable, ce qui pose problème lorsque vous essayez d'utiliser l'expéditeur dans un domaine d'application différent ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top