Thay đổi con trỏ trong WPF đôi khi hoạt động, đôi khi không


123

Trên một số điều khiển người dùng của tôi, tôi thay đổi con trỏ bằng cách sử dụng

this.Cursor = Cursors.Wait;

khi tôi nhấp vào cái gì đó

Bây giờ tôi muốn làm điều tương tự trên trang WPF chỉ bằng một nút bấm. Khi tôi di chuột qua nút của mình, con trỏ chuyển sang một bàn tay, nhưng khi tôi nhấp vào nó, nó không thay đổi thành con trỏ chờ. Tôi tự hỏi nếu điều này có liên quan đến thực tế rằng đó là một nút, hoặc bởi vì đây là một trang chứ không phải là một điều khiển người dùng? Đây dường như là hành vi kỳ lạ.

Câu trả lời:


211

Bạn có cần con trỏ chỉ là con trỏ "chờ" khi nó ở trên trang / điều khiển người dùng cụ thể đó không? Nếu không, tôi khuyên bạn nên sử dụng Mouse.OverrideCoder :

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

Điều này ghi đè con trỏ cho ứng dụng của bạn thay vì chỉ là một phần của giao diện người dùng, vì vậy vấn đề bạn mô tả sẽ biến mất.


Tương tự như câu trả lời của riêng tôi , ngày 3 năm sau (gần như chính xác!). Tôi thích câu trả lời trong câu hỏi này, nhưng câu trả lời đơn giản nhất luôn hấp dẫn nhất :)
Robin Maben

Giải pháp này sẽ thay đổi con trỏ thành con trỏ "chờ" nhưng nó sẽ không vô hiệu hóa bất kỳ đầu vào chuột nào nữa. Tôi đã thử sử dụng giải pháp này và mặc dù chuột đã thay đổi thành con trỏ chờ nhưng tôi vẫn có thể nhấp vào bất kỳ thành phần UI nào trong ứng dụng WPF của mình mà không gặp vấn đề gì. Bất kỳ ý tưởng nào làm thế nào tôi có thể ngăn người dùng thực sự sử dụng chuột trong khi con trỏ chờ được kích hoạt?
Thomas Huber

2
Cũ như nó là và được chấp nhận như nó là, nó KHÔNG phải là câu trả lời thích hợp. Ghi đè con trỏ ứng dụng khác với ghi đè con trỏ điều khiển (và thứ hai có vấn đề trong WPF). Ghi đè con trỏ ứng dụng có thể có các tác dụng phụ khó chịu, ví dụ, hộp thông báo bật lên (lỗi) có thể bị buộc sử dụng nhầm con trỏ bị ghi đè trong khi ý định chỉ ghi đè trong khi chuột đang di chuột qua điều khiển thực tế và hoạt động.
Gábor

64

Một cách chúng tôi thực hiện điều này trong ứng dụng của mình là sử dụng IDis Dùng và sau đó với using(){}các khối để đảm bảo con trỏ được đặt lại khi hoàn tất.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

và sau đó trong mã của bạn:

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

Việc ghi đè sẽ kết thúc khi một trong hai: kết thúc câu lệnh sử dụng hoặc; nếu một ngoại lệ được ném và điều khiển rời khỏi khối lệnh trước khi kết thúc câu lệnh.

Cập nhật

Để ngăn con trỏ nhấp nháy, bạn có thể làm:

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
Giải pháp tốt đẹp với phần sử dụng. Tôi thực sự đã viết chính xác như vậy trong một số dự án của chúng tôi (không có ngăn xếp, đó là). Một điều bạn có thể đơn giản hóa trong cách sử dụng là chỉ viết: bằng cách sử dụng (OverrideCoder mới (Cursors.Wait)) {// do Stuff} thay vì gán cho nó một biến mà bạn có thể sẽ không sử dụng.
Olli

1
Không cần thiết. Nếu bạn thiết lập Mouse.OverrideCursorđể nullnó là unset và không ghi đè còn là hệ thống con trỏ. NẾU tôi đã sửa đổi con trỏ hiện tại trực tiếp (tức là không ghi đè) thì có thể có một vấn đề.
Dennis

2
Điều này là tốt, nhưng không an toàn nếu nhiều chế độ xem đang cập nhật con trỏ cùng một lúc. Dễ dàng đi vào điều kiện cuộc đua trong đó ViewA đặt con trỏ, sau đó ViewB đặt một con trỏ khác, sau đó ViewA cố gắng đặt lại con trỏ của nó (sau đó bật ViewB ra khỏi ngăn xếp và để con trỏ của ViewA hoạt động). Mãi cho đến khi ViewB đặt lại con trỏ của nó, mọi thứ mới trở lại bình thường.
Simon Gillbee

2
@SimonGillbee điều đó thực sự có thể - đó không phải là vấn đề mà tôi đã có 10 năm trước khi tôi viết bài này. nếu bạn tìm thấy một giải pháp, có thể sử dụng một ConcurrentStack<Cursor>, vui lòng chỉnh sửa câu trả lời ở trên hoặc thêm câu trả lời của riêng bạn.
Dennis

2
@Dennis Tôi thực sự đã viết điều này một vài ngày trước (đó là lý do tại sao tôi đang tìm kiếm thông qua SO). Tôi đã chơi với ConcurrencyStack, nhưng hóa ra đó là bộ sưu tập sai. Stack chỉ cho phép bạn bật ra khỏi đầu. Trong trường hợp này, bạn muốn xóa từ giữa ngăn xếp nếu con trỏ đó bị loại bỏ trước khi đỉnh của ngăn xếp được xử lý. Tôi đã kết thúc việc sử dụng Danh sách <T> với ReaderWriterLockSlim để điều chỉnh truy cập đồng thời.
Simon Gillbee

38

Bạn có thể sử dụng trình kích hoạt dữ liệu (với mô hình chế độ xem) trên nút để bật con trỏ chờ.

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

Đây là mã từ mô hình xem:

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
Tôi cũng không nhận được downvote. Câu trả lời này rất hữu ích khi bạn đang sử dụng MVvM (vì vậy không có mã phía sau) và muốn điều khiển con trỏ cho một điều khiển cụ thể. Rất hữu ích.
Simon Gillbee

4
Tôi đang tận dụng những lợi ích của MVVM và đây là câu trả lời hoàn hảo.
g1ga

Tôi thích giải pháp này vì tôi tin rằng nó sẽ hoạt động tốt hơn với MVVM, viewmodels, v.v.
Rod

Vấn đề tôi thấy với mã này là các con trỏ chỉ "Chờ" trong khi chuột ở trên nút, nhưng khi bạn di chuyển chuột ra, nó sẽ trở thành "Mũi tên".
nhện

7

Nếu ứng dụng của bạn sử dụng công cụ không đồng bộ và bạn đang nghịch với con trỏ của Chuột, có lẽ bạn chỉ muốn làm điều đó trong luồng UI chính. Bạn có thể sử dụng chuỗi Dispatcher của ứng dụng cho điều đó:

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

0

Sau đây làm việc cho tôi:

ForceCursor = true;
Cursor = Cursors.Wait;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.