C# Source Code: Automatically setting the wait cursor for an application
[
Home
|
Contents
|
Search
|
Reply
| Previous |
Next
]
C# Source Code
Automatically setting the wait cursor for an application
By:
Andrew Baker
Email (spam proof):
Email the originator of this post
Date:
Friday, August 12, 2005
Hits:
19241
Category:
Windows Forms/GUI/Controls
Article:
It can be extremely tiresome to have to continually remember to set and unset the wait cursor for an application. If an exception occurs you have to remember to add a try finally block to restore the cursor, or if you popup a message box you must remember to change the cursor first otherwise the user will just sit there thinking the application is busy! These are just a few of the many irritations that I have tried to solve with the class below. The class below automatically monitors the state of an application and sets and restores the cursor according to whether the application is busy or not. All that’s required are a few lines of setup code and your done (see the example in the class header). If you have a multithreaded application, it won't change the cursor unless the main input thread is blocked. Infact you can remove all of your cursor setting code everywhere! using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; namespace VBusers.Windows.Forms.Utils { ///
/// This static utility class can be used to automatically show a wait cursor when the application /// is busy (ie not responding to user input). The class automatically monitors the application /// state, removing the need for manually changing the cursor. ///
///
/// To use, simply insert the following line in your Application startup code /// /// private void Form1_Load(object sender, System.EventArgs e) /// { /// AutoWaitCursor.Cursor = Cursors.WaitCursor; /// AutoWaitCursor.Delay = new TimeSpan(0, 0, 0, 0, 25); /// // Set the window handle to the handle of the main form in your application /// AutoWaitCursor.MainWindowHandle = this.Handle; /// AutoWaitCursor.Start(); /// } /// /// This installs changes to cursor after 100ms of blocking work (ie. work carried out on the main application thread). /// /// Note, the above code GLOBALLY replaces the following: /// /// public void DoWork() /// { /// try /// { /// Screen.Cursor = Cursors.Wait; /// GetResultsFromDatabase(); /// } /// finally /// { /// Screen.Cursor = Cursors.Default; /// } /// } ///
[DebuggerStepThrough()] public class AutoWaitCursor { #region Member Variables private static readonly TimeSpan DEFAULT_DELAY = new TimeSpan(0, 0, 0, 0, 25); ///
/// The application state monitor class (which monitors the application busy status). ///
private static ApplicationStateMonitor _appStateMonitor = new ApplicationStateMonitor( Cursors.WaitCursor, DEFAULT_DELAY ); #endregion #region Constructors ///
/// Default Constructor. ///
private AutoWaitCursor() { // Intentionally blank } #endregion #region Public Static Properties ///
/// Returns the amount of time the application has been idle. ///
public TimeSpan ApplicationIdleTime { get{ return _appStateMonitor.ApplicationIdleTime; } } ///
/// Returns true if the auto wait cursor has been started. ///
public static bool IsStarted { get{ return _appStateMonitor.IsStarted; } } ///
/// Gets or sets the Cursor to use during Application busy periods. ///
public static Cursor Cursor { get { return _appStateMonitor.Cursor; } set { _appStateMonitor.Cursor = value; } } ///
/// Enables or disables the auto wait cursor. ///
public static bool Enabled { get { return _appStateMonitor.Enabled; } set { _appStateMonitor.Enabled = value; } } ///
/// Gets or sets the period of Time to wait before showing the WaitCursor whilst Application is working ///
public static TimeSpan Delay { get { return _appStateMonitor.Delay; } set { _appStateMonitor.Delay = value; } } ///
/// Gets or sets the main window handle of the application (ie the handle of an MDI form). /// This is the window handle monitored to detect when the application becomes busy. ///
public static IntPtr MainWindowHandle { get { return _appStateMonitor.MainWindowHandle; } set { _appStateMonitor.MainWindowHandle = value; } } #endregion #region Public Methods ///
/// Starts the auto wait cursor monitoring the application. ///
public static void Start() { _appStateMonitor.Start(); } ///
/// Stops the auto wait cursor monitoring the application. ///
public static void Stop() { _appStateMonitor.Stop(); } #endregion #region Private Class ApplicationStateMonitor ///
/// Private class that monitors the state of the application and automatically /// changes the cursor accordingly. ///
private class ApplicationStateMonitor : IDisposable { #region Member Variables ///
/// The time the application became inactive. ///
private DateTime _inactiveStart = DateTime.Now; ///
/// If the monitor has been started. ///
private bool _isStarted = false; ///
/// Delay to wait before calling back ///
private TimeSpan _delay; ///
/// The windows handle to the main process window. ///
private IntPtr _mainWindowHandle = IntPtr.Zero; ///
/// Thread to perform the wait and callback ///
private Thread _callbackThread = null; ///
/// Stores if the class has been disposed of. ///
private bool _isDisposed = false; ///
/// Stores if the class is enabled or not. ///
private bool _enabled = true; ///
/// GUI Thread Id . ///
private uint _mainThreadId; ///
/// Callback Thread Id. ///
private uint _callbackThreadId; ///
/// Stores the old cursor. ///
private Cursor _oldCursor; ///
/// Stores the new cursor. ///
private Cursor _waitCursor; #endregion #region PInvokes [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)] [return:MarshalAs(UnmanagedType.Bool)] private static extern bool SendMessageTimeout(IntPtr hWnd, int Msg, int wParam, string lParam, int fuFlags, int uTimeout, out int lpdwResult); [DllImport("USER32.DLL")] private static extern uint AttachThreadInput(uint attachTo, uint attachFrom, bool attach); [DllImport("KERNEL32.DLL")] private static extern uint GetCurrentThreadId(); private const int SMTO_NORMAL = 0x0000; private const int SMTO_BLOCK = 0x0001; private const int SMTO_NOTIMEOUTIFNOTHUNG = 0x0008; #endregion #region Constructors ///
/// Default member initialising Constructor ///
///
The wait cursor to use. ///
The delay before setting the cursor to the wait cursor. public ApplicationStateMonitor(Cursor waitCursor, TimeSpan delay) { // Constructor is called from (what is treated as) the main thread _mainThreadId = GetCurrentThreadId(); _delay = delay; _waitCursor = waitCursor; // Gracefully shuts down the state monitor Application.ThreadExit +=new EventHandler(_OnApplicationThreadExit); } #endregion #region IDisposable ///
/// On Disposal terminates the Thread, calls Finish (on thread) if Start has been called ///
public void Dispose() { if (_isDisposed) { return; } // Kills the Thread loop _isDisposed = true; } #endregion IDisposable #region Public Methods ///
/// Starts the application state monitor. ///
public void Start() { if ( !_isStarted ) { _isStarted = true; this._CreateMonitorThread(); } } ///
/// Stops the application state monitor. ///
public void Stop() { if ( _isStarted ) { _isStarted = false; } } ///
/// Set the Cursor to wait. ///
public void SetWaitCursor() { // Start is called in a new Thread, grab the new Thread Id so we can attach to Main thread's input _callbackThreadId = GetCurrentThreadId(); // Have to call this before calling Cursor.Current AttachThreadInput(_callbackThreadId, _mainThreadId, true); _oldCursor = Cursor.Current; Cursor.Current = _waitCursor; } ///
/// Finish showing the Cursor (switch back to previous Cursor) ///
public void RestoreCursor() { // Restore the cursor Cursor.Current = _oldCursor; // Detach from Main thread input AttachThreadInput(_callbackThreadId, _mainThreadId, false); } ///
/// Enable/Disable the call to Start (note, once Start is called it *always* calls the paired Finish) ///
public bool Enabled { get { return _enabled; } set { _enabled = value; } } ///
/// Gets or sets the period of Time to wait before calling the Start method ///
public TimeSpan Delay { get { return _delay; } set { _delay = value; } } #endregion #region Public Properties ///
/// Returns true if the auto wait cursor has been started. ///
public bool IsStarted { get{ return _isStarted; } } ///
/// Gets or sets the main window handle of the application (ie the handle of an MDI form). /// This is the window handle monitored to detect when the application becomes busy. ///
public IntPtr MainWindowHandle { get { return _mainWindowHandle; } set { _mainWindowHandle = value; } } ///
/// Gets or sets the Cursor to show ///
public Cursor Cursor { get { return _waitCursor; } set { _waitCursor = value; } } ///
/// Returns the amount of time the application has been idle. ///
public TimeSpan ApplicationIdleTime { get{ return DateTime.Now.Subtract( _inactiveStart ) ; } } #endregion #region Private Methods ///
/// Prepares the class creating a Thread that monitors the main application state. ///
private void _CreateMonitorThread() { // Create the monitor thread _callbackThread = new Thread(new ThreadStart(_ThreadCallbackLoop)); _callbackThread.Name = "AutoWaitCursorCallback"; _callbackThread.IsBackground = true; // Start the thread _callbackThread.Start(); } ///
/// Thread callback method. /// Loops calling SetWaitCursor and RestoreCursor until Disposed. ///
private void _ThreadCallbackLoop() { try { do { if ( !_enabled || _mainWindowHandle == IntPtr.Zero ) { // Just sleep Thread.Sleep( _delay ); } else { // Wait for start if ( _IsApplicationBusy( _delay, _mainWindowHandle ) ) { try { this.SetWaitCursor(); _WaitForIdle(); } finally { // Always calls Finish (even if we are Disabled) this.RestoreCursor(); // Store the time the application became inactive _inactiveStart = DateTime.Now; } } else { // Wait before checking again Thread.Sleep( 25 ); } } } while (!_isDisposed && _isStarted); } catch ( ThreadAbortException ) { // The thread is being aborted, just reset the abort and exit gracefully Thread.ResetAbort(); } } ///
/// Blocks until the application responds to a test message. /// If the application doesn't respond with the timespan, will return false, /// else returns true. ///
private bool _IsApplicationBusy( TimeSpan delay, IntPtr windowHandle) { const int INFINITE = Int32.MaxValue; const int WM_NULL = 0; int result = 0; bool success; // See if the application is responding if ( delay == TimeSpan.MaxValue ) { success = SendMessageTimeout( windowHandle, WM_NULL,0, null, SMTO_BLOCK, INFINITE , out result); } else { success = SendMessageTimeout( windowHandle, WM_NULL,0, null, SMTO_BLOCK, System.Convert.ToInt32( delay.TotalMilliseconds ) , out result); } if ( result != 0 ) { return true; } return false; } ///
/// Waits for the ResetEvent (set by Dispose and Reset), /// since Start has been called we *have* to call RestoreCursor once the thread is idle again. ///
private void _WaitForIdle() { // Wait indefinately until the application is idle _IsApplicationBusy( TimeSpan.MaxValue, _mainWindowHandle ); } ///
/// The application is closing, shut the state monitor down. ///
///
///
private void _OnApplicationThreadExit(object sender, EventArgs e) { this.Dispose(); } #endregion } #endregion } }
Terms and Conditions
Support this site
Download a trial version of the best FTP application on the internet