I'm building a desktop app in C# with Windows Forms. I have a custom Contro开发者_如何学JAVAl, and I'd like to be able to drag and drop it within my application (not outside). Right now I'm implementing that with the usual DoDragDrop/OnDragOver/OnDragDrop methods. Is there any way to continuously paint the control as it gets dragged around--sort of what you see with JQuery's drag-and-drop? I want the actual control to stay in place, but I want to paint a copy of its appearance as the user drags it. Ideally the copy would even be semi-transparent, but that's more a "nice to have."
The only way I can think to do this is to put the paint code in the main form's OnPaint method, but that seems like an inelegant solution. Any other ideas? Are things any easier if the Control paints itself as just a Bitmap?
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();
}
}
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.
Drag an ImageList onto your form and use the Image List functions to move it around:
- Create a Bitmap object of the size of your control (but not more than 256x256).
- Copy the image of your control into the Bitmap: using (Graphics gfx = Graphics.FromImage(bmp)) { gfx.CopyFromScreen(...) }
- Add it to your ImageList.
- Call ImageList_BeginDrag().
- Call DoDragDrop()
- In your OnDragMove handler, call ImageList_DragMove() to move it around as the mouse moves.
- When DoDragDrop returns, call ImageList_DragLeave().
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.
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);
精彩评论