Durante a implementação de uma tela Swing, surgiu a necessidade de capturar as teclas que eram digitadas no formulário antes que elas fossem enviadas aos componentes (de forma similar à funcionalidade KeyPreview do Delphi).
De início, tentei apenas criar um KeyListener para o JFrame, mas isto não funcionava se o foco estivesse em algum dos controles-filho do mesmo, ou seja, os eventos de tecla sempre estavam indo somente para o controle que estivesse focado, não importando quem fosse o pai.
Procurei por alternativas mas não encontrei. O máximo que eu consegui foi este código http://exampledepot.com/egs/java.awt/DispatchKey.html, que no entanto captura os eventos de tecla da VM inteira, não se restringindo ao frame atual.
Baseado no código acima, bolei a seguinte classe utilitária:
/**
* <p>
* Como o Java não possui nenhum recurso similar à propriedade KeyPreview
* do Delphi, o recurso teve que ser implementado por meio desta classe
* utilitária.
* </p>
* <p>
* Basicamente, utiliza-se {@link java.awt.KeyboardFocusManager}
* para capturar todos os eventos de tecla do sistema e verifica-se se o
* componente que disparou o evento ou um ou mais de seus pais está registrado
* no objeto KeyPreviewManager. Os observadores sao executados na ordem do mais
* abrangente para o menos abrangente.
* </p>
*
* @author Haroldo de Oliveira Pinheiro
*/
public class KeyPreviewManager {
private static KeyPreviewManager instance;
private Map<Component, Stack<KeyListener>> listeners;
private KeyPreviewManager() {
this.listeners = new WeakHashMap<Component, Stack<KeyListener>>();
this.captureKeyEvents();
}
public static KeyPreviewManager getInstance() {
if (instance == null) {
instance = new KeyPreviewManager();
}
return instance;
}
public void addKeyListener(Component target, KeyListener listener) {
Stack<KeyListener> targetListeners = this.listeners.get(target);
if (targetListeners == null) {
targetListeners = new Stack<KeyListener>();
this.listeners.put(target, targetListeners);
}
targetListeners.push(listener);
}
private void captureKeyEvents() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(
new KeyEventDispatcher() {
public boolean dispatchKeyEvent(KeyEvent e) {
return instance.dispatchKeyEvent(e);
}
});
}
private boolean dispatchKeyEvent(KeyEvent e) {
return this.dispatchKeyEvent(e.getComponent(), e);
}
private boolean dispatchKeyEvent(Component target, KeyEvent e) {
if (target == null) {
return false;
}
if (this.dispatchKeyEvent(target.getParent(), e)) {
return true;
}
Stack<KeyListener> targetListeners = this.listeners.get(target);
if (targetListeners != null) {
PreviewedKeyEvent pke = new PreviewedKeyEvent(target, e);
for (KeyListener listener : targetListeners) {
if (this.dispatchKeyListenerEvent(pke, listener)) {
return true;
}
}
}
return false;
}
private boolean dispatchKeyListenerEvent(PreviewedKeyEvent pke, KeyListener listener) {
switch (pke.getID()) {
case KeyEvent.KEY_PRESSED:
listener.keyPressed(pke);
break;
case KeyEvent.KEY_RELEASED:
listener.keyReleased(pke);
break;
case KeyEvent.KEY_TYPED:
listener.keyTyped(pke);
break;
}
return pke.isConsumed();
}
public static class PreviewedKeyEvent extends KeyEvent {
private Component container;
private KeyEvent cause;
public PreviewedKeyEvent(Component container, KeyEvent cause) {
super(container, cause.getID(), cause.getWhen(), cause.getModifiers(), cause.getKeyCode(), cause.getKeyChar(), cause.getKeyLocation());
this.container = container;
this.cause = cause;
}
public Component getContainer() {
return this.container;
}
public KeyEvent getCause() {
return this.cause;
}
}
}
Esta classe, basicamente, utiliza o KeyboardFocusManager para capturar os eventos de tecla, olha se o evento é pertinente a algum dos componentes registrados (ou seja, se o evento ocorreu no próprio componente ou num de seus filhos), e o despacha para os respectivos listeners.
A pergunta é: a forma como eu implementei pode causar algum efeito colateral? Existe alguma forma de implementar a funcionalidade “dando menos voltas”?