#713 – Setting the Cursor to an Image of an UIElement While Dragging
December 17, 2012 6 Comments
You can use the GiveFeedback to change the cursor during a drag-and-drop operation. You can go a bit further and set the cursor to an image that represents the user interface element that you are dragging by rendering the UIElement to a bitmap and then converting that bitmap to a cursor.
This example is based on code written by Brandon Cannaday, at http://www.switchonthecode.com/tutorials/wpf-tutorial-how-to-use-custom-cursors . To start with, here is Brandon’s code, as modified by reader “Swythan”:
public class CursorHelper { private static class NativeMethods { public struct IconInfo { public bool fIcon; public int xHotspot; public int yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } [DllImport("user32.dll")] public static extern SafeIconHandle CreateIconIndirect(ref IconInfo icon); [DllImport("user32.dll")] public static extern bool DestroyIcon(IntPtr hIcon); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo); } [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] private class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid { public SafeIconHandle() : base(true) { } override protected bool ReleaseHandle() { return NativeMethods.DestroyIcon(handle); } } private static Cursor InternalCreateCursor(System.Drawing.Bitmap bmp) { var iconInfo = new NativeMethods.IconInfo(); NativeMethods.GetIconInfo(bmp.GetHicon(), ref iconInfo); iconInfo.xHotspot = 0; iconInfo.yHotspot = 0; iconInfo.fIcon = false; SafeIconHandle cursorHandle = NativeMethods.CreateIconIndirect(ref iconInfo); return CursorInteropHelper.Create(cursorHandle); } public static Cursor CreateCursor(UIElement element) { element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); element.Arrange(new Rect(new Point(), element.DesiredSize)); RenderTargetBitmap rtb = new RenderTargetBitmap( (int)element.DesiredSize.Width, (int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32); rtb.Render(element); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(rtb)); using (var ms = new MemoryStream()) { encoder.Save(ms); using (var bmp = new System.Drawing.Bitmap(ms)) { return InternalCreateCursor(bmp); } } } }
Now that we have the helper class, we can use it to set the cursor to the image of a Label that we are dragging. Here’s the XAML defining a label to drag from and one to drag to.
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="45"> <Label Content="Data to drag" Background="AliceBlue" Padding="15,10" MouseLeftButtonDown="Label_MouseLeftButtonDown" GiveFeedback="Label_GiveFeedback"/> <Label Content="Drag to here" Background="MediumSpringGreen" Padding="15,10" Margin="20" AllowDrop="True" Drop="Label_Drop"/> </StackPanel>
Here’s the relevant drag-and-drop related code:
private void Label_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { DataObject data = new DataObject(DataFormats.Text, ((Label)e.Source).Content); DragDrop.DoDragDrop((DependencyObject)e.Source, data, DragDropEffects.Copy); } private void Label_Drop(object sender, DragEventArgs e) { ((Label)e.Source).Content = (string)e.Data.GetData(DataFormats.Text); } private Cursor customCursor = null; private void Label_GiveFeedback(object sender, GiveFeedbackEventArgs e) { if (e.Effects == DragDropEffects.Copy) { if (customCursor == null) customCursor = CursorHelper.CreateCursor(e.Source as UIElement); if (customCursor != null) { e.UseDefaultCursors = false; Mouse.SetCursor(customCursor); } } else e.UseDefaultCursors = true; e.Handled = true; }
Pingback: Dew Drop – December 18, 2012 (#1,465) | Alvin Ashcraft's Morning Dew
Nice but I have a secondary effect when using with a listboxitem. The clicked listboxitem becomes black and a copy of it covers the first listboxitem of the list… any idea?
Nothing immediate comes to mind. You might want to post code that demonstrates what you’re seeing, to stackoverflow.com
this happend to me too
to solve this i have clone the ui element and render the copy.
I simply copied pasted your code and I pass the listboxitem element to it. With the code for a text cursor it works fine.
Tested with a simple image. Works fine too. Did some changes and instead of using the element itself, I use a VisualBrush to render the cursor. Now it works without problems:
public static Cursor CreateCursor(UIElement element)
{
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(element);
dc.DrawRectangle(vb, null, new Rect(new Point(), new Size(element.RenderSize.Width, element.RenderSize.Height)));
}
// The BitmapSource that is rendered with a Visual.
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)element.RenderSize.Width, (int)element.RenderSize.Height,
96, 96, PixelFormats.Pbgra32);
rtb.Render(dv);
// Encoding the RenderBitmapTarget into a bitmap (as PNG)
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var ms = new MemoryStream())
{
encoder.Save(ms);
using (var bmp = new System.Drawing.Bitmap(ms))
{
return InternalCreateCursor(bmp);
}
}
}