#714 – Setting the Cursor to Render Some Text While Dragging

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;
        }

714-001

About Sean
Software developer in the Twin Cities area, passionate about .NET technologies. Equally passionate about my own personal projects related to family history and preservation of family stories and photos.

3 Responses to #714 – Setting the Cursor to Render Some Text While Dragging

  1. Pingback: Setting the Cursor to Render Some Text While Dragging in WPF | Around computing

  2. Miguel de Campos says:

    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;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: