Jak mogę ustawić kursor na kursor oczekiwania?

263

Mam aplikację C #, która loguje się do niej użytkownicy, a ponieważ algorytm haszujący jest drogi, zajmuje to trochę czasu. Jak mogę wyświetlić kursor oczekujący / zajęty (zwykle klepsydrę) użytkownikowi, aby poinformować go, że program coś robi?

Projekt jest w C #.

Malfist
źródło

Odpowiedzi:

451

Możesz użyć Cursor.Current.

// Set cursor as hourglass
Cursor.Current = Cursors.WaitCursor;

// Execute your time-intensive hashing code here...

// Set cursor as default arrow
Cursor.Current = Cursors.Default;

Jeśli jednak operacja mieszania jest naprawdę długa (MSDN określa to jako ponad 2-7 sekund), prawdopodobnie powinieneś użyć wizualnego wskaźnika sprzężenia zwrotnego innego niż kursor, aby powiadomić użytkownika o postępie. Bardziej szczegółowy zestaw wytycznych znajduje się w tym artykule .

Edycja:
Jak wskazał @Am, może być konieczne zadzwonienie Application.DoEvents();po, Cursor.Current = Cursors.WaitCursor;aby upewnić się, że klepsydra jest rzeczywiście wyświetlana.

Pączek
źródło
23
nie będzie to konieczne zmieniać kursora - jeśli pętla komunikatów nie zostanie wywołana podczas kodu czasochłonnego. aby go włączyć, musisz dodać Application.DoEvents (); po pierwszym ustawieniu kursora.
Amirshk
16
Prawdopodobnie zechcesz też spróbować ... ostatecznie zablokować po ustawieniu prądu (upewniając się, że prąd zostanie zresetowany do domyślnego).
TrueWill,
7
Do twojej wiadomości, nie mogłem zmusić powyższego do działania, ale zmieniając go na this.cursor = cursors.waitcursor; zadziałało.
Hans Rudel
4
Klepsydra nie była wyświetlana, jeśli użyłem Application.DoEvents () po Cursor.Current = Cursors.WaitCursor Jednak działało to bez Application.DoEvents (). Nie jestem pewien, dlaczego
Vbp
14
Lepiej jest używać Application.UseWaitCursor = trueiApplication.UseWaitCursor = false
Gianpiero
169

Tak właściwie,

Cursor.Current = Cursors.WaitCursor;

tymczasowo ustawia kursor Oczekiwanie, ale nie zapewnia, że ​​kursor Oczekiwanie będzie wyświetlany do końca operacji. Inne programy lub elementy sterujące w twoim programie mogą łatwo zresetować kursor z powrotem do domyślnej strzałki, co w rzeczywistości dzieje się, gdy poruszasz myszą, gdy operacja jest nadal uruchomiona.

O wiele lepszym sposobem pokazania kursora Wait jest ustawienie właściwości UseWaitCursor w formie na true:

form.UseWaitCursor = true;

Spowoduje to wyświetlenie kursora oczekiwania dla wszystkich kontrolek w formularzu, dopóki ta właściwość nie zostanie ustawiona na false. Jeśli chcesz, aby kursor był wyświetlany na poziomie aplikacji, powinieneś użyć:

Application.UseWaitCursor = true;
draganstankovic
źródło
Dobrze wiedzieć. Próbowałem zrobić to samo w WPF i skończyłem z Cursor = Cursors.Wait i Cursor = Cursors.Arrow . Ale nie mogłem znaleźć kursora w aplikacji
jego
2
Nie można znaleźć UseWaitCursor w aplikacji!
Chandra Eskay
Zauważyłem, że przy ustawianiu form UseWaitCursor = false na końcu operacji, tak naprawdę nie resetuje kursora, dopóki nie poruszysz lub nie klikniesz myszą. OTOH, form.Cursor nie ma tego problemu. Nie mogłem w ogóle uruchomić Cursor.Current.
Stewart
39

Opierając się na poprzednim, moim preferowanym podejściem (ponieważ jest to często wykonywana czynność) jest zawinięcie kodu kursora oczekiwania w klasę pomocniczą IDisposable, aby można go było używać za pomocą () (jeden wiersz kodu), brać parametry opcjonalne, uruchamiać kod wewnątrz, a następnie wyczyść (przywróć kursor).

public class CursorWait : IDisposable
{
    public CursorWait(bool appStarting = false, bool applicationCursor = false)
    {
        // Wait
        Cursor.Current = appStarting ? Cursors.AppStarting : Cursors.WaitCursor;
        if (applicationCursor) Application.UseWaitCursor = true;
    }

    public void Dispose()
    {
        // Reset
        Cursor.Current = Cursors.Default;
        Application.UseWaitCursor = false;
    }
}

Stosowanie:

using (new CursorWait())
{
    // Perform some code that shows cursor
}
mhapps
źródło
Nie widziałem tego, ale tak podobne podejście. Tworzy kopię zapasową bieżącego kursora, a następnie przywraca go, co może być przydatne, jeśli wykonujesz intensywną zmianę kursora.
mhapps
27

Użycie UseWaitCursor jest łatwiejsze na poziomie formularza lub okna. Typowy przypadek użycia może wyglądać jak poniżej:

    private void button1_Click(object sender, EventArgs e)
    {

        try
        {
            this.Enabled = false;//optional, better target a panel or specific controls
            this.UseWaitCursor = true;//from the Form/Window instance
            Application.DoEvents();//messages pumped to update controls
            //execute a lengthy blocking operation here, 
            //bla bla ....
        }
        finally
        {
            this.Enabled = true;//optional
            this.UseWaitCursor = false;
        }
    }

