Skip to main content

C# WPF Tutorial - How To Use Custom Cursors [Intermediate]


So a while back, I did a tutorial on how to do custom cursors inWinForms. In that post I said that at some point in the future, I would write a post on how to do custom cursors in WPF - and here we are! A lot of the code used today is based off of the code from that previous tutorial, so if you haven't read it, I would go do so before you continue.

Sadly, creating and using a custom cursor for WPF is actually more difficult than for WinForms. This is pretty much all due in the end to a single problem - WPF does not use GDI, but the cursor still does. Since the cursor is something at the Windows level (it needs to exist across all application and work smoothly), it is still GDI, and has all the benefits and flaws that come with that fact. So we end up needing to cross that boundary every time you want to do something special with the cursor in WPF.

But don't worry! Fortunately, WPF wraps all the standard cursors (so you can still easily change to, say, a wait cursor), but as soon as you want to create a special image to use as your cursor, you are on your own. Well, not really on your own - that is what this tutorial is here for. And so, in we go. First, we are going to look at the rather small change to the actual "cursor creation part of the code. If you remember, to create a cursor for WinForms, you can just say something like this:
IntPtr curPtr;

/* CurPtr gets set to
 a pointer to an icon */

Cursor myCur = new Cursor(curPtr);
 
Sadly, you can't quite do that in WPF. This is because it is a different Cursor object we are creating, and Microsoft did not give us a constructor that takes an IntPtr. For WinForms, we used a System.Windows.Forms.Cursor, but for WPF, we will be creating a System.Windows.Input.Cursor. But while there is no constructor for it, there is still a way to get a Cursor out of an IntPtr. It just happens to be hidden elsewhere:
IntPtr curPtr;

/* CurPtr gets set to
 a pointer to an icon */

SafeFileHandle handle = new SafeFileHandle(ptr, true);
Cursor myCur = System.Windows.Interop.CursorInteropHelper.Create(handle);

So first we have to convert the IntPtr into a SafeHandle (SafeFileHandle extends SafeHandle - you can't just create a SafeHandle, it is an abstract class). Then we pass that handle into the nice and easy to find (thats sarcasm in case you can't tell) Create method under System.Windows.Interop.CursorInteropHelper, and we get a Cursor back that we can use with WPF.

So if we bring in the rest of the code from the previous tutorial, we might get a class that looks like this:
using System;
using System.Windows.Interop;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Windows.Input;

namespace WPFCursorTest
{
  public class CursorHelper
  {
    private struct IconInfo
    {
      public bool fIcon;
      public int xHotspot;
      public int yHotspot;
      public IntPtr hbmMask;
      public IntPtr hbmColor;
    }

    [DllImport("user32.dll")]
    private static extern IntPtr CreateIconIndirect(ref IconInfo icon);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);


    public static Cursor CreateCursor(System.Drawing.Bitmap bmp, int xHotSpot, 
        int yHotSpot)
    {
      IconInfo tmp = new IconInfo();
      GetIconInfo(bmp.GetHicon(), ref tmp);
      tmp.xHotspot = xHotSpot;
      tmp.yHotspot = yHotSpot;
      tmp.fIcon = false;

      IntPtr ptr = CreateIconIndirect(ref tmp);
      SafeFileHandle handle = new SafeFileHandle(ptr, true);
      return CursorInteropHelper.Create(handle);
    }
  }
}
 
One thing you will need to remeber if you use this is that we are dealing with a System.Drawing.Bitmap here. This is the old GDI type bitmap object - a concept foreign to the new WPF classes. So you will probably need to pull in a reference to System.Drawing in your references section in your Visual Studio project, since WPF projects do not have this reference by default.

I tried, quite hard, to figure out a way to create a cursor in WPF without having to lean on System.Drawing.Bitmap. But in the end, I need to get an Icon pointer out of the bitmap (thats the function GetHicon), and the WPF bitmap objects refuse to ever give you a pointer.

But wait, not all hope is lost! I may have to deal with a System.Drawing.Bitmap, but that does not mean that everyone will need to. It is possible to wrap this method in another method that can take in a WPF construct, instead of a GDI one:
public static Cursor CreateCursor(UIElement element, int xHotSpot, int yHotSpot)
{
  element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
      element.DesiredSize.Height));

  RenderTargetBitmap rtb = new RenderTargetBitmap((int)element.DesiredSize.Width, 
      (int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
  rtb.Render(element);

  PngBitmapEncoder encoder = new PngBitmapEncoder();
  encoder.Frames.Add(BitmapFrame.Create(rtb));

  MemoryStream ms = new MemoryStream();
  encoder.Save(ms);

  System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms);

  ms.Close();
  ms.Dispose();

  Cursor cur = InternalCreateCursor(bmp, xHotSpot, yHotSpot);

  bmp.Dispose();

  return cur;
}
 
