Search code examples
javaswingfontsjtextfieldgetcaretpos

Bad caret position message


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.


Solution

  • 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();
             }
          });
       }
    }