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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1201812/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:46:02  来源:igfitidea点击:

Drag and Drop between Instances of the same Windows Forms Application

c#winformsdrag-and-drop

提问by Pedery

I have created a small Windows Forms test application to try out some drag/drop code. The form consists of three PictureBoxes. My intention was to grab a picture from one PictureBox, display it as a custom cursor during the drag operation, then drop it on another PictureBox target.

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

This works fine from one PictureBox to another as long as they are on the same form.

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

If I open two instances of the same application and attempt to drag/drop between them, I get the following cryptic error:

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

This remoting proxy has no channel sink which means either the server has no registered server channels that are listening, or this application has no suitable client channel to talk to the server.

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

For some reason, however, it does work to drag/drop to Wordpad (but not MS Word or Paintbrush).

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

The three PictureBoxes get their events hooked up like this:

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

Then there are the four events like this:

然后是这样的四个事件:

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

Any help would be greatly appreciated!

任何帮助将不胜感激!

采纳答案by Michael A. McCloskey

After much gnashing of teeth and pulling of hair, I was able to come up with a workable solution. It seems there is some undocumented strangeness going on under the covers with .NET and its OLE drag and drop support. It appears to be trying to use .NET remoting when performing drag and drop between .NET applications, but is this documented anywhere? No, I don't think it is.

经过多次咬牙切齿和拉头发,我能够想出一个可行的解决方案。.NET 及其 OLE 拖放支持的幕后似乎有一些未记录的奇怪之处。在 .NET 应用程序之间执行拖放时,它似乎试图使用 .NET 远程处理,但这是否有记录?不,我认为不是。

So the solution I came up with involves a helper class to help marshal the bitmap data between processes. First, here is the class.

所以我想出的解决方案涉及一个帮助程序类来帮助在进程之间编组位图数据。首先,这里是类。

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

To use the class in a manner that will support both .NET and unmanaged recipients of the bitmap, a DataObject class is used for the drag and drop operation as follows.

为了以同时支持 .NET 和非托管位图接收方的方式使用该类,DataObject 类用于拖放操作,如下所示。

To start the drag operation:

要开始拖动操作:

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

To complete the operation:

要完成操作:

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

The check for the customer BitmapTransfer is performed first so it takes precedence over the existence of a regular Bitmap in the data object. The BitmapTransfer class could be placed in a shared library for use with multiple applications. It must be marked serializable as shown for drag and drop between applications. I tested it with drag and drop of bitmaps within an application, between applications, and from a .NET application to Wordpad.

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

Hope this helps you out.

希望这可以帮助你。

回答by genki

Just out of curiousity, in the DragDrop method, have you tried testing whether you can get the bitmap image out of the DragEventArgs at all? Without doing the sender cast? I'm wondering whether the picturebox object isn't serializable, which causes the issue when you try to use the sender in a different app domain...

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

回答by Pedery

Following hours and hours of frustration with steam coming out of my ears, I finally arrived at a second solution to this problem. Exactly which solution is the most elegant is probably in the eyes of the beholder. I hope that Michael's and my solutions will both aid frustrated programmers and save them time when they embark on similar quests.

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

First of all, one thing that did strike me was that Wordpad was able to receive the drag/drop images just out of the box. Thus the packaging of the file was probably not the problem, but there was perhaps something fishy going on at the receiving end.

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

And fishy there was. It turns out there are seveal types of IDataObjects floating about the .Net framework. As Michael pointed out, OLE drag and drop support attempts to use .Net remoting when interacting between applications. This actually puts a System.Runtime.Remoting.Proxies.__TransparentProxy where the image is supposed to be. Clearly this is not (entirely) correct.

那里很腥。事实证明,.Net 框架中有几种类型的 IDataObjects。正如迈克尔指出的那样,OLE 拖放支持在应用程序之间交互时尝试使用 .Net 远程处理。这实际上将 System.Runtime.Remoting.Proxies.__TransparentProxy 放在图像应该在的位置。显然这不是(完全)正确的。

The following article gave me a few pointers in the right direction:

下面的文章给了我一些正确方向的指示:

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

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

Windows Forms defaults to System.Windows.Forms.IDataObject. However, since we're dealing with different processes here, I decided to give System.Runtime.InteropServices.ComTypes.IDataObject a shot instead.

Windows 窗体默认为 System.Windows.Forms.IDataObject。但是,由于我们在这里处理不同的进程,因此我决定尝试使用 System.Runtime.InteropServices.ComTypes.IDataObject。

In the dragdrop event, the following code solves the problem:

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

The two GetData functions only share the same name. One returns an object, the other is defined to return void and instead passes the info into the stgMedium outparameter:

这两个 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;

Finally, to avoid memory leaks, it's probably a good idea to call the OLE function ReleaseStgMedium:

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

ReleaseStgMedium(ref stgMedium);

That function can be included as follows:

该函数可以包含如下:

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

...and this code seems to work perfectly with drag and drop operations (of bitmaps) between two applications. The code could easily be extended to other valid clipboard formats and probably custom clipboard formats too. Since nothing was done with the packaging part, you can still dragdrop an image to Wordpad, and since it accepts bitmap formats, you can also drag an image from Word into the application.

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

As a side note, dragging and dropping an image directly from IE does not even raise the DragDrop event. Strange.

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

回答by dariusriggins

I recently came across this problem, and was using a custom format in the clipboard, making Interop a bit more difficult. Anyway, with a bit of light reflection I was able to get to the original System.Windows.Forms.DataObject, and then call the GetData and get my custom item out of it like normal.

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