在同一Windows窗体应用程序的实例之间拖放

时间:2009-07-29 17:23:21

标签: c# winforms drag-and-drop

我创建了一个小型Windows窗体测试应用程序来尝试一些拖放代码。该表单由三个PictureBoxes组成。我的目的是从一个PictureBox中抓取一张图片,在拖动操作期间将其显示为自定义光标,然后将其放在另一个PictureBox目标上。

只要它们位于同一个表单,从一个PictureBox到另一个就可以正常工作。

如果我打开同一个应用程序的两个实例并尝试在它们之间拖放,我会收到以下隐藏错误:

  

此远程处理代理没有频道   接收器,这意味着服务器具有   没有注册的服务器频道   听,或这个应用程序没有   合适的客户渠道与之交谈   服务器

出于某种原因,它可以拖放到Wordpad(但不是MS Word或画笔)。

这三个PictureBox将他们的事件连接起来:

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

然后有四个这样的事件:

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

非常感谢任何帮助!

4 个答案:

答案 0 :(得分:10)

在咬牙切齿和拔毛之后,我能够提出一个可行的解决方案。似乎有一些无证的陌生感在.NET及其OLE拖放支持下进行。它似乎是在.NET应用程序之间执行拖放时尝试使用.NET远程处理,但这在任何地方都有记录吗?不,我认为不是。

因此,我提出的解决方案涉及一个帮助程序类,以帮助编组进程之间的位图数据。首先,这是课程。

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

要以支持位图的.NET和非托管收件人的方式使用该类,DataObject类将用于拖放操作,如下所示。

开始拖动操作:

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

完成操作:

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

首先执行对客户BitmapTransfer的检查,因此它优先于数据对象中存在的常规Bitmap。 BitmapTransfer类可以放在共享库中,以便与多个应用程序一起使用。它必须标记为可序列化,如图所示,用于在应用程序之间拖放。我通过在应用程序中,应用程序之间以及从.NET应用程序到Wordpad中拖放位图来测试它。

希望这可以帮助你。

答案 1 :(得分:7)

我最近遇到了这个问题,并且在剪贴板中使用自定义格式,使Interop更加困难。无论如何,通过一些光反射,我能够获得原始的System.Windows.Forms.DataObject,然后调用GetData并像平常一样从我的自定义项中获取。

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

答案 2 :(得分:6)

经过几个小时的沮丧,我的耳朵里传来了蒸汽,我终于找到了解决这个问题的第二个方案。究竟哪种解决方案最优雅的可能是在旁观者的眼中。我希望迈克尔和我的解决方案能够帮助沮丧的程序员,并在他们开始类似任务时节省时间。

首先,有一件事让我感到震惊的是,Wordpad能够立即接收拖放图像。因此,文件的打包可能不是问题,但接收端可能还有一些可疑的东西。

还有鱼腥味。事实证明,有很多类型的IDataObjects在.Net框架中浮动。正如迈克尔所指出的,OLE拖放支持尝试在应用程序之间进行交互时使用.Net远程处理。这实际上将一个System.Runtime.Remoting.Proxies .__ TransparentProxy放在图像所在的位置。显然,这不是(完全)正确的。

以下文章向我提出了正确方向的几点建议:

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

Windows窗体默认为System.Windows.Forms.IDataObject。但是,由于我们在这里处理不同的进程,所以我决定改为给System.Runtime.InteropServices.ComTypes.IDataObject一个镜头。

在dragdrop事件中,以下代码解决了问题:

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;

两个GetData函数只共享相同的名称。一个返回一个对象,另一个被定义为返回void,而是将信息传递给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;

最后,为了避免内存泄漏,调用OLE函数ReleaseStgMedium可能是个好主意:

ReleaseStgMedium(ref stgMedium);

该功能可以包括如下:

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

...此代码似乎与两个应用程序之间的拖放操作(位图)完美配合。代码可以很容易地扩展到其他有效的剪贴板格式,也可能是自定义剪贴板格式。由于打包部件没有任何操作,您仍然可以将图像拖放到Wordpad,并且由于它接受位图格式,您还可以将图像从Word拖动到应用程序中。

作为旁注,直接从IE拖放图像甚至不会引发DragDrop事件。奇怪。

答案 3 :(得分:1)

出于好奇,在DragDrop方法中,您是否尝试过测试是否可以从DragEventArgs中获取位图图像?没有做发送者演员?我想知道图片框对象是否不可序列化,当您尝试在不同的应用程序域中使用发件人时会导致问题...