代码之家  ›  专栏  ›  技术社区  ›  Pedery

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

  •  16
  • Pedery  · 技术社区  · 17 年前

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

    从一个图片框到另一个图片框都可以 只要他们在同一张表格上 .

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

    此远程处理代理没有通道 接收器,这意味着服务器 没有注册的服务器通道 正在侦听,或者此应用程序没有 合适的客户机渠道与 服务器。

    但是,出于某种原因,它确实可以拖放到写字板(而不是MS Word或画笔)。

    这三个图片框将它们的事件连接起来,如下所示:

    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 回复  |  直到 16 年前
        1
  •  10
  •   Michael A. McCloskey    17 年前

    在咬牙切齿和拔头发之后,我终于想出了一个可行的解决办法。在.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类可以放在一个共享库中,用于多个应用程序。它必须标记为可序列化,如应用程序之间的拖放所示。我在应用程序内部、应用程序之间以及从.NET应用程序到写字板之间拖放位图来测试它。

    希望这能帮到你。

        2
  •  7
  •   dariusriggins    17 年前

    我最近遇到了这个问题,并在剪贴板中使用了自定义格式,使得互操作变得更加困难。总之,有了一点光线反射,我就能够找到原来的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);
    
        3
  •  6
  •   Pedery    17 年前

    经过几个小时的挫折,我终于找到了解决这个问题的第二个办法。在旁观者的眼中,哪种解决方案最优雅。我希望迈克尔和我的解决方案都能帮助受挫的程序员,并在他们开始类似的任务时节省时间。

    首先,有一件事让我印象深刻,那就是WordPad能够从盒子里接收拖放图像。因此,文件的打包可能不是问题所在,但在接收端可能发生了一些可疑的事情。

    那里有鱼。事实证明,有七种类型的IDataObject在.NET框架中浮动。正如Michael指出的,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 外面的 参数:

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

    …而且这段代码似乎可以很好地处理两个应用程序之间的拖放操作(位图)。代码可以很容易地扩展到其他有效的剪贴板格式,也可能是自定义的剪贴板格式。由于没有对打包部分做任何操作,您仍然可以将图像拖放到写字板上,并且由于它接受位图格式,所以您还可以将图像从Word拖到应用程序中。

    附带说明,直接从IE拖放图像甚至不会引发DragDrop事件。奇怪。

        4
  •  1
  •   genki    17 年前

    出于好奇,在DragDrop方法中,您是否尝试过测试是否可以从DragEventArgs中获取位图图像?不做发送者广播?我想知道PictureBox对象是否是不可序列化的,这会导致在其他应用程序域中尝试使用发件人时出现问题…