Como acionar os botões de uma calculadora através do teclado

Um dos exercícios favoritos de vários professores de Java é pedir para o aluno criar uma calculadora. Tudo vai bem, até que o aluno resolve fazer com que os botões sejam automaticamente acionados pelo teclado. Então, o inferno começa.

Por que? Simplesmente, porque o local adequado para tratar esse tipo de evento não é nos KeyListeners, que provavelmente foram ensinados em sala de aula. Um keylistener só trata a ação de tecla no componente onde foi pressionado, logo, seria necessário incluir keylisteners de todas as teclas, em todos os componentes, o que não é nada prático.

Então, qual seria o local certo? Como dizer para o Java tratar teclas que foram pressionadas “no geral”?

Todo componente Swing tem associado a ele dois mapas. O InputMap e o ActionMap. O InputMap associa uma tecla a um nome, enquanto o ActionMap associa esse nome a uma ação. Os componentes tem três tipos de InputMaps:

  1. WHEN_IN_FOCUSED_WINDOW: Dispara sempre que a tecla é pressionada quando a janela estiver em foco. Não importa se sobre o componente ou não.
  2. WHEN_FOCUSED: Dispara quando a tecla foi pressionada no momento em que o componente estava em foco;
  3. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT: Dispara quando uma tecla foi pressionada no ancestral imediato do componente, geralmente, o painel onde o componente está.

No caso da calculadora, nos interessa o primeiro caso.

O código abaixo, cria um painel, tipico de todas as aplicações de calculadora, e associa a esse painel os botões de 0 até 9.
Para cada botão é criada uma ação, correspondente ao pressionar de sua tecla. Essa ação é associada primeiramente ao próprio botão, pois será disparada também quando o botão for clicado.

Depois, criamos um apelido para cada ação no painel principal da calculadora. E, então, associamos esse apelido ao pressionar das teclas 0 até 9, tanto do teclado principal, quanto do teclado numérico. Como a ação será disparada sempre que a janela estiver em foco, usamos para isso o InputMap WHEN_IN_FOCUSED_WINDOW.

Os demais botões foram deixados sem função. Desculpe decepcionar quem veio aqui em busca da solução completa desse exercício, mas a idéia desse tópico foi simplesmente fazer com que os alunos dedicados parem de perder suas noites de sono nessa funcionalidade e não resolver o problema da calculadora em si.

O ActionMap e o InputMap também são ideais para registrar eventos em jogos.

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 acaoBotao1 = new BotaoNumericoAction(1);
	private BotaoNumericoAction acaoBotao2 = new BotaoNumericoAction(2);
	private BotaoNumericoAction acaoBotao3 = new BotaoNumericoAction(3);
	private BotaoNumericoAction acaoBotao4 = new BotaoNumericoAction(4);
	private BotaoNumericoAction acaoBotao5 = new BotaoNumericoAction(5);
	private BotaoNumericoAction acaoBotao6 = new BotaoNumericoAction(6);
	private BotaoNumericoAction acaoBotao7 = new BotaoNumericoAction(7);
	private BotaoNumericoAction acaoBotao8 = new BotaoNumericoAction(8);	
	private BotaoNumericoAction acaoBotao9 = new BotaoNumericoAction(9);
	private BotaoNumericoAction acaoBotao0 = new BotaoNumericoAction(0);

	public CalculadoraFrame()
	{
		super("Calculadora");
		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(acaoBotao7));
		pnlBotoes.add(new JButton(acaoBotao8));
		pnlBotoes.add(new JButton(acaoBotao9));
		pnlBotoes.add(new JButton("/"));

		pnlBotoes.add(new JButton(acaoBotao4));
		pnlBotoes.add(new JButton(acaoBotao5));
		pnlBotoes.add(new JButton(acaoBotao6));
		pnlBotoes.add(new JButton("*"));

		pnlBotoes.add(new JButton(acaoBotao1));
		pnlBotoes.add(new JButton(acaoBotao2));
		pnlBotoes.add(new JButton(acaoBotao3));
		pnlBotoes.add(new JButton("-"));
		
		pnlBotoes.add(new JButton(acaoBotao0));
		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) {
		//Damos um nome para cada ação. Esse nome é útil pois mais de 
		//uma tecla pode ser associada a cada ação, como veremos abaixo
		ActionMap actionMap = painel.getActionMap();
		actionMap.put("botao1", acaoBotao1);
		actionMap.put("botao2", acaoBotao2);
		actionMap.put("botao3", acaoBotao3);
		actionMap.put("botao4", acaoBotao4);
		actionMap.put("botao5", acaoBotao5);
		actionMap.put("botao6", acaoBotao6);
		actionMap.put("botao7", acaoBotao7);
		actionMap.put("botao8", acaoBotao8);
		actionMap.put("botao9", acaoBotao9);
		actionMap.put("botao0", acaoBotao0);
		painel.setActionMap(actionMap);
		
		//Pegamos o input map que ocorre sempre que a janela atual está em foco
		InputMap imap = painel.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
		
		//Associamos o pressionar das teclas (keystroke) aos eventos.
		//O nome do KeyStroke pode ser obtido através da classe KeyEvent.
		//Lá está cheio de constantes como KeyEvent.VK_NUMPAD1. 
		//Essa string é o nome sem o VK_
		
		//Teclas da parte de cima do teclado.
		imap.put(KeyStroke.getKeyStroke("1"), "botao1");
		imap.put(KeyStroke.getKeyStroke("2"), "botao2");
		imap.put(KeyStroke.getKeyStroke("3"), "botao3");
		imap.put(KeyStroke.getKeyStroke("4"), "botao4");
		imap.put(KeyStroke.getKeyStroke("5"), "botao5");
		imap.put(KeyStroke.getKeyStroke("6"), "botao6");
		imap.put(KeyStroke.getKeyStroke("7"), "botao7");
		imap.put(KeyStroke.getKeyStroke("8"), "botao8");
		imap.put(KeyStroke.getKeyStroke("9"), "botao9");
		imap.put(KeyStroke.getKeyStroke("0"), "botao0");	
		
		//Botões do teclado numérico
		imap.put(KeyStroke.getKeyStroke("NUMPAD1"), "botao1");
		imap.put(KeyStroke.getKeyStroke("NUMPAD2"), "botao2");
		imap.put(KeyStroke.getKeyStroke("NUMPAD3"), "botao3");
		imap.put(KeyStroke.getKeyStroke("NUMPAD4"), "botao4");
		imap.put(KeyStroke.getKeyStroke("NUMPAD5"), "botao5");
		imap.put(KeyStroke.getKeyStroke("NUMPAD6"), "botao6");
		imap.put(KeyStroke.getKeyStroke("NUMPAD7"), "botao7");
		imap.put(KeyStroke.getKeyStroke("NUMPAD8"), "botao8");
		imap.put(KeyStroke.getKeyStroke("NUMPAD9"), "botao9");
		imap.put(KeyStroke.getKeyStroke("NUMPAD0"), "botao0");	
	}
	
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable()
		{
			@Override
			public void run() {
				new CalculadoraFrame().setVisible(true);
			}
		});
	}	
}

Obviamente, muito código poderá ser reduzido usando a instrução for. Mas preferi deixar de maneira mais explícita, por ser mais didático.

1 curtida

resolvido

Na verdade, isso já foi tão perguntado aqui no GUJ, que resolvi criar esse tópico e adicionar aos meus favoritos.
Agora já tenho um local para linkar quando a dúvida surgir no futuro (e vai surgir). :wink:

aham

Pode crer parcero
Bom, obrigado por sua ajuda
ate mais
Flwww

Esse post deveria passar para os Artigos!!!
Muito bom.

[quote=gabrielmskate]Esse post deveria passar para os Artigos!!!
Muito bom.[/quote]

Concordo.

Este tópico já me ajudou muito.

[quote=ArchV][quote=gabrielmskate]Esse post deveria passar para os Artigos!!!
Muito bom.[/quote]

Concordo.

Este tópico já me ajudou muito.[/quote]

Concordo[2].

Vini, seguindo os seus artigos no Ponto V, implementei os exemplos propostos e em cima dos mesmos começei a fazer alterações e cheguei em um ponto parecido…

Para implementar eventos de teclas apertadas simultaneamente, esse método que você implementou acima resolve?

Boa Viny!
Parabéns!
D+
:smiley: :smiley:
vlw

[quote=Hellmanss]Vini, seguindo os seus artigos no Ponto V, implementei os exemplos propostos e em cima dos mesmos começei a fazer alterações e cheguei em um ponto parecido…

Para implementar eventos de teclas apertadas simultaneamente, esse método que você implementou acima resolve?

[/quote]

No caso de jogos, você precisa tratar os eventos de KeyDown e KeyUp mesmo. Até pq geralmente vc precisa saber que essas teclas se mantiveram pressionadas.

Muito bom o post.
Me ajudou. Funciona certinho. Visitei este exemplo tb: http://www.daniweb.com/forums/thread125682.html, funciona certinho.
Mas tenho a seguinte dificuldade. Se eu usar o NetBenas para gerar o código automaticamente, como posso adicionar um listener no meu formulario? Tentei colocar o evento keyPressed, keyTyped, keyReleased direto no código e também colocar os eventos de forma visual pelas propriedades do formulário, dai ele cria: formKeyReleased, formKeyPressed e formKeyTyped, mas nenhum deles funciona. Preciso que quando o usuario clique no botão chame uma função. Por enquanto estou apenas tentando fazer aparecer uma menssagem na tela, usando JOptionPane.showMessageDialog.

Este tópico realmente foi muito esclarecedor. Parabéns Vini.

colega, e se eu usar ao inves de um JPanel, um JFrame?

Olá! Gostaria de ajuda no seguinte problema.

Tenho um JFrame com 4 JButton nomeados de A, B, C e D respectivamente, e um JLabel nomeado de status.

Quero que cada JButton possa setar um texto no JLabel. Até ai tudo bem, já fiz isso.
No caso, ao clicar em A o texto do JLabel muda para “A” e assim sucessivamente.

Agora preciso que cada JButton responda a sua respectiva letra do teclado.
Exemplo: Ao pressionar a tecla C, o JLabel mude para “C”. Ao pressionar a tecla A, o JLabel mude para “A”. Sem que o foco esteja sobre o JButton correspondente.

Desde já, agradeço pela ajuda.
No aguardo…

E qual é o problema? Basta usar o que descrevo no primeiro post.

Boa noite.

ViniGodoy, gostaria de lhe dar uma opinião, a de fixar este post por alguns meses no início do Fórum, porque sei que certamente, como você disse, haverá dúvidas no futuro.

Um abraço.

Vini,
Tenho uma duvida: na minha aplicacao gostaria de tratar somente a tecla ‘alt’ sem combinacao com outras teclas, no evento keyPressed e keyRelease do Jpanel. No JtextField eu consigo, mas no Jpanel nao.
Tentei usar sua solucao e funcionou perfeitamente para a combinacao de ‘alt’ + outra tecla, porem sem ser combinada, ou seja, pressionando somente ‘alt’ nao funcionou.

Vc teria alguma outra sugestao?

Desde já agradeço a atencao.

[quote=AlissonGuj]resolvido
[/quote]

Consegui perceber como acionar os botões de uma calculadora através do teclado.
Mas o outro problema é controlar a última entrada de um dado numérico, separa-lo dos sinais de operação para efetuar a operação com próximo número.
Por favor ajudem aí.

Repetindo:

Desculpe, mas não fazemos lição de casa.

Eu nunca tive a intenção de mostrar como resolver os problemas efetivamente relacionados ao trabalho escolar de “fazer uma calculadora”.

O tópico serve só para ajudar a resolver esse problema específico, que não é óbvio, não é ensinado nas aulas básicas de Swing, mas que acaba incomodando bastante quem se dedida.

Consulte o material do seu professor para o resto, pois lá deve ter informação suficiente para concluir o trabalo.