Cambiar el cursor en WPF a veces funciona, a veces no


123

En varios de mis controles de usuario, cambio el cursor usando

this.Cursor = Cursors.Wait;

cuando hago clic en algo

Ahora quiero hacer lo mismo en una página WPF con un clic de botón. Cuando paso el mouse sobre mi botón, el cursor cambia a una mano, pero cuando hago clic en él, no cambia al cursor de espera. Me pregunto si esto tiene algo que ver con el hecho de que es un botón, o porque es una página y no un control de usuario. Esto parece un comportamiento extraño.

Respuestas:


211

¿Necesita que el cursor sea un cursor de "espera" solo cuando está sobre esa página / control de usuario en particular? Si no, sugeriría usar Mouse.OverrideCursor :

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

Esto anula el cursor de su aplicación en lugar de solo una parte de su interfaz de usuario, por lo que el problema que está describiendo desaparece.


Similar a mi propia respuesta , fechada 3 años después (¡casi exactamente!). Me gustan las respuestas en esta pregunta, pero la más simple siempre es la más tentadora :)
Robin Maben

Esta solución cambiará el cursor para que sea un cursor de "espera" pero no deshabilitará ninguna entrada adicional del mouse. Intenté usar esta solución y, aunque el mouse cambió al cursor de espera, todavía puedo hacer clic en cualquier elemento de la interfaz de usuario de mi aplicación WPF sin ningún problema. ¿Alguna idea de cómo puedo evitar que el usuario realmente use el mouse mientras el cursor de espera está activo?
Thomas Huber

2
Viejo como es y aceptado como es, NO es la respuesta adecuada. Anular el cursor de la aplicación es diferente de anular un cursor de control (y el segundo tiene problemas en WPF). Anular el cursor de la aplicación puede tener efectos secundarios desagradables, por ejemplo, un cuadro de mensaje emergente (error) podría verse obligado a usar el mismo cursor anulado por error, mientras que la intención era anular solo mientras el mouse se desplaza sobre el control real y activo.
Gábor

64

Una forma de hacer esto en nuestra aplicación es usar IDisposable y luego con using(){}bloques para asegurar que el cursor se reinicie cuando haya terminado.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

y luego en tu código:

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

La anulación finalizará cuando: se alcance el final de la instrucción using o; si se produce una excepción y el control abandona el bloque de la declaración antes del final de la declaración.

Actualizar

Para evitar que el cursor parpadee, puede hacer lo siguiente:

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
Buena solución con la parte que usa. De hecho, escribí exactamente lo mismo en algunos de nuestros proyectos (sin la pila, claro). Una cosa que puede simplificar en el uso es simplemente escribir: usando (nuevo OverrideCursor (Cursors.Wait)) {// do stuff} en lugar de asignarle una variable que probablemente no usará.
Olli

1
Innecesario. Si se establece Mouse.OverrideCursorque nulles definido y ningún anulaciones más largos del cursor del sistema. SI estaba modificando el cursor actual directamente (es decir, no anulando), entonces podría haber un problema.
Dennis

2
Esto es bueno, pero no es seguro si varias vistas están actualizando el cursor al mismo tiempo. Es fácil entrar en una condición de carrera donde ViewA establece el cursor, luego ViewB establece uno diferente, luego ViewA intenta restablecer su cursor (que luego saca a ViewB de la pila y deja el cursor de ViewA activo). Hasta que ViewB restablezca su cursor, las cosas no volverán a la normalidad.
Simon Gillbee

2
@SimonGillbee eso es realmente posible: no era un problema que tenía hace 10 años cuando escribí esto. Si encuentra una solución, tal vez usando a ConcurrentStack<Cursor>, no dude en editar la respuesta anterior o agregar la suya.
Dennis

2
@Dennis De hecho, escribí esto hace unos días (por eso estaba buscando SO). Jugué con ConcurrentStack, pero resultó ser una colección incorrecta. La pila solo te permite saltar desde la parte superior. En este caso, desea eliminar del medio de la pila si ese cursor está dispuesto antes de que se elimine la parte superior de la pila. Terminé usando List <T> con ReaderWriterLockSlim para controlar el acceso concurrente.
Simon Gillbee

38

Puede usar un activador de datos (con un modelo de vista) en el botón para habilitar un cursor de espera.

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

Aquí está el código del modelo de vista:

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

44
Yo tampoco entiendo el voto negativo. Esta respuesta es útil cuando está utilizando MVvM (por lo que no hay código subyacente) y desea controlar el cursor para un control específico. Muy útil.
Simon Gillbee

44
Estoy aprovechando los beneficios de MVVM y esta es la respuesta perfecta.
g1ga

Me gusta esta solución porque creo que funcionará mejor con MVVM, modelos de vista, etc.
Rod

El problema que veo con este código es que los cursores son "Esperar" solo mientras el mouse se pasa sobre el botón, pero a medida que lo mueves, vuelve a ser una "Flecha".
Spiderman

7

Si su aplicación usa material asíncrono y está jugando con el cursor del mouse, probablemente quiera hacerlo solo en el hilo principal de la interfaz de usuario. Puede usar el hilo Dispatcher de la aplicación para eso:

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

0

Lo siguiente funcionó para mí:

ForceCursor = true;
Cursor = Cursors.Wait;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.