Mengubah kursor di WPF terkadang berhasil, terkadang tidak

123

Di beberapa kontrol pengguna saya, saya mengubah kursor dengan menggunakan

this.Cursor = Cursors.Wait;

saat saya mengklik sesuatu.

Sekarang saya ingin melakukan hal yang sama pada halaman WPF dengan satu klik tombol. Saat saya mengarahkan kursor ke tombol saya, kursor berubah menjadi tangan, tetapi saat saya mengkliknya, kursor tidak berubah menjadi kursor tunggu. Saya ingin tahu apakah ini ada hubungannya dengan fakta bahwa ini adalah tombol, atau karena ini adalah halaman dan bukan kontrol pengguna? Ini sepertinya perilaku yang aneh.

ScottG
sumber

Jawaban:

211

Apakah Anda memerlukan kursor sebagai kursor "tunggu" hanya saat kursor berada di atas halaman / kontrol pengguna tertentu? Jika tidak, saya sarankan menggunakan Mouse.OverrideCursor :

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

Ini mengganti kursor untuk aplikasi Anda dan bukan hanya untuk sebagian UI-nya, jadi masalah yang Anda jelaskan akan hilang.

Matt Hamilton
sumber
Mirip dengan jawaban saya sendiri , tertanggal 3 tahun kemudian (hampir persis!). Saya suka jawaban dalam pertanyaan ini, tetapi yang paling sederhana selalu yang paling menggoda :)
Robin Maben
Solusi ini akan mengubah kursor menjadi kursor "tunggu" tetapi tidak akan menonaktifkan input mouse lebih lanjut. Saya mencoba menggunakan solusi ini dan meskipun mouse berubah menjadi kursor tunggu, saya masih dapat mengklik elemen UI apa pun dalam aplikasi WPF saya tanpa masalah. Adakah ide bagaimana saya dapat mencegah pengguna dari benar-benar menggunakan mouse selama kursor menunggu aktif?
Thomas Huber
2
Tua apa adanya dan diterima apa adanya, BUKAN jawaban yang tepat. Mengganti kursor aplikasi berbeda dengan menimpa kursor kontrol (dan yang kedua memiliki masalah di WPF). Mengganti kursor aplikasi dapat memiliki efek samping yang buruk, misalnya, kotak pesan (kesalahan) yang muncul mungkin terpaksa menggunakan kursor yang sama secara keliru sementara maksudnya hanya untuk menimpa saat mouse melayang di atas kontrol aktual dan aktif.
Gábor
64

Salah satu cara kami melakukan ini di aplikasi kami menggunakan IDisposable dan kemudian dengan using(){}blok untuk memastikan kursor disetel ulang saat selesai.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

lalu di kode Anda:

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

Penimpaan akan berakhir jika: akhir dari pernyataan penggunaan tercapai atau; jika pengecualian dilempar dan kontrol meninggalkan blok pernyataan sebelum akhir pernyataan.

Memperbarui

Untuk mencegah kursor berkedip, Anda dapat melakukan:

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

}
Dennis
sumber
2
Solusi bagus dengan menggunakan bagian. Saya sebenarnya menulis persis sama di beberapa proyek kami (tanpa tumpukan, yaitu). Satu hal yang dapat Anda sederhanakan dalam penggunaannya adalah dengan hanya menulis: using (new OverrideCursor (Cursors.Wait)) {// do stuff} alih-alih menetapkannya sebagai variabel yang mungkin tidak akan Anda gunakan.
Olli
1
Tidak dibutuhkan. Jika Anda mengatur Mouse.OverrideCursoruntuk nullitu diset dan tidak ada menimpa lagi sistem kursor. JIKA saya memodifikasi kursor saat ini secara langsung (yaitu tidak menimpa) maka mungkin ada masalah.
Dennis
2
Ini bagus, tetapi tidak aman jika beberapa tampilan memperbarui kursor pada saat yang bersamaan. Mudah untuk masuk ke kondisi balapan di mana kursor set ViewA, lalu ViewB menetapkan yang berbeda, lalu ViewA mencoba menyetel ulang kursornya (yang kemudian memunculkan ViewB dari tumpukan dan membiarkan kursor ViewA aktif). Tidak sampai ViewB me-reset kursornya melakukan hal-hal kembali normal.
Simon Gillbee
2
@SimonGillbee itu memang mungkin - itu bukan masalah yang saya alami 10 tahun yang lalu ketika saya menulis ini. jika Anda menemukan solusi, mungkin menggunakan a ConcurrentStack<Cursor>, silakan edit jawaban di atas atau tambahkan jawaban Anda sendiri.
Dennis
2
@ Dennis Saya sebenarnya menulis ini beberapa hari yang lalu (itulah sebabnya saya mencari melalui SO). Saya bermain dengan ConcurrentStack, tetapi ternyata koleksi yang salah. Stack hanya memungkinkan Anda untuk keluar dari atas. Dalam kasus ini, Anda ingin mengeluarkan dari tengah tumpukan jika kursor tersebut dibuang sebelum bagian atas tumpukan dibuang. Saya akhirnya hanya menggunakan List <T> dengan ReaderWriterLockSlim untuk mengatur akses bersamaan.
Simon Gillbee
38

Anda dapat menggunakan pemicu data (dengan model tampilan) pada tombol untuk mengaktifkan kursor tunggu.

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

Berikut kode dari model tampilan:

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;
      }
   }
}
Zamboni
sumber
4
Saya juga tidak mendapatkan suara negatifnya. Jawaban ini berguna saat Anda menggunakan MVvM (jadi tidak ada kode di belakang) dan ingin mengontrol kursor untuk kontrol tertentu. Sangat berguna.
Simon Gillbee
4
Saya memanfaatkan manfaat MVVM dan ini adalah jawaban yang tepat.
g1ga
Saya suka solusi ini karena saya yakin ini akan bekerja lebih baik dengan MVVM, model tampilan, dll.
Batang
Masalah yang saya lihat dengan kode ini adalah bahwa kursornya "Tunggu" hanya saat mouse di atas tombol, tetapi saat Anda menggerakkan mouse keluar, kursor tersebut kembali menjadi "Panah".
spiderman
7

Jika aplikasi Anda menggunakan hal-hal asinkron dan Anda mengutak-atik kursor Mouse, Anda mungkin ingin melakukannya hanya di utas UI utama. Anda dapat menggunakan utas Dispatcher aplikasi untuk itu:

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

Yang berikut berhasil untuk saya:

ForceCursor = true;
Cursor = Cursors.Wait;
Mike Lowery
sumber