在WPF中更改光标有时行得通,有时行不通


123

在几个用户控件上,我通过使用

this.Cursor = Cursors.Wait;

当我点击某些东西时。

现在,我想在WPF页面上单击按钮来执行相同的操作。当我将鼠标悬停在按钮上时,光标变为手形,但是单击它时,光标不会变为等待光标。我想知道这是否与按钮有关,还是因为这是页面而不是用户控件?这似乎是奇怪的行为。

Answers:


211

仅当光标位于特定页面/用户控件上方时,您才需要将其作为“等待”光标吗?如果没有,我建议使用Mouse.OverrideCursor

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

这将覆盖应用程序的光标,而不仅仅是应用程序UI的一部分,因此您要描述的问题消失了。


类似于我自己的答案,日期是3年后(几乎完全相同!)。我喜欢这个问题的答案,但最简单的答案总是最诱人的:)
罗宾·马本

此解决方案会将光标更改为“等待”光标,但不会禁用任何其他鼠标输入。我尝试使用此解决方案,尽管鼠标更改为等待光标,但我仍然可以单击WPF应用程序中的任何UI元素而没有任何问题。有什么想法可以防止用户在等待光标处于活动状态时实际使用鼠标吗?
Thomas Huber 2012年

2
尽管它已经很老了并且被人们接受了,但这不是正确的答案。覆盖应用程序游标与覆盖控制游标不同(第二个在WPF中存在问题)。覆盖应用程序光标可能会带来讨厌的副作用,例如,当鼠标悬停在实际控件和活动控件上时,可能会被强制错误地使用弹出对话框(错误)来使用相同的覆盖光标。
加博尔(Gábor)2014年

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
使用部分很好的解决方案。实际上,我在我们的某些项目中编写了完全相同的内容(即没有堆栈)。您可以简化用法的一件事就是编写:使用(new OverrideCursor(Cursors.Wait)){//做东西}而不是为其分配可能不会使用的变量。
奥利(Olli)2010年

1
没有必要。如果设置Mouse.OverrideCursornull它没有设置,不再覆盖系统光标。如果我直接修改了当前游标(即未覆盖),则可能存在问题。
丹尼斯,

2
很好,但是如果多个视图同时更新游标,那是不安全的。很容易陷入竞争状态,在这种情况下,ViewA设置了光标,然后ViewB设置了另一个光标,然后ViewA尝试重置其光标(然后将ViewB弹出堆栈,并使ViewA的光标处于活动状态)。直到ViewB重置其光标后,事情才恢复正常。
西蒙·吉尔比

2
@SimonGillbee的确是可能的-这不是我10年前写这篇文章时遇到的问题。如果您找到解决方案,也许使用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(因此无需任何代码隐藏)并且想要控制特定控件的光标时,此答案很有用。很有用。
Simon Gillbee 2010年

4
我正在利用MVVM的优势,这是一个完美的答案。
g1ga

我喜欢这个解决方案,因为我相信它可以与MVVM,视图模型等一起使用。–
Rod

我在此代码中看到的问题是,仅当鼠标悬停在按钮上时,光标才处于“等待”状态,但是当您将鼠标移出时,它将返回为“箭头”。
蜘蛛侠

7

如果您的应用程序使用异步内容,并且您不喜欢Mouse的光标,则可能只想在主UI线程中进行操作。您可以为此使用应用程序的Dispatcher线程:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.