Changer le curseur dans WPF fonctionne parfois, parfois non


123

Sur plusieurs de mes contrôles utilisateur, je change le curseur en utilisant

this.Cursor = Cursors.Wait;

quand je clique sur quelque chose.

Maintenant, je veux faire la même chose sur une page WPF en cliquant sur un bouton. Lorsque je survole mon bouton, le curseur se transforme en main, mais lorsque je clique dessus, il ne se transforme pas en curseur d'attente. Je me demande si cela a quelque chose à voir avec le fait que c'est un bouton, ou parce que c'est une page et non un contrôle utilisateur? Cela semble être un comportement étrange.

Réponses:


211

Avez-vous besoin que le curseur soit un curseur «d'attente» uniquement lorsqu'il se trouve sur cette page / usercontrol en particulier? Sinon, je suggérerais d'utiliser Mouse.OverrideCursor :

Mouse.OverrideCursor = Cursors.Wait;
try
{
    // do stuff
}
finally
{
    Mouse.OverrideCursor = null;
}

Cela remplace le curseur de votre application plutôt que simplement pour une partie de son interface utilisateur, de sorte que le problème que vous décrivez disparaît.


Similaire à ma propre réponse , datée de 3 ans plus tard (presque exactement!). J'aime les réponses à cette question, mais la plus simple est toujours la plus tentante :)
Robin Maben

Cette solution changera le curseur en un curseur "d'attente" mais ne désactivera aucune autre entrée de souris. J'ai essayé d'utiliser cette solution et bien que la souris soit devenue le curseur d'attente, je suis toujours capable de cliquer sur n'importe quel élément de l'interface utilisateur dans mon application WPF sans aucun problème. Des idées sur la façon dont je peux empêcher l'utilisateur d'utiliser réellement la souris pendant que le curseur d'attente est actif?
Thomas Huber

2
Aussi ancien qu'il soit et accepté tel quel, ce n'est PAS la bonne réponse. Le remplacement du curseur d'application est différent du remplacement d'un curseur de contrôle (et le second a des problèmes dans WPF tout droit). Le remplacement du curseur de l'application peut avoir des effets secondaires désagréables, par exemple, une boîte de message (d'erreur) qui s'affiche peut être forcée d'utiliser le même curseur remplacé par erreur alors que l'intention était uniquement de remplacer pendant que la souris survole le contrôle réel et actif.
Gábor

64

Une façon de le faire dans notre application consiste à utiliser IDisposable, puis avec des using(){}blocs pour garantir que le curseur est réinitialisé une fois terminé.

public class OverrideCursor : IDisposable
{

  public OverrideCursor(Cursor changeToCursor)
  {
    Mouse.OverrideCursor = changeToCursor;
  }

  #region IDisposable Members

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

  #endregion
}

puis dans votre code:

using (OverrideCursor cursor = new OverrideCursor(Cursors.Wait))
{
  // Do work...
}

Le remplacement prendra fin lorsque: la fin de l'instruction using est atteinte ou; si une exception est levée et que le contrôle quitte le bloc d'instructions avant la fin de l'instruction.

Mettre à jour

Pour éviter que le curseur ne scintille, vous pouvez faire:

public class OverrideCursor : IDisposable
{
  static Stack<Cursor> s_Stack = new Stack<Cursor>();

  public OverrideCursor(Cursor changeToCursor)
  {
    s_Stack.Push(changeToCursor);

    if (Mouse.OverrideCursor != changeToCursor)
      Mouse.OverrideCursor = changeToCursor;
  }

  public void Dispose()
  {
    s_Stack.Pop();

    Cursor cursor = s_Stack.Count > 0 ? s_Stack.Peek() : null;

    if (cursor != Mouse.OverrideCursor)
      Mouse.OverrideCursor = cursor;
  }

}

