La modifica del cursore in WPF a volte funziona, a volte no


123

Su molti dei miei controlli utente, cambio il cursore usando

this.Cursor = Cursors.Wait;

quando clicco su qualcosa.

Ora voglio fare la stessa cosa su una pagina WPF facendo clic su un pulsante. Quando passo il mouse sul mio pulsante, il cursore si trasforma in una mano, ma quando lo faccio clic, non si trasforma nel cursore di attesa. Mi chiedo se questo ha qualcosa a che fare con il fatto che è un pulsante o perché questa è una pagina e non un controllo utente? Sembra un comportamento strano.

Risposte:


211

Hai bisogno che il cursore sia un cursore di "attesa" solo quando si trova su quella particolare pagina / controllo utente? In caso contrario, suggerirei di utilizzare Mouse.OverrideCursor :

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

Questo sovrascrive il cursore per la tua applicazione piuttosto che solo per una parte della sua interfaccia utente, quindi il problema che stai descrivendo scompare.


Simile alla mia risposta , datata 3 anni dopo (quasi esattamente!). Mi piacciono le risposte a questa domanda, ma la più semplice è sempre la più allettante :)
Robin Maben

Questa soluzione cambierà il cursore in un cursore di "attesa" ma non disabiliterà ulteriori input del mouse. Ho provato a utilizzare questa soluzione e sebbene il mouse sia cambiato nel cursore di attesa, sono comunque in grado di fare clic su qualsiasi elemento dell'interfaccia utente all'interno della mia applicazione WPF senza alcun problema. Qualche idea su come impedire all'utente di utilizzare effettivamente il mouse mentre il cursore di attesa è attivo?
Thomas Huber

2
Vecchio com'è e accettato com'è, NON è la risposta corretta. L'override del cursore dell'app è diverso dall'override di un cursore di controllo (e il secondo ha problemi in WPF, va bene). L'override del cursore dell'app può avere effetti collaterali spiacevoli, ad esempio, una finestra di messaggio (di errore) che si apre potrebbe essere costretta a utilizzare lo stesso cursore sovrascritto erroneamente mentre l'intenzione era solo di eseguire l'override mentre il mouse passa sopra il controllo effettivo e attivo.
Gábor

64

Un modo in cui lo facciamo nella nostra applicazione è usare IDisposable e poi con i using(){}blocchi per assicurarci che il cursore venga resettato al termine.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

e poi nel tuo codice:

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

L'override terminerà quando: viene raggiunta la fine dell'istruzione using oppure; se viene generata un'eccezione e il controllo lascia il blocco dell'istruzione prima della fine dell'istruzione.

Aggiornare

Per evitare lo sfarfallio del cursore puoi fare:

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
Bella soluzione con la parte utilizzando. In realtà ho scritto esattamente lo stesso in alcuni dei nostri progetti (senza lo stack, cioè). Una cosa che puoi semplificare nell'utilizzo è semplicemente scrivere: usando (new OverrideCursor (Cursors.Wait)) {// do stuff} invece di assegnargli una variabile che probabilmente non userai.
Olli

1
Non necessario. Se impostato Mouse.OverrideCursorsu, nullnon viene impostato e non sovrascrive più il cursore di sistema. SE stavo modificando direttamente il cursore corrente (cioè senza sovrascrivere), potrebbe esserci un problema.
Dennis

2
Questo è carino, ma non è sicuro se più visualizzazioni stanno aggiornando il cursore contemporaneamente. È facile entrare in una condizione di competizione in cui ViewA imposta il cursore, quindi ViewB ne imposta uno diverso, quindi ViewA tenta di reimpostare il suo cursore (che quindi fa uscire ViewB dallo stack e lascia il cursore di ViewA attivo). Finché ViewB non reimposta il cursore, le cose tornano alla normalità.
Simon Gillbee

2
@ SimonGillbee è davvero possibile - non è stato un problema che ho avuto 10 anni fa quando ho scritto questo. se trovi una soluzione, magari usando a ConcurrentStack<Cursor>, sentiti libero di modificare la risposta sopra o aggiungerne una tua.
Dennis

2
@ Dennis in realtà l'ho scritto pochi giorni fa (motivo per cui stavo guardando SO). Ho giocato con ConcurrentStack, ma si è rivelato essere la raccolta sbagliata. Stack ti consente solo di saltare fuori dalla parte superiore. In questo caso si desidera rimuovere dal centro dello stack se il cursore viene eliminato prima che venga eliminata la parte superiore dello stack. Ho finito per usare List <T> con ReaderWriterLockSlim per governare l'accesso simultaneo.
Simon Gillbee,

38

È possibile utilizzare un trigger di dati (con un modello di visualizzazione) sul pulsante per abilitare un cursore di attesa.

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

Ecco il codice dal view-model:

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
Nemmeno io ricevo il voto negativo. Questa risposta è utile quando si utilizza MVvM (quindi nessun code-behind) e si desidera controllare il cursore per un controllo specifico. Molto utile.
Simon Gillbee

4
Sto sfruttando i vantaggi di MVVM e questa è la risposta perfetta.
g1ga

Mi piace questa soluzione perché credo che funzionerà meglio con MVVM, viewmodels, ecc.
Rod

Il problema che vedo con questo codice è che il cursore è "Attendi" solo mentre il mouse passa sopra il pulsante, ma quando sposti il ​​mouse fuori torna ad essere una "Freccia".
Spiderman

7

Se la tua applicazione utilizza elementi asincroni e stai giocherellando con il cursore del mouse, probabilmente vorrai farlo solo nel thread dell'interfaccia utente principale. Puoi utilizzare il thread Dispatcher dell'app per questo:

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

0

Quanto segue ha funzionato per me:

ForceCursor = true;
Cursor = Cursors.Wait;
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.