Here, I am taking in a UIElement - a core WPF construct. I measure and arrage it (to make sure that it is internally rendered properly), and then I "take a picture of it". I do this by creating a RenderTargetBitmap. This is a class for WPF thats lets you take a UIElement and draw it to a bitmap - but remember, this is a WPF bitmap (a System.Windows.Media.Imaging.RenderTargetBitmap, to be precise). It is not the same thing as a System.Drawing.Bitmap. So I create my RenderTargetBitmap to be the correct size, and I give it a dpi of 96 (pretty standard). Then, I render the UIElement on the bitmap.

Ok, now I have a RenderTargetBitmap in my hands, and I want to get to a System.Drawing.Bitmap. Microsoft could not have possibly made this more difficult, and I'm really quite annoyed by it. There are a couple methods that let you go from a System.Drawing.Bitmap to a WPF Bitmap (they create a System.Windows.Interop.InteropBitmap), but there are no methods that go in the other direction. Or at least none that I could find - and I scoured the docs and read through Bitmap code in Reflector for quite a while. If anyone knows of such a method (or a better way than the way I am about to describe), please let me know.

I convert from a RenderTargetBitmap to a System.Drawing.Bitmap by first creating an encoder, and encoding my bitmap as a PNG (you could pick any encoder you like). I then create a MemoryStream and save my encoded bitmap to the stream. And now, since the System.Drawing.Bitmap has a constructor that can take a stream, I can create my new bitmap. Once that is done, I clean up the memory stream, and proceed to create the cursor. And, of course, once the cursor is created, I dispose the System.Drawing.Bitmap that I had created.

So in the end, the code for the whole class looks like this:
using System;
using System.Windows.Interop;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Windows;

namespace WPFCursorTest
{
  public class CursorHelper
  {
    private struct IconInfo
    {
      public bool fIcon;
      public int xHotspot;
      public int yHotspot;
      public IntPtr hbmMask;
      public IntPtr hbmColor;
    }



    [DllImport("user32.dll")]
    private static extern IntPtr CreateIconIndirect(ref IconInfo icon);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);


    private static Cursor InternalCreateCursor(System.Drawing.Bitmap bmp, 
        int xHotSpot, int yHotSpot)
    {
      IconInfo tmp = new IconInfo();
      GetIconInfo(bmp.GetHicon(), ref tmp);
      tmp.xHotspot = xHotSpot;
      tmp.yHotspot = yHotSpot;
      tmp.fIcon = false;

      IntPtr ptr = CreateIconIndirect(ref tmp);
      SafeFileHandle handle = new SafeFileHandle(ptr, true);
      return CursorInteropHelper.Create(handle);
    }

    public static Cursor CreateCursor(UIElement element, int xHotSpot, int yHotSpot)
    {
      element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
      element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
          element.DesiredSize.Height));

      RenderTargetBitmap rtb = new RenderTargetBitmap((int)element.DesiredSize.Width, 
          (int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
      rtb.Render(element);

      PngBitmapEncoder encoder = new PngBitmapEncoder();
      encoder.Frames.Add(BitmapFrame.Create(rtb));

      MemoryStream ms = new MemoryStream();
      encoder.Save(ms);

      System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms);

      ms.Close();
      ms.Dispose();

      Cursor cur = InternalCreateCursor(bmp, xHotSpot, yHotSpot);

      bmp.Dispose();

      return cur;
    }
  }
}
 
Kind of ugly, don't you think? But at least it is encapsulated.

Now you probably wondering how to use this class. Well, it is really easy:
public partial class CursorTest : Window
{
  public CursorTest()
  {
    InitializeComponent();

    TextBlock tb = new TextBlock();
    tb.Text = "{ } Switch On The Code";
    tb.FontSize = 10;
    tb.Foreground = Brushes.Green;

    this.Cursor = CursorHelper.CreateCursor(tb, 5, 5);
  }
}
 
Here, I have a window, and I want my cursor to say "{ } Switch On The Code". So in the constructor, I make a TextBlock with that text, and just call CreateCursor. And all I need to do is set the Cursor property of my window to the result. That code would make something that looks like this:

Example WPF Custom Cursor

I hope this code helps anyone who has been trying to create and use custom cursors in WPF. If you would like, you can download a Visual Studio solution here, which contains both the CursorHelper class and the sample test window shown above. And if anyone comes up with a way to get rid of the need for having to bring in System.Drawing, or figures out how to convert from a WPF and GDI bitmap easily, let us know!
Source Files:

Comments

  1. Unfortunately this solution doesn't work with .Net Framework v. 4.7.2
    The App crashes when the cursor is disposed.

    ReplyDelete

Post a Comment

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...