[RESOLVIDO]Teclado - InputMap e ActionMap

10 respostas
L

Bom pessoal, lendo os seguintes tópicos:

http://www.guj.com.br/java/75002-duvida----actionmap-e-inputmap
http://www.guj.com.br/java/140986-como-acionar-os-bots-de-uma-calculadora-atrav-do-teclado

Ainda não consegui entender direito esse lance do InputMap e ActionMap.
Não adiantaria nada eu pegar copiar e colar sem entender, então o que fiz? O de sempre, ler a API.

Lendo sobre o InputMap vi que ele é uma associação da tecla( geralmente do teclado ) com um Objeto. Geralmente InputMap é usado com o ActionMap, que determina a AÇÃO quando a tecla é APERTADA. Se eu estiver errado, me corrijam. Até ai entendi.

Depois vi o método put que está nos exemplos. Ele recebe dois parâmetros, um KeyStroke( que é uma combinação de teclas ) e um objeto( um classe que extenda Object, ou seja toda classe do mundo, mas nesse caso, geralmente ActionMap ).

Esta linha eu entendi:

InputMap iMap = painelTeclado.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ); // recebe objeto Imap quando a janela está com foco

Agora o restante eu não entendi. Entendi apenas que o primeiro parâmetro a tecla que eu quero associar a um método actionMap, mas porque o segundo parâmetro está como string? Não deveria ser um método de um objeto ActionMap?

imap.put(KeyStroke.getKeyStroke("0"), "panel.button0press");    
imap.put(KeyStroke.getKeyStroke("1"), "panel.button1press");  
imap.put(KeyStroke.getKeyStroke("2"), "panel.button2press");

E:

ActionMap amap = panel.getActionMap();     

//Mapeie os nomes de ação (aqueles, fornecidos na string ali de cima), para as ações em si:

amap.put("panel.buttonF1press", new HelpAction());     
amap.put("panel.buttonF2press", new SaveAction());     
amap.put("panel.buttonF3press", new ClearAction());

Isso quer dizer que o segundo parâmetro do método put de InputMap, deve ser um nome apenas REPRESENTATIVO, que está associado ao verdadeiro método que irá resolver o que fazer com a tecla apertada e essa associação ocorre no método put do ActionMap?

10 Respostas

E

É isso mesmo, porque você pode atribuir a mesma Action a várias coisas, não só a uma tecla, mas a um botão, a escolher uma opção do menu, digitar algo em um JTextField e mover para o próximo campo etc. - Todas essas coisas aceitam “Actions”.
E é por isso que o InputMap não chama a Action direto e sim chama o identificador da Action, que é uma String por comodidade.

L

entanglement:
É isso mesmo, porque você pode atribuir a mesma Action a várias coisas, não só a uma tecla, mas a um botão, a escolher uma opção do menu, digitar algo em um JTextField e mover para o próximo campo etc. - Todas essas coisas aceitam “Actions”.
E é por isso que o InputMap não chama a Action direto e sim chama o identificador da Action, que é uma String por comodidade.

Mas no caso do InputMap.put(), ele usa um KeyStroke. Como seria com um JButton e/ou JTextField? Poderia dar um exemplo para esclarecer melhor minha mente?
E no caso o segundo parâmetro dos ActionMap são objetos de classes internas que tratam os “eventos” dos KeyStroke( nessa caso sei que não são eventos mas não sei o nome certo ). Obrigado

L

Fiz do seguinte modo e não funcionou quando eu aperto 0 no teclado( não númerico )

public void executaAcao() {
        /** * pega o InputMap sempre que a janela atual está em foco */
        InputMap iMap  = painelTeclado.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW );
        ActionMap aMap = painelTeclado.getActionMap();
        
        iMap.put( KeyStroke.getKeyStroke("0"), "painel.botao0apertado" );    
        iMap.put( KeyStroke.getKeyStroke("1"), "painel.botao1apertado" );  
        iMap.put( KeyStroke.getKeyStroke("2"), "painel.botao2apertado" );
        
        aMap.put("painel.botao0apertado", new ZeroAction());       
        //aMap.put("painel.botao1apertado", new UmAction());       
        //aMap.put("painel.botao2apertado", new DoisAction());    
    }
    
    class ZeroAction extends AbstractAction{
        @Override
        public void actionPerformed( ActionEvent e ) {
            areaDeTexto.setText("0");
        }
        
    }
V

E você chamou o método executaAcao()?

L

Posso xingar? Pqp, tenho que xingar, a pior falta de atenção da minha vida¬¬

L

Só mas uma coisinha super simples, eu tenho que criar um classe mesmo para cada tecla( ou seja o teclado quase inteiro ) ou uso verificações? Vi em um post seu para criar classes internas e não usar switch, etc. É melhor fazer isso mesmo? Se sim, por que?

V

Switchs geralmente configuram maus cheiros de códigos (no livro Refatoração, do Martin Fowler, há uma ampla discussão sobre isso).

O código com switch é menos legível e menos eficiente.
O mecanismo de eventos já foi criado para você não ter que usá-lo.

Olhe a idiotice com switch:

a) os botões disparam eventos para lugares separados;

b) Você concentra os eventos, que  estavam separados, num mesmo listener;

c) Você usa o getSource() e um switch para separar os eventos novamente.

