Thursday, June 19, 2008
Mouse Pointers and Cursors
We've all seen it, and we all want to do it. If an application is busy doing something for extended period of time, we want to change the cursor so that the user knows the application is still busy. But, cursor control can be a bit confusing in C#.NET because there are several ways to control the cursor. I'll try to sum how cursors behave for WinForms within this post.
For starters, just to get the terminology straight from a Windows' point of view, the mouse pointer is the location on the screen that changes as the user moves the mouse, and the cursor is the graphical image that is drawn with its hot spot at the mouse pointer.
In .NET, each cursor image is represented by an instance of the
Instances of
In addition to using instances of the
These static properties describing the current cursor are a bit interesting when it comes to threaded applications. No matter what thread you are on, the
Although you can do it without throwing an exception, getting or setting the
Each WinForms control has a
The
Setting the
If you know you are going to execute code that will block the main UI thread for a bit, you could set
If you know you are going to block the main UI thread fo awhile, you could set the
Each WinForms control has a
The
There are a few last subtle point to note...
Unlike the
Also, if
And, using the wait cursor via
Finally, there are even more ways to change the cursor, such as via the
For starters, just to get the terminology straight from a Windows' point of view, the mouse pointer is the location on the screen that changes as the user moves the mouse, and the cursor is the graphical image that is drawn with its hot spot at the mouse pointer.
In .NET, each cursor image is represented by an instance of the
System.Windows.Forms.Cursor class. Instances of the Cursor class can be used to represent the standard Windows cursors, as well as custom-defined cursors (generally compiled into a resource file).Instances of
Cursor that represent the standard Windows cursors are available as static properties of the System.Windows.Forms.Cursors class. For example, Cursors.WaitCursor returns an instance of a Cursor that contains information about how to display the "wait cursor" configured in Windows.In addition to using instances of the
Cursor class to define what different cursors look like, the Cursor class also has several static properties that described the currently active cursor. Most noteably, the Cursor.Current property sets or gets the Cursor instance that is currently active, and Cursor.Position property gets or sets the current location of the mouse pointer.These static properties describing the current cursor are a bit interesting when it comes to threaded applications. No matter what thread you are on, the
Cursor.Position property always gets or sets the Windows mouse pointer position immediately. But, the Cursor.Current property does not behave the same way!Although you can do it without throwing an exception, getting or setting the
Cursor.Current property form any thread other than the main UI thread is somewhat pointless. If you get the Cursor.Current property from a non-UI thread, you will notice that it defaults to Cursors.WaitCursor even if the actual Windows cursor is something else. And, if you set the Cursor.Current property from a non-UI thread the actual Windows cursor will not change. After doing so, however, calling get from a non-UI thread will return that set value.Each WinForms control has a
Cursor property inherited from the System.Windows.Forms.Control class. When the mouse pointer is over a control, the Windows cursor is changed to whatever is specified by the Cursor property of the control, and the Cursor.Current property is changed to return that cursor as well.The
Control.Cursor property is a so-called ambient property. That means a control will use its container's cursor as long as it has not been specifically set on the control itself. For example, if you have a button on a form, then setting the Cursor property of the form (both in Visual Studio and prgrammatically at run-time) will also change the cursor for the button as long as you have not specifically set the button's Cursor property.Setting the
Cursor.Current property from a non-UI thread may not be useful, but setting it from the main UI thread is a way of temporarily overridding the cursors specified by the Cursor properties of all controls in the application (or at least those on the same UI thread). Setting the Cursor.Current property from the main UI thread changes the Windows cursor immediately and application-globally, but only temporarily. Once Cursor.Current is specifically set, it will continue to override the controls' cursors until it is changed to something else, or until the UI thread "catches up" and runs out of messages to process.If you know you are going to execute code that will block the main UI thread for a bit, you could set
Cursor.Current before executing that code and not have to worry about setting it back or changing it on every form. Once the UI starts responding again, the cursor will go back to being whatever is set on the Control.Cursor property of the control the mouse pointer is over.If you know you are going to block the main UI thread fo awhile, you could set the
Cursor.Current to Cursors.WaitCursor, and Windows would display the wait cursor for the entire application until the main UI thread was no longer busy. But, ideally we do not want to block the main thread for extended periods of time because non-responsive, partially-painted applications not only look bad but also make users think something is wrong. So, unless a long-runnning task has to be on the main UI thread (such as when creating a bunch of items for a combo box), most programmers should choose to do long-running tasks on background threads or in dedicated threads so the main UI thread is not blocked. But, in such cases, we cannot use Cursor.Current to display the wait cursor because the cursor would immediately reset to whatever is appropriate for the control the mouse pointer is over because the UI would be responsive. That's where the UseWaitCursor properties come in!Each WinForms control has a
UseWaitCursor property inherited from System.Windows.Forms.Control. When this property is true, the wait cursor is used instead of whatever cursor is specified in the Control.Cursor property whenever the mouse pointer is over the control or any of it's child controls.The
System.Windows.Forms.Application class also has a UseWaitCursor property. Setting Application.UseWaitCursor causes the framework to loop over all of the open forms and set the Control.UseWaitCursor properties on all of those forms.There are a few last subtle point to note...
Unlike the
Cursor.Current property that assumes the main UI thread will be blocked and therefore changes the Windows cursor immediately, setting the Control.UseWaitCursor or Application.UseWaitCursor properties assumes the UI will remain responsive and therefore does not change the Windows cursor immediately but instead processes it as any other property change. Therefore, if you set the UseWaitCursor and then block the UI thread, you may never see it!Also, if
UseWaitCursor is true, then setting the Cursor.Current cannot be used to override the wait cursor. And, using the wait cursor via
UseWaitCursor does not prohibit other mouse events. It is still possible for the user to press buttons, size forms, enter text, etc. even if UseWaitCursor is true.Finally, there are even more ways to change the cursor, such as via the
DragDropEffects! (Perhaps I will blog about how they interact with the cursors mentioned above in the future?)Subscribe to Posts [Atom]