I see one thing in your code that could be a problem and that may be fixed without too much difficulty: you're using KeyListeners. This should in general be avoided in Swing GUI's and instead you should try to use Key Bindings which are more flexible and which do not require that the bound component have focus.
Regarding your GroupLayout sizing issue, I have to admit to being very weak on use of GroupLayout, and in fact I try to avoid its use assiduously. Other layouts to consider include GridBagLayout or the MigLayout.
Edit:
However, I have now read the GroupLayout tutorial, including section labeled "To force a component to be resizable (allow shrinking and growing):". It appears that you must add some parameters when you add your component to the layout which in my code example looks like:
.addComponent(board2, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
For instance, here's my code showing components laid out as desired, and also showing use of KeyBindings and a PropertyChangeListener. Notice that by using Key Bindings, focus is no longer as big of an issue as I don't have to set the JPanel to be focusable nor to give it the focus:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
/**
* uses GroupLayout and Key Bindings
* @link http://stackoverflow.com/questions/14784773/grouplayout-makes-action-listener-loses-focus
* @author Pete
*
*/
@SuppressWarnings("serial")
public class HelloWorldSwing2GroupLayout extends JPanel {
private JLabel[] labels = {new JLabel("A"), new JLabel("B")};
private Board2 board2 = new Board2();
private JLabel directionLabel = new JLabel();
public HelloWorldSwing2GroupLayout() {
directionLabel.setHorizontalAlignment(SwingConstants.CENTER);
board2.add(directionLabel);
board2.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (Board2.DIRECTION.equals(pcEvt.getPropertyName())) {
Direction dir = (Direction)pcEvt.getNewValue();
if (dir != null) {
directionLabel.setText(dir.getText());
} else {
directionLabel.setText("");
}
}
}
});
GroupLayout layout = new GroupLayout(this);
setLayout(layout);
int lWidth = board2.getPreferredSize().width;
int lHeight = board2.getPreferredSize().height / 2;
Dimension preferredSize = new Dimension(lWidth, lHeight);
for (JLabel label : labels) {
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setVerticalAlignment(SwingConstants.CENTER);
label.setBorder(BorderFactory.createLineBorder(Color.black));
// please, please forgive me Jeanette! This is for demo purposes only.
label.setPreferredSize(preferredSize);
}
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
layout.setHorizontalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(labels[0], 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(labels[1], 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(board2, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(layout.createParallelGroup()
.addGroup(layout.createSequentialGroup()
.addComponent(labels[0], 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(labels[1], 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(board2, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
}
private static void createAndShowGui() {
HelloWorldSwing2GroupLayout mainPanel = new HelloWorldSwing2GroupLayout();
JFrame frame = new JFrame("HelloWorldSwing2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
@SuppressWarnings("serial")
class Board2 extends JPanel {
private static final int PREF_W = 200;
private static final int PREF_H = 400;
public static final String DIRECTION = "direction";
private Direction direction = null;
public Board2() {
setBorder(BorderFactory.createTitledBorder("Board2"));
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
for (Direction dir : Direction.values()) {
inputMap.put(dir.getKeyStroke(), dir.getText());
actionMap.put(dir.getText(), new MyArrowBinding(dir));
}
}
private class MyArrowBinding extends AbstractAction {
private Direction dir;
public MyArrowBinding(Direction dir) {
super(dir.getText());
this.dir = dir;
putValue(ACTION_COMMAND_KEY, dir);
}
@Override
public void actionPerformed(ActionEvent e) {
setDirection(dir);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void setDirection(Direction direction) {
Direction oldValue = this.direction;
Direction newValue = direction;
this.direction = newValue;
firePropertyChange(DIRECTION, oldValue, newValue);
}
public Direction getDirection() {
return direction;
}
}
enum Direction {
UP("Up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)),
DOWN("Down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)),
LEFT("Left", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)),
RIGHT("Right", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0));
Direction(String text, KeyStroke keyStroke) {
this.text = text;
this.keyStroke = keyStroke;
}
private String text;
private KeyStroke keyStroke;
public String getText() {
return text;
}
public KeyStroke getKeyStroke() {
return keyStroke;
}
@Override
public String toString() {
return text;
}
}
Which will look like so:
I'm a big fan of using PropertyChangeListeners for this sort of thing since it allows for easy decoupling of your code. Now the Board2 class doesn't have to worry about how other classes react to any changes in its direction. All it must do is broadcast this change to any classes that happening to be listening to it, and they each respond as they see fit.