Come posso far girare il cursore sul cursore di attesa?


263

Ho un'applicazione C # che consente agli utenti di accedervi e, poiché l'algoritmo di hashing è costoso, ci vuole un po 'di tempo per farlo. Come posso visualizzare il cursore di attesa / occupato (di solito la clessidra) per far sapere loro che il programma sta facendo qualcosa?

Il progetto è in C #.

Risposte:


451

È 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 è veramente lunga (MSDN lo definisce come più di 2-7 secondi), probabilmente dovresti usare un indicatore di feedback visivo diverso dal cursore per avvisare l'utente dell'avanzamento. Per una serie più approfondita di linee guida, consulta questo articolo .

Modifica:
come sottolineato da @Am, potrebbe essere necessario chiamare Application.DoEvents();dopo Cursor.Current = Cursors.WaitCursor;per assicurarsi che la clessidra sia effettivamente visualizzata.


23
questo non cambierà necessariamente 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 cursore impostato.
Amirshk,

16
Probabilmente vuoi provare anche tu ... definitivamente dopo aver impostato Current (assicurando che Current venga ripristinato su Default).
TrueWill,

7
Cordiali saluti, non sono riuscito a far funzionare quanto sopra, ma cambiandolo in this.cursor = cursors.waitcursor; ha funzionato.
Hans Rudel,

4
La clessidra non veniva visualizzata se utilizzavo Application.DoEvents () dopo Cursor.Current = Cursors.WaitCursor Tuttavia, funzionava senza Application.DoEvents (). Non so perché
Vbp

14
È meglio usare Application.UseWaitCursor = trueeApplication.UseWaitCursor = false
Gianpiero

169

In realtà,

Cursor.Current = Cursors.WaitCursor;

imposta temporaneamente il cursore di attesa, ma non garantisce che il cursore di attesa venga visualizzato fino alla fine dell'operazione. Altri programmi o controlli all'interno del tuo programma possono facilmente ripristinare il cursore sulla freccia predefinita, come in effetti accade quando si sposta il mouse mentre l'operazione è ancora in esecuzione.

Un modo molto migliore per mostrare il cursore Wait è impostare la proprietà UseWaitCursor in un modulo su true:

form.UseWaitCursor = true;

Ciò visualizzerà il cursore di attesa per tutti i controlli nel modulo fino a quando non si imposta questa proprietà su false. Se si desidera visualizzare il cursore di attesa a livello di applicazione, è necessario utilizzare:

Application.UseWaitCursor = true;

Buono a sapersi. Stavo provando a fare lo stesso in WPF, e ho finito con Cursor = Cursors.Wait e Cursor = Cursors.Arrow . Ma non riuscivo a trovare il cursore sotto App
itsho

2
Impossibile trovare UseWaitCursor in Applicazione!
Chandra Eskay,

Ho scoperto che, quando si imposta form.UseWaitCursor = false alla fine dell'operazione, in realtà non reimposta il cursore finché non si sposta o si fa clic con il mouse. OTOH, form. Il cursore non ha questo problema. Non riuscivo a far funzionare Cursor.
Stewart,

39

Sulla base del precedente, il mio approccio preferito (poiché si tratta di un'azione eseguita di frequente) è di avvolgere il codice del cursore di attesa in una classe helper IDisposable in modo che possa essere utilizzato con using () (una riga di codice), accetta parametri opzionali, esegui il codice all'interno, quindi ripulisci (ripristina 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;
    }
}

Uso:

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


Non l'avevo visto, ma sì un approccio simile. Esegue il backup del cursore corrente, quindi lo ripristina, il che può essere utile se si sta effettuando una modifica pesante del cursore.
mhapps,

29

È più semplice utilizzare UseWaitCursor a livello di modulo o finestra. Un caso d'uso tipico 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 dell'interfaccia utente, è necessario utilizzare Asynchrony da un thread diverso.


2
Questa dovrebbe essere la risposta ACCETTATA. È l'unico che utilizza try-finally.
John Henckel,

1
ho il mio voto, mi mancava un tentativo-finalmente nella mia implementazione
Jack

19

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

Quindi cambia il cursore in questo modo:

this.Cursor = Cursors.Wait;

E nell'evento di fine del thread ripristinare il cursore:

this.Cursor = Cursors.Default;

Nota, questo può essere fatto anche per controlli specifici, quindi il cursore sarà la clessidra solo quando il mouse è sopra di essi.


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

4

OK, quindi ho creato un metodo asincrono statico. Ciò ha disabilitato il controllo che avvia l'azione e modifica il cursore dell'applicazione. Esegue l'azione come un'attività e attende che finisca. Il controllo ritorna al chiamante mentre attende. Quindi l'applicazione rimane reattiva, anche mentre l'icona di occupato 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;
    }

Ecco il codice dal 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 logger separato per l'azione fittizia (sto usando Nlog) e il mio logger principale sta scrivendo nell'interfaccia utente (una casella di testo ricca). Non sono stato in grado di visualizzare il cursore di occupato solo quando si trovava su un particolare contenitore nel modulo (ma non ci ho provato molto.) 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 le cose che accadono nell'ordine che 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

2

Con la classe qui sotto puoi rendere il suggerimento di Donut "eccezione sicura".

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

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

2

Okey, la vista di altre persone è molto chiara, ma vorrei fare alcune aggiunte, come segue:

Cursor tempCursor = Cursor.Current;

Cursor.Current = Cursors.WaitCursor;

//do Time-consuming Operations         

Cursor.Current = tempCursor;

2

Per le applicazioni Windows Form può essere molto utile disabilitare facoltativamente un UI-Control. Quindi il mio suggerimento è simile al seguente:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.