96 votes

afficher Hourglass quand l'application est occupée

Pour une vue construite avec WPF, je souhaite changer le curseur de la souris en sablier lorsque l'application est occupée et ne répond pas.

Une solution consiste à ajouter

  this.Cursor = Cursors.Wait;
 

à tous les endroits où l’interface utilisateur risque de ne plus répondre. Mais évidemment ce n’est pas la meilleure solution. Je me demande quel est le meilleur moyen d'y parvenir?

Est-il possible d'y parvenir en utilisant des styles ou des ressources?

Merci,

236voto

Carlo Points 8638

Nous avons fait un cours jetable qui change le curseur pour nous quand l'application va prendre longtemps, ça ressemble à ça:

 public class WaitCursor : IDisposable
{
    private Cursor _previousCursor;

    public WaitCursor()
    {
        _previousCursor = Mouse.OverrideCursor;

        Mouse.OverrideCursor = Cursors.Wait;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Mouse.OverrideCursor = _previousCursor;
    }

    #endregion
}
 

Et nous l'utilisons comme ceci:

 using(new WaitCursor())
{
    // very long task
}
 

Ce n'est peut-être pas le plus grand dessein, mais ça ne marche pas =)

40voto

huttelihut Points 1621

J'ai utilisé les réponses ici de construire quelque chose qui fonctionne mieux pour moi. Le problème est que lors de l'utilisation de bloc de Carlo réponse finitions, l'INTERFACE utilisateur peut, en fait, toujours occupé de la liaison de données. Il pourrait être paresseux-les données chargées ou des événements de tir en tant que résultat de ce qui a été fait dans le bloc. Dans mon cas, cela prend parfois plusieurs secondes à partir de la waitcursor disparu jusqu'à ce que l'INTERFACE utilisateur a été fait prêt. Je l'ai résolu par la création d'une méthode d'assistance que définit la waitcursor et prend également soin de mettre en place un timer qui va automatiquement mettre le curseur en arrière lorsque l'INTERFACE utilisateur est prêt. Je ne peux pas être sûr que cette conception va fonctionner dans tous les cas, mais cela a fonctionné pour moi:

    /// <summary>
    ///   Contains helper methods for UI, so far just one for showing a waitcursor
    /// </summary>
    public static class UiServices
    {

    /// <summary>
    ///   A value indicating whether the UI is currently busy
    /// </summary>
    private static bool IsBusy;

    /// <summary>
    /// Sets the busystate as busy.
    /// </summary>
    public static void SetBusyState()
    {
        SetBusyState(true);
    }

    /// <summary>
    /// Sets the busystate to busy or not busy.
    /// </summary>
    /// <param name="busy">if set to <c>true</c> the application is now busy.</param>
        private static void SetBusyState(bool busy)
        {
            if (busy != IsBusy)
            {
                IsBusy = busy;
                Mouse.OverrideCursor = busy ? Cursors.Wait : null;

                if (IsBusy)
                {
                    new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, Application.Current.Dispatcher);
                }
            }
        }

        /// <summary>
        /// Handles the Tick event of the dispatcherTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private static void dispatcherTimer_Tick(object sender, EventArgs e)
        {
                var dispatcherTimer = sender as DispatcherTimer;
                if (dispatcherTimer != null)
                {
                    SetBusyState(false);
                    dispatcherTimer.Stop();
                }
        }
    }

6voto

John Gardner Points 10882

La meilleure façon serait de ne pas causer de l'INTERFACE utilisateur de répondre jamais, le déchargement tout le travail pour les autres threads/tâches appropriées.

Autre que cela, vous êtes kindof dans un catch-22: si vous n'avez ajouter un moyen de détecter que l'interface utilisateur est non-réactif, il n'y a pas de bonne façon de changer le curseur, comme l'endroit où vous auriez besoin de le faire (le même thread) est non-réactif... Vous pourriez être en mesure de pinvoke à win32 standard code pour changer le curseur de la totalité de la fenêtre, si?

Sinon, vous auriez à le faire de manière préventive, comme votre question le suggère.

5voto

Eric Ouellet Points 1370

Personnellement, je préfère ne pas voir de pointeur de la souris de commutation à plusieurs reprises à partir de sablier à la flèche. Pour prévenir l'apparition de ce comportement, tout en appelant intégré des fonctions qui prennent un certain temps et que chacun essaie de contrôler le pointeur de la souris, j'utilise une pile (compteur) que j'appelle une LifeTrackerStack. Et seulement quand la pile est vide (compteur à 0) que je fais revenir l'heure de verre d'une flèche.

