Pergunta

Eu criei um aplicativo de formulários pequeno teste do Windows para experimentar algum código arrastar / soltar. A forma consiste em três PictureBoxes. Minha intenção era pegar uma imagem de um PictureBox, exibi-lo como um cursor personalizado durante a operação de arrastar, em seguida, solte-o em outro alvo PictureBox.

Isso funciona bem a partir de um PictureBox para outro , enquanto eles estão no mesmo formulário .

Se eu abrir duas instâncias do mesmo aplicativo e tentar arrastar / soltar entre eles, eu recebo o seguinte erro enigmática:

Este proxy comunicação remota não tem canal pia que significa tanto o servidor tem canais de servidor não registrados que são ouvindo, ou esta aplicação não tem canal do cliente adequado para falar com o servidor.

Por alguma razão, no entanto, ela não funciona para arrastar / soltar para Wordpad (mas não MS Word ou Paintbrush).

Os três PictureBoxes obter os seus eventos ligados assim:

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

Depois, há os quatro 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);
}

Qualquer ajuda seria muito apreciada!

Foi útil?

Solução

Depois de muito ranger de dentes e puxando de cabelo, eu era capaz de chegar a uma solução viável. Parece que há alguma estranheza em situação irregular acontecendo debaixo das cobertas com .NET e sua OLE suporte arrastar e soltar. Parece estar tentando usar o .NET comunicação remota ao executar arrastar e soltar entre NET aplicações, mas é isso documentado em qualquer lugar? Não, eu não acho que ele é.

Portanto, a solução que eu vim com envolve uma classe auxiliar para ajuda marshal os dados de bitmap entre processos. Primeiro, aqui é a 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;
    }
}

Para usar a classe de uma maneira que irá apoiar tanto .NET e destinatários não gerenciados do bitmap, uma classe DataObject é usado para a operação de arrastar e soltar os seguintes.

Para iniciar a operação de arrastar:

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 concluir a operação:

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

A verificação para o cliente BitmapTransfer é realizada pela primeira vez por isso tem precedência sobre a existência de um Bitmap regular no objeto de dados. A classe BitmapTransfer poderia ser colocado em uma biblioteca compartilhada para uso com múltiplas aplicações. Deve ser marcado seriável como mostrado para arrastar e soltar entre as aplicações. Testei com arrastar e soltar de mapas de bits dentro de uma aplicação, entre as aplicações, e a partir de uma aplicação .NET para Wordpad.

Hope isso ajuda-lo.

Outras dicas

Recentemente, deparei com este problema, e estava usando um formato personalizado na área de transferência, tornando Interop um pouco mais difícil. De qualquer forma, com um pouco de reflexão da luz eu era capaz de chegar ao System.Windows.Forms.DataObject original, e depois chamar o GetData e buscar o meu item personalizado com isso como normal.

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

Após horas e horas de frustração com vapor saindo dos meus ouvidos, eu finalmente chegou a uma segunda solução para este problema. Exatamente qual é a solução mais elegante é provavelmente nos olhos de quem vê. Espero que Michael e minhas soluções serão ambos ajuda programadores frustrado e poupar-lhes tempo quando eles embarcam em missões semelhantes.

Antes de tudo, uma coisa que me pareceu foi que Wordpad foi capaz de receber as imagens de arrastar / soltar apenas fora da caixa. Assim, a embalagem do arquivo provavelmente não era o problema, mas não havia talvez algo suspeito acontecendo no final de recebimento.

E suspeito que havia. Acontece que existem tipos seveal de IDataObjects flutuantes sobre o framework .Net. Como Michael indicou, OLE arrasto e de suporte gota tentativas para usar Remoting ao interagir entre aplicações. Isso realmente coloca um System.Runtime.Remoting.Proxies .__ TransparentProxy onde a imagem é suposto ser. É evidente que esta não é (totalmente) correto.

O seguinte artigo me deu algumas indicações no sentido correto:

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

Windows Forms padrões para System.Windows.Forms.IDataObject. No entanto, uma vez que estamos lidando com diferentes processos aqui, eu decidi dar System.Runtime.InteropServices.ComTypes.IDataObject um tiro em seu lugar.

No caso dragdrop, os seguintes resolve código do 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;

As duas funções GetData só compartilham o mesmo nome. Um retorna um objeto, o outro é definido para retornar void e em vez disso passa a informação para o STGMEDIUM a parâmetro:

(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 vazamentos de memória, é provavelmente uma boa idéia para chamar a função ReleaseStgMedium OLE:

ReleaseStgMedium(ref stgMedium);

Essa função pode ser incluído o seguinte:

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

... e este código parece funcionar perfeitamente com operações de arrastar e soltar (de bitmaps) entre duas aplicações. O código pode ser facilmente estendido para outros formatos de área de transferência válida e formatos provavelmente costume prancheta também. Uma vez que nada foi feito com a parte de embalagem, você ainda pode dragdrop uma imagem para Wordpad, e uma vez que aceita formatos bitmap, você também pode arrastar uma imagem a partir do Word para o aplicativo.

Como uma nota lateral, arrastando e soltando uma imagem diretamente do IE nem sequer aumentar o evento DragDrop. Strange.

Só por curiosidade, no método DragDrop, você já tentou testar se você pode obter a imagem bitmap fora das DragEventArgs em tudo? Sem fazer o elenco remetente? Eu estou querendo saber se o objeto picturebox não é serializável, o que causa o problema quando você tentar usar o remetente em um domínio de aplicativo diferente ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top