#714 – Setting the Cursor to Render Some Text While Dragging
December 18, 2012 5 Comments
You can use the GiveFeedback to change the cursor during a drag-and-drop operation. You can set the cursor to be some text by rendering a visual element to a bitmap and then converting that bitmap to a cursor.
Here’s a helper class containing a method that converts some text to a cursor.
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(string cursorText) { // Text to render FormattedText fmtText = new FormattedText(cursorText, new CultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Normal, new FontStretch()), 12.0, // FontSize Brushes.Black); // The Visual to use as the source of the RenderTargetBitmap. DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); drawingContext.DrawText(fmtText, new Point()); drawingContext.Close(); // The BitmapSource that is rendered with a Visual. RenderTargetBitmap rtb = new RenderTargetBitmap( (int)drawingVisual.ContentBounds.Width, (int)drawingVisual.ContentBounds.Height, 96, // dpiX 96, // dpiY PixelFormats.Pbgra32); rtb.Render(drawingVisual); // Encoding the RenderBitmapTarget into a bitmap (as PNG) PngBitmapEncoder 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); } } } }
We can test this code by setting the cursor to some text when we drag a Label.
<StackPanel> <Label Content="Mayflower" Background="AliceBlue" HorizontalAlignment="Center" Margin="15" Padding="20,5" MouseLeftButtonDown="Label_MouseLeftButtonDown" GiveFeedback="Label_GiveFeedback"/> <Label Content="Drag to here" Background="SpringGreen" HorizontalAlignment="Center" Margin="15" Drop="Label_Drop"/> </StackPanel>
Here’s the code for the drag-and-drop operation.
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("Mayflower - 18 Dec 1620"); if (customCursor != null) { e.UseDefaultCursors = false; Mouse.SetCursor(customCursor); } } else e.UseDefaultCursors = true; e.Handled = true; }
Pingback: Setting the Cursor to Render Some Text While Dragging in WPF | Around computing
Hi Sean,
Thanks for your incredibly useful blog.
InternalCreateCursor() is leaking GDI Objects. Here is how I fixed the problem:
[DllImport(“gdi32.dll”)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject(IntPtr hObject);
private static Cursor InternalCreateCursor(System.Drawing.Bitmap bmp)
{
var iconInfo = new NativeMethods.IconInfo();
Cursor retCursor = null;
IntPtr hIcon = bmp.GetHicon();
NativeMethods.GetIconInfo(hIcon, ref iconInfo);
iconInfo.xHotspot = 0;
iconInfo.yHotspot = 0;
iconInfo.fIcon = false;
SafeIconHandle cursorHandle = NativeMethods.CreateIconIndirect(ref iconInfo);
retCursor = CursorInteropHelper.Create(cursorHandle);
NativeMethods.DestroyIcon(hIcon);
NativeMethods.DeleteObject(iconInfo.hbmColor);
NativeMethods.DeleteObject(iconInfo.hbmMask);
return retCursor;
}
Thanks Miguel !
This works on Windows Server 2008, Windows 7, Windows 8 and Windows 10.
However, it does not work on Windows Server 2012, Windows Server 2016… Do you have an idea why?
It doesn’t work very well, I get an error all the time after closing the application: “System.Runtime.InteropServices.SEHException:”