Comment puis-je faire tourner le curseur sur le curseur d'attente?

263

J'ai une application C # à laquelle les utilisateurs se connectent, et comme l'algorithme de hachage est cher, cela prend un peu de temps. Comment puis-je afficher le curseur Attente / Occupé (généralement le sablier) à l'utilisateur pour lui faire savoir que le programme fait quelque chose?

Le projet est en C #.

Malfist
la source

Réponses:

451

Vous pouvez utiliser 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;

Cependant, si l'opération de hachage est vraiment longue (MSDN définit cela comme plus de 2 à 7 secondes), vous devez probablement utiliser un indicateur de retour visuel autre que le curseur pour informer l'utilisateur de la progression. Pour un ensemble de directives plus approfondies, consultez cet article .

Modifier:
Comme l'a souligné @Am, vous devrez peut-être appeler Application.DoEvents();après Cursor.Current = Cursors.WaitCursor;pour vous assurer que le sablier est bien affiché.

Donut
la source
23
cela ne changera pas nécessairement le curseur - si la boucle de message ne sera pas appelée pendant le code chronophage. pour l'activer, vous devez ajouter Application.DoEvents (); après le premier jeu de curseurs.
Amirshk
16
Vous voudrez probablement un bloc try..finally après avoir également défini Current (en vous assurant que Current est réinitialisé sur Default).
TrueWill
7
Pour info, je ne pouvais pas faire fonctionner ce qui précède, mais en le changeant en this.cursor = cursors.waitcursor; ça a marché.
Hans Rudel
4
Le sablier ne s'affichait pas si j'utilisais Application.DoEvents () après Cursor.Current = Cursors.WaitCursor Cependant, cela fonctionnait sans Application.DoEvents (). Je ne sais pas pourquoi
Vbp
14
Il vaut mieux utiliser Application.UseWaitCursor = trueetApplication.UseWaitCursor = false
Gianpiero
169

Réellement,

Cursor.Current = Cursors.WaitCursor;

définit temporairement le curseur d'attente, mais ne garantit pas qu'il s'affiche jusqu'à la fin de votre opération. D'autres programmes ou contrôles au sein de votre programme peuvent facilement réinitialiser le curseur sur la flèche par défaut, comme cela se produit en fait lorsque vous déplacez la souris alors que l'opération est toujours en cours d'exécution.

Une bien meilleure façon d'afficher le curseur Wait est de définir la propriété UseWaitCursor dans un formulaire sur true:

form.UseWaitCursor = true;

Cela affichera le curseur d'attente pour tous les contrôles du formulaire jusqu'à ce que vous définissiez cette propriété sur false. Si vous souhaitez que le curseur d'attente s'affiche au niveau de l'application, vous devez utiliser:

Application.UseWaitCursor = true;
draganstankovic
la source
Bon à savoir. J'essayais de faire la même chose dans WPF, et je me suis retrouvé avec Cursor = Cursors.Wait et Cursor = Cursors.Arrow . Mais je n'ai pas trouvé le curseur sous App
itsho
2
Impossible de trouver UseWaitCursor sous Application!
Chandra Eskay, le
J'ai trouvé que, lors de la définition de form.UseWaitCursor = false à la fin de l'opération, il ne réinitialise pas le curseur tant que vous ne déplacez pas ou ne cliquez pas sur la souris. OTOH, form.Cursor n'a pas ce problème. Je n'ai pas réussi à faire en sorte que Cursor.Current fonctionne.
Stewart
39

En s'appuyant sur la précédente, mon approche préférée (car il s'agit d'une action fréquemment effectuée) consiste à encapsuler le code du curseur d'attente dans une classe d'aide IDisposable afin qu'il puisse être utilisé avec using () (une ligne de code), prendre des paramètres facultatifs, exécuter le code à l'intérieur, puis nettoyer (restaurer le curseur) aprè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;
    }
}

Usage:

using (new CursorWait())
{
    // Perform some code that shows cursor
}
mhapps
la source
Je n'avais pas vu ça, mais oui approche similaire. Il sauvegarde le curseur actuel puis le restaure, ce qui peut être utile si vous effectuez un changement de curseur important.
mhapps
29

Il est plus facile d'utiliser UseWaitCursor au niveau du formulaire ou de la fenêtre. Un cas d'utilisation typique peut ressembler à ci-dessous:

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

Pour une meilleure expérience de l'interface utilisateur, vous devez utiliser Asynchrony à partir d'un thread différent.

dmihailescu
la source
2
Cela devrait être la réponse ACCEPTÉE. C'est le seul qui utilise try-finally.
John Henckel
1
avoir mon vote positif, il me manquait un essai-enfin dans ma mise en œuvre
Jack
19

Mon approche serait de faire tous les calculs dans un travailleur en arrière-plan.

Puis changez le curseur comme ceci:

this.Cursor = Cursors.Wait;

Et dans l'événement de fin du thread, restaurez le curseur:

this.Cursor = Cursors.Default;

Notez que cela peut également être fait pour des contrôles spécifiques, de sorte que le curseur sera le sablier uniquement lorsque la souris est au-dessus d'eux.

Amirshk
la source
@Malfist: bonne approche :), alors tout ce que vous avez à faire est de placer la restauration dans l'événement final, et c'est fait.
Amirshk
4

OK, j'ai donc créé une méthode asynchrone statique. Cela a désactivé le contrôle qui lance l'action et modifie le curseur d'application. Il exécute l'action en tant que tâche et attend la fin. Le contrôle revient à l'appelant pendant qu'il attend. Ainsi, l'application reste réactive, même lorsque l'icône occupée tourne.

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

Voici le code du formulaire 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
        {
        }
    }

J'ai dû utiliser un enregistreur distinct pour l'action factice (j'utilise Nlog) et mon enregistreur principal écrit dans l'interface utilisateur (une zone de texte riche). Je n'ai pas pu obtenir le curseur occupé uniquement sur un conteneur particulier du formulaire (mais je n'ai pas essayé très fort.) Tous les contrôles ont une propriété UseWaitCursor, mais cela ne semble pas avoir d'effet sur les contrôles J'ai essayé (peut-être parce qu'ils n'étaient pas au top?)

Voici le journal principal, qui montre les choses qui se passent dans l'ordre que nous attendons:

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
la source
2

Avec la classe ci-dessous, vous pouvez faire de la suggestion de Donut une "exception sûre".

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;
}
Georg
la source
2

Okey, le point de vue des autres personnes est très clair, mais je voudrais en ajouter quelques-unes, comme suit:

Cursor tempCursor = Cursor.Current;

Cursor.Current = Cursors.WaitCursor;

//do Time-consuming Operations         

Cursor.Current = tempCursor;
wenha
la source
2

Pour les applications Windows Forms, une désactivation facultative d'un contrôle d'interface utilisateur peut être très utile. Donc, ma suggestion ressemble à ceci:

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

Usage:

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

Utilisez ceci avec WPF:

Cursor = Cursors.Wait;

// Your Heavy work here

Cursor = Cursors.Arrow;
Abdulrazzaq Alzayed
la source