Here's a message I am plagued by, which occurs when I attempt to programmatically "select" cells (whether empty or not) in a grid
via keypress combo shift-rightarrow
or shift-leftarrow
:
Exception in thread "AWT-EventQueue-0" javax.swing.text.StateInvariantError:
Bad caret position
(Note that there is NO problem if I "select" via shift-uparrow
or shift-downarrow
.)
It happens when I attempt to change the font of the "selected" cells:
static Font fontSelected = new Font("Serif", Font.BOLD , POINTSIZE);
static Font fontNormal = new Font("Serif", Font.PLAIN, POINTSIZE);
(If I make the Font.type the SAME (both BOLD, both PLAIN, both ITALIC), no problem.)
The error occurs near code where I push
a "selected" JTextField
onto a stack (named stack
), which is defined like so:
class GenericStack<E>:
public LinkedList <E> stack = new LinkedList<>();
Here's the class declaration where the stack and fonts are used:
public class Grid extends GenericStack<JTextField> implements ActionListener, KeyListener, KeyCodes, Serializable
Here's what's pushed onto stack
:
public static JTextField[][] cells = new JTextField[N][N];
Here's how cells
are created:
guiFrame.add(textPanel);
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
cells[i][j] = addCell(textPanel, i, j);
private JTextField addCell (Container parent, int row, int col) {
JTextField cell;
cell = new JTextField();
cell.setFont(fontNormal); // 'default' font set
cell.setText("x"); // for debugging
String r, c; // 11x11 grid
if(row < N-1) r = "" + row; else r = "A"; // rows r: 0,1,2,...A
if(col < N-1) c = "" + col; else c = "A"; // cols c: 0,1,2,...A
cell.setActionCommand(r + c); // cell rc: 00..0A;10..1A;...A0..AA;
cell.addKeyListener(this);
cell.setHorizontalAlignment(JTextField.CENTER);
parent.add(cell);
return cell;
}
Here's main:
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Grid();
}
});
}
Here's where the font is changed (for any "selected" cell):
if(currentCell.selected){
Grid.cells[currentCell.row][currentCell.col].setBackground(Color.RED);
Grid.cells[currentCell.row][currentCell.col].setFont(fontSelected);
stack.push(Grid.cells[currentCell.row][currentCell.col]);
}
The error occurs in this block of code--if I comment out the setFont
line, no problem; if I instead change the font declarations to involve the same font, no problem.
Especially puzzling me is that the stack trace doesn't specify which line of code caused the error.
I'm not sure why your exception is occurring, but it can be solved by queuing the font change on the Swing event thread:
@Override
public void keyPressed(KeyEvent evt) {
final JComponent comp = (JComponent) evt.getSource();
int keyCode = evt.getKeyCode();
boolean shiftIsDown = evt.isShiftDown();
currentCell.selected = ((shiftIsDown & (keyCode == RIGHT | keyCode == UP
| keyCode == LEFT | keyCode == DOWN)));
if (currentCell.selected) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
comp.setFont(fontSelected);
}
});
}
}
Myself, I try to avoid KeyListeners with Swing applications but instead prefer key bindings. For example:
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
@SuppressWarnings("serial")
public class SSCCE2 extends JPanel {
private static final int ROW_COUNT = 11;
private static final int colCount = 3;
private static final Font NORMAL_FONT = new Font("Serif", Font.PLAIN, 18);
private static final Font SELECTED_FONT = NORMAL_FONT.deriveFont(Font.BOLD);
private JTextField[][] fields = new JTextField[ROW_COUNT][ROW_COUNT];
public SSCCE2() {
FontAction fontAction = new FontAction();
int condition = WHEN_FOCUSED;
setLayout(new GridLayout(ROW_COUNT, ROW_COUNT));
for (int i = 0; i < fields.length; i++) {
for (int j = 0; j < fields[i].length; j++) {
JTextField cell = new JTextField(colCount);
InputMap inputMap = cell.getInputMap(condition);
ActionMap actionMap = cell.getActionMap();
int[] arrowKeyCodes = {KeyEvent.VK_UP, KeyEvent.VK_DOWN,
KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT};
for (int keyCode : arrowKeyCodes) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode,
KeyEvent.SHIFT_DOWN_MASK);
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), fontAction);
}
cell.setFont(NORMAL_FONT);
cell.setHorizontalAlignment(JTextField.CENTER);
add(cell);
fields[i][j] = cell;
}
}
}
private class FontAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent evt) {
for (JTextField[] row : fields) {
for (JTextField textField : row) {
if (textField.hasFocus()) {
textField.setFont(SELECTED_FONT);
} else {
textField.setFont(NORMAL_FONT);
}
}
}
}
}
private static void createAndShowGui() {
SSCCE2 mainPanel = new SSCCE2();
JFrame frame = new JFrame("SSCCE2");
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();
}
});
}
}