简体   繁体   English

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

[英]Drag and Drop between Instances of the same Windows Forms Application

I have created a small Windows Forms test application to try out some drag/drop code. 我创建了一个小型Windows窗体测试应用程序来尝试一些拖放代码。 The form consists of three PictureBoxes. 该表单由三个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. 我的目的是从一个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). 但是,出于某种原因,它可以拖放到Wordpad(但不是MS Word或画笔)。

The three PictureBoxes get their events hooked up like this: 三个PictureBoxes将它们的事件连接起来如下:

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! 任何帮助将不胜感激!

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. 似乎有一些无证的陌生感在.NET及其OLE拖放支持下进行。 It appears to be trying to use .NET remoting when performing drag and drop between .NET applications, but is this documented anywhere? 它似乎是在.NET应用程序之间执行拖放时尝试使用.NET远程处理,但这在任何地方都有记录吗? No, I don't think it is. 不,我认为不是。

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. 首先执行对客户BitmapTransfer的检查,因此它优先于数据对象中常规Bitmap的存在。 The BitmapTransfer class could be placed in a shared library for use with multiple applications. BitmapTransfer类可以放在共享库中,以便与多个应用程序一起使用。 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. 我通过在应用程序中,应用程序之间以及从.NET应用程序到Wordpad中拖放位图来测试它。

Hope this helps you out. 希望这可以帮助你。

I recently came across this problem, and was using a custom format in the clipboard, making Interop a bit more difficult. 我最近遇到了这个问题,并且在剪贴板中使用自定义格式,使Interop更加困难。 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);

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. 我希望迈克尔和我的解决方案能够帮助沮丧的程序员,并在他们开始类似任务时节省时间。

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. 首先,有一件事让我感到震惊的是,Wordpad能够立即接收拖放图像。 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. 事实证明,有很多类型的IDataObjects在.Net框架中浮动。 As Michael pointed out, OLE drag and drop support attempts to use .Net remoting when interacting between applications. 正如迈克尔所指出的,OLE拖放支持尝试在应用程序之间进行交互时使用.Net远程处理。 This actually puts a System.Runtime.Remoting.Proxies.__TransparentProxy where the image is supposed to be. 这实际上将一个System.Runtime.Remoting.Proxies .__ TransparentProxy放在图像所在的位置。 Clearly this is not (entirely) correct. 显然,这不是(完全)正确的。

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. Windows窗体默认为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. 但是,由于我们在这里处理不同的进程,所以我决定给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. 两个GetData函数只共享相同的名称。 One returns an object, the other is defined to return void and instead passes the info into the stgMedium out parameter: 一个返回一个对象,另一个被定义为返回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;

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. 由于打包部件没有任何操作,您仍然可以将图像拖放到Wordpad,并且由于它接受位图格式,因此您还可以将图像从Word拖动到应用程序中。

As a side note, dragging and dropping an image directly from IE does not even raise the DragDrop event. 作为旁注,直接从IE拖放图像甚至不会引发DragDrop事件。 Strange. 奇怪。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM