¿Cómo puedo hacer que el cursor gire hacia el cursor de espera?

263

Tengo una aplicación C # que tiene usuarios que inician sesión, y debido a que el algoritmo de hash es costoso, lleva un poco de tiempo hacerlo. ¿Cómo puedo mostrar el cursor de espera / ocupado (generalmente el reloj de arena) al usuario para informarles que el programa está haciendo algo?

El proyecto está en C #.

Malfist
fuente

Respuestas:

451

Puedes usar Cursor.Current.

// Set cursor as hourglass
Cursor.Current = Cursors.WaitCursor;

// Execute your time-intensive hashing code here...

// Set cursor as default arrow
Cursor.Current = Cursors.Default;

Sin embargo, si la operación de hash es realmente larga (MSDN define esto como más de 2-7 segundos), probablemente debería usar un indicador de retroalimentación visual que no sea el cursor para notificar al usuario sobre el progreso. Para un conjunto más profundo de pautas, vea este artículo .

Editar:
como señaló @Am, es posible que deba llamar Application.DoEvents();después Cursor.Current = Cursors.WaitCursor;para asegurarse de que realmente se muestre el reloj de arena.

Rosquilla
fuente
23
esto no necesariamente cambiará el cursor, si no se llamará al bucle de mensajes durante el código que requiere mucho tiempo. para habilitarlo necesita agregar Application.DoEvents (); después del primer conjunto de cursores.
Amirshk el
16
Probablemente desee probar ... finalmente bloquear después de configurar Current también (asegurando que Current se restablezca a Default).
TrueWill
77
Para su información, no pude hacer que lo anterior funcione, pero al cambiarlo a this.cursor = cursors.waitcursor; funcionó.
Hans Rudel
44
El reloj de arena no se muestra si utilizo Application.DoEvents () después de Cursor.Current = Cursors.WaitCursor Sin embargo, funcionó sin Application.DoEvents (). No estoy seguro de por qué
Vbp
14
Es mejor usar Application.UseWaitCursor = trueyApplication.UseWaitCursor = false
Gianpiero
169

Realmente,

Cursor.Current = Cursors.WaitCursor;

establece temporalmente el cursor de espera, pero no garantiza que se muestre hasta el final de su operación. Otros programas o controles dentro de su programa pueden restablecer fácilmente el cursor a la flecha predeterminada, como sucede de hecho cuando mueve el mouse mientras la operación aún se está ejecutando.

Una forma mucho mejor de mostrar el cursor de espera es establecer la propiedad UseWaitCursor en un formulario en verdadero:

form.UseWaitCursor = true;

Esto mostrará el cursor de espera para todos los controles en el formulario hasta que establezca esta propiedad en falso. Si desea que se muestre el cursor de espera en el nivel de Aplicación, debe usar:

Application.UseWaitCursor = true;
draganstankovic
fuente
Bueno saber. Estaba tratando de hacer lo mismo en WPF, y terminé con Cursor = Cursors.Wait y Cursor = Cursors.Arrow . Pero no pude encontrar el cursor en la aplicación
itsho
2
No se pudo encontrar UseWaitCursor en la aplicación.
Chandra Eskay
Descubrí que, al configurar form.UseWaitCursor = false al final de la operación, en realidad no restablece el cursor hasta que se mueve o hace clic con el mouse. OTOH, form. El cursor no tiene este problema. No pude conseguir Cursor. Corriente para trabajar en absoluto.
Stewart
39

Partiendo de lo anterior, mi enfoque preferido (ya que esta es una acción que se realiza con frecuencia) es envolver el código del cursor de espera en una clase auxiliar IDisposable para que pueda usarse con el uso () (una línea de código), tomar parámetros opcionales, ejecutar el código dentro, luego limpiar (restaurar el cursor) después.

public class CursorWait : IDisposable
{
    public CursorWait(bool appStarting = false, bool applicationCursor = false)
    {
        // Wait
        Cursor.Current = appStarting ? Cursors.AppStarting : Cursors.WaitCursor;
        if (applicationCursor) Application.UseWaitCursor = true;
    }

    public void Dispose()
    {
        // Reset
        Cursor.Current = Cursors.Default;
        Application.UseWaitCursor = false;
    }
}

Uso:

using (new CursorWait())
{
    // Perform some code that shows cursor
}
mhapps
fuente
No lo había visto, pero sí un enfoque similar. Realiza una copia de seguridad del cursor actual y luego lo restaura, lo que puede ser útil si está haciendo un cambio pesado del cursor.
mhapps
29

Es más fácil usar UseWaitCursor a nivel de formulario o ventana. Un caso de uso típico puede verse a continuación:

    private void button1_Click(object sender, EventArgs e)
    {

        try
        {
            this.Enabled = false;//optional, better target a panel or specific controls
            this.UseWaitCursor = true;//from the Form/Window instance
            Application.DoEvents();//messages pumped to update controls
            //execute a lengthy blocking operation here, 
            //bla bla ....
        }
        finally
        {
            this.Enabled = true;//optional
            this.UseWaitCursor = false;
        }
    }

Para una mejor experiencia de UI, debe usar Asynchrony desde un hilo diferente.

dmihailescu
fuente
2
Esta debería ser la respuesta ACEPTADA. Es el único que usa try-finally.
John Henckel
1
tengo mi voto a favor, me estaba perdiendo un intento por fin en mi implementación
Jack
19

Mi enfoque sería hacer todos los cálculos en un trabajador de fondo.

Luego cambia el cursor así:

this.Cursor = Cursors.Wait;

Y en el evento de finalización del hilo, restaure el cursor:

this.Cursor = Cursors.Default;

Tenga en cuenta que esto también se puede hacer para controles específicos, por lo que el cursor será el reloj de arena solo cuando el mouse esté sobre ellos.

Amirshk
fuente
@Malfist: buen enfoque :), entonces todo lo que necesita hacer es colocar la restauración al final del evento, y listo.
Amirshk
4

OK, así que creé un método asíncrono estático. Eso deshabilitó el control que inicia la acción y cambia el cursor de la aplicación. Ejecuta la acción como una tarea y espera a que termine. El control vuelve a la persona que llama mientras espera. Por lo tanto, la aplicación sigue respondiendo, incluso mientras el ícono ocupado gira.

async public static void LengthyOperation(Control control, Action action)
{
    try
    {
        control.Enabled = false;
        Application.UseWaitCursor = true;
        Task doWork = new Task(() => action(), TaskCreationOptions.LongRunning);
        Log.Info("Task Start");
        doWork.Start();
        Log.Info("Before Await");
        await doWork;
        Log.Info("After await");
    }
    finally
    {
        Log.Info("Finally");
        Application.UseWaitCursor = false;
        control.Enabled = true;
    }

Aquí está el código del formulario principal

    private void btnSleep_Click(object sender, EventArgs e)
    {
        var control = sender as Control;
        if (control != null)
        {
            Log.Info("Launching lengthy operation...");
            CursorWait.LengthyOperation(control, () => DummyAction());
            Log.Info("...Lengthy operation launched.");
        }

    }

    private void DummyAction()
    {
        try
        {
            var _log = NLog.LogManager.GetLogger("TmpLogger");
            _log.Info("Action - Sleep");
            TimeSpan sleep = new TimeSpan(0, 0, 16);
            Thread.Sleep(sleep);
            _log.Info("Action - Wakeup");
        }
        finally
        {
        }
    }

Tuve que usar un registrador separado para la acción ficticia (estoy usando Nlog) y mi registrador principal está escribiendo en la interfaz de usuario (un cuadro de texto enriquecido). No pude mostrar el cursor ocupado solo cuando estaba sobre un contenedor particular en el formulario (pero no lo intenté mucho). Todos los controles tienen una propiedad UseWaitCursor, pero no parece tener ningún efecto en los controles Lo intenté (¿tal vez porque no estaban arriba?)

Aquí está el registro principal, que muestra las cosas que suceden en el orden que esperamos:

16:51:33.1064 Launching lengthy operation...
16:51:33.1215 Task Start
16:51:33.1215 Before Await
16:51:33.1215 ...Lengthy operation launched.
16:51:49.1276 After await
16:51:49.1537 Finally
Darrel Lee
fuente
2

Con la clase a continuación puede hacer la sugerencia de Donut "excepción segura".

using (new CursorHandler())
{
    // Execute your time-intensive hashing code here...
}

la clase CursorHandler

public class CursorHandler
    : IDisposable
{
    public CursorHandler(Cursor cursor = null)
    {
        _saved = Cursor.Current;
        Cursor.Current = cursor ?? Cursors.WaitCursor;
    }

    public void Dispose()
    {
        if (_saved != null)
        {
            Cursor.Current = _saved;
            _saved = null;
        }
    }

    private Cursor _saved;
}
Georg
fuente
2

Okey, la opinión de otras personas es muy clara, pero me gustaría agregar algunas, como sigue:

Cursor tempCursor = Cursor.Current;

Cursor.Current = Cursors.WaitCursor;

//do Time-consuming Operations         

Cursor.Current = tempCursor;
Wenha
fuente
2

Para las aplicaciones de Windows Forms, una desactivación opcional de un UI-Control puede ser muy útil. Entonces mi sugerencia se ve así:

public class AppWaitCursor : IDisposable
{
    private readonly Control _eventControl;

    public AppWaitCursor(object eventSender = null)
    {
         _eventControl = eventSender as Control;
        if (_eventControl != null)
            _eventControl.Enabled = false;

        Application.UseWaitCursor = true;
        Application.DoEvents();
    }

    public void Dispose()
    {
        if (_eventControl != null)
            _eventControl.Enabled = true;

        Cursor.Current = Cursors.Default;
        Application.UseWaitCursor = false;
    }
}

Uso:

private void UiControl_Click(object sender, EventArgs e)
{
    using (new AppWaitCursor(sender))
    {
        LongRunningCall();
    }
}
HEF
fuente
1

Use esto con WPF:

Cursor = Cursors.Wait;

// Your Heavy work here

Cursor = Cursors.Arrow;
Abdulrazzaq Alzayed
fuente