9 Replies Latest reply on Aug 12, 2009 5:35 PM by 843806

    JTable editor issue

    843806
      Hi guys

      I'm sure this question has been asked before, but I must've been searching with the wrong keywords. I have a custom component containing a JTextField, and a JButton. I have a cell editor that should use an instance of my component for editing. The problem is, when a cell is selected (not editing) and I start typing I want whatever I typed to be inserted into the text field, and put the focus on the text field (basically the same behaviour as a DefaultCellEditor with a JTextField). I tried overriding request focus on my component, as I thought that might be called. Can anyone tell me how to get the behaviour that I want? The following (rather messy) code illustrates the problem.

      import java.awt.BorderLayout;
      import java.awt.Component;
      import java.awt.Dimension;
      import java.awt.GridLayout;
      import javax.swing.AbstractCellEditor;
      import javax.swing.JButton;
      import javax.swing.JComponent;
      import javax.swing.JFrame;
      import javax.swing.JScrollPane;
      import javax.swing.JTable;
      import javax.swing.JTextField;
      import javax.swing.table.TableCellEditor;
      
      public class JTableEditorTest {
      
          public static void main(String[] args) {
              JFrame frame = new JFrame("Editor Test");
              frame.setBounds(20, 20, 400, 400);
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setLayout(new GridLayout(1, 1));
              
              JTable table = new JTable(new Object[][] {{"", ""}}, new Object[] {"col1", "col2"});
              
              table.getColumnModel().getColumn(0).setCellEditor(new CustomComponentCellEditor(new CustomComponent()));
              
              frame.add(new JScrollPane(table));
              
              frame.setVisible(true);
          }
      
      }
      
      class CustomComponent extends JComponent {
          
          protected JTextField textField;
          protected JButton button;
          
          public CustomComponent() {
              textField = new JTextField();
              button = new JButton();
              
              button.setPreferredSize(new Dimension(25, 25));
              textField.setPreferredSize(new Dimension(125, 25));
              
              this.setLayout(new BorderLayout());
              
              this.add(button, BorderLayout.EAST);
              this.add(textField, BorderLayout.CENTER);
          }
      
          @Override
          public void requestFocus() {
              System.out.println("Called requestFocus()");
              this.textField.requestFocusInWindow();
          }
      }
      
      class CustomComponentCellEditor extends AbstractCellEditor implements TableCellEditor {
          
          private CustomComponent component;
      
          public CustomComponentCellEditor(CustomComponent component) {
              this.component = component;
          }
          
          public Object getCellEditorValue() {
              return this.component.textField.getText();
          }
      
          public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
              System.out.println("Getting table cell editor component");
              this.component.requestFocus();                  //Tried this
              this.component.textField.setCaretPosition(0);   //and this
              return this.component;
          }
      }
      Thanks a lot,
      Regards
        • 1. Re: JTable editor issue
          darrylburke
          Use ComponentListener#componentShown and wrap the requestFocus in a SwingUtilities#invokeLater to be sure of consistent behavior.
          import java.awt.BorderLayout;
          import java.awt.Component;
          import java.awt.Dimension;
          import java.awt.GridLayout;
          import java.awt.event.ComponentAdapter;
          import java.awt.event.ComponentEvent;
          import javax.swing.AbstractCellEditor;
          import javax.swing.JButton;
          import javax.swing.JComponent;
          import javax.swing.JFrame;
          import javax.swing.JScrollPane;
          import javax.swing.JTable;
          import javax.swing.JTextField;
          import javax.swing.SwingUtilities;
          import javax.swing.table.TableCellEditor;
          
          public class JTableEditorTest {
          
             public static void main(String[] args) {
                SwingUtilities.invokeLater(new Runnable() {
          
                   public void run() {
                      JFrame frame = new JFrame("Editor Test");
                      frame.setBounds(20, 20, 400, 400);
                      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                      frame.setLayout(new GridLayout(1, 1));
          
                      JTable table = new JTable(new Object[][]{{"", ""}, {"", ""}}, new Object[]{
                                 "col1",
                                 "col2"
                              });
          
                      table.getColumnModel().getColumn(0).setCellEditor(new CustomComponentCellEditor(
                              new CustomComponent()));
          
                      frame.add(new JScrollPane(table));
          
                      frame.setVisible(true);
                   }
                });
             }
          }
          
          class CustomComponent extends JComponent {
          
             protected JTextField textField;
             protected JButton button;
          
             public CustomComponent() {
                textField = new JTextField();
                button = new JButton();
          
                button.setPreferredSize(new Dimension(25, 25));
                textField.setPreferredSize(new Dimension(125, 25));
          
                this.setLayout(new BorderLayout());
          
                this.add(textField, BorderLayout.CENTER);
                this.add(button, BorderLayout.EAST);
          
                this.addComponentListener(new ComponentAdapter() {
          
                   @Override
                   public void componentShown(ComponentEvent e) {
                      SwingUtilities.invokeLater(new Runnable() {
          
                         public void run() {
                            CustomComponent.this.textField.requestFocusInWindow();
                         }
                      });
                   }
                });
             }
          }
          
          class CustomComponentCellEditor extends AbstractCellEditor implements
                  TableCellEditor {
          
             private CustomComponent component;
          
             public CustomComponentCellEditor(CustomComponent component) {
                this.component = component;
             }
          
             public Object getCellEditorValue() {
                return this.component.textField.getText();
             }
          
             public Component getTableCellEditorComponent(JTable table, Object value,
                     boolean isSelected, int row, int column) {
                return this.component;
             }
          }
          db
          • 2. Re: JTable editor issue
            843806
            Hi db

            Thank you for your solution, but the problem still exists. I apologise if I was unclear in my original post. When the user starts typing while a cell is selected, but the editor isn't opened yet, I want the character typed to be passed to the editor, and the text field should be selected, with the caret just after the character passed from the table. Any ideas on how I can do that?

            Below is my updated code. I have overridden isCellEditable() to insert the character into the text field (this is probably not be the best way to do it), and to check for a double click before starting editing. (I think this will make it easier to demonstrate the problem.)
            import java.awt.BorderLayout;
            import java.awt.Component;
            import java.awt.Dimension;
            import java.awt.GridLayout;
            import java.awt.event.ComponentAdapter;
            import java.awt.event.ComponentEvent;
            import java.awt.event.KeyEvent;
            import java.awt.event.MouseEvent;
            import java.util.EventObject;
            import javax.swing.AbstractCellEditor;
            import javax.swing.JButton;
            import javax.swing.JComponent;
            import javax.swing.JFrame;
            import javax.swing.JScrollPane;
            import javax.swing.JTable;
            import javax.swing.JTextField;
            import javax.swing.SwingUtilities;
            import javax.swing.table.TableCellEditor;
             
            public class JTableEditorTest {
             
               public static void main(String[] args) {
                  SwingUtilities.invokeLater(new Runnable() {
             
                     public void run() {
                        JFrame frame = new JFrame("Editor Test");
                        frame.setBounds(20, 20, 400, 400);
                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                        frame.setLayout(new GridLayout(1, 1));
             
                        JTable table = new JTable(new Object[][]{{"", ""}, {"", ""}}, new Object[]{
                                   "col1",
                                   "col2"
                                });
             
                        table.getColumnModel().getColumn(0).setCellEditor(new CustomComponentCellEditor(
                                new CustomComponent()));
             
                        frame.add(new JScrollPane(table));
             
                        frame.setVisible(true);
                     }
                  });
               }
            }
             
            class CustomComponent extends JComponent {
             
               protected JTextField textField;
               protected JButton button;
             
               public CustomComponent() {
                  textField = new JTextField();
                  button = new JButton();
             
                  button.setPreferredSize(new Dimension(25, 25));
                  textField.setPreferredSize(new Dimension(125, 25));
             
                  this.setLayout(new BorderLayout());
             
                  this.add(textField, BorderLayout.CENTER);
                  this.add(button, BorderLayout.EAST);
             
                  this.addComponentListener(new ComponentAdapter() {
             
                     @Override
                     public void componentShown(ComponentEvent e) {
                        SwingUtilities.invokeLater(new Runnable() {
             
                           public void run() {
                              CustomComponent.this.textField.requestFocusInWindow();
                           }
                        });
                     }
                  });
               }
            }
             
            class CustomComponentCellEditor extends AbstractCellEditor implements
                    TableCellEditor {
             
               private CustomComponent component;
             
               public CustomComponentCellEditor(CustomComponent component) {
                  this.component = component;
               }
             
               public Object getCellEditorValue() {
                  return this.component.textField.getText();
               }
             
               public Component getTableCellEditorComponent(JTable table, Object value,
                       boolean isSelected, int row, int column) {
                  return this.component;
               }
            
                @Override
                public boolean isCellEditable(EventObject evt) {
                    if (evt instanceof MouseEvent) {
                        if (((MouseEvent)evt).getClickCount() == 2) {
                            return true;
                        } else {
                            return false;
                        }
                    } else if (evt instanceof KeyEvent) {
                        component.textField.setText(String.valueOf(((KeyEvent)evt).getKeyChar()));
                    }
                    
                    return true;
                }
            
            }
            • 3. Re: JTable editor issue
              terai
              Hi
              How about to use HierarchyListener:
              import java.awt.*;
              import java.awt.event.*;
              import javax.swing.*;
              import javax.swing.table.*;
              public class JTableEditorTest3 {
                public static void main(String[] args) {
                  SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                      JFrame frame = new JFrame("Editor Test");
                      frame.setBounds(200, 200, 400, 400);
                      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                      frame.setLayout(new GridLayout(1, 1));
                      JTable table = new JTable(
                        new Object[][] {{"", "", ""}, {"", "", ""}},
                        new Object[] {"col1", "col2", "default"});
                      table.getColumnModel().getColumn(0).setCellEditor(
                        new CustomComponentCellEditor());
                      table.getColumnModel().getColumn(1).setCellEditor(
                        new CustomComponentCellEditor2(new JTextField()));
                      frame.add(new JScrollPane(table));
                      frame.setVisible(true);
                    }
                  });
                }
              }
              class CustomComponentCellEditor extends AbstractCellEditor
                  implements TableCellEditor {
                protected final JTextField textField;
                protected JButton button;
                private final JPanel panel = new JPanel(new BorderLayout());
                public CustomComponentCellEditor() {
                  super();
                  textField = new JTextField();
                  button = new JButton();
                  button.setPreferredSize(new Dimension(25, 25));
                  textField.setBorder(BorderFactory.createEmptyBorder(0,2,0,0));
                  panel.add(textField);
                  panel.add(button, BorderLayout.EAST);
                  textField.addHierarchyListener(new HierarchyListener() {
                    public void hierarchyChanged(HierarchyEvent e) {
                      if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED)!=0
                          && textField.isShowing()) {
                        System.out.println("hierarchyChanged: SHOWING_CHANGED");
                        textField.requestFocusInWindow();
                      }
                    }
                  });
                }
                public Object getCellEditorValue() {
                  System.out.println("  "+textField.getText());
                  return textField.getText();
                }
                public Component getTableCellEditorComponent(JTable table, Object value,
                    boolean isSelected, int row, int column) {
                  return panel;
                }
                public boolean isCellEditable(java.util.EventObject evt) {
                  System.out.println("isCellEditable");
                  if (evt instanceof MouseEvent) {
                    if (((MouseEvent)evt).getClickCount() == 2) {
                      return true;
                    } else {
                      return false;
                    }
                  } else if (evt instanceof KeyEvent) {
                    textField.setText(String.valueOf(((KeyEvent)evt).getKeyChar()));
                  }
                  return true;
                }
              }
              class CustomComponentCellEditor2 extends DefaultCellEditor
                    implements TableCellEditor {
                protected final JButton button = new JButton();
                public CustomComponentCellEditor2(final JTextField textField) {
                  super(textField);
                  textField.add(button);
                  textField.setBorder(BorderFactory.createEmptyBorder(0,2,0,25));
                  textField.addHierarchyListener(new HierarchyListener() {
                    public void hierarchyChanged(HierarchyEvent e) {
                      if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED)!=0
                          && textField.isShowing()) {
                        System.out.println("hierarchyChanged: SHOWING_CHANGED");
                        Rectangle r = textField.getBounds();
                        button.setBounds(r.width-25, 0, 25, r.height);
                      }
                    }
                  });
                }
              }
              • 4. Re: JTable editor issue
                843806
                Nicely done, thanks!
                • 5. Re: JTable editor issue
                  843806
                  Darryl.Burke wrote:
                  Use ComponentListener#componentShown and wrap the requestFocus in a SwingUtilities#invokeLater to be sure of consistent behavior.
                  I would like to know why you recommended the SwingUtilities#invokeLater part. Wouldn't that start an unnecessary thread? Unless my understanding of SwingUtilities#invokeLater is totally wrong.
                  • 6. Re: JTable editor issue
                    darrylburke
                    I'll explain the invokeLater at the end of the post.

                    This is my upgraded attempt, with a custom editor that extends DefaultCellEditor to use its default functionality.
                    import java.awt.BorderLayout;
                    import java.awt.Component;
                    import java.awt.Dimension;
                    import java.awt.GridLayout;
                    import java.awt.event.KeyEvent;
                    import java.util.EventObject;
                    import javax.swing.DefaultCellEditor;
                    import javax.swing.JButton;
                    import javax.swing.JComponent;
                    import javax.swing.JFrame;
                    import javax.swing.JScrollPane;
                    import javax.swing.JTable;
                    import javax.swing.JTextField;
                    import javax.swing.SwingUtilities;
                    
                    public class JTableEditorTest {
                    
                       public static void main(String[] args) {
                          SwingUtilities.invokeLater(new Runnable() {
                    
                             public void run() {
                                JFrame frame = new JFrame("Editor Test");
                                frame.setBounds(20, 20, 400, 400);
                                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                frame.setLayout(new GridLayout(1, 1));
                    
                                JTable table = new JTable(new Object[][]{{"", ""}, {"", ""}},
                                        new Object[]{"col1", "col2"});
                    
                                table.getColumnModel().getColumn(0).
                                        setCellEditor(new CustomComponentCellEditor(
                                        new CustomComponent()));
                    
                                frame.add(new JScrollPane(table));
                                frame.setVisible(true);
                             }
                          });
                       }
                    }
                    
                    class CustomComponent extends JComponent {
                    
                       protected JTextField textField;
                       protected JButton button;
                    
                       public CustomComponent() {
                          textField = new JTextField();
                          button = new JButton();
                    
                          button.setPreferredSize(new Dimension(25, 25));
                          textField.setPreferredSize(new Dimension(125, 25));
                    
                          this.setLayout(new BorderLayout());
                    
                          this.add(textField, BorderLayout.CENTER);
                          this.add(button, BorderLayout.EAST);
                       }
                    }
                    
                    class CustomComponentCellEditor extends DefaultCellEditor {
                    
                       private CustomComponent component;
                       private JTextField textField;
                    
                       public CustomComponentCellEditor(CustomComponent component) {
                          super(component.textField);
                          this.component = component;
                          textField = component.textField;
                       }
                    
                       @Override
                       public Component getComponent() {
                          return component.textField;
                       }
                    
                       @Override
                       public boolean isCellEditable(final EventObject anEvent) {
                          if (anEvent instanceof KeyEvent) {
                             final KeyEvent keyEvent = (KeyEvent) anEvent;
                             
                             SwingUtilities.invokeLater(new Runnable() {
                    
                                public void run() {
                                   if (!Character.isIdentifierIgnorable(keyEvent.getKeyChar())) {
                                      textField.setText(textField.getText() + keyEvent.getKeyChar());
                                   }
                                   textField.setCaretPosition(textField.getText().length());
                                   textField.requestFocusInWindow();
                                }
                             });
                          }
                          return super.isCellEditable(anEvent);
                       }
                    
                       @Override
                       public Component getTableCellEditorComponent(JTable table, Object value,
                               boolean isSelected, int row, int column) {
                          textField.setText(value.toString());
                          return this.component;
                       }
                    }
                    Now.

                    First, the ComponentListener was a bad idea, it didin't do what I thought it would.

                    SwingUtilities#invokeLater does not start a new Thread. Read the API.
                    Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread.

                    Wrapping your custom code in invokeLater allows all pending Swing events (and there usually are some!) to be executed before your code. It's often seen that a requestFocus "doesn't work" -- because there are other events in the pipeline that steal the focus from your component of choce the instant after it gets it.

                    I haven't read the other solution offered, but you need to take care of ignorable characters, else if you press <Delete> (and maybe some other characters -- haven't tested exhaustively) to start editing, you'll get a non-printing character (displayed in most Windows fonts as a square box) in the text field.

                    db
                    • 7. Re: JTable editor issue
                      terai
                      @Override
                      public boolean isCellEditable(final EventObject anEvent) {
                      if (anEvent instanceof KeyEvent) {
                      final KeyEvent keyEvent = (KeyEvent) anEvent;
                      
                      SwingUtilities.invokeLater(new Runnable() {
                      
                      public void run() {
                      if (!Character.isIdentifierIgnorable(keyEvent.getKeyChar())) {
                      textField.setText(textField.getText() + keyEvent.getKeyChar());
                      }
                      textField.setCaretPosition(textField.getText().length());
                      textField.requestFocusInWindow();
                      }
                      });
                      }
                      return super.isCellEditable(anEvent);
                      }
                      @Override
                      public Component getTableCellEditorComponent(JTable table, Object value,
                      boolean isSelected, int row, int column) {
                      textField.setText(value.toString());
                      return this.component;
                      }
                      }
                      It is useful for me too :)
                      thx!
                      • 8. Re: JTable editor issue
                        843806
                        Thanks a lot db. You pointed out quite a few things that I would've missed.
                        • 9. Re: JTable editor issue
                          843806
                          I am not sure whether to start a new forum or to wake this forum up. So apologies if I am wrong.

                          I used the code submitted by DarrylBurke to solve the same problem that I was having (the problem of this topic), particularly the "isCellEditable(final EventObject anEvent)" where the request focus code is. Everything seemed to work fine but now I noticed that pressing "F2" doesn't get the focus in the selected cell. I can post an SSCCE if required.

                          regards
                          nirvan.