2
Belle solution avec la partie utilisable. En fait, j'ai écrit exactement la même chose dans certains de nos projets (sans la pile, c'est-à-dire). Une chose que vous pouvez simplifier dans l'utilisation est d'écrire simplement: using (new OverrideCursor (Cursors.Wait)) {// do stuff} au lieu de lui assigner une variable que vous n'utiliserez probablement pas.
Olli

1
Pas besoin. Si vous définissez Mouse.OverrideCursorà nullil est hors service et aucun remplacement plus le curseur du système. SI je modifiais directement le curseur actuel (c'est-à-dire sans écraser), il pourrait y avoir un problème.
Dennis

2
C'est bien, mais ce n'est pas sûr si plusieurs vues mettent à jour le curseur en même temps. Facile à entrer dans une condition de concurrence où le curseur de ViewA set, puis ViewB définit un différent, puis ViewA essaie de réinitialiser son curseur (qui fait alors sortir ViewB de la pile et laisse le curseur de ViewA actif). Ce n'est que lorsque ViewB réinitialise son curseur que les choses reviennent à la normale.
Simon Gillbee

2
@SimonGillbee c'est en effet possible - ce n'était pas un problème que j'avais eu il y a 10 ans quand j'ai écrit ceci. si vous trouvez une solution, peut-être en utilisant un ConcurrentStack<Cursor>, n'hésitez pas à modifier la réponse ci-dessus ou à ajouter la vôtre.
Dennis

2
@Dennis J'ai en fait écrit ceci il y a quelques jours (c'est pourquoi je cherchais à travers SO). J'ai joué avec ConcurrentStack, mais il s'est avéré que ce n'était pas la bonne collection. Stack vous permet uniquement de sortir du haut. Dans ce cas, vous souhaitez supprimer du milieu de la pile si ce curseur est supprimé avant que le haut de la pile ne soit supprimé. J'ai fini par utiliser simplement List <T> avec ReaderWriterLockSlim pour gérer l'accès simultané.
Simon Gillbee

38

Vous pouvez utiliser un déclencheur de données (avec un modèle de vue) sur le bouton pour activer un curseur d'attente.

<Button x:Name="NextButton"
        Content="Go"
        Command="{Binding GoCommand }">
    <Button.Style>
         <Style TargetType="{x:Type Button}">
             <Setter Property="Cursor" Value="Arrow"/>
             <Style.Triggers>
                 <DataTrigger Binding="{Binding Path=IsWorking}" Value="True">
                     <Setter Property="Cursor" Value="Wait"/>
                 </DataTrigger>
             </Style.Triggers>
         </Style>
    </Button.Style>
</Button>

Voici le code du modèle de vue:

public class MainViewModel : ViewModelBase
{
   // most code removed for this example

   public MainViewModel()
   {
      GoCommand = new DelegateCommand<object>(OnGoCommand, CanGoCommand);
   }

   // flag used by data binding trigger
   private bool _isWorking = false;
   public bool IsWorking
   {
      get { return _isWorking; }
      set
      {
         _isWorking = value;
         OnPropertyChanged("IsWorking");
      }
   }

   // button click event gets processed here
   public ICommand GoCommand { get; private set; }
   private void OnGoCommand(object obj)
   {
      if ( _selectedCustomer != null )
      {
         // wait cursor ON
         IsWorking = true;
         _ds = OrdersManager.LoadToDataSet(_selectedCustomer.ID);
         OnPropertyChanged("GridData");

         // wait cursor off
         IsWorking = false;
      }
   }
}

4
Je ne reçois pas non plus le vote négatif. Cette réponse est utile lorsque vous utilisez MVvM (donc pas de code-behind) et que vous souhaitez contrôler le curseur pour un contrôle spécifique. Très utile.
Simon Gillbee

4
J'exploite les avantages de MVVM et c'est la réponse parfaite.
g1ga

J'aime cette solution car je pense qu'elle fonctionnera mieux avec MVVM, viewmodels, etc.
Rod

Le problème que je vois avec ce code est que les curseurs sont "Attendre" uniquement lorsque la souris survole le bouton, mais lorsque vous déplacez la souris, il redevient une "Flèche".
spiderman

7

Si votre application utilise des éléments asynchrones et que vous manipulez le curseur de la souris, vous voudrez probablement le faire uniquement dans le thread d'interface utilisateur principal. Vous pouvez utiliser le thread Dispatcher de l'application pour cela:

Application.Current.Dispatcher.Invoke(() =>
{
    // The check is required to prevent cursor flickering
    if (Mouse.OverrideCursor != cursor)
        Mouse.OverrideCursor = cursor;
});

0

Ce qui suit a fonctionné pour moi:

ForceCursor = true;
Cursor = Cursors.Wait;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.