Como posso fazer o cursor girar para o cursor de espera?

263

Eu tenho um aplicativo C # que permite que os usuários façam login e, como o algoritmo de hash é caro, leva um tempo para ser feito. Como posso exibir o cursor de espera / ocupado (geralmente a ampulheta) para o usuário para que ele saiba que o programa está fazendo alguma coisa?

O projeto está em c #.

Malfist
fonte

Respostas:

451

Você pode usar 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;

No entanto, se a operação de hash for realmente longa (o MSDN define isso como mais de 2 a 7 segundos), você provavelmente deve usar um indicador de feedback visual diferente do cursor para notificar o usuário sobre o progresso. Para um conjunto mais detalhado de diretrizes, consulte este artigo .

Editar:
Como apontou a @Am, talvez seja necessário ligar Application.DoEvents();depois Cursor.Current = Cursors.WaitCursor;para garantir que a ampulheta seja realmente exibida.

Rosquinha
fonte
23
isso não será necessário alterar o cursor - se o loop da mensagem não for chamado durante o código demorado. para habilitá-lo, você precisa adicionar Application.DoEvents (); após o primeiro cursor definido.
Amirshk 14/10/09
16
Provavelmente, você também deseja tentar ... o bloco final depois de definir a Corrente (garantindo que a Corrente seja redefinida como Padrão).
TrueWill 23/12/2009
7
Para sua informação, não consegui que o acima funcione, mas alterei para this.cursor = cursors.waitcursor; funcionou.
Hans Rudel
4
A ampulheta não era exibida se eu usasse Application.DoEvents () após Cursor.Current = Cursors.WaitCursor No entanto, funcionou sem Application.DoEvents (). Não tenho certeza porque
Vbp
14
É melhor usar Application.UseWaitCursor = trueeApplication.UseWaitCursor = false
Gianpiero
169

Na realidade,

Cursor.Current = Cursors.WaitCursor;

define temporariamente o cursor Wait, mas não garante que o cursor Wait seja exibido até o final da sua operação. Outros programas ou controles dentro do seu programa podem facilmente redefinir o cursor de volta à seta padrão, como acontece de fato quando você move o mouse enquanto a operação ainda está em execução.

Uma maneira muito melhor de mostrar o cursor Wait é definir a propriedade UseWaitCursor em um formulário como true:

form.UseWaitCursor = true;

Isso exibirá o cursor de espera para todos os controles no formulário até você definir esta propriedade como false. Se você deseja que o cursor de espera seja mostrado no nível do aplicativo, você deve usar:

Application.UseWaitCursor = true;
draganstankovic
fonte
Bom saber. Eu estava tentando fazer o mesmo no WPF e acabei com Cursor = Cursors.Wait e Cursor = Cursors.Arrow . Mas não consegui encontrar o Cursor em App
itsho
2
Não foi possível encontrar o UseWaitCursor em Aplicativo!
precisa saber é o seguinte
Descobri que, ao definir form.UseWaitCursor = false no final da operação, ele não redefine o cursor até você mover ou clicar no mouse. OTOH, form.Cursor não tem esse problema. Não consegui que o Cursor.Current funcionasse.
Stewart
39

Com base no anterior, minha abordagem preferida (já que essa é uma ação executada com frequência) é agrupar o código do cursor de espera em uma classe auxiliar IDisposable para que possa ser usado com o uso de () (uma linha de código), use parâmetros opcionais, execute o código dentro e limpe (restaure o cursor) depois.

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

Uso:

using (new CursorWait())
{
    // Perform some code that shows cursor
}
mhapps
fonte
Não tinha visto isso, mas sim abordagem semelhante. Ele faz o backup do cursor atual e depois o restaura, o que pode ser útil se você estiver fazendo alterações pesadas no cursor.
mhapps
29

É mais fácil usar UseWaitCursor no nível do formulário ou da janela. Um caso de uso típico pode se parecer abaixo:

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

Para uma melhor experiência de interface do usuário, você deve usar o Asynchrony de um thread diferente.

dmihailescu
fonte
2
Essa deve ser a resposta ACEITADA. É o único que usa try-finalmente.
John Henckel
1
tenho o meu upvote, eu estava faltando um try-finally na minha implementação
Jack
19

Minha abordagem seria fazer todos os cálculos em um trabalhador em segundo plano.

Então mude o cursor assim:

this.Cursor = Cursors.Wait;

E no evento de término do segmento, restaure o cursor:

this.Cursor = Cursors.Default;

Observe que isso também pode ser feito para controles específicos; portanto, o cursor será a ampulheta somente quando o mouse estiver acima deles.

Amirshk
fonte
@ Malfist: boa abordagem :), então tudo que você precisa fazer é colocar a restauração no evento final e pronto.
Amirshk 14/10/09
4

OK, então eu criei um método assíncrono estático. Isso desativou o controle que inicia a ação e altera o cursor do aplicativo. Ele executa a ação como uma tarefa e aguarda o término. O controle retorna ao chamador enquanto aguarda. Portanto, o aplicativo permanece responsivo, mesmo enquanto o ícone ocupado gira.

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

Aqui está o código do formulário principal

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

Eu tive que usar um logger separado para a ação fictícia (estou usando o Nlog) e meu logger principal está gravando na interface do usuário (uma caixa de rich text). Não consegui exibir o cursor ocupado apenas quando estava sobre um contêiner específico no formulário (mas não tentei muito.) Todos os controles têm uma propriedade UseWaitCursor, mas parece não ter efeito nos controles Eu tentei (talvez porque eles não estavam no topo?)

Aqui está o log principal, que mostra as coisas acontecendo na ordem que esperamos:

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

Com a turma abaixo, você pode fazer a sugestão de Donut "exceção segura".

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

a classe 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
fonte
2

Okey, a visão de outras pessoas é muito clara, mas eu gostaria de acrescentar algumas, como segue:

Cursor tempCursor = Cursor.Current;

Cursor.Current = Cursors.WaitCursor;

//do Time-consuming Operations         

Cursor.Current = tempCursor;
wenha
fonte
2

Para aplicativos Windows Forms, a desativação opcional de um controle de interface do usuário pode ser muito útil. Então, minha sugestão é assim:

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

Uso:

private void UiControl_Click(object sender, EventArgs e)
{
    using (new AppWaitCursor(sender))
    {
        LongRunningCall();
    }
}
HEF
fonte
1

Use isso com o WPF:

Cursor = Cursors.Wait;

// Your Heavy work here

Cursor = Cursors.Arrow;
Abdulrazzaq Alzayed
fonte