javaswingjtabletreetablecelleditor

Lose selection of a JTable cell


When you click on a JTable cell, the row becomes "selected", I want it so that when I click on anything else, it becomes unselected.

I'm thinking of doing this with a mouse listener on the table but not sure how to recognize (click not on table). Any ideas?

This is what I'm trying:

 jTable.addMouseListener(new MouseAdapter(){
                @Override
                public void mouseClicked(MouseEvent e){
                    System.out.println("click");}});

But it only prints click when I click in the first column and surely not when I click on something that's not the table.

When I recognize that event I'd call this method:

public void loseCellFocus()
{
    jTable.getCellEditor().stopCellEditing();
    jTable.clearSelection();
}

Solution

  • Use a FocusListener attached to the JTable, this will tell you when focus moves away from the table.

    See How to Write a Focus Listener for more details

    For example...

        table.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                loseCellFocus();
            }           
        });
    

    This will, of course, only work when keyboard focus is transferred to a new component capable for receiving keyboard focus

    This causes my loseCellFocus() method to be called right away, as soon as I click on any cell it's called

    You could use JTable#setSurrendersFocusOnKeystroke or check to see if the component that focus has been transferred to is a child of the JTable

    For example...

    table.addFocusListener(new FocusAdapter() {
        @Override
        public void focusLost(FocusEvent e) {
            if (e.getOppositeComponent().getParent() != table) {
                loseCellFocus();
            }
        }
    });
    

    Runnable Example...

    Okay, that got messy. Not only did I have to add a FocusListener to the JTable, but I had to make sure one was added to the TableCellEditor component :P

    This is a proof of concept only, I'd have specialised classes which capable for either raising events or triggering the required functionality via some common interface

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.EventQueue;
    import java.awt.event.FocusAdapter;
    import java.awt.event.FocusEvent;
    import javax.swing.DefaultCellEditor;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.JTextField;
    import javax.swing.JViewport;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.border.EmptyBorder;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableCellEditor;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            public TestPane() {
                setLayout(new BorderLayout());
    
                JTable table = new JTable(new DefaultTableModel(10, 10));
    
                JTextField editorField = new JTextField(10);
                editorField.setBorder(new EmptyBorder(1, 1, 1, 1));
                editorField.addFocusListener(new FocusAdapter() {
                    @Override
                    public void focusLost(FocusEvent e) {
                        TableCellEditor cellEditor = table.getCellEditor();
                        if (cellEditor != null) {
                            if (!cellEditor.stopCellEditing()) {
                                cellEditor.cancelCellEditing();
                            }
                        }
    
                        Component gotFocus = e.getOppositeComponent();
                        if (!gotFocus.equals(table)) {
                            table.clearSelection();
                        }
                    }
                });
                DefaultCellEditor editor = new DefaultCellEditor(editorField);
                table.setDefaultEditor(Object.class, editor);
    
                add(new JScrollPane(table));
                table.addFocusListener(new FocusAdapter() {
    
                    @Override
                    public void focusLost(FocusEvent e) {
                        Component gotFocus = e.getOppositeComponent();
                        if (!gotFocus.getParent().equals(table)) {
                            TableCellEditor cellEditor = table.getCellEditor();
                            if (cellEditor != null) {
                                if (!cellEditor.stopCellEditing()) {
                                    cellEditor.cancelCellEditing();
                                }
                            }
                            table.clearSelection();
                        }
                    }
    
                });
    
                JTextField field = new JTextField(10);
                add(field, BorderLayout.SOUTH);
            }
    
        }
    
    }
    

    AWTEventListener Example...

    Okay, didn't really want to go this route as it ends up in a mess of cross conditions, but. Basically this monitors ALL MouseEvent and FocusEvents, it does some backwards summersults to test for valid conditions (as we need to make sure that not only if a JTable is part of the event, but if the editor is part of the event) and based on those results, stops cell editing and clears the selection...

    import java.awt.AWTEvent;
    import java.awt.Component;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.KeyboardFocusManager;
    import java.awt.Toolkit;
    import java.awt.event.AWTEventListener;
    import java.awt.event.FocusEvent;
    import java.awt.event.MouseEvent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.JTextField;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.border.EmptyBorder;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableCellEditor;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            public TestPane() {
                JTable table = new JTable(new DefaultTableModel(5, 5));
                setLayout(new GridBagLayout());
                setBorder(new EmptyBorder(20, 20, 20, 20));
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridwidth = GridBagConstraints.REMAINDER;
                add(new JScrollPane(table), gbc);
                add(new JTextField(10), gbc);
    
                Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
                    @Override
                    public void eventDispatched(AWTEvent event) {
                        if (event instanceof FocusEvent) {
                            FocusEvent focusEvent = (FocusEvent) event;
                            if (focusEvent.getID() == FocusEvent.FOCUS_LOST) {
                                Component focusTo = focusEvent.getOppositeComponent();
                                Component focusFrom = focusEvent.getComponent();
    
                                JTable table = getTableFrom(focusFrom);
                                if (focusTo == null || !focusTo.getParent().equals(table)) {
    
                                    stopCellEditing(table);
                                    clearSelection(table);
    
                                }
                            }
                        } else if (event instanceof MouseEvent) {
                            MouseEvent mouseEvent = (MouseEvent) event;
                            if (mouseEvent.getID() == MouseEvent.MOUSE_CLICKED) {
                                Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
                                JTable table = getTableFrom(focusOwner);
                                System.out.println("     table = " + table);
                                System.out.println("focusOwner = " + focusOwner);
    //                          if ((table != null && mouseEvent.getComponent() != table) || (focusOwner != null && !focusOwner.getParent().equals(table))) {
                                if ((table != null && mouseEvent.getComponent() != table) && (focusOwner != null && !focusOwner.getParent().equals(table))) {
                                    stopCellEditing(table);
                                    clearSelection(table);
                                }
                            }
                        }
                    }
    
                    protected JTable getTableFrom(Component component) {
                        JTable table = null;
                        if (component instanceof JTable) {
                            table = (JTable) component;
                        } else if (component != null && component.getParent() instanceof JTable) {
                            table = (JTable) component.getParent();
                        }
                        return table;
                    }
    
                    protected void clearSelection(JTable table) {
                        if (table != null) {
                            table.clearSelection();
                        }
                    }
    
                    protected void stopCellEditing(JTable table) {
    
                        if (table != null) {
                            TableCellEditor cellEditor = table.getCellEditor();
                            if (cellEditor != null) {
                                if (!cellEditor.stopCellEditing()) {
                                    cellEditor.cancelCellEditing();
                                }
                            }
                        }
                    }
                }, AWTEvent.FOCUS_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
            }
    
        }
    
    }
    

    This example basically works for ALL JTables (once the AWTEventListener is registered), but you could configure it to monitor a single table, by changing some of the event sources and comparing them to each other :P