JTable with JPopupMenu
Asked Answered
L

2

10

how can I prevent triggering and showing JPopupMenu only if is Mouse Cursor over selected JTable'Row

my question: if is there another way as getBounds from selected row and determine/compare that with Mouse position...

my simple sscce demonstrated just un-wanted opposite status, any row could be selected and JPopupMenu is triggered from whole JTable

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

public class TableCheckBox extends JFrame {

    private static final long serialVersionUID = 1L;
    private JTable table;

    public TableCheckBox() {
        Object[] columnNames = {"Type", "Company", "Shares", "Price", "Boolean"};
        Object[][] data = {
            {"Buy", "IBM", new Integer(1000), new Double(80.50), false},
            {"Sell", "MicroSoft", new Integer(2000), new Double(6.25), true},
            {"Sell", "Apple", new Integer(3000), new Double(7.35), true},
            {"Buy", "Nortel", new Integer(4000), new Double(20.00), false}
        };
        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        table = new JTable(model) {

            private static final long serialVersionUID = 1L;

            @Override
            public Class getColumnClass(int column) {
                return getValueAt(0, column).getClass();
            }
        };
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
        createPopupMenu();
    }

    private void createPopupMenu() {
        JPopupMenu popup = new JPopupMenu();
        JMenuItem myMenuItem1 = new JMenuItem("cccccccccccccccccccccc");
        JMenuItem myMenuItem2 = new JMenuItem("bbbbbbbbbbbbbbbbbbbbbb");
        popup.add(myMenuItem1);
        popup.add(myMenuItem2);
        MouseListener popupListener = new PopupListener(popup);
        table.addMouseListener(popupListener);
    }

    private class PopupListener extends MouseAdapter {

        private JPopupMenu popup;

        PopupListener(JPopupMenu popupMenu) {
            popup = popupMenu;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            maybeShowPopup(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (table.getSelectedRow() != -1) {
                maybeShowPopup(e);
            }
        }

        private void maybeShowPopup(MouseEvent e) {
            if (e.isPopupTrigger()) {
                popup.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                TableCheckBox frame = new TableCheckBox();
                frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocation(150, 150);
                frame.setVisible(true);
            }
        });
    }
}
Lalalalage answered 14/9, 2011 at 22:2 Comment(2)
do. not. subclass. the. view . for. model. reasons.Laufer
The solution that worked best for me (also by kleopatra) isn't linked here yet: https://mcmap.net/q/188946/-jpopupmenu-on-jtable-gt-get-the-cell-the-menu-was-created-onYounglove
L
8

It's an interesting question, because it highlights missing api on JComponent :-)

As we all know, the recommended way to register popupMenus is to use the componentPopupMenu property. Related api is

 void setComponentPopupMenu(JPopupMenu);
 JPopupMenu getComponentPopupMenu();
 Point getPopupLocation(MouseEvent);

what is missing (and actually needed for this requirement) is

JPopupMenu getComponentPopupMenu(MouseEvent);

this lack is all the more annoying, as the getPopupLocation is called (by AWTEventHelper deep in the LAF) after getComponentPopup(). So there's no leeway for a hack like storing the last mouse event which might have triggered the popup and then decide which/if to return popup. And returning null for the location will only result in showing it at the mouse location

The only (dirty) hack (around my utter reluctance to get my hands dirty with a MouseListener ;-) is to override getComponentPopup and decide there whether or not to return it based on current mouse position

    table = new JTable(model) {

        /** 
         * @inherited <p>
         */
        @Override
        public JPopupMenu getComponentPopupMenu() {
            Point p = getMousePosition();
            // mouse over table and valid row
            if (p != null && rowAtPoint(p) >= 0) {
                // condition for showing popup triggered by mouse
                if (isRowSelected(rowAtPoint(p))) {
                    return super.getComponentPopupMenu();
                } else {
                    return null;
                }
            }
            return super.getComponentPopupMenu();
        }

    };

the side-effect is that popup showing isn't triggered by keyboard as long as the mouse is anywhere above the table, which might or not be a problem.

