Перетаскивание между экземплярами одного и того же приложения 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);
}
Любая помощь будет принята с благодарностью!
Ответы
Ответ 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? Без выполнения отправки отправителя? Я задаюсь вопросом, не является ли объект фотокабеля сериализуемым, что вызывает проблему при попытке использовать отправителя в другом домене приложения...