Перетаскивание между экземплярами одного приложения Windows Forms

StackOverflow https://stackoverflow.com/questions/1201812

  •  05-07-2019
  •  | 
  •  

Вопрос

Я создал небольшое тестовое приложение Windows Forms для проверки кода перетаскивания. Форма состоит из трех PictureBox. Мое намерение состояло в том, чтобы взять изображение из одного PictureBox, отобразить его как пользовательский курсор во время операции перетаскивания, а затем поместить его в другой объект PictureBox.

Это прекрасно работает от одного PictureBox к другому , если они находятся в одной форме .

Если я открываю два экземпляра одного и того же приложения и пытаюсь перетаскивать их между собой, я получаю следующую загадочную ошибку:

  

Этот удаленный прокси не имеет канала   раковина, что означает, что либо сервер имеет   нет зарегистрированных каналов сервера, которые   прослушивания, или это приложение не имеет   подходящий клиентский канал для общения с   сервер.

Однако по какой-то причине он работает с перетаскиванием в Wordpad (но не в 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);
}

Любая помощь будет принята с благодарностью!

Это было полезно?

Решение

После долгих скрежетаний зубов и выпадения волос мне удалось найти работоспособное решение. Кажется, что под покровом .NET и поддержки OLE drag and drop происходит какая-то недокументированная странность. Кажется, он пытается использовать .NET Remoting при перетаскивании между приложениями .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, поэтому он имеет приоритет над существованием обычного растрового изображения в объекте данных. Класс BitmapTransfer может быть помещен в общую библиотеку для использования с несколькими приложениями. Он должен быть помечен как сериализуемый, как показано для перетаскивания между приложениями. Я протестировал его с помощью перетаскивания растровых изображений в приложении, между приложениями и из приложения .NET в Wordpad.

Надеюсь, это поможет вам.

Другие советы

Недавно я столкнулся с этой проблемой и использовал нестандартный формат в буфере обмена, что усложняло 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);

После нескольких часов разочарования от пара, выходящего из моих ушей, я, наконец, нашел второе решение этой проблемы. Точно, какое решение является самым изящным, вероятно, в глазах смотрящего. Я надеюсь, что решения Майкла и мои помогут и разочарованным программистам, и сэкономят им время, когда они приступят к подобным квестам.

Прежде всего, меня поразило то, что Wordpad мог получать изображения перетаскивания из коробки. Таким образом, упаковка файла, вероятно, не была проблемой, но, возможно, на приемном конце происходило что-то подозрительное.

И там была рыбка. Оказывается, есть несколько типов IDataObjects, плавающих в .Net Framework. Как отметил Майкл, поддержка OLE drag and drop пытается использовать .Net remoting при взаимодействии между приложениями. Это фактически помещает 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 Forms по умолчанию имеет значение 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 и вместо этого передает информацию в параметр out 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);

... и этот код отлично работает с операциями перетаскивания (растровых изображений) между двумя приложениями. Код можно легко распространить на другие допустимые форматы буфера обмена и, возможно, также на собственные форматы буфера обмена. Поскольку с упаковочной частью ничего не было сделано, вы все равно можете перетащить изображение в Wordpad, а поскольку оно принимает растровые форматы, вы также можете перетащить изображение из Word в приложение.

Как примечание, перетаскивание изображения непосредственно из IE даже не вызывает событие DragDrop. Странно.

Просто из любопытства вы пытались проверить в методе DragDrop, можно ли вообще получить растровое изображение из DragEventArgs? Не делая отправителя? Мне интересно, не является ли объект picturebox сериализуемым, что вызывает проблему при попытке использовать отправителя в другом домене приложения ...

scroll top