Подтвердить что ты не робот

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

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

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

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

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

По какой-то причине, однако, он работает, чтобы перетащить на Wordpad (но не MS Word или Paintbrush).

Три 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);
    }
}

Тогда есть четыре события, подобные этому:

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

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

4b9b3361

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

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

Ответ 2

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

Ответ 3

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

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

И был рыбный. Оказывается, существуют отдельные типы IDataObjects, плавающие вокруг .Net framework. Как отметил Майкл, поддержка перетаскивания 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 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 и вместо этого передает информацию в параметр 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);

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

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

Ответ 4

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