Изменение курсора в WPF иногда работает, иногда - нет


123

В некоторых пользовательских элементах управления я меняю курсор, используя

this.Cursor = Cursors.Wait;

когда я что-то нажимаю.

Теперь я хочу сделать то же самое на странице WPF одним нажатием кнопки. Когда я наводю курсор на свою кнопку, курсор меняется на руку, но когда я нажимаю на нее, он не меняется на курсор ожидания. Интересно, связано ли это с тем, что это кнопка, или потому, что это страница, а не пользовательский элемент управления? Это похоже на странное поведение.

Ответы:


211

Вам нужно, чтобы курсор был курсором ожидания только тогда, когда он находится над этой конкретной страницей / пользовательским элементом управления? Если нет, я бы предложил использовать Mouse.OverrideCursor :

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

Это переопределяет курсор для вашего приложения, а не только для части его пользовательского интерфейса, поэтому проблема, которую вы описываете, исчезнет.


Подобно моему собственному ответу , датированному 3 годами позже (почти точно!). Мне нравятся ответы на этот вопрос, но самый простой всегда самый соблазнительный :)
Робин Мабен

Это решение изменит курсор на курсор «ожидания», но не отключит дальнейшие действия мыши. Я попытался использовать это решение, и, хотя мышь изменилась на курсор ожидания, я все еще могу без проблем щелкнуть любой элемент пользовательского интерфейса в моем приложении WPF. Есть идеи, как я могу запретить пользователю фактически использовать мышь, когда курсор ожидания активен?
Thomas Huber

2
Каким бы старым оно ни было и как оно принято, это НЕ правильный ответ. Переопределение курсора приложения отличается от переопределения курсора управления (а у второго есть проблемы в WPF). Переопределение курсора приложения может иметь неприятные побочные эффекты, например, всплывающее окно сообщения (об ошибке) может быть вынуждено использовать один и тот же переопределенный курсор ошибочно, в то время как намерение состояло только в том, чтобы переопределить, когда мышь находится над фактическим и активным элементом управления.
Gábor

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

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

Обновить

Чтобы курсор не мерцал, вы можете:

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 (new OverrideCursor (Cursors.Wait)) {// do stuff} вместо того, чтобы назначать ему переменную, которую вы, вероятно, не будете использовать.
Olli

1
Не нужно. Если вы установите Mouse.OverrideCursorего, nullон не установлен и больше не перекрывает системный курсор. ЕСЛИ я изменял текущий курсор напрямую (т.е. не переопределял), тогда могла возникнуть проблема.
Деннис

2
Это хорошо, но небезопасно, если несколько представлений обновляют курсор одновременно. Легко попасть в состояние гонки, когда ViewA устанавливает курсор, затем ViewB устанавливает другой, затем ViewA пытается сбросить свой курсор (который затем выталкивает ViewB из стека и оставляет курсор ViewA активным). Пока ViewB не сбрасывает курсор, все возвращается в норму.
Саймон Гиллби

2
@SimonGillbee, это действительно возможно - это не было проблемой, которая была у меня 10 лет назад, когда я писал это. если вы найдете решение, возможно, используя a ConcurrentStack<Cursor>, не стесняйтесь редактировать приведенный выше ответ или добавлять свой собственный.
Деннис

2
@Dennis Я написал это несколько дней назад (вот почему я смотрел ТАК). Я играл с ConcurrentStack, но это оказалась не та коллекция. Стек позволяет вам выскочить только сверху. В этом случае вы хотите удалить из середины стека, если этот курсор расположен до того, как будет удалена вершина стека. В итоге я просто использовал List <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>

Вот код из модели представления:

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, моделями просмотра и т. Д.
Род

Проблема, которую я вижу с этим кодом, заключается в том, что курсоры находятся в состоянии «Ждать» только тогда, когда указатель мыши находится над кнопкой, но когда вы перемещаете указатель мыши, он снова становится «стрелкой».
Человек-паук

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.