在WPF中更改光标有时有效,有时无效。

133

在我的几个用户控件中,我使用下面的代码来改变光标:

this.Cursor = Cursors.Wait;

当我点击某个东西时。

现在我想在 WPF 页面上通过单击按钮来做同样的事情。当我将鼠标悬停在按钮上时,光标会变成手形,但是当我单击它时,它不会变成等待光标。我想知道这是否与它是一个按钮有关,或者因为这是一个页面而不是用户控件?这似乎是奇怪的行为。

5个回答

224

如果您只需要在光标位于该特定页面/用户控件上时将其设置为“等待”光标,那么我建议使用Mouse.OverrideCursor

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

这将覆盖整个应用程序的光标,而不仅仅是其用户界面的一部分,因此您所描述的问题将消失。


类似于我自己的答案(https://dev59.com/5EfRa4cB1Zd3GeqP7kE8#8211178),只是晚了三年(几乎完全一样!)。我喜欢这个问题中的答案,但最简单的答案总是最诱人的 :) - Robin Maben
这个方案将改变光标为“等待”光标,但它不会禁用任何进一步的鼠标输入。我尝试过这个方案,虽然鼠标改变成了等待光标,但我仍然能够在我的WPF应用程序中点击任何UI元素,没有任何问题。有什么想法可以防止用户在等待光标激活期间实际使用鼠标吗? - Thomas Huber
3
虽然这个答案已经被接受且历史悠久,但它并不是正确的答案。覆盖应用程序光标与覆盖控件光标是不同的(后者在WPF中也存在问题)。覆盖应用程序光标可能会产生不良影响,例如,弹出(错误)消息框可能会被强制使用相同的覆盖光标,尽管意图仅是在鼠标悬停在实际和活动控件上时进行覆盖。 - Gábor

66

我们在应用程序中的一种方法是使用using(){}块和IDisposable来确保在完成操作时重置光标。

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

当override语句块执行结束或在语句块结束前抛出异常并离开语句块时,override将会结束。

更新

为了防止光标闪烁,你可以这样做:

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,则它将被取消设置并不再覆盖系统光标。但是,如果我直接修改当前光标(即不进行覆盖),那么可能会出现问题。 - Dennis
2
这很好,但如果多个视图同时更新光标,则不安全。很容易陷入竞争条件,其中ViewA设置光标,然后ViewB设置不同的光标,然后ViewA尝试重置其光标(然后弹出ViewB的堆栈并使ViewA的光标处于活动状态)。只有在ViewB重置其光标之后,事情才会恢复正常。 - Simon Gillbee
2
@SimonGillbee 确实可能实现 - 当我在10年前编写这个问题时,这并不是一个问题。如果您找到了解决方案,可以尝试使用 ConcurrentStack<Cursor>,请随意编辑上面的答案或添加自己的答案。 - Dennis
2
@Dennis 我实际上几天前就写了这个(这也是我在浏览SO的原因)。我试用了ConcurrentStack,但结果证明它不是正确的集合。栈只允许你弹出顶部。在这种情况下,如果在堆栈顶部被释放之前,要从堆栈中间删除,则需要从中间删除。最终,我使用了List<T>和ReaderWriterLockSlim来控制并发访问。 - Simon Gillbee
显示剩余3条评论

43

您可以在按钮上使用数据触发器(与视图模型一起),以启用等待光标。

<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
5
我正在利用MVVM的好处,这是完美的答案。 - g1ga
我喜欢这个解决方案,因为我相信它会更好地与MVVM、视图模型等配合使用。 - Rod
我看到这段代码的问题在于,当鼠标悬停在按钮上时,光标只是“等待”,但是当你将鼠标移出时,它会恢复成“箭头”。 - spiderman

7

如果你的应用程序使用了异步功能,并且你想要更改鼠标指针,那么你可能只想在主UI线程中进行操作。你可以使用应用程序的Dispatcher线程来实现:

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

0

以下方法对我有效:

ForceCursor = true;
Cursor = Cursors.Wait;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接