Laufer answered 15/9, 2011 at 8:16 Comment(11)
thanks for elaborating, how easy is learning.... hehehe, sure your post is answer and closing another my issue, my +1Lalalalage
There may be an additional problem, I think, due to the use of a JScrollPane embedding the JTable. In the past, I remember having problems with events not occurring on the JTable when clicking outside any row (but inside the JScrollPane, in the empty space): I had to listen for events on the JScrollPane itself (or the JViewport can't remember exactly). So maybe @mKorbel, you'll also have to consider that (that should happen whenever the JScrollPane is higher than the JTable).Trabzon
@Trabzon no idea how is possible to consume events from Mouse to JViewPort, can you provide an example about consiming MouseEvents from/to JViewPort, because I consider this to be completely deaf JComponent :-)Lalalalage
@Trabzon hmm ... don't quite understand which problem you mean: you can adjust the logic of if-show-what as needed. Nowadays, all tables are configured to fit the viewport height anyway (SwingX does by default and nobody complained - which is a rather remarkable fact in itself, because most are the born-complaining type :-)Laufer
@Laufer you can set a minimum number of visible rows for JTable so that it fits the layout you want for your form. However, sometimes the actual table content is smaller than this minimum, then an empty area appears below the table, inside the JScrollPane boundaries; this empty space doesn't belong to the JTable but you may need it for popup display (eg a popup with "Create New Row..." item). Note that I have mentioned I couldn't remember if the Mouselistener had to be added to JScrollPane or JViewport, but I needed to add it somewhere else, in addition to, the JTable itself.Trabzon
Found it! In HiveGUI (well, about 4 years ago...), for popup handling in JTable, I added the same MouseListener to the JTable and to the JViewport it was embedded in, so I the user could show the popup even when clicking ouside a row.Trabzon
@Trabzon ahh, see what mean, thanks. Just: that empty space (not belonging to the table) doesn't happen anymore (with trackViewportHeight set to true)Laufer
OK but I can still have a JScrollPane area that shows more than the number of actual rows in the JTable, right? In this case, there's still some empty space appearing, where does it belong? The JTable?Trabzon
@Trabzon .... ehh ... no, dont think so: the height is completely filled by the table, viewport stretches its until an exact fitLaufer
@Laufer you mean setFillsViewportHeight() I guess; it didn't exist at the time of HiveGUI, and I hadn't paid much attention to this new API.Trabzon
@Trabzon exactly, thanks for looking up the correct name :-)Laufer
D
12

Are you looking for something like this perhaps?

To show popup over selected row(s) only

  private void maybeShowPopup(MouseEvent e) {
     if (e.isPopupTrigger()) {

        // get row that pointer is over
        int row = table.rowAtPoint(e.getPoint());

        // if pointer is over a selected row, show popup
        if (table.isRowSelected(row)) {
           popup.show(e.getComponent(), e.getX(), e.getY());
        }
     }
  }

Or the converse, to prevent popup from showing over selected rows only:

  private void maybeShowPopup(MouseEvent e) {
     if (e.isPopupTrigger()) {
        int row = table.rowAtPoint(e.getPoint());
        int[] selectedRows = table.getSelectedRows();

        if (!table.isRowSelected(row)) {
           popup.show(e.getComponent(), e.getX(), e.getY());
        }
     }
Demilitarize answered 14/9, 2011 at 22:16 Comment(5)
+1 despite my different point of view :) . In effect yours is an easy an quick nice solution.Sisyphean
+1 - once you'll have replaced all that looping and manual checking by isRowSelected(row) :-)Laufer
sorry to all. I was completely wrong. It seems that what I want to do, isn't possible. But @kleopatra, I'd like to know: is there any good reason for the fact that cell renderers can't handle mouse event?Sisyphean
@Sisyphean - they are not part of the container hierarchyLaufer
@Hovercraft Full Of Eels thanks your post directly solved my question, but Jeanette's answer solved another my issue too, just +1Lalalalage
L
8