Agora, veja no exemplo da calculadora que se o comportamento for similar, você não precisa criar uma classe por tecla. Ali mesmo, eu criei uma classe só para todos os botões. Além disso, lembre-se que os objetos actions são objetos como outros quaisquer. Podem ser colocados em listas ou em arrays (no caso da calculadora, eu poderia ter criado todos os objetos com um for e te-los colocado numa lista. Só não fiz isso por questões didáticas).

L

Switchs geralmente configuram maus cheiros de códigos (no livro Refatoração, do Martin Fowler, há uma ampla discussão sobre isso).

O código com switch é menos legível e menos eficiente.
O mecanismo de eventos já foi criado para você não ter que usá-lo.

Olhe a idiotice com switch:

a) os botões disparam eventos para lugares separados;

b) Você concentra os eventos, que  estavam separados, num mesmo listener;

c) Você usa o getSource() e um switch para separar os eventos novamente.

Agora, veja no exemplo da calculadora que se o comportamento for similar, você não precisa criar uma classe por tecla. Ali mesmo, eu criei uma classe só para todos os botões. Além disso, lembre-se que os objetos actions são objetos como outros quaisquer. Podem ser colocados em listas ou em arrays (no caso da calculadora, eu poderia ter criado todos os objetos com um for e te-los colocado numa lista. Só não fiz isso por questões didáticas).

Ótima explicação. Obrigado.

V
A título de exemplo, o mesmo código da calculadora com for e array:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;

public class CalculadoraFrame extends JFrame {
	private JPanel pnlPrincipal;
	private JTextField txtVisor;
	private JPanel pnlBotoes;

	// Criação das ações dos botões.
	private BotaoNumericoAction acaoBotoes[] = new BotaoNumericoAction[10];

	public CalculadoraFrame() {
		super("Calculadora");
		
		// Cria as ações dos botões
		for (int i = 0; i < 10; i++)
			acaoBotoes[i] = new BotaoNumericoAction(i);
		
		setContentPane(getPnlPrincipal());
		setSize(200, 250);
		setLocationRelativeTo(null);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	private JPanel getPnlPrincipal() {
		if (pnlPrincipal != null)
			return pnlPrincipal;

		pnlPrincipal = new JPanel(new BorderLayout());
		pnlPrincipal.add(getTxtVisor(), BorderLayout.NORTH);
		pnlPrincipal.add(getPnlBotoes(), BorderLayout.CENTER);
		registrarAcoesDoTeclado(pnlPrincipal);
		return pnlPrincipal;
	}

	private JTextField getTxtVisor() {
		if (txtVisor != null)
			return txtVisor;

		txtVisor = new JTextField();
		txtVisor.setEditable(false);
		txtVisor.setHorizontalAlignment(JTextField.RIGHT);
		return txtVisor;
	}

	private JPanel getPnlBotoes() {
		if (pnlBotoes != null)
			return pnlBotoes;
		pnlBotoes = new JPanel();
		pnlBotoes.setLayout(new GridLayout(4, 4));

		// Associamos os botões as suas respectivas ações.
		// Isso só associará a ação ao clique do botão.

		pnlBotoes.add(new JButton(acaoBotoes[7]));
		pnlBotoes.add(new JButton(acaoBotoes[8]));
		pnlBotoes.add(new JButton(acaoBotoes[9]));
		pnlBotoes.add(new JButton("/"));

		pnlBotoes.add(new JButton(acaoBotoes[4]));
		pnlBotoes.add(new JButton(acaoBotoes[5]));
		pnlBotoes.add(new JButton(acaoBotoes[6]));
		pnlBotoes.add(new JButton("*"));

		pnlBotoes.add(new JButton(acaoBotoes[1]));
		pnlBotoes.add(new JButton(acaoBotoes[2]));
		pnlBotoes.add(new JButton(acaoBotoes[3]));
		pnlBotoes.add(new JButton("-"));

		pnlBotoes.add(new JButton(acaoBotoes[0]));
		pnlBotoes.add(new JButton("C"));
		pnlBotoes.add(new JButton("="));
		pnlBotoes.add(new JButton("+"));

		return pnlBotoes;
	}

	// Ações para o botão numérico. Ela simplesmente concatena o número ao final
	// do texto do visor.
	private class BotaoNumericoAction extends AbstractAction {
		private int numero;

		public BotaoNumericoAction(int numero) {
			super(Integer.toString(numero));
			this.numero = numero;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			getTxtVisor().setText(getTxtVisor().getText() + numero);
		}
	}

	private void registrarAcoesDoTeclado(JPanel painel) {
		ActionMap actionMap = painel.getActionMap();
		InputMap imap = painel.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
		for (int i = 0; i < 10; i++) {
			String actionName = "botao" + i;
			actionMap.put(actionName, acaoBotoes[i]);
			imap.put(KeyStroke.getKeyStroke("" + i), actionName);
			imap.put(KeyStroke.getKeyStroke("NUMPAD" + i), actionName);
		}
	}

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				new CalculadoraFrame().setVisible(true);
			}
		});
	}
}
L

Oxi Oxi, melhor impossível, Salvar os dois códigos aqui para análise.

Criado 15 de maio de 2012
Ultima resposta 15 de mai. de 2012
Respostas 10
Participantes 3