Forum Stats

  • 3,757,137 Users
  • 2,251,200 Discussions
  • 7,869,740 Comments

Discussions

JTable editor issue

843806
843806 Member Posts: 49,998
edited Aug 12, 2009 1:35PM in Swing
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

Comments

  • darrylburke
    darrylburke Member Posts: 18,007
    edited Jan 27, 2009 3:22AM
    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
  • 843806
    843806 Member Posts: 49,998
    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;
        }
    
    }
  • aterai
    aterai Member Posts: 223 Bronze Badge
    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);
            }
          }
        });
      }
    }
  • 843806
    843806 Member Posts: 49,998
    Nicely done, thanks!
  • 843806
    843806 Member Posts: 49,998
    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.
  • darrylburke
    darrylburke Member Posts: 18,007
    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
  • aterai
    aterai Member Posts: 223 Bronze Badge
    @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!
  • 843806
    843806 Member Posts: 49,998
    Thanks a lot db. You pointed out quite a few things that I would've missed.
  • 843806
    843806 Member Posts: 49,998
    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.
This discussion has been closed.