Перетаскивание между экземплярами одного приложения Windows Forms
-
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, где должно быть изображение. Понятно, что это не совсем верно.
Следующая статья дала мне несколько указаний в правильном направлении:
Просто из любопытства вы пытались проверить в методе DragDrop, можно ли вообще получить растровое изображение из DragEventArgs? Не делая отправителя? Мне интересно, не является ли объект picturebox сериализуемым, что вызывает проблему при попытке использовать отправителя в другом домене приложения ...