Зміна курсору в WPF іноді спрацьовує, іноді не працює


123

На кількох моїх контролях користувачів я змінюю курсор за допомогою

this.Cursor = Cursors.Wait;

коли я щось натискаю.

Тепер я хочу зробити те ж саме на сторінці WPF, натиснувши кнопку. Коли я наведіть курсор на свою кнопку, курсор змінюється на руку, але коли я натискаю на неї, він не змінюється на курсор очікування. Цікаво, чи це щось пов’язане з тим, що це кнопка, або тому, що це сторінка, а не контроль користувача? Це здається дивним поведінкою.

Відповіді:


211

Чи потрібен курсор, щоб бути курсором "очікування" лише тоді, коли він знаходиться над цією певною сторінкою / контролем користувача? Якщо ні, я б запропонував використовувати Mouse.OverrideCursor :

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

Це переосмислює курсор для вашої програми, а не лише частину його інтерфейсу, тому проблема, яку ви описуєте, усувається.


Схожа на мою власну відповідь , датовану 3 роками пізніше (майже точно!). Мені подобаються відповіді в цьому питанні, але найпростіший завжди є найпривабливішим :)
Робін Мабен

Це рішення змінить курсор на "очікування" курсору, але воно не вимкне подальші введення миші. Я спробував використати це рішення, і хоча миша змінилася на курсор очікування, я все ще в змозі без проблем натиснути будь-який елемент інтерфейсу в моєму додатку WPF. Будь-які ідеї, як я можу запобігти користувачеві фактично використовувати мишу під час активного курсору очікування?
Томас Хубер

2
Старий як є і прийнятий таким, який він є, це НЕ належна відповідь. Перевизначення курсору програми відрізняється від перекриття курсору управління (а другий має проблеми з WPF у порядку). Переосмислення курсору програми може мати неприємні побічні ефекти, наприклад, спливаюче вікно (помилка) може бути змушене помилково використовувати той самий перекритий курсор, тоді як намір полягав лише в тому, щоб перекрити, поки миша наводить курсор над фактичним та активним керуванням.
Габор

64

Один із способів зробити це у нашому додатку - це використання IDisposable, а потім за допомогою using(){}блоків, щоб забезпечити скидання курсору після завершення.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

а потім у своєму коді:

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

Перезапис закінчиться, коли: буде досягнуто кінця використовуючого оператора або; якщо буде викинуто виняток і контроль залишає блок операторів до кінця оператора.

Оновлення

Щоб запобігти мерехтіння курсору, ви можете:

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
Приємне рішення з використанням деталі. Насправді я написав саме те саме в деяких наших проектах (без стека, тобто). Одне, що ви можете спростити у використанні, - це просто написати: using (новий OverrideCursor (Cursors.Wait)) {// do stuff}, а не призначати йому змінну, яку ви, ймовірно, не будете використовувати.
Оллі

1
Не потрібно. Якщо ви встановите Mouse.OverrideCursorна nullнього не знята з охорони і більше не перекриває система управління курсором. Якби я змінював поточний курсор безпосередньо (тобто не переосмислював), то може виникнути проблема.
Денніс

2
Це добре, але не є безпечним, якщо курсор оновлює курсор одночасно. Легко потрапити в стан гонки, де курсор набору ViewA, потім ViewB встановлює інший, потім ViewA намагається скинути курсор (який потім вискакує ViewB зі стека та залишає курсор ViewA активним). Доки ViewB не скидає курсор, все не повернеться до норми.
Саймон Гіллбі

2
@SimonGillbee це справді можливо - це не була проблема, яку я мав 10 років тому, коли я писав це. якщо ви знайдете рішення, можливо, використовуючи " ConcurrentStack<Cursor>, не соромтесь відредагувати вищевказану відповідь або додати свою.
Денніс

2
@Dennis Я насправді написав це кілька днів тому (саме тому я дивився через ТАК). Я грав з ConcurrentStack, але виявився неправильний збір. Стек дозволяє лише вискочити зверху. У цьому випадку потрібно видалити з середини стека, якщо цей курсор розміщений до того, як буде розташована верхня частина стека. Я в кінцевому підсумку просто використовував Список <T> з ReaderWriterLockSlim для керування паралельним доступом.
Саймон Гіллбі

38

Ви можете використовувати тригер даних (з моделлю перегляду) на кнопці, щоб увімкнути курсор очікування.

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

Ось код із 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
Я також не отримую голосування. Ця відповідь корисна, коли ви використовуєте MVvM (тому немає коду) і хочете керувати курсором для конкретного елемента керування. Дуже корисний.
Саймон Гіллбі

4
Я використовую переваги MVVM, і це ідеальна відповідь.
g1ga

Мені подобається це рішення, оскільки я вважаю, що він буде краще працювати з MVVM, viewmodels тощо
Пд

Проблема, яку я бачу з цим кодом, полягає в тому, що курсори "Зачекайте" лише тоді, коли миша переходить над кнопкою, але при переміщенні миші вона повертається як "Стрілка".
павук

7

Якщо у вашій програмі використовуються речі асинхронізації, і ви обмінюєтесь курсором Миші, ви, ймовірно, хочете це зробити лише в основному потоці інтерфейсу користувача. Для цього можна скористатися потоком диспетчера програми:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.