I've been developing for Windows CE for a while now, and generally speaking I've seen just about every class of business problem that people might run into. Or so I thought.
I had a request from a client who wanted to change the default mouse cursor on their device for their solution. The challenge was that their solution consists of not just one application, but two, and one is a managed application.
Initial research on the web said that this would be fairly easy. Just put the cursor file out in the file system somewhere, then have each app load it up at runtime. If only reality were that simple.
It turns out that Windows CE is substantially limited in its ability to handle cursors. The big problem is that LoadCursorFromFile, which is exactly what I needed, flat out doesn't exist in Windows CE. The managed Cursor class also doesn't have a constructor that takes in a filename (likely because of the lack of that API). I message around a bit trying to load a Bitmap and use the HBITMAP in some cursor calls, but every step led me to a dead-end. So after an hour or two of doing nothing but exploring and ruling out options, I decided that maybe I'd accept a limitation that the cursor would have to be embedded as a resource in a native DLL.
Of course this still wasn't super straightforward or simple either. LoadImage can only load an image from a compiled resource file by name. Of course EnumResourceNames, which you might use to get a resource name, doesn't exist either.
My first goal was to just get the cursor changed for the Form. I assumed that the cursor would likely not be correct for child controls like TextBoxes, but really, baby steps are fine. I used LoadLibraryEx to load up the native DLL, followed by LoadCursor to load the cursor resource by ID, then finally a call to SetCursor.
My initial test were returning valid (i.e. non-zero) return values but the cursor would change only briefly. As soon as I moved the mouse, the cursor would revert to the default. A little more research and I came across this gem in the MSDN remarks for SetCursor:
"If your application must set the cursor while it is in a window, make sure the class cursor for the specified window's class is set to NULL. If the class cursor is not NULL, the system restores the class cursor each time the mouse is moved."
That sounded a whole lot like the behavior I was seeing. A little more searching and I came to the conclusion that I needed to call SetClassLong with GCL_HCURSOR. This required that I give a pointer to the loaded cursor, so I have to move the results of LoadCursor to a broader scope, but once I'd done that, bingo! The cursor was my custom cursor when on the Form. (I wanted to put a screen shot in this post here, but screen captures on the device omit the mouse cursor, so it's not terribly interesting.)
So now the goal was to get it to not change when I moved over another Control. Again it was time to do more research. It turns out that it could be done for each control class (so all TextBoxes, all Forms, etc) but that would mean I'd have to iterate through all of these types and set it. And who knows what a custom or user control might turn up. That just seemed risky and kludgy to me. What are my other options?
It seems that if you handle the WM_SETCURSOR message for a control, you can override the default behavior to set the default cursor. So all I had to do was subclass my Form. Of course that's not 100% straightforward with the Compact Framework. You can't just override WndProc like you can on the desktop, but still it wasn't groundbreaking territory and I'd done it before. I ended up creating a simple Subclasser class that you could attach to a Form, then in it load up your cursor (using the code I'd done before) and then just call SetCursor with my desired cursor instead of the default.
The end result worked perfectly. The source code for the work can be downloaded here: CursorTest.zip (9.15 KB) (the native source that holds the actual cursor is here: Cursors.zip (12.56 KB))
Some other thoughts I have on this that I leave to the ambitious:
- I think you could probably use a similar technique to the Form subclass to do an application-wide cursor replacement using an OpenNETCF.Windows.Forms.IMessageFilter implementation, but I've not tested that.
- This is going to kill your wait cursor, so if you want/need one, you're going to have to plumb in support for that
- If you want to have multiple custom cursors (if you want to have a custom Form and a custom TextBox for example), then you're probably going to have to add support for that if you use the subclassing technique.
- I've noticed that when you click a Button and it redraws, it appears to draw over the cursor until you move the mouse again, so this needs to be looked into.