Das Ändern des Cursors in WPF funktioniert manchmal, manchmal nicht

123

Bei einigen meiner Benutzersteuerungen ändere ich den Cursor mithilfe von

this.Cursor = Cursors.Wait;

wenn ich auf etwas klicke.

Jetzt möchte ich dasselbe auf einer WPF-Seite mit einem Klick auf eine Schaltfläche tun. Wenn ich mit der Maus über meine Schaltfläche fahre, ändert sich der Cursor in eine Hand, aber wenn ich darauf klicke, ändert er sich nicht in den Wartecursor. Ich frage mich, ob dies etwas mit der Tatsache zu tun hat, dass es sich um eine Schaltfläche handelt, oder weil dies eine Seite und keine Benutzersteuerung ist. Dies scheint ein seltsames Verhalten zu sein.

ScottG
quelle

Antworten:

211

Müssen Sie den Cursor nur dann als "Warte" -Cursor verwenden, wenn er sich über dieser bestimmten Seite / Benutzersteuerung befindet? Wenn nicht, würde ich die Verwendung von Mouse.OverrideCursor vorschlagen :

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

Dadurch wird der Cursor für Ihre Anwendung überschrieben und nicht nur für einen Teil der Benutzeroberfläche, sodass das von Ihnen beschriebene Problem behoben wird.

Matt Hamilton
quelle
Ähnlich meiner eigenen Antwort , datiert 3 Jahre später (fast genau!). Ich mag die Antworten in dieser Frage, aber die einfachste ist immer die verlockendste :)
Robin Maben
Diese Lösung ändert den Cursor in einen "Warte" -Cursor, deaktiviert jedoch keine weiteren Mauseingaben. Ich habe versucht, diese Lösung zu verwenden, und obwohl die Maus auf den Wartecursor geändert wurde, kann ich problemlos auf ein beliebiges UI-Element in meiner WPF-Anwendung klicken. Irgendwelche Ideen, wie ich verhindern kann, dass der Benutzer die Maus tatsächlich benutzt, während der Wartecursor aktiv ist?
Thomas Huber
2
Alt wie es ist und akzeptiert wie es ist, ist es NICHT die richtige Antwort. Das Überschreiben des App-Cursors unterscheidet sich vom Überschreiben eines Steuercursors (und der zweite hat Probleme mit WPF). Das Überschreiben des App-Cursors kann schlimme Nebenwirkungen haben. Beispielsweise kann ein aufkommendes (Fehler-) Meldungsfeld gezwungen sein, denselben überschriebenen Cursor fälschlicherweise zu verwenden, während nur beabsichtigt wurde, ihn zu überschreiben, während sich die Maus über dem tatsächlichen und aktiven Steuerelement befindet.
Gábor
64

Eine Möglichkeit, dies in unserer Anwendung zu tun, besteht darin, IDisposable und dann using(){}Blöcke zu verwenden, um sicherzustellen, dass der Cursor nach Abschluss zurückgesetzt wird.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

und dann in Ihrem Code:

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

Die Überschreibung endet, wenn entweder: das Ende der using-Anweisung erreicht ist oder; Wenn eine Ausnahme ausgelöst wird und das Steuerelement den Anweisungsblock vor dem Ende der Anweisung verlässt.

Aktualisieren

Um ein Flackern des Cursors zu verhindern, haben Sie folgende Möglichkeiten:

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

}
Dennis
quelle
2
Schöne Lösung mit dem Verwendungsteil. Ich habe in einigen unserer Projekte genau dasselbe geschrieben (also ohne Stack). Eine Sache, die Sie in der Verwendung vereinfachen können, ist, einfach zu schreiben: using (new OverrideCursor (Cursors.Wait)) {// do stuff}, anstatt ihm eine Variable zuzuweisen, die Sie wahrscheinlich nicht verwenden werden.
Olli
1
Nicht benötigt. Wenn Sie es einstellen Mouse.OverrideCursor, ist nulles nicht gesetzt und überschreibt den Systemcursor nicht mehr. Wenn ich den aktuellen Cursor direkt geändert habe (dh nicht überschrieben habe), liegt möglicherweise ein Problem vor.
Dennis
2
Dies ist nett, aber nicht sicher, wenn mehrere Ansichten gleichzeitig den Cursor aktualisieren. Es ist einfach, in einen Race-Zustand zu gelangen, in dem der Cursor von ViewA gesetzt ist, dann ViewB einen anderen setzt und ViewA versucht, seinen Cursor zurückzusetzen (wodurch ViewB vom Stapel genommen wird und der Cursor von ViewA aktiv bleibt). Erst wenn ViewB den Cursor zurücksetzt, werden die Dinge wieder normal.
Simon Gillbee
2
@ SimonGillbee das ist in der Tat möglich - es war kein Problem, das ich vor 10 Jahren hatte, als ich das schrieb. Wenn Sie eine Lösung finden, z. B. mit, können Sie ConcurrentStack<Cursor>die obige Antwort bearbeiten oder Ihre eigene hinzufügen.
Dennis
2
@ Tennis Ich habe das tatsächlich vor ein paar Tagen geschrieben (weshalb ich durch SO gesucht habe). Ich habe mit ConcurrentStack gespielt, aber es stellte sich heraus, dass es sich um die falsche Sammlung handelte. Mit Stack können Sie nur von oben abspringen. In diesem Fall möchten Sie aus der Mitte des Stapels entfernen, wenn dieser Cursor vor der Oberseite des Stapels angeordnet ist. Am Ende habe ich nur List <T> mit ReaderWriterLockSlim verwendet, um den gleichzeitigen Zugriff zu steuern.
Simon Gillbee
38

Sie können einen Datenauslöser (mit einem Ansichtsmodell) auf der Schaltfläche verwenden, um einen Wartecursor zu aktivieren.

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

Hier ist der Code aus dem Ansichtsmodell:

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;
      }
   }
}
Zamboni
quelle
4
Ich bekomme auch keine Gegenstimme. Diese Antwort ist nützlich, wenn Sie MVvM verwenden (also kein Code-Behind) und den Cursor für ein bestimmtes Steuerelement steuern möchten. Sehr hilfreich.
Simon Gillbee
4
Ich nutze die Vorteile von MVVM und dies ist die perfekte Antwort.
g1ga
Ich mag diese Lösung, da ich glaube, dass sie mit MVVM, Ansichtsmodellen usw. besser funktioniert.
Rod
Das Problem, das ich bei diesem Code sehe, ist, dass der Cursor nur "Warten" ist, während die Maus über die Schaltfläche fährt, aber wenn Sie die Maus herausziehen, wird sie wieder zu einem "Pfeil".
Spiderman
7

Wenn Ihre Anwendung asynchrones Material verwendet und Sie mit dem Mauszeiger herumspielen, möchten Sie dies wahrscheinlich nur im Haupt-UI-Thread tun. Sie können den Dispatcher-Thread der App dafür verwenden:

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

Folgendes hat bei mir funktioniert:

ForceCursor = true;
Cursor = Cursors.Wait;
Mike Lowery
quelle