How to make two JPanels listen to the same event?

java, swing

Solution

Instead of `KeyListener`, use Key Bindings and have distinct `Action` implementations for each panel. By using the `WHEN_ANCESTOR_OF_FOCUSED_COMPONENT` input map, both panels can respond.

Addendum: Because the search ends after finding a valid binding for the key, the example below forwards the event to the elements of a `List<MyPanel>`, each of which can respond differently via an available `Action`.

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

/** @see http://stackoverflow.com/q/10011564/230513 */
public class TwoPanelsTest extends JFrame {

    private MyPanel one = new MyPanel("One");
    private MyPanel two = new MyPanel("Two");
    private List<MyPanel> list = Arrays.asList(one, two);

    public TwoPanelsTest() {
        super("TwoPanelsTest");
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        JPanel panel = new JPanel(new GridLayout(0, 1, 10, 10));
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        panel.add(one);
        panel.add(two);
        panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
            .put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
        panel.getActionMap().put("up", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                for (MyPanel panel : list) {
                    panel.getAction().actionPerformed(e);
                }
            }
        });
        this.add(panel);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private static class MyPanel extends JPanel {

        private String string = " will be updated though its action.";
        private Action action = new UpdateAction(this);
        private String name;
        private JLabel label;

        public MyPanel(String name) {
            this.name = name;
            this.label = new JLabel(name + string, JLabel.CENTER);
            this.setLayout(new GridLayout());
            this.setFocusable(true);
            this.add(label);
        }

        public Action getAction() {
            return action;
        }

        private void update() {
            label.setText(name + ": " + System.nanoTime());
        }

        private static class UpdateAction extends AbstractAction {

            private MyPanel panel;

            public UpdateAction(MyPanel panel) {
                this.panel = panel;
            }

            @Override
            public void actionPerformed(ActionEvent ae) {
                panel.update();
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                TwoPanelsTest t = new TwoPanelsTest();
            }
        });
    }
}

Problem

I have a `JFrame` and, inside this `JFrame` there are two `JPanel`s. When I press a key, both of them must listen to this key event and act. I want to take all the keyboard events, and deliver them to both of the `JPanel`s. Do you know how to do it? Edit: Since they must do different things, I need two different listeners, sorry for not being specific. Edit2: I made a simple code to show you the problem. When I press the up key, both of the `JPanel`s displayed must change their string; in this code only one of them actually react! ``` import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.KeyStroke; /** * * @author antonioruffolo */ public class TwoPanelsTest extends JFrame { public TwoPanelsTest() { setDefaultCloseOperation(EXIT_ON_CLOSE); setResizable(false); setSize(800, 600); PanelTest panelTest1= new PanelTest(); PanelTest panelTest2= new PanelTest(); GridBagLayout layout= new GridBagLayout(); this.setLayout(layout); GridBagConstraints c = new GridBagConstraints(); c.ipadx = 220; c.ipady = 390; c.insets.right= 0; c.insets.left=30; layout.setConstraints(panelTest1, c); this.add(panelTest1); layout.setConstraints(panelTest2, c); c.ipadx = 220; c.ipady = 390; c.insets.right=250; c.insets.left=50; this.add(panelTest2); setVisible(true); setLocationRelativeTo(null); setTitle("Test"); setFocusable(false); } private class PanelTest extends JPanel{ private String string="I'm not called by the event"; private InputMap inputmap; private ActionMap actionmap; public PanelTest(){ setFocusable(false); setDoubleBuffered(true); this.setBackground(Color.WHITE); inputmap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up"); actionmap = getActionMap(); actionmap.put("up", new ActionController(this)); } public void setString(String string){ this.string=string; } @Override public void paintComponent( Graphics g){ super.paintComponent(g); Font infoFont= new Font("OCR A Std", Font.BOLD, 10); g.setFont(infoFont); g.drawString(string, 10, 50); } }//PanelTest private class ActionController extends AbstractAction{ private PanelTest panel; public ActionController (PanelTest panel){ this.panel=panel; } @Override public void actionPerformed(ActionEvent ae) { panel.setString("Action performed"); panel.repaint(); } } public static void main(String[] args) { TwoPanelsTest t = new TwoPanelsTest(); } } ```

Original source

Related problems