0

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();
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Aequitas
  • 2,205
  • 1
  • 25
  • 51

2 Answers2

1

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

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Sounds perfect! Trying now – Aequitas Aug 13 '15 at 05:14
  • This causes my `loseCellFocus()` method to be called right away, as soon as I click on any cell it's called – Aequitas Aug 13 '15 at 05:18
  • You could use [`JTable#setSurrendersFocusOnKeystroke`](http://docs.oracle.com/javase/7/docs/api/javax/swing/JTable.html#setSurrendersFocusOnKeystroke(boolean)) or check to see if the component that focus has been transferred to is a child of the `JTable` – MadProgrammer Aug 13 '15 at 05:24
  • Thankyou for your help, unfortunately neither of those two suggestions worked. I'm guessing it's because of my table renderer or editor, so i'll try looking through those and see if I can get it to work with what you've told me. Thanks. – Aequitas Aug 13 '15 at 05:31
  • I've added a working example, it got a little complicated :P – MadProgrammer Aug 13 '15 at 06:15
  • I've managed to get it working somewhat after reading your example, thanks! However it only works if the click that makes it lose focus can have a focus. So if I select a cell, and then click a (differen't table's) cell then the original cell will no longer be selected, which is good, same if I click the buttons on the page, or even the minimize button. However if I click on just the background then nothing happens. – Aequitas Aug 13 '15 at 22:26
  • @Aequitas Yeah, that's a difficult one. You could use `Toolkit.addAWTEventListener` to monitor both the `focus` and `mouse` events, but it quickly becomes messy. Let me see if I can come up with a working example – MadProgrammer Aug 13 '15 at 22:41
  • I've tried adding a "mouseclicked" mouse listener to the panel to `requestFocus` but that does nothing – Aequitas Aug 13 '15 at 22:42
  • So instead of the panel.requestFocus I can make an arbitrary button gain focus on the panel click, kind of a hack but it's okay I guess, is there a way I can make the button lose focus immediately after gaining focus? `button.unrequestFocus` – Aequitas Aug 13 '15 at 22:52
  • Sorry for asking so many questions but, also it only works if I click on that particular panel, is there a way for if I click on any where in the frame, all children panels of it? – Aequitas Aug 13 '15 at 23:07
  • The problem is trying to come up with a solution which will cover ALL possible scenarios, when you can't possible predict in what kind of hierarchy the table might be placed in. I've done a `AWTEventListener` example, it gets a little convoluted as it needs to do multiple checks against one source of the event or another to determine what it should, if anything at all – MadProgrammer Aug 14 '15 at 00:32
1

I did this sometime back with a JTree. The focus listener etc. did not work. Finally the solution I used is to use Toolkit.getDefaultToolkit().addAWTEventListener and capture keyboard and mouse events. Look for the JTree in the component hierarchy of the event and call a method to remove the selection if the item doesn't belong to the tree.

I can search for the code if you need.

Edit

The AWTEventListener in the accepted answer is a bit more complicated than needed.

        final AWTEventListener focusTracker = new AWTEventListener() {
            @Override public void eventDispatched(AWTEvent event) {
                if (event.getID() != MouseEvent.MOUSE_CLICKED && event.getID() != KeyEvent.KEY_PRESSED)
                    return;
                if (!isPartOfTable((Component) event.getSource())) {
                    if (table.isEditing()) {
                        TableCellEditor cellEditor = table.getCellEditor();
                        cellEditor.cancelCellEditing();
                    }
                    table.clearSelection();
                    table.dispatchEvent(new FocusEvent(table, FocusEvent.FOCUS_LOST));
                }
            }

            protected boolean isPartOfTable(Component component) {
                while (component != null && component != table)
                    component = component.getParent();
                return component == table;
            }

        };
        Toolkit.getDefaultToolkit().addAWTEventListener(focusTracker, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);

Note: You need to remove the listener when the frame is closed. I did not like the table cell still being highlighted when clicked outside. So I am posting a focus lost event to the table.

Dakshinamurthy Karra
  • 5,353
  • 1
  • 17
  • 28