Aby uzyskać lepszy interfejs użytkownika, należy użyć asynchronii z innego wątku.

dmihailescu
źródło
2
To powinna być AKCEPTOWANA odpowiedź. To jedyny, który używa try-wreszcie.
John Henckel,
1
mam opinię, w mojej implementacji brakowało próby
Jack
19

Moje podejście polegałoby na tym, aby wszystkie obliczenia były przetwarzane w tle.

Następnie zmień kursor w następujący sposób:

this.Cursor = Cursors.Wait;

W zdarzeniu zakończenia wątku przywróć kursor:

this.Cursor = Cursors.Default;

Uwaga: można to również zrobić w przypadku określonych elementów sterujących, więc kursor będzie klepsydrą tylko wtedy, gdy mysz znajdzie się nad nimi.

Amirszk
źródło
@Malfist: dobre podejście :), wtedy wszystko, co musisz zrobić, to umieścić przywracanie w zdarzeniu końcowym i gotowe.
Amirshk
4

OK, więc utworzyłem statyczną metodę asynchroniczną. Spowodowało to wyłączenie kontrolki uruchamiającej akcję i zmieniającej kursor aplikacji. Uruchamia akcję jako zadanie i czeka na zakończenie. Kontrola wraca do dzwoniącego podczas oczekiwania. Dzięki temu aplikacja pozostaje responsywna, nawet gdy ikona zajętości się obraca.

async public static void LengthyOperation(Control control, Action action)
{
    try
    {
        control.Enabled = false;
        Application.UseWaitCursor = true;
        Task doWork = new Task(() => action(), TaskCreationOptions.LongRunning);
        Log.Info("Task Start");
        doWork.Start();
        Log.Info("Before Await");
        await doWork;
        Log.Info("After await");
    }
    finally
    {
        Log.Info("Finally");
        Application.UseWaitCursor = false;
        control.Enabled = true;
    }

Oto kod z formularza głównego

    private void btnSleep_Click(object sender, EventArgs e)
    {
        var control = sender as Control;
        if (control != null)
        {
            Log.Info("Launching lengthy operation...");
            CursorWait.LengthyOperation(control, () => DummyAction());
            Log.Info("...Lengthy operation launched.");
        }

    }

    private void DummyAction()
    {
        try
        {
            var _log = NLog.LogManager.GetLogger("TmpLogger");
            _log.Info("Action - Sleep");
            TimeSpan sleep = new TimeSpan(0, 0, 16);
            Thread.Sleep(sleep);
            _log.Info("Action - Wakeup");
        }
        finally
        {
        }
    }

Musiałem użyć osobnego programu rejestrującego dla akcji fikcyjnej (korzystam z Nlog), a mój główny program rejestrujący pisze w interfejsie użytkownika (bogate pole tekstowe). Nie byłem w stanie wyświetlić zajętego kursora tylko wtedy, gdy nad konkretnym kontenerem w formularzu (ale nie bardzo się starałem). Wszystkie kontrolki mają właściwość UseWaitCursor, ale nie ma to żadnego wpływu na kontrolki Próbowałem (może dlatego, że nie były na topie?)

Oto główny dziennik, który pokazuje, co dzieje się w oczekiwanej kolejności:

16:51:33.1064 Launching lengthy operation...
16:51:33.1215 Task Start
16:51:33.1215 Before Await
16:51:33.1215 ...Lengthy operation launched.
16:51:49.1276 After await
16:51:49.1537 Finally
Darrel Lee
źródło
2

Korzystając z poniższej klasy, możesz uczynić sugestię dotyczącą pączka „wyjątkowym bezpiecznym”.

using (new CursorHandler())
{
    // Execute your time-intensive hashing code here...
}

klasa CursorHandler

public class CursorHandler
    : IDisposable
{
    public CursorHandler(Cursor cursor = null)
    {
        _saved = Cursor.Current;
        Cursor.Current = cursor ?? Cursors.WaitCursor;
    }

    public void Dispose()
    {
        if (_saved != null)
        {
            Cursor.Current = _saved;
            _saved = null;
        }
    }

    private Cursor _saved;
}
Georg
źródło
2

Okej, poglądy innych ludzi są bardzo jasne, ale chciałbym dodać trochę więcej:

Cursor tempCursor = Cursor.Current;

Cursor.Current = Cursors.WaitCursor;

//do Time-consuming Operations         

Cursor.Current = tempCursor;
wenha
źródło
2

W przypadku aplikacji Windows Forms bardzo przydatne może być opcjonalne wyłączenie interfejsu użytkownika. Więc moja sugestia wygląda następująco:

public class AppWaitCursor : IDisposable
{
    private readonly Control _eventControl;

    public AppWaitCursor(object eventSender = null)
    {
         _eventControl = eventSender as Control;
        if (_eventControl != null)
            _eventControl.Enabled = false;

        Application.UseWaitCursor = true;
        Application.DoEvents();
    }

    public void Dispose()
    {
        if (_eventControl != null)
            _eventControl.Enabled = true;

        Cursor.Current = Cursors.Default;
        Application.UseWaitCursor = false;
    }
}

Stosowanie:

private void UiControl_Click(object sender, EventArgs e)
{
    using (new AppWaitCursor(sender))
    {
        LongRunningCall();
    }
}
HEF
źródło
1

Użyj tego z WPF:

Cursor = Cursors.Wait;

// Your Heavy work here

Cursor = Cursors.Arrow;
Abdulrazzaq Alzayed
źródło