Simulando KeyPreview no Swing

3 respostas
H

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

3 Respostas

ViniGodoy

Não sei exatamente o que a função keypreview do delphi faz. Entretanto, acho que uma maneira de dar menos voltas é igual a essa aqui:
http://www.guj.com.br/posts/list/140986.java

Note que ali capturo o digitar os números independente de que componente esteja focado no JFrame.

ViniGodoy

Andei lendo sobre o keypreview, parece que os ActionMaps e InputMaps se aproximam do que você precisa mesmo. Mas, o que exatamente você gostaria de fazer? Por que está implementando essa funcionalidade? Numa dessas, existe uma maneira melhor de, no Java, resolver o seu problema.

H

Bom, basicamente, os requisitos de inteface com usuário que eu procurava atender eram, originalmente, três:

  • Teclas de atalho: ActionMaps + InputMaps resolvem bem este caso;
  • Navegação com : O código em http://java.sun.com/docs/books/tutorial/uiswing/misc/focus.html resolve bem o problema;
  • Se o usuário pressionar qualquer tecla alfanumérica quando não estiver focado em nenhum dos controles de edição, focar automaticamente em um dos controles e repassar esta tecla para o mesmo: ActionMaps + InputMaps poderia atender o caso, embora não me pareça muito elegante para este caso, especificamente.
Criado 18 de dezembro de 2009
Ultima resposta 18 de dez. de 2009
Respostas 3
Participantes 2