It's an interesting question, because it highlights missing api on JComponent :-)

As we all know, the recommended way to register popupMenus is to use the componentPopupMenu property. Related api is

 void setComponentPopupMenu(JPopupMenu);
 JPopupMenu getComponentPopupMenu();
 Point getPopupLocation(MouseEvent);

what is missing (and actually needed for this requirement) is

JPopupMenu getComponentPopupMenu(MouseEvent);

this lack is all the more annoying, as the getPopupLocation is called (by AWTEventHelper deep in the LAF) after getComponentPopup(). So there's no leeway for a hack like storing the last mouse event which might have triggered the popup and then decide which/if to return popup. And returning null for the location will only result in showing it at the mouse location

The only (dirty) hack (around my utter reluctance to get my hands dirty with a MouseListener ;-) is to override getComponentPopup and decide there whether or not to return it based on current mouse position

    table = new JTable(model) {

        /** 
         * @inherited <p>
         */
        @Override
        public JPopupMenu getComponentPopupMenu() {
            Point p = getMousePosition();
            // mouse over table and valid row
            if (p != null && rowAtPoint(p) >= 0) {
                // condition for showing popup triggered by mouse
                if (isRowSelected(rowAtPoint(p))) {
                    return super.getComponentPopupMenu();
                } else {
                    return null;
                }
            }
            return super.getComponentPopupMenu();
        }

    };

the side-effect is that popup showing isn't triggered by keyboard as long as the mouse is anywhere above the table, which might or not be a problem.

Laufer answered 15/9, 2011 at 8:16 Comment(11)
thanks for elaborating, how easy is learning.... hehehe, sure your post is answer and closing another my issue, my +1Lalalalage
There may be an additional problem, I think, due to the use of a JScrollPane embedding the JTable. In the past, I remember having problems with events not occurring on the JTable when clicking outside any row (but inside the JScrollPane, in the empty space): I had to listen for events on the JScrollPane itself (or the JViewport can't remember exactly). So maybe @mKorbel, you'll also have to consider that (that should happen whenever the JScrollPane is higher than the JTable).Trabzon
@Trabzon no idea how is possible to consume events from Mouse to JViewPort, can you provide an example about consiming MouseEvents from/to JViewPort, because I consider this to be completely deaf JComponent :-)Lalalalage
@Trabzon hmm ... don't quite understand which problem you mean: you can adjust the logic of if-show-what as needed. Nowadays, all tables are configured to fit the viewport height anyway (SwingX does by default and nobody complained - which is a rather remarkable fact in itself, because most are the born-complaining type :-)Laufer
@Laufer you can set a minimum number of visible rows for JTable so that it fits the layout you want for your form. However, sometimes the actual table content is smaller than this minimum, then an empty area appears below the table, inside the JScrollPane boundaries; this empty space doesn't belong to the JTable but you may need it for popup display (eg a popup with "Create New Row..." item). Note that I have mentioned I couldn't remember if the Mouselistener had to be added to JScrollPane or JViewport, but I needed to add it somewhere else, in addition to, the JTable itself.Trabzon
Found it! In HiveGUI (well, about 4 years ago...), for popup handling in JTable, I added the same MouseListener to the JTable and to the JViewport it was embedded in, so I the user could show the popup even when clicking ouside a row.Trabzon
@Trabzon ahh, see what mean, thanks. Just: that empty space (not belonging to the table) doesn't happen anymore (with trackViewportHeight set to true)Laufer
OK but I can still have a JScrollPane area that shows more than the number of actual rows in the JTable, right? In this case, there's still some empty space appearing, where does it belong? The JTable?Trabzon
@Trabzon .... ehh ... no, dont think so: the height is completely filled by the table, viewport stretches its until an exact fitLaufer
@Laufer you mean setFillsViewportHeight() I guess; it didn't exist at the time of HiveGUI, and I hadn't paid much attention to this new API.Trabzon
@Trabzon exactly, thanks for looking up the correct name :-)Laufer

© 2022 - 2024 — McMap. All rights reserved.