2009-10-14 19 views
195

Tengo una aplicación C# que hace que los usuarios inicien sesión en ella, y debido a que el algoritmo de hashing es costoso, toma un poco de tiempo hacerlo. ¿Cómo puedo mostrar el Cursor Espera/Ocupado (generalmente el reloj de arena) para que el usuario sepa que el programa está haciendo algo?¿Cómo puedo hacer que el cursor gire hacia el cursor de espera?

El proyecto está en C#.

Respuesta

334

Puede 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 como más de 2-7 segundos), probablemente debería utilizar un indicador de retroalimentación visual que no sea el cursor para notificar al usuario de los avances . Para obtener un conjunto más detallado de directrices, consulte this article.

Editar:
Como @Am señaló, puede que tenga que llamar a Application.DoEvents(); después Cursor.Current = Cursors.WaitCursor; para asegurarse de que el reloj de arena se muestra realmente.

+20

esto no necesariamente cambiará el cursor - si el bucle de mensaje no se llamará durante el código de tiempo intensivo. para habilitarlo, necesita agregar _Application.DoEvents(); _ después del primer cursor establecido. – Amirshk

+13

Probablemente desee intentarlo ... bloque final después de establecer Actual, también (asegurándose de que Current se restablezca a Predeterminado). – TrueWill

+6

FYI, no pude hacer funcionar lo anterior, pero cambiándolo a this.cursor = cursores.esperacursor; funcionó. –

16

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

a continuación, cambiar el cursor de esta manera:

this.Cursor = Cursors.Wait; 

Y en caso de acabado de la rosca restablecer el cursor:

this.Cursor = Cursors.Default; 

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

+0

Se hacen en un hilo de fondo ... – Malfist

+0

@Malfist: buen enfoque :), entonces todo lo que necesita hacer es colocar la restauración en el evento final, y listo. – Amirshk

132

En realidad,

Cursor.Current = Cursors.WaitCursor; 

temporalmente establece el cursor de espera, pero no asegura que el cursor de espera muestra hasta el final de su operación. Otros programas o controles dentro de su programa pueden restablecer fácilmente el cursor de vuelta a la flecha predeterminada, ya que de hecho ocurre cuando mueve el mouse mientras la operación todavía está ejecutándose.

Una mejor manera de mostrar el cursor de espera es para establecer la propiedad UseWaitCursor en una forma de verdad:

form.UseWaitCursor = true; 

Esto mostrará esperar cursor para todos los controles en el formulario hasta que se establece esta propiedad en false . Si desea esperar cursor que se mostrará en el nivel de aplicación se debe utilizar:

Application.UseWaitCursor = true; 
+0

Bueno saber. Intenté hacer lo mismo en WPF, y terminé con * Cursor = Cursors.Wait * y * Cursor = Cursors.Arrow *. Pero no pude encontrar el Cursor en * Aplicación * – itsho

+1

¡No pude encontrar UseWaitCursor en la aplicación! –

+7

¡Esto funciona mucho mejor que la solución aceptada! – JPProgrammer

20

Es más fácil de usar UseWaitCursor a nivel del formulario o ventana. Un caso de uso típico puede parecerse 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 interfaz de usuario que debe utilizar la asincronía de un hilo diferente.

+0

Esta debería ser la respuesta ACEPTABLE. Es el único que usa try-finally. –

+0

tengo mi voto favorable, me faltaba una oportunidad, finalmente en mi implementación – Jack

24

Sobre la base de la anterior, mi enfoque preferido (ya que esta es una acción realizada con frecuencia) es ajustar el código del cursor de espera en una clase auxiliar IDisposable para que pueda usarse con() (una línea de código), tomar parámetros opcionales, ejecute el código dentro, luego limpie (restaure 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 
} 
+0

Inspirado en: http://www.codeproject.com/Articles/6287/WaitCursor-hack-using -using – torno

+0

No lo había visto, pero sí similar enfoque. Hace 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

3

OK, así que creó un método asíncrono estática. 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 para terminar. El control vuelve a la persona que llama mientras espera. De modo que la aplicación sigue siendo receptiva, incluso mientras el ícono de 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á la forma de 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 independiente para la acción ficticia (estoy usando Nlog) y mi principal registrador escribe para la interfaz de usuario (una rica caja de texto). No pude ver el cursor ocupado solo cuando estaba sobre un contenedor en particular en el formulario (pero no lo intenté muy duro). Todos los controles tienen una propiedad UseWaitCursor, pero no parece tener ningún efecto en los controles probé (tal vez porque no estaban en la parte superior?)

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 
1

Con la clase, usted puede hacer la sugerencia de Donut "excepción segura".

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

la CursorHandler clase

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; 
} 
0

Okey, vista de otras personas son muy claras, pero me gustaría hacer algún añadido, de la siguiente manera:

Cursor tempCursor = Cursor.Current; 

Cursor.Current = Cursors.WaitCursor; 

//do Time-consuming Operations   

Cursor.Current = tempCursor; 
0

usar esto con WPF :

Cursor = Cursors.Wait; 

// Your Heavy work here 

Cursor = Cursors.Arrow; 
Cuestiones relacionadas