J'utilise aussi MVVM. Je préfère aussi thread-safe code.

Dans ma classe racine de modèle je déclare mon LifeTrackerStack que je soit remplir dans l'enfant des classes de modèle ou utiliser directement à partir de modèle de l'enfant des classes lorsque j'ai accès à la leur.

Ma vie tracker avons 2 membres/actions:

  • Vivant (compteur > 0) => tourner le Modèle.IsBusy true;
  • Fait (compteur == 0) => tourner le Modèle.IsBusy false;

Alors de mon point de vue, j'ai manuellement lier à mon Modèle.IsBusy et à faire:

void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (e.PropertyName == "IsBusy")
    {
        if (this._modelViewAnalysis.IsBusy)
        {
            if (Application.Current.Dispatcher.CheckAccess())
            {
                this.Cursor = Cursors.Wait;
            }
            else
            {
                Application.Current.Dispatcher.Invoke(new Action(() => this.Cursor = Cursors.Wait));
            }
        }
        else
        {
            Application.Current.Dispatcher.Invoke(new Action(() => this.Cursor = null));
        }
    }

C'est ma classe LifeTrackerStack:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace HQ.Util.General
{
    /// <summary>
    /// Usage is to have only one event for a recursive call on many objects
    /// </summary>
    public class LifeTrackerStack
    {
        // ******************************************************************
        protected readonly Action _stackCreationAction;
        protected readonly Action _stackDisposeAction;
        private int _refCount = 0;
        private object _objLock = new object();
        // ******************************************************************
        public LifeTrackerStack(Action stackCreationAction = null, Action stackDisposeAction = null)
        {
            _stackCreationAction = stackCreationAction;
            _stackDisposeAction = stackDisposeAction;
        }

        // ******************************************************************
        /// <summary>
        /// Return a new LifeTracker to be used in a 'using' block in order to ensure reliability
        /// </summary>
        /// <returns></returns>
        public LifeTracker GetNewLifeTracker()
        {
            LifeTracker lifeTracker = new LifeTracker(AddRef, RemoveRef);

            return lifeTracker;
        }

        // ******************************************************************
        public int Count
        {
            get { return _refCount; }
        }

        // ******************************************************************
        public void Reset()
        {
            lock (_objLock)
            {
                _refCount = 0;
                if (_stackDisposeAction != null)
                {
                    _stackDisposeAction();
                }
            }
        }

        // ******************************************************************
        private void AddRef()
        {
            lock (_objLock)
            {
                if (_refCount == 0)
                {
                    if (_stackCreationAction != null)
                    {
                        _stackCreationAction();
                    }
                }
                _refCount++;
            }
        }

        // ******************************************************************
        private void RemoveRef()
        {
            bool shouldDispose = false;
            lock (_objLock)
            {
                if (_refCount > 0)
                {
                    _refCount--;
                }

                if (_refCount == 0)
                {
                    if (_stackDisposeAction != null)
                    {
                        _stackDisposeAction();
                    }
                }
            }
        }

        // ******************************************************************
    }
}


using System;

namespace HQ.Util.General
{
    public delegate void ActionDelegate();

    public class LifeTracker : IDisposable
    {
        private readonly ActionDelegate _actionDispose;
        public LifeTracker(ActionDelegate actionCreation, ActionDelegate actionDispose)
        {
            _actionDispose = actionDispose;

            if (actionCreation != null)
                actionCreation();
        }

        private bool _disposed = false;
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this._disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    _actionDispose();
                }

                // Note disposing has been done.
                _disposed = true;
            }
        }
    }
}

Et l'utilisation de celui-ci:

    _busyStackLifeTracker = new LifeTrackerStack
        (
            () =>
            {
                this.IsBusy = true;
            },
            () =>
            {
                this.IsBusy = false;
            }
        );

Partout, j'ai de longues jogging, je fais:

        using (this.BusyStackLifeTracker.GetNewLifeTracker())
        {
            // long job
        }

Il fonctionne pour moi. Espérons que cela pourrait les aider! Eric

4voto

AllenM Points 96

Attention ici parce que de jongler avec le Curseur d'Attente peut entraîner des problèmes avec les threads STA. Assurez-vous que si vous utilisez cette chose que vous faites au sein de son propre thread. J'ai posté un exemple ici Exécutent à l'intérieur d'un STA qui utilise cette option pour afficher un WaitCursor tandis que le engendré fichier est en cours de démarrage, et de ne pas exploser (la principale de l'application) AFAICT.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X