Zmiana kursora w WPF czasami działa, czasami nie


123

W kilku kontrolkach użytkownika zmieniam kursor za pomocą

this.Cursor = Cursors.Wait;

kiedy coś kliknę.

Teraz chcę zrobić to samo na stronie WPF po kliknięciu przycisku. Kiedy najeżdżam na mój przycisk, kursor zmienia się w dłoń, ale kiedy go klikam, nie zmienia się w kursor oczekiwania. Zastanawiam się, czy ma to coś wspólnego z faktem, że jest to przycisk, czy też dlatego, że jest to strona, a nie kontrola użytkownika? To wydaje się dziwne zachowanie.

Odpowiedzi:


211

Czy chcesz, aby kursor był kursorem „czekania” tylko wtedy, gdy znajduje się nad tą konkretną stroną / kontrolką użytkownika? Jeśli nie, sugerowałbym użycie Mouse.OverrideCursor :

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

Powoduje to przesłonięcie kursora aplikacji, a nie tylko części jej interfejsu użytkownika, więc opisywany problem znika.


Podobnie jak moja własna odpowiedź , datowana 3 lata później (prawie dokładnie!). Podobają mi się odpowiedzi w tym pytaniu, ale najprostsze jest zawsze najbardziej kuszące :)
Robin Maben

To rozwiązanie zmieni kursor na kursor „czekania”, ale nie wyłącza żadnych dalszych działań myszy. Próbowałem użyć tego rozwiązania i chociaż mysz zmieniła się w kursor oczekiwania, nadal mogę bez problemu kliknąć dowolny element interfejsu użytkownika w mojej aplikacji WPF. Jakieś pomysły, jak mogę uniemożliwić użytkownikowi faktyczne używanie myszy, gdy kursor oczekiwania jest aktywny?
Thomas Huber

2
Tak jak jest stary i zaakceptowany takim, jakim jest, NIE jest to właściwa odpowiedź. Zastępowanie kursora aplikacji różni się od zastępowania kursora kontrolnego (a drugi ma problemy z WPF w porządku). Zastąpienie kursora aplikacji może mieć nieprzyjemne skutki uboczne, na przykład wyskakujące okno komunikatu (błąd) może być zmuszone do błędnego użycia tego samego zastąpionego kursora, podczas gdy intencją było tylko przesłonięcie, gdy mysz znajduje się nad rzeczywistą i aktywną kontrolką.
Gábor

64

Jednym ze sposobów, w jaki robimy to w naszej aplikacji, jest użycie IDisposable, a następnie using(){}bloków, aby upewnić się, że kursor zostanie zresetowany po zakończeniu.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

a następnie w swoim kodzie:

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

Przesłonięcie zakończy się, gdy: osiągnięty zostanie koniec instrukcji using lub; jeśli zostanie zgłoszony wyjątek, a sterowanie pozostawi blok instrukcji przed końcem instrukcji.

Aktualizacja

Aby zapobiec migotaniu kursora, możesz:

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
Ładne rozwiązanie z częścią użytkową. Właściwie napisałem dokładnie to samo w niektórych naszych projektach (to znaczy bez stosu). Jedną rzeczą, którą możesz uprościć w użyciu, jest po prostu napisanie: używając (new OverrideCursor (Cursors.Wait)) {// do stuff} zamiast przypisywać mu zmienną, której prawdopodobnie nie będziesz używać.
Olli

1
Nie są potrzebne. Jeśli ustawisz Mouse.OverrideCursorto, nullnie jest ustawiony i nie przesłania już kursora systemowego. JEŚLI modyfikowałem bieżący kursor bezpośrednio (tj. Bez nadpisywania), może wystąpić problem.
Dennis,

2
Jest to przyjemne, ale nie jest bezpieczne, jeśli wiele widoków aktualizuje kursor w tym samym czasie. Łatwo dostać się do sytuacji wyścigu, w której kursor ViewA ustawia, a następnie ViewB ustawia inny, a następnie ViewA próbuje zresetować swój kursor (co następnie zdejmuje ViewB ze stosu i pozostawia aktywny kursor ViewA). Dopiero gdy ViewB zresetuje swój kursor, wszystko wróci do normy.
Simon Gillbee

2
@SimonGillbee to rzeczywiście możliwe - to nie był problem, który miałem 10 lat temu, kiedy to pisałem. Jeśli znajdziesz rozwiązanie, na przykład używając ConcurrentStack<Cursor>, możesz edytować powyższą odpowiedź lub dodać własną.
Dennis

2
@Dennis Właściwie napisałem to kilka dni temu (dlatego szukałem SO). Grałem z ConcurrentStack, ale okazało się, że to zła kolekcja. Stos pozwala tylko na wyskakiwanie z góry. W tym przypadku chcesz usunąć ze środka stosu, jeśli ten kursor zostanie umieszczony przed usunięciem szczytu stosu. Skończyło się na tym, że użyłem List <T> z ReaderWriterLockSlim do zarządzania współbieżnym dostępem.
Simon Gillbee

38

Możesz użyć wyzwalacza danych (z modelem widoku) na przycisku, aby włączyć kursor oczekiwania.

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

Oto kod z modelu widoku:

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
Ja też nie mam głosów przeciw. Ta odpowiedź jest przydatna, gdy używasz MVvM (więc bez kodu) i chcesz kontrolować kursor dla określonej kontrolki. Bardzo przydatne.
Simon Gillbee,

4
Wykorzystuję zalety MVVM i to jest doskonała odpowiedź.
g1ga

Podoba mi się to rozwiązanie, ponieważ uważam, że będzie działać lepiej z MVVM, modelami widoków itp.
Rod

Problem, który widzę z tym kodem, polega na tym, że kursory to „Czekaj” tylko wtedy, gdy wskaźnik myszy znajduje się nad przyciskiem, ale gdy przesuwasz kursor, wraca on do postaci „Strzałki”.
spiderman

7

Jeśli twoja aplikacja używa elementów asynchronicznych i bawisz się kursorem myszy, prawdopodobnie chcesz to zrobić tylko w głównym wątku interfejsu użytkownika. Możesz użyć do tego wątku Dispatcher aplikacji:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.