Existe uma maneira fácil de alterar o comportamento de um controle Java/Swing quando ele recebe foco?
-
09-06-2019 - |
Pergunta
Para a maioria das GUIs que usei, quando um controle que contém texto recebe o foco, todo o conteúdo do controle é selecionado.Isso significa que se você começar a digitar, substituirá completamente o conteúdo anterior.
Exemplo:Você tem controle de rotação inicializado com o valor zero.Você acessa ele e digita "1". O valor no controle agora é 1.
Com o Swing isso não acontece.O texto no controle não é selecionado e o quilate aparece em uma extremidade ou outra do texto existente.Continuando o exemplo acima:
Com um Swing JSpinner, quando você clica no controle de rotação, o quilate fica à esquerda.Você digita “1” e o valor no controle agora é 10.
Isso me deixa (e meus usuários) enlouquecidos e gostaria de mudar isso.Ainda mais importante, gostaria de alterá-lo globalmente para que o novo comportamento se aplique a JTextField, JPasswordField, JFormattedTextField, JTextArea, JComboBox, JSpinner e assim por diante.A única maneira que encontrei de fazer isso é adicionar um FocusAdapter a cada controle e substituir o método focusGained() por Do The Right Thing[tm].
Deve haver uma maneira mais fácil e menos frágil.Por favor?
EDITAR:Uma informação adicional para este caso específico.O formulário com o qual estou trabalhando foi gerado usando o designer de formulários do Idea.Isso significa que normalmente não escrevo o código para criar os componentes.É possível dizer ao Idea que você mesmo deseja criá-los, mas isso é um incômodo que gostaria de evitar.
Lema:Todos os bons programadores são basicamente preguiçosos.
Solução 4
Depois de ler as respostas até agora (Obrigado!) Passei o JPanel mais externo para o seguinte método:
void addTextFocusSelect(JComponent component){
if(component instanceof JTextComponent){
component.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent event) {
super.focusGained(event);
JTextComponent component = (JTextComponent)event.getComponent();
// a trick I found on JavaRanch.com
// Without this, some components don't honor selectAll
component.setText(component.getText());
component.selectAll();
}
});
}
else
{
for(Component child: component.getComponents()){
if(child instanceof JComponent){
addTextFocusSelect((JComponent) child);
}
}
}
}
Funciona!
Outras dicas
Quando precisei disso no passado, criei subclasses dos componentes aos quais queria adicionar também a funcionalidade de "limpeza automática".por exemplo:
public class AutoClearingTextField extends JTextField {
final FocusListener AUTO_CLEARING_LISTENER = new FocusListener(){
@Override
public void focusLost(FocusEvent e) {
//onFocusLost(e);
}
@Override
public void focusGained(FocusEvent e) {
selectAll();
}
};
public AutoClearingTextField(String string) {
super(string);
addListener();
}
private void addListener() {
addFocusListener(AUTO_CLEARING_LISTENER);
}
}
O maior problema é que não encontrei uma maneira "boa" de obter todos os construtores padrão sem escrever substituições.Adicioná-los e forçar uma chamada para addListener é a abordagem mais geral que encontrei.
Outra opção é observar ContainerEvents em um contêiner de nível superior com um ContainerListeer para detectar a presença de novos widgets e adicionar um ouvinte de foco correspondente com base nos widgets que foram adicionados.(por exemplo:se o evento de contêiner for causado pela adição de um TextField, adicione um ouvinte de foco que saiba como selecionar todo o texto em um TextField e assim por diante.) Se um Container for adicionado, será necessário adicionar recursivamente o ContainerListener a esse novo subcontêiner também.
De qualquer forma, você não precisará se preocupar com ouvintes de foco em seu código de UI real - tudo será resolvido em um nível superior.
Eu não tentei isso sozinho (só me envolvi nisso há um tempo), mas você provavelmente pode obter o componente atual em foco usando:KeyboardfocusManager (existe um método estático getCurrentKeyboardfocusManager ()) e adicionando um PropertyChangelistener a ele.A partir daí, você pode descobrir se o componente é um JTextComponent e selecionar todo o texto.
Uma classe separada que anexa um FocusListener ao campo de texto desejado pode ser escrita.Tudo o que o ouvinte de foco faria seria chamar selectAll() no widget de texto quando ele ganhasse o foco.
public class SelectAllListener implements FocusListener {
private static INSTANCE = new SelectAllListener();
public void focusLost(FocusEvent e) { }
public void focusGained(FocusEvent e) {
if (e.getSource() instanceof JTextComponent) {
((JTextComponent)e.getSource()).selectAll();
}
};
public static void addSelectAllListener(JTextComponent tc) {
tc.addFocusListener(INSTANCE);
}
public static void removeSelectAllListener(JTextComponent tc) {
tc.removeFocusListener(INSTANCE);
}
}
Ao aceitar um JTextComponent como argumento, esse comportamento pode ser adicionado diretamente a JTextArea, JPasswordField e todos os outros componentes de edição de texto.Isso também permite que a classe adicione selecionar tudo a caixas de combinação editáveis e JSpinners, onde seu controle sobre o componente do editor de texto pode ser mais limitado.Métodos de conveniência podem ser adicionados:
public static void addSelectAllListener(JSpinner spin) {
if (spin.getEditor() instanceof JTextComponent) {
addSelectAllListener((JTextComponent)spin.getEditor());
}
}
public static void addSelectAllListener(JComboBox combo) {
JComponent editor = combo.getEditor().getEditorComponent();
if (editor instanceof JTextComponent) {
addSelectAllListener((JTextComponent)editor);
}
}
Além disso, os métodos remove listener provavelmente são desnecessários, já que o listener não contém referências externas a quaisquer outras instâncias, mas podem ser adicionados para tornar as revisões de código mais suaves.
A única maneira que conheço é criar um FocusListener e anexá-lo ao seu componente.Se você deseja que este FocusListener seja global para todos os componentes do seu aplicativo, você pode considerar o uso de Programação Orientada a Aspectos (AOP).Com AOP é possível codificá-lo uma vez e aplicar seu ouvinte de foco a todos os componentes instanciados em seu aplicativo sem precisar copiar e colar o componente.addFocusListener(ouvinte) código em todo o seu aplicativo.
Seu aspecto teria que interceptar a criação de um JComponent (ou as subclasses às quais você deseja adicionar esse comportamento) e adicionar o ouvinte de foco à instância recém-criada.A abordagem AOP é melhor do que copiar e colar o FocusListener em todo o seu código porque você mantém tudo em um único trecho de código e não cria um pesadelo de manutenção quando decide alterar seu comportamento global, como remover o ouvinte para JSpinners.
Existem muitas estruturas AOP disponíveis para você escolher.Eu gosto JBossAOP já que é Java 100% puro, mas você pode querer dar uma olhada AspectJ.