Перетаскивание между экземплярами одного приложения Windows Forms - PullRequest
16 голосов
/ 29 июля 2009

Я создал небольшое тестовое приложение 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);
}

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

Ответы [ 4 ]

10 голосов
/ 30 июля 2009

После долгих скрежетаний зубов и выпадения волос я смог найти работоспособное решение. Кажется, что под покровом .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.

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

7 голосов
/ 13 августа 2009

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

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

Прежде всего, меня поразило то, что 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 и вместо этого передает информацию в параметр 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. Странно.

1 голос
/ 29 июля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...