C # Arrastrar y soltar: muestra el elemento arrastrado mientras se arrastra

Estoy construyendo una aplicación de escritorio en C # con Windows Forms. Tengo un Control personalizado, y me gustaría poder arrastrarlo y soltarlo dentro de mi aplicación (no afuera). En este momento estoy implementando eso con los métodos habituales DoDragDrop / OnDragOver / OnDragDrop. ¿Hay alguna manera de pintar continuamente el control a medida que se arrastra, algo así como lo que ves con el arrastrar y soltar de JQuery? Quiero que el control real permanezca en su lugar, pero quiero pintar una copia de su apariencia a medida que el usuario lo arrastra. Idealmente, la copia sería incluso semitransparente, pero eso es más un “agradable tener”.

La única forma en que puedo pensar para hacer esto es poner el código de pintura en el método OnPaint de la forma principal, pero parece ser una solución poco elegante. ¿Alguna otra idea? ¿Son las cosas más fáciles si el Control se pinta solo como un bitmap?

Pensé que debería volver y responder a esto yo mismo, ya que eventualmente lo hice funcionar.

Creé una clase CursorUtil con estas funciones:

public struct IconInfo { public bool fIcon; public int xHotspot; public int yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } public class CursorUtil { [DllImport("user32.dll")] public static extern IntPtr CreateIconIndirect(ref IconInfo icon); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo); [DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr handle); [DllImport("user32.dll", CharSet = CharSet.Auto)] extern static bool DestroyIcon(IntPtr handle); // Based on the article and comments here: // http://www.switchonthecode.com/tutorials/csharp-tutorial-how-to-use-custom-cursors // Note that the returned Cursor must be disposed of after use, or you'll leak memory! public static Cursor CreateCursor(Bitmap bm, int xHotspot, int yHotspot) { IntPtr cursorPtr; IntPtr ptr = bm.GetHicon(); IconInfo tmp = new IconInfo(); GetIconInfo(ptr, ref tmp); tmp.xHotspot = xHotspot; tmp.yHotspot = yHotspot; tmp.fIcon = false; cursorPtr = CreateIconIndirect(ref tmp); if (tmp.hbmColor != IntPtr.Zero) DeleteObject(tmp.hbmColor); if (tmp.hbmMask != IntPtr.Zero) DeleteObject(tmp.hbmMask); if (ptr != IntPtr.Zero) DestroyIcon(ptr); return new Cursor(cursorPtr); } public static Bitmap AsBitmap(Control c) { Bitmap bm = new Bitmap(c.Width, c.Height); c.DrawToBitmap(bm, new Rectangle(0, 0, c.Width, c.Height)); return bm; } 

Luego escribí una clase Drag (también no orientada a objetos, por desgracia, pero pensé que solo puedes arrastrar una cosa a la vez en una aplicación de escritorio). Aquí hay un poco de ese código:

  public static void StartDragging(Control c) { Dragged = c; DisposeOldCursors(); Bitmap bm = CursorUtil.AsBitmap(c); DragCursorMove = CursorUtil.CreateCursor((Bitmap)bm.Clone(), DragStart.X, DragStart.Y); DragCursorLink = CursorUtil.CreateCursor((Bitmap)bm.Clone(), DragStart.X, DragStart.Y); DragCursorCopy = CursorUtil.CreateCursor(CursorUtil.AddCopySymbol(bm), DragStart.X, DragStart.Y); DragCursorNo = CursorUtil.CreateCursor(CursorUtil.AddNoSymbol(bm), DragStart.X, DragStart.Y); //Debug.WriteLine("Starting drag"); } // This gets called once when we move over a new control, // or continuously if that control supports dropping. public static void UpdateCursor(object sender, GiveFeedbackEventArgs fea) { //Debug.WriteLine(MainForm.MousePosition); fea.UseDefaultCursors = false; //Debug.WriteLine("effect = " + fea.Effect); if (fea.Effect == DragDropEffects.Move) { Cursor.Current = DragCursorMove; } else if (fea.Effect == DragDropEffects.Copy) { Cursor.Current = DragCursorCopy; } else if (fea.Effect == DragDropEffects.None) { Cursor.Current = DragCursorNo; } else if (fea.Effect == DragDropEffects.Link) { Cursor.Current = DragCursorLink; } else { Cursor.Current = DragCursorMove; } } 

Puede usar estos métodos cuando configure sus controles, por ejemplo en el constructor:

 GiveFeedback += new GiveFeedbackEventHandler(Drag.UpdateCursor); 

y en este método:

  protected override void OnMouseMove(MouseEventArgs mea) { if (Drag.IsDragging(mea)) { Drag.StartDragging(this); DragDropEffects dde = DoDragDrop(Plan, DragDropEffects.Move | DragDropEffects.Copy); Drag.StopDragging(); } } 

esta podría ser una opción:

 private void btntarget_MouseDown(object sender, MouseEventArgs e) { Bitmap bmp = new Bitmap(btntarget.Width, btntarget.Height); btntarget.DrawToBitmap(bmp, new Rectangle(Point.Empty, bmp.Size)); //optionally define a transparent color bmp.MakeTransparent(Color.White); Cursor cur = new Cursor(bmp.GetHicon()); Cursor.Current = cur; } 

el punto de acceso del cursor se creará en el medio de la imagen

Eso generalmente es manejado por el evento GiveFeedback .

Arrastre una ImageList en su formulario y use las funciones de la Lista de imágenes para moverla:

  1. Cree un objeto Bitmap del tamaño de su control (pero no más de 256×256).
  2. Copie la imagen de su control en el bitmap: usando (Graphics gfx = Graphics.FromImage (bmp)) {gfx.CopyFromScreen (…)}
  3. Agrégalo a tu ImageList.
  4. Llamar a ImageList_BeginDrag ().
  5. Llamar a DoDragDrop ()
  6. En su controlador OnDragMove, invoque ImageList_DragMove () para moverlo mientras se mueve el mouse.
  7. Cuando DoDragDrop regresa, llama a ImageList_DragLeave ().

He estado llamando ImageList_DragEnter () y ImageList_DragLeave (), que parece convertir las coordenadas utilizadas por ImageList_DragMove () a las coordenadas del cliente, pero mi lectura de la documentación sugiere que no es necesario.