I\'m building a desktop app in C# with Windows Forms. I have a custom Control, and I\'d like to be able to drag and drop it within my application (not outside). Right now I\
Drag an ImageList onto your form and use the Image List functions to move it around:
I have been calling ImageList_DragEnter() and ImageList_DragLeave(), which seems to convert the coordinates used by ImageList_DragMove() to client coordinates, but my reading of the documentation suggests it is not necessary.
I thought I should come back and answer this myself, since I did get it working eventually.
I created a CursorUtil class with these functions:
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;
}
Then I wrote a Drag class (also not object-oriented, alas, but I figured you can only drag one thing at a time in a desktop app). Here is a bit of that code:
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;
}
}
You can use these methods when you set up your controls, for example in the constructor:
GiveFeedback += new GiveFeedbackEventHandler(Drag.UpdateCursor);
and in this method:
protected override void OnMouseMove(MouseEventArgs mea) {
if (Drag.IsDragging(mea)) {
Drag.StartDragging(this);
DragDropEffects dde = DoDragDrop(Plan, DragDropEffects.Move | DragDropEffects.Copy);
Drag.StopDragging();
}
}
Based on a previous answer:
First define the method MouseDown (in my case i use a button, but is aplicable at other controls).
private void btn_MouseDown(object sender, MouseEventArgs e)
{
//Cast the sender to control type youre using
Button send = (Button)sender;
//Copy the control in a bitmap
Bitmap bmp = new Bitmap(send.Width, send.Height);
send.DrawToBitmap(bmp, new Rectangle(Point.Empty, bmp.Size));
//In a variable save the cursor with the image of your controler
this.BitMapCursor = new Cursor(bmp.GetHicon());
send.DoDragDrop(send.Text, DragDropEffects.Move);
}
i am casting the sender because in my aplicattion the buttón is be generated in real time.
Now define the method GiveFeedBack(this method occurs during a drag operation.)
private void btn_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
//Deactivate the default cursor
e.UseDefaultCursors = false;
//Use the cursor created from the bitmap
Cursor.Current = this.BitMapCursor;
}
To end dont forget suscribe the control to the methods
btn.Click += new EventHandler(ClickButton);
btn.MouseDown += new MouseEventHandler(btn_MouseDown);
btn.GiveFeedback += new GiveFeedbackEventHandler(btn_GiveFeedback);
this could be an option:
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;
}
the cursor's hotspot will be created at the middle of the image
That's usually handled by the GiveFeedback
event.