AndroidX AndroidX - 1 month ago 17
Java Question

Force JTable row selection if clicking outside visible JPopupMenu

I have added a

JPopupMenu
to a
JTable
with
setComponentPopupMenu
. The problem is that while the
JPopupMenu
is open/visible, when I left-click on a row outside the popup menu, the menu closes but the row is not selected so I have to click again on it to highlight it. Is there any way to fix that?

EDIT

I've added sample code.

By the way this behavior occurs only in Windows LaF. I've just tested it and it appears that the default Java LaF allows for left-click row selection while the JPopupMenu is open.

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

public class TableSTACK {

private static void createAndShowGUI() {

String[] headers = {"Column 1", "Column 2"};

Object[][] data = { {"Row", "1"}, {"Row", "2"},
{"Row", "3"}, {"Row", "4"}, {"Row", "5"},
{"Row", "6"}, {"Row", "7"}, {"Row", "8"}, };

JTable table = new JTable(data, headers);
table.setFillsViewportHeight(true);
table.setPreferredScrollableViewportSize(table.getPreferredSize());

final JMenuItem item1 = new JMenuItem();
item1.setText("Menu Item 1");
final JMenuItem item2 = new JMenuItem();
item2.setText("Menu Item 2");
final JMenuItem item3 = new JMenuItem();
item3.setText("Menu Item 3");

final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(item1);
popupMenu.addSeparator();
popupMenu.add(item2);
popupMenu.add(item3);
table.setComponentPopupMenu(popupMenu);

popupMenu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// force selection of row upon right-click (it works)
int rowAtPoint = table.rowAtPoint(SwingUtilities.convertPoint(popupMenu, new Point(0, 0), table));
if (rowAtPoint > -1) {
table.setRowSelectionInterval(rowAtPoint, rowAtPoint);
}
}
});
}

@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// force row selection upon exiting popup menu
// does not work; rowAtPoint always returns -1
int rowAtPoint = table.rowAtPoint(SwingUtilities.convertPoint(null, new Point(0, 0), table));
//int rowAtPoint = table.rowAtPoint(SwingUtilities.convertPoint(popupMenu, new Point(0, 0), table));
if (rowAtPoint > -1) {
table.setRowSelectionInterval(rowAtPoint, rowAtPoint);
}
}
});
}

@Override
public void popupMenuCanceled(PopupMenuEvent e) {
// TODO
}
});


GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.gridy = 0;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 0;

JScrollPane scrollPane = new JScrollPane(table);

JPanel contentPane = new JPanel();
contentPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
contentPane.setLayout(new GridBagLayout());
contentPane.add(scrollPane, gbc);

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(contentPane);
frame.pack();
frame.setMinimumSize(new Dimension(500, 400));
frame.setVisible(true);
}

public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
//UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
createAndShowGUI();
}
});
}
}

Answer

This a LAF issue.

It works for me when I use the default LAF but doesn't work when I use the platform LAF, which for me is Windows.

A potential solution on Windows is to use a MouseListener to select the line. Note the code is added to the mouseReleased event. For some reason the table does not receive the mousePressed event even though according to the AWTEventListener the table is the source of the mousePressed event.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class TablePopupListener extends JPanel
{
    public TablePopupListener()
    {
        JTable table = new JTable(10, 5);
        add( new JScrollPane( table ) );

        JPopupMenu popup = new JPopupMenu();
        popup.add( new JMenuItem("Do Something1") );
        popup.add( new JMenuItem("Do Something2") );

        table.setComponentPopupMenu( popup );

        table.addMouseListener( new MouseAdapter()
        {
            public void mousePressed(MouseEvent e)
            {
                System.out.println("Pressed JTable");
            }

            public void mouseReleased(MouseEvent e)
            {
                System.out.println("Released JTable");

                int row = table.rowAtPoint( e.getPoint() );

                if (row != -1
                && !table.isRowSelected(row))
                {
                   table.setRowSelectionInterval(row, row);
                }
            }
        });
    }

    private static void createAndShowGUI()
    {
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception ex) { System.out.println(ex); }

        JFrame frame = new JFrame("TablePopupListener");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new TablePopupListener());
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );

        Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener()
        {
            public void eventDispatched(AWTEvent e)
            {
                String event = null;

                switch (e.getID())
                {
                    case MouseEvent.MOUSE_PRESSED:  event = "Pressed: " ; break;
                    case MouseEvent.MOUSE_RELEASED:  event = "Released: " ; break;
                    case MouseEvent.MOUSE_ENTERED:  event = "Entered: " ; break;
                    case MouseEvent.MOUSE_EXITED:  event = "Exited: " ; break;
                    default: event = null; break;
                }

                if (event != null)
                {
                    System.out.println();
                    System.out.println(event + e.getSource().getClass());
                }
            }
        },  AWTEvent.MOUSE_EVENT_MASK);
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater( () -> createAndShowGUI() );
/*
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
*/
    }
}