2009-10-14 12 views
195

Ho un'applicazione C# a cui gli utenti hanno accesso e perché l'algoritmo hashing è costoso, ci vuole un po 'di tempo per farlo. Come posso visualizzare il cursore di attesa/occupato (di solito la clessidra) all'utente per far sapere loro che il programma sta facendo qualcosa?Come posso posizionare il cursore sul cursore di attesa?

Il progetto è in C#.

risposta

334

È possibile utilizzare 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; 

Tuttavia, se l'operazione di hashing è davvero lunga (MSDN definisce questo come più di 2-7 secondi), probabilmente si dovrebbe utilizzare un indicatore di feedback visivo diverso il cursore per informare l'utente dello stato di avanzamento . Per un set di linee guida più approfondito, vedi this article.

Edit:
Come @Am sottolineato, potrebbe essere necessario chiamare Application.DoEvents(); dopo Cursor.Current = Cursors.WaitCursor; per garantire che la clessidra è effettivamente visualizzato.

+20

non è necessario modificare il cursore, se il ciclo del messaggio non verrà chiamato durante il codice che richiede molto tempo. per abilitarlo è necessario aggiungere _Application.DoEvents(); _ dopo il primo set di cursori. – Amirshk

+13

Probabilmente vorrai provare..finalmente bloccare dopo aver impostato anche Current (assicurandoti che Current venga ripristinato al valore predefinito). – TrueWill

+6

FYI, non ho potuto ottenere quanto sopra per funzionare, ma cambiandolo in this.cursor = cursors.waitcursor; ha funzionato. –

16

Il mio approccio sarebbe quello di fare tutti i calcoli in un lavoratore in background.

quindi modificare il cursore in questo modo:

this.Cursor = Cursors.Wait; 

E nel caso in finitura del thread ripristinare il cursore:

this.Cursor = Cursors.Default; 

Nota, questo può essere fatto anche per i controlli specifici, in modo che il cursore essere la clessidra solo quando il mouse è sopra di loro.

+0

Sono fatti in un thread in background ... – Malfist

+0

@Malfist: buon approccio :), quindi tutto ciò che devi fare è posizionare il ripristino nell'evento finale e il tuo fatto. – Amirshk

132

In realtà,

Cursor.Current = Cursors.WaitCursor; 

temporaneamente imposta il cursore di attesa, ma non garantisce che il cursore di attesa mostra fino alla fine della vostra attività. Altri programmi o controlli all'interno del programma possono facilmente riportare il cursore sulla freccia predefinita, come in effetti accade quando si sposta il mouse mentre l'operazione è ancora in esecuzione.

Un modo migliore per mostrare il cursore di attesa è quello di impostare la proprietà UseWaitCursor in una forma di vero:

form.UseWaitCursor = true; 

Questo mostrerà l'ora del cursore per tutti i controlli sul modulo finché non si imposta questa proprietà su false . Se si vuole attendere il cursore da visualizzare sul livello di applicazione si dovrebbe usare:

Application.UseWaitCursor = true; 
+0

Buono a sapersi. Stavo cercando di fare lo stesso in WPF e ho finito con * Cursor = Cursors.Wait * e * Cursor = Cursors.Arrow *. Ma non sono riuscito a trovare il Cursore sotto * App * – itsho

+1

Impossibile trovare UseWaitCursor in Applicazione! –

+7

Funziona molto meglio della soluzione accettata! – JPProgrammer

20

E 'più facile da usare UseWaitCursor a livello di modulo o finestra. Un tipico caso d'uso può apparire come di seguito:

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

Per una migliore esperienza utente è necessario utilizzare Asynchrony da un thread diverso.

+0

Questa dovrebbe essere la risposta accettata. È l'unico che usa try-finally. –

+0

avere il mio upvote, mi mancava un try-finally nella mia implementazione – Jack

24

Basandosi sul precedente, il mio approccio preferito (poiché si tratta di un'azione eseguita di frequente) consiste nel disporre il codice del cursore di attesa in una classe helper IDisposable in modo che possa essere utilizzato con using() (una riga di codice), prendere parametri opzionali, eseguire il codice all'interno, quindi pulire (ripristinare il cursore) in seguito.

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

Usage:

using (new CursorWait()) 
{ 
    // Perform some code that shows cursor 
} 
+0

Ispirato da: http://www.codeproject.com/Articles/6287/WaitCursor-hack-using-using – torno

+0

Non l'avevo visto, ma sì simile approccio. Esegue il backup del cursore corrente e lo ripristina, il che può essere utile se si sta eseguendo un cambio di cursore pesante. – mhapps

3

OK così ho creato un metodo asincrono statico. Che disabilita il controllo che avvia l'azione e modifica il cursore dell'applicazione. Esegue l'azione come compito e attende di finire. Il controllo ritorna al chiamante mentre attende. Quindi l'applicazione rimane reattiva, anche mentre l'icona occupata ruota.

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

Ecco il modulo di codice del modulo principale

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 
     { 
     } 
    } 

ho dovuto usare un registratore separato per l'azione fittizio (sto usando nlog) e il mio logger principale sta scrivendo l'interfaccia utente (una ricca casella di testo). Non è stato possibile visualizzare lo stato occupato del cursore solo quando si trovava su un determinato contenitore del modulo (ma non ci ho provato molto duramente.) Tutti i controlli hanno una proprietà UseWaitCursor, ma non sembra avere alcun effetto sui controlli ho provato (forse perché non erano in cima?)

Ecco il registro principale, che mostra cose che accadono in ordine ci aspettiamo:

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 classe di seguito si può fare il suggerimento di Donut "Eccezionale".

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

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

Okey, vista di altre persone sono molto chiari, ma vorrei fare qualche aggiunta, come segue:

Cursor tempCursor = Cursor.Current; 

Cursor.Current = Cursors.WaitCursor; 

//do Time-consuming Operations   

Cursor.Current = tempCursor; 
0

Utilizzare questo con WPF :

Cursor = Cursors.Wait; 

// Your Heavy work here 

Cursor = Cursors.Arrow; 
Problemi correlati