Как сделать так, чтобы курсор превратился в курсор ожидания?

263

У меня есть приложение на C #, в которое входят пользователи, и поскольку алгоритм хеширования стоит дорого, это займет немного времени. Как я могу отобразить курсор Ожидание / Занят (обычно песочные часы) для пользователя, чтобы сообщить им, что программа что-то делает?

Проект находится на C #.

Malfist
источник

Ответы:

451

Вы можете использовать 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;

Однако, если операция хеширования действительно длительная (MSDN определяет это как более 2-7 секунд), вам, вероятно, следует использовать визуальный индикатор обратной связи, кроме курсора, чтобы уведомить пользователя о ходе выполнения. Более подробный набор рекомендаций см. В этой статье .

Редактировать:
Как отметил @Am, вам может потребоваться позвонить Application.DoEvents();после, Cursor.Current = Cursors.WaitCursor;чтобы убедиться, что песочные часы действительно отображаются.

Пончик
источник
23
в этом нет необходимости менять курсор - если цикл сообщений не будет вызываться во время кода, требующего много времени. чтобы включить его, нужно добавить Application.DoEvents (); после первого набора курсора.
Амиршк
16
Вы, вероятно, хотите блокировать try..finally после установки Current (гарантируя, что Current будет сброшен в Default).
TrueWill
7
К вашему сведению, я не могу заставить работать вышеперечисленное, но изменив его на this.cursor = cursors.waitcursor; это сработало.
Ганс Рудель
4
«Песочные часы» не отображались, если я использовал Application.DoEvents () после Cursor.Current = Cursors.WaitCursor. Однако он работал без Application.DoEvents (). Не уверен, почему
Vbp
14
Лучше использовать Application.UseWaitCursor = trueиApplication.UseWaitCursor = false
Джанпьеро
169

Фактически,

Cursor.Current = Cursors.WaitCursor;

временно устанавливает курсор ожидания, но не гарантирует, что курсор ожидания будет отображаться до конца вашей операции. Другие программы или элементы управления в вашей программе могут легко сбросить курсор обратно на стрелку по умолчанию, как на самом деле происходит, когда вы перемещаете мышь во время работы.

Гораздо лучший способ показать курсор ожидания - установить для свойства UseWaitCursor в форме значение true:

form.UseWaitCursor = true;

Будет отображаться курсор ожидания для всех элементов управления в форме, пока вы не установите для этого свойства значение false. Если вы хотите, чтобы курсор ожидания отображался на уровне приложения, вы должны использовать:

Application.UseWaitCursor = true;
draganstankovic
источник
Хорошо знать. Я пытался сделать то же самое в WPF, и в итоге я получил Cursor = Cursors.Wait и Cursor = Cursors.Arrow . Но я не смог найти курсор под App
itho
2
Не удалось найти UseWaitCursor в приложении!
Чандра Эскай
Я обнаружил, что при установке form.UseWaitCursor = false в конце операции он фактически не сбрасывает курсор, пока вы не переместите или не щелкнете мышью. OTOH, form.Cursor не имеет этой проблемы. Я не мог заставить Курсора. Тока работать вообще.
Стюарт
39

Основываясь на предыдущем, мой предпочтительный подход (так как это часто выполняемое действие) заключается в том, чтобы заключить код курсора ожидания в вспомогательный класс IDisposable, чтобы его можно было использовать с помощью () (одна строка кода), принять необязательные параметры, выполнить код внутри, а затем очистить (восстановить курсор).

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

Использование:

using (new CursorWait())
{
    // Perform some code that shows cursor
}
mhapps
источник
Вдохновленный: codeproject.com/Articles/6287/WaitCursor-hack-using-using
торно
Не видел этого, но да, похожий подход. Он создает резервную копию текущего курсора, а затем восстанавливает его, что может быть полезно, если вы интенсивно меняете курсор.
Mhapps
29

Использовать UseWaitCursor проще на уровне формы или окна. Типичный вариант использования может выглядеть следующим образом:

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

Для лучшего пользовательского интерфейса вы должны использовать асинхронность из другого потока.

dmihailescu
источник
2
Это должен быть ПРИНЯТЫЙ ответ. Это единственный, который использует try-finally.
Джон Хенкель
1
у меня упущение, я пропустил попытку-наконец в моей реализации
Джек
19

Мой подход заключается в том, чтобы сделать все вычисления в фоновом режиме.

Затем измените курсор следующим образом:

this.Cursor = Cursors.Wait;

И в событии окончания потока восстановите курсор:

this.Cursor = Cursors.Default;

Обратите внимание, что это также может быть сделано для определенных элементов управления, поэтому курсор будет песочными часами только тогда, когда мышь находится над ними.

Amirshk
источник
@Malfist: хороший подход :), тогда все, что вам нужно сделать, это поместить восстановление в конечное событие, и все готово.
Амиршк
4

Итак, я создал статический асинхронный метод. Это отключило элемент управления, который запускает действие и изменяет курсор приложения. Он запускает действие как задачу и ожидает завершения. Контроль возвращается к вызывающей стороне, пока он ждет. Таким образом, приложение остается отзывчивым, даже когда значок «занят» вращается.

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

Вот код формы основной формы

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

Мне пришлось использовать отдельный регистратор для фиктивного действия (я использую Nlog), и мой основной регистратор пишет в пользовательский интерфейс (расширенное текстовое поле). Мне не удалось отобразить курсор занятости только при нахождении над конкретным контейнером в форме (но я не очень старался). Все элементы управления имеют свойство UseWaitCursor, но, похоже, это не влияет на элементы управления. Я пытался (возможно, потому что они не были на вершине?)

Вот основной журнал, который показывает, что происходит в том порядке, в котором мы ожидаем:

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
Даррел Ли
источник
2

С классом ниже вы можете сделать предложение о пончик "исключение безопасно".

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

класс 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
источник
2

Окей, мнение других людей очень ясно, но я хотел бы сделать некоторые из них, как следует:

Cursor tempCursor = Cursor.Current;

Cursor.Current = Cursors.WaitCursor;

//do Time-consuming Operations         

Cursor.Current = tempCursor;
wenha
источник
2

Для приложений Windows Forms может быть полезно дополнительное отключение UI-Control. Итак, мое предложение выглядит так:

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

Использование:

private void UiControl_Click(object sender, EventArgs e)
{
    using (new AppWaitCursor(sender))
    {
        LongRunningCall();
    }
}
HEF
источник
1

Используйте это с WPF:

Cursor = Cursors.Wait;

// Your Heavy work here

Cursor = Cursors.Arrow;
Абдулраццак Альзайед
источник