Alterar o cursor no WPF às vezes funciona, às vezes não


123

Em vários dos meus controles de usuário, altero o cursor usando

this.Cursor = Cursors.Wait;

quando clico em algo.

Agora eu quero fazer a mesma coisa em uma página WPF em um clique de botão. Quando passo o mouse sobre o botão, o cursor muda para uma mão, mas quando clico nele, não muda para o cursor de espera. Gostaria de saber se isso tem algo a ver com o fato de ser um botão ou porque é uma página e não um controle de usuário? Isso parece um comportamento estranho.

Respostas:


211

Você precisa que o cursor seja um cursor de "espera" apenas quando estiver sobre essa página / controle de usuário específico? Caso contrário, sugiro usar o Mouse.OverrideCursor :

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

Isso substitui o cursor do seu aplicativo e não apenas de uma parte da interface do usuário; portanto, o problema que você está descrevendo desaparece.


Semelhante à minha própria resposta , datada de 3 anos depois (quase exatamente!). I como as respostas a esta pergunta, mas a mais simples é sempre o mais tentador :)
Robin Maben

Essa solução alterará o cursor para "esperar", mas não desabilitará outras entradas do mouse. Tentei usar esta solução e, embora o mouse tenha mudado para o cursor de espera, ainda posso clicar em qualquer elemento da interface do usuário no meu aplicativo WPF sem nenhum problema. Alguma idéia de como posso impedir que o usuário realmente use o mouse durante o cursor de espera está ativo?
Thomas Huber

2
Velho como é e aceito como é, NÃO é a resposta correta. Substituir o cursor do aplicativo é diferente de substituir um cursor de controle (e o segundo apresenta problemas no WPF). A substituição do cursor do aplicativo pode ter efeitos colaterais desagradáveis, por exemplo, uma caixa de mensagem pop-up (erro) pode ser forçada a usar o mesmo cursor substituído erroneamente, enquanto a intenção era substituir apenas enquanto o mouse está passando o mouse sobre o controle real e ativo.
Gábor

64

Uma maneira de fazer isso em nosso aplicativo é usar IDisposable e, em seguida, com using(){}blocos para garantir que o cursor seja redefinido quando terminar.

public class OverrideCursor : IDisposable
{

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

  #region IDisposable Members

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

  #endregion
}

e depois no seu código:

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

A substituição terminará quando: o final da instrução using for atingido ou; se uma exceção for lançada e o controle deixar o bloco de instruções antes do final da instrução.

Atualizar

Para evitar que o cursor pisque, você pode:

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
Solução agradável com a parte de uso. Na verdade, eu escrevi exatamente o mesmo em alguns de nossos projetos (sem a pilha, ou seja). Uma coisa que você pode simplificar no uso é simplesmente escrever: using (new OverrideCursor (Cursors.Wait)) {// faça coisas} em vez de atribuir uma variável que você provavelmente não usará.
Olli

1
Não é necessário. Se você definir Mouse.OverrideCursora nullé desactivado e sem substituições mais longos do cursor do sistema. Se eu estivesse modificando o cursor atual diretamente (ou seja, não substituindo), poderia haver um problema.
Dennis

2
Isso é legal, mas não é seguro se várias visualizações estiverem atualizando o cursor ao mesmo tempo. É fácil entrar em uma condição de corrida em que o cursor do conjunto do ViewA, o ViewB, define um diferente e, em seguida, o ViewA tenta redefinir o cursor (que, em seguida, retira o ViewB da pilha e deixa o cursor do ViewA ativo). Até o ViewB redefinir o cursor, as coisas voltam ao normal.
Simon Gillbee

2
@ SimonGillbee isso é realmente possível - não era um problema que eu tinha 10 anos atrás quando escrevi isso. se você encontrar uma solução, talvez usando a ConcurrentStack<Cursor>, sinta-se à vontade para editar a resposta acima ou adicionar a sua.
Dennis

2
@ Dennis, na verdade, escrevi isso alguns dias atrás (e é por isso que estava procurando SO). Joguei com o ConcurrentStack, mas acabou sendo a coleção errada. A pilha apenas permite que você saia do topo. Nesse caso, você deseja remover do meio da pilha se esse cursor estiver disposto antes que a parte superior da pilha esteja posicionada. Acabei usando a List <T> com ReaderWriterLockSlim para controlar o acesso simultâneo.
Simon Gillbee

38

Você pode usar um acionador de dados (com um modelo de vista) no botão para ativar um cursor de espera.

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

Aqui está o código do modelo de exibição:

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
Também não recebi o voto negativo. Essa resposta é útil quando você estiver usando o MVvM (portanto, sem code-behind) e deseja controlar o cursor para um controle específico. Muito útil.
Simon Gillbee

4
Estou aproveitando os benefícios do MVVM e esta é a resposta perfeita.
G1ga

Eu gosto dessa solução, pois acredito que funcionará melhor com MVVM, viewmodels etc.
Rod

O problema que vejo neste código é que os cursores são "Aguardar" apenas enquanto o mouse está pressionando o botão, mas quando você move o mouse para fora, ele volta a ser uma "Seta".
spiderman

7

Se seu aplicativo usa coisas assíncronas e você está mexendo no cursor do Mouse, provavelmente deseja fazê-lo apenas no thread principal da interface do usuário. Você pode usar o segmento Dispatcher do aplicativo para isso:

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

0

O seguinte funcionou para mim:

ForceCursor = true;
Cursor = Cursors.Wait;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.