#713 – Setting the Cursor to an Image of an UIElement While Dragging

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

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.

6 Responses to #713 – Setting the Cursor to an Image of an UIElement While Dragging

  1. Pingback: Dew Drop – December 18, 2012 (#1,465) | Alvin Ashcraft's Morning Dew

  2. Olivier Voutat says:

    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?

  3. Olivier Voutat says:

    I simply copied pasted your code and I pass the listboxitem element to it. With the code for a text cursor it works fine.

  4. Olivier Voutat says:

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

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: