How to use arrow keys, like up, down, from the keyboard to navigate a list of buttons?

Asked

Viewed 558 times

1

I have these buttons implemented with mouse click, only I wanted the following: that I could navigate through them direction keys up and down and enter through the enter. Does anyone know how it implements? It’s the buttons: btInitialize, btAbout and btSair.

I know it’s right that I have something done for you to help, but I’ve only done the ActionListener buttons. Keyboard actions I really don’t know how to use.

Follow the code I have:

package ju;

import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class Botoes extends JFrame {

private JPanel contentPane;
private JButton btIniciar;
private JButton btSobre;
private JButton btSair;

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                Botoes frame = new Botoes();
                frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

public Botoes() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setBounds(100, 100, 450, 300);
    contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    setContentPane(contentPane);
    contentPane.setLayout(null);

    btIniciar = new JButton("Iniciar");
    btIniciar.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
            System.out.println("foi clicado");
        }
    });
    btIniciar.setBounds(146, 101, 125, 37);
    contentPane.add(btIniciar);

    btSobre = new JButton("Sobre");
    btSobre.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("foi clicado");
        }
    });
    btSobre.setBounds(146, 156, 125, 37);
    contentPane.add(btSobre);

    btSair = new JButton("Sair");
    btSair.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    });
    btSair.setBounds(145, 213, 126, 37);
    contentPane.add(btSair);
}
}

I put it in my code, but it didn’t work (I ran it and it worked, but I only sent a sketch). Is it the way my buttons are? Take a look at what my code actually looks like.

package visao;

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import controle.Constantes;


public class TelaInicial extends JFrame {

    private JPanel painelTI;
    private boolean seJogando;
    private JButton btIniciarTI;
    private JButton btSobreTI;
    private JButton btSairTI;

    private JLabel lblImgTelaInicialTI;
    private JLabel lblTextNomeDoJogo;
    public static int coluna=0;
    JPanel p1;

    public TelaInicial() {

        setFocusable(true);
        setUndecorated(true);   
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize( Constantes.ALTURA, Constantes.LARGURA);
        setLocationRelativeTo(null);
        painelTI = new JPanel();
        painelTI.setLayout(null);
        setContentPane(painelTI);

        lblTextNomeDoJogo = new JLabel("");
        lblTextNomeDoJogo.setForeground(Color.WHITE);
        lblTextNomeDoJogo.setFont(GerenciarFonte.FontePlain(47));
        lblTextNomeDoJogo.setBounds(242, 250, 633, 46);
        painelTI.add(lblTextNomeDoJogo);

        btIniciarTI = new JButton("Iniciar");
        btIniciarTI.setFont(GerenciarFonte.FontePlain(44));
        btIniciarTI.setForeground(Color.WHITE);
        btIniciarTI.setFocusable(false);
        btIniciarTI.setContentAreaFilled(false);
        btIniciarTI.setFocusPainted(true);
        btIniciarTI.setBounds(445, 350, 185, 47);
        painelTI.add(btIniciarTI);


        btSobreTI = new JButton("Sobre");
        btSobreTI.setFont(GerenciarFonte.FontePlain(42));
        btSobreTI.setForeground(Color.WHITE);//laranja 255 69 0
        btSobreTI.setFocusable(false);
        btSobreTI.setBorderPainted(true);
        btSobreTI.setContentAreaFilled(false);
        btSobreTI.setFocusPainted(true);
        btSobreTI.setBounds(461, 414, 156, 41);
        painelTI.add(btSobreTI);

        btSairTI = new JButton("Sair");
        btSairTI.setFont(GerenciarFonte.FontePlain(42));//RockoUltraFLF
        btSairTI.setForeground(Color.WHITE);
        btSairTI.setFocusable(false);
        btSairTI.setBorderPainted(true);
        btSairTI.setContentAreaFilled(false);
        btSairTI.setFocusPainted(true);
        btSairTI.setBounds(471, 472, 131, 37);
        painelTI.add(btSairTI);

        OrdenadorDeFoco of = new OrdenadorDeFoco(this);
        of.configurarOrdem(btIniciarTI, btSobreTI, btSairTI);

        lblImgTelaInicialTI = new JLabel();
        lblImgTelaInicialTI.setIcon(new ImageIcon(getClass().getResource("/imagem/11.jpg")));
        lblImgTelaInicialTI.setBounds(0, 0,Constantes.ALTURA, Constantes.LARGURA);
        painelTI.add(lblImgTelaInicialTI);
        setResizable(false);
        setVisible(true);

    }

    public void paint(Graphics g){
        super.paint(g);
        g.setColor(Color.WHITE);
        g.drawRect(436, 341, 201, 58);
        g.drawRect(436, 408, 201, 47);
        g.drawRect(436, 463, 201, 47);
    }


    public class Panel extends JGradientPanel {

        private static final long serialVersionUID = 1L;

        public void paintComponent(Graphics g) {
            super.paintComponent(g);


            g.setColor(new Color(255,69,0));
            g.draw3DRect( 414, 273, 180, 37, true );

            g.draw3DRect( 414, 363, 180, 37, true );

            g.draw3DRect( 414, 453, 180, 37, true ); 
        }


        public Panel(Color initialColor, Color finalColor) {
            super(initialColor, finalColor);
        }
    }

    public JButton getBtIniciarTI() {
        return btIniciarTI;
    }

    public JButton getBtSairTI() {
        return btSairTI;
    }

    public JButton getBtSobreTI() {
        return btSobreTI;
    }

    public boolean isSeJogando() {
        return seJogando;
    }

    public void setSeJogando(boolean seJogando) {
        this.seJogando = seJogando;
    }

}
package visao;

import java.awt.Font;
import java.awt.FontFormatException;
import java.io.IOException;

public class GerenciarFonte {

public Font carregarFonte(String caminho, int tipo, int tamanho) {
    Font minhaFonte = null;

    try {

        minhaFonte = Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream(caminho)).deriveFont(tipo,
                tamanho);

    } catch (IOException e) {
        // TODO: handle exception
    } catch (FontFormatException e) {
        // TODO: handle exception
    }

    return minhaFonte;
}

public static Font FontePlain(int tamanho){
    GerenciarFonte f = new GerenciarFonte();
    Font cooper = f.carregarFonte("/fonte/cooper-black.ttf", Font.PLAIN, tamanho);
    return cooper;
}

public static Font FonteBold(int tamanho){
    GerenciarFonte f = new GerenciarFonte();
    Font cooper = f.carregarFonte("/fonte/cooper-black.ttf", Font.BOLD, tamanho);
    return cooper;
}

public static Font FontePlainListen(int tamanho){
    GerenciarFonte f = new GerenciarFonte();
    Font cooper = f.carregarFonte("/fonte/TypoSlabserif-Light.ttf", Font.BOLD, tamanho);
    return cooper;
}

}
package visao;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import javax.swing.JPanel;

public class JGradientPanel extends JPanel {
    private Color finalColor;
    private Color initialColor;

public JGradientPanel(Color initialColor, Color finalColor) {
    if (initialColor == null)
        throw new IllegalArgumentException("Invalid initial color!");
    if (finalColor == null)
        throw new IllegalArgumentException("Invalid final color!");
    this.initialColor = initialColor;
    this.finalColor = finalColor;
}

public void setInitialColor(Color color) {
    this.initialColor = color;
    invalidate();
}

public void setFinalColor(Color color) {
    this.finalColor = color;
    invalidate();
}

public Color getInitialColor() {
    return initialColor;
}

public Color getFinalColor() {
    return finalColor;
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    if (!isOpaque()) {
        return;
    }
    GradientPaint paint = new GradientPaint(new Point2D.Float(getWidth() / 2, 0), initialColor,
            new Point2D.Float(getWidth() / 2, getHeight()), finalColor);
    g2d.setPaint(paint);
    g2d.fillRect(0, 0, getWidth(), getHeight());
    g2d.dispose();
}
}
public abstract class Constantes {

public static final int ALTURA = 1000;
public static final int LARGURA = 700;


}

this class focus sorter , using MVC, it should be on which layer?

Botões como quero

  • 2

    Then add what you have done so far, in the form of [mcve], otherwise it is complicated to help. The answer below may not solve the problem, since keyevent only captures events and does not navigate. If you provide an example, it is easier to help.

  • This is swing, javafx, android (think not, but go that) or something else?

  • This is swing. I put the code I have

  • You only have the 3 buttons even on this screen? There are no other components not ne?

  • Can’t be with TAB not? If it is with tab, you do not need to implement much. If it is with arrows, there is more complicated because it has to mess with Focus subsystem and it’s quite boring to implement.

  • Without the class code Constantes, GerenciarFonte and JGradientPanel and the icon "/imagem/11.jpg" it becomes more complicated to answer. I tried to kick the values of constants, comment on the lines of GerenciarFonte and the icon and replace JGradientPanel for JPanel, but the result was just an empty gray screen.

  • Ah, and by the way, the OrdenadorDeFoco is in the vision layer.

  • Ah, I kicked higher values for the constants and the buttons appeared.

  • put the other classes

Show 4 more comments

1 answer

3


I think it would be something like that (version 2.0):

import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Map;
import java.util.WeakHashMap;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JWindow;
import javax.swing.KeyStroke;

public class OrdenadorDeFoco {

    private static final String UP = "up";
    private static final String DOWN = "down";
    private static final String ENTER = "enter";

    private final Map<Component, Component> upMap = new WeakHashMap<>(20);
    private final Map<Component, Component> downMap = new WeakHashMap<>(20);

    private Component primeiro;

    public OrdenadorDeFoco(JFrame window) {
        this(window, (JComponent) window.getContentPane());
    }

    public OrdenadorDeFoco(JDialog window) {
        this(window, (JComponent) window.getContentPane());
    }

    public OrdenadorDeFoco(JWindow window) {
        this(window, (JComponent) window.getContentPane());
    }

    private OrdenadorDeFoco(Window window, JComponent contentPane) {
        InputMap im = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), UP);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, 0), UP);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), DOWN);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, 0), DOWN);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ENTER);

        contentPane.getActionMap().put(UP, new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Component este = window.getFocusOwner();
                Component depois = este == null ? null : upMap.get(este);
                if (depois == null) depois = primeiro;
                depois.requestFocusInWindow();
            }
        });

        contentPane.getActionMap().put(DOWN, new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Component este = window.getFocusOwner();
                Component depois = este == null ? null : downMap.get(este);
                if (depois == null) depois = primeiro;
                depois.requestFocusInWindow();
            }
        });

        contentPane.getActionMap().put(ENTER, new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Component este = window.getFocusOwner();
                if (!(este instanceof AbstractButton)) return;
                ((AbstractButton) este).doClick();
            }
        });

        window.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                int k = e.getKeyCode();
                if (k == KeyEvent.VK_UP || k == KeyEvent.VK_KP_UP || k == KeyEvent.VK_DOWN || k == KeyEvent.VK_KP_DOWN) {
                    Component agora = window.getFocusOwner();
                    if (agora == null) primeiro.requestFocusInWindow();
                }
            }
        });
    }

    public void configurarOrdem(Component... elementos) {
        int n = elementos.length;
        if (primeiro == null && n > 0) primeiro = elementos[0];

        for (int i = 0; i < n; i++) {
            Component antes = elementos[(i + n - 1) % n]; 
            Component este = elementos[i];
            Component depois = elementos[(i + n + 1) % n];
            upMap.put(este, antes);
            downMap.put(este, depois);
        }
    }
}

That class OrdenadorDeFoco is responsible for doing the magic. In the builder, she receives the JFrame, JDialog or JWindow with which she will work. The trick is in the two Maps that map which is the button above and which is the button below each other button (actually, not necessarily button, can be any component).

The InputMap and the contentPane.getActionMap() are used to sequester the default behavior of the above and below arrow keys (including the numeric keyboard) and the two keys enter to take custom actions with her.

After building an instance of OrdenadorDeFoco, the method configurarOrdem is used to sort buttons. You can call it multiple times with the same instance of a mapper to apply the sequence to multiple groups of buttons.

The KeyAdapter serves in case the focus is with the screen without being with any of its components, in which case the first element added with configurarOrdem will receive the focus.

The WeakHashMap is used so that components that are key to the Map can be collected as garbage even if they are in the Map. This serves to prevent the bug that would occur from OrdenadorDeFoco prevent garbage collection of components that are no longer anywhere.

The OrdenadorDeFoco is a component in the MVC vision layer. The reason for this is that it deals directly with the components of the graphical interface, but does not worry at all about what happens when you click on one of them.

Here’s a way to use this (version 2.0 too):

import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;

public class TesteBotoes {

    private static final Color VERDE_FOLHA = new Color(47, 126, 95);

    public static void main(String[] args) {
        EventQueue.invokeLater(TesteBotoes::criarTela);
    }

    private static void criarTela() {
        JFrame jf = new JFrame("Teste");
        jf.setFocusable(true);
        jf.setUndecorated(true);
        jf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        JPanel jp = new JPanel();
        jp.setBorder(new EmptyBorder(5, 5, 5, 5));
        jp.setBackground(VERDE_FOLHA);
        GridLayout g = new GridLayout(3, 1);
        g.setVgap(10);
        jp.setLayout(g);
        JButton btIniciar = criarBotao(jp, "Iniciar", e -> System.out.println("Iniciar"));
        JButton btSobre = criarBotao(jp, "Sobre", e -> System.out.println("Sobre"));
        JButton btSair = criarBotao(jp, "Sair", e -> jf.dispose());
        jf.setContentPane(jp);

        OrdenadorDeFoco of = new OrdenadorDeFoco(jf);
        of.configurarOrdem(btIniciar, btSobre, btSair);

        jf.setLocationRelativeTo(null);
        jf.setSize(145, 151);
        jf.setVisible(true);
    }

    private static JButton criarBotao(Container pai, String titulo, ActionListener acao) {
        JButton botao = new JButton(titulo);
        botao.setBackground(VERDE_FOLHA);
        botao.setForeground(Color.WHITE);
        botao.setBorder(new LineBorder(Color.WHITE));
        botao.addActionListener(acao);
        pai.add(botao);
        return botao;
    }
}

You can use the method criarBotao to configure the buttons, including changing the font, color, etc. I decided to use a GridLayout instead of the null layout.

If you use the algumBotao.setFocusable(false);, probably bad or weird things will happen.

This code of OrdenadorDeFoco should solve your problem for now, although I personally did not like it very much and I think it can create other problems after.

  • This subfocus system is a use bag, I never understood why they complicated so much something that should be simpler to do in the API.

  • Although repairing better, you did not use focus subsystem, used keybindings. I traveled legal haha

  • Almost everything in the javax.swing is detestable. rs

  • 1

    @Gabriel Yeah, swing is a hard-to-work monster. If it had been designed more carefully, appreciated for simplicity and with less haste, surely Java would have been spread in the desktop environment much more than it was.

  • edited my question

  • @Gabriella and will not work even, you removed the focus of all buttons, if the buttons can not receive focus, impossible to access them using keyboard. Remove the setFocusable(false); of all the buttons that will work;

  • removed, but had no change

  • @Gabriella Reply updated.

  • @Victorstafusa Really this. I appreciate the help. Get well!

Show 4 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.