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

Posted

技术标签:

【中文标题】在同一 Windows 窗体应用程序的实例之间拖放【英文标题】:Drag and Drop between Instances of the same Windows Forms Application 【发布时间】:2010-11-15 04:00:49 【问题描述】:

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

从一个 PictureBox 到另一个 只要它们在同一个表单上,这都可以正常工作

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

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

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

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

任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

在咬牙切齿和拔头发之后,我想出了一个可行的解决方案。 .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 应用程序到写字板的位图拖放对其进行了测试。

希望对你有所帮助。

【讨论】:

嗨,迈克尔!我喜欢你的方法。感谢您的回答!这困扰了我很长一段时间,您的解决方案是解决反复出现的问题的好方法。但是,在传输常见剪贴板格式的情况下,我确实找到了另一种可能更好(至少更短)的解决方案。该解决方案如下所述。无论如何,我想给您“接受的答案”的信誉,因为您的解决方案可能更适合一般情况。请在下面查看我的解决方案,了解解决此问题的不同方法。 - 佩德 - 您的发现非常有趣,可能是您的案例的最佳解决方案。我完全打算深入研究那篇文章并尝试使用这种技术。有点遗憾的是,我们双方都需要付出这样的努力来解决这个问题。每当我需要与 .NET 的 shell 交互时,我都会感到畏缩,因为婚姻通常是艰难的。感谢您的信任,甚至更多关于此主题的其他信息。 好吧,我在这方面花了很多时间,因为我真的很想深入了解它。我在网上找到的示例要么是丑陋的解决方法,要么是文章中的非常复杂的示例。我真的很惊讶这并没有像您期望的那样嵌入到框架中,也没有像其他任何地方记录的那样。无论如何,在我的情况下,您的方法实际上可能会更好,因为我的意图一直是来回传递自定义对象。所以我非常感谢你花时间想出自己的方法来解决这个谜。【参考方案2】:

我最近遇到了这个问题,并且在剪贴板中使用了自定义格式,这使得互操作变得更加困难。无论如何,通过一点光反射,我能够找到原始的 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);

【讨论】:

有趣。你认为你的方法是“安全的”吗,这意味着它在某些计算机上不起作用而在其他计算机上运行? 唯一的潜在问题是获取 OleConverter 类型,它应该始终与 PresentationCore 一起存在,因此可以通过使用您知道将在 PresentationCore 中的类型来获得完全合格的程序集名称。但除此之外,它应该可以正常工作。 你可以尝试做一个简单的转换来拥有 Interop IDataObject: (System.Runtime.InteropServices.ComTypes.IDataObject) e.Data【参考方案3】:

在我耳边冒出蒸汽数小时后感到沮丧之后,我终于找到了解决此问题的第二个解决方案。究竟哪种解决方案最优雅可能在旁观者的眼中。我希望 Michael 和我的解决方案都能帮助沮丧的程序员,并在他们开始类似任务时节省他们的时间。

首先,让我印象深刻的一件事是写字板能够接收开箱即用的拖放图像。因此,文件的打包可能不是问题,但接收端可能有问题。

而且很可疑。事实证明,.Net 框架中有几种类型的 IDataObjects 浮动。正如 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。

在拖放事件中,下面的代码解决了问题:

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

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

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

【讨论】:

> 哪个操作系统?在 Vista+ 上,已经完成了一些工作以防止来自 Internet 区域的内容被丢弃到毫无戒心的应用程序中。您必须在注册表中注册您的应用程序才能接受退出 IE。【参考方案4】:

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

【讨论】:

我尝试创建一个完全独立的位图并改用它。结果相同。可以在内部拖放,而不是使用单独的应用程序。要回答您的问题,是的,在 DragDrop 事件中,图像确实以位图的形式出现。只是应用程序因上述错误而崩溃。

以上是关于在同一 Windows 窗体应用程序的实例之间拖放的主要内容,如果未能解决你的问题,请参考以下文章

拖放时将桌面图标移动到Windows窗体上?

WPF窗口与windowsForm窗体之间的问题,怎么调用close()

在DELPHI中窗体之间 如何传递数据?

如何在 Windows 窗体应用程序和网站之间进行通信?

Windows 窗体 C#(拖放 100 个对象)

C#窗体设计——多个窗体之间的调用