0

I am currently attempting to display a JPopupMenu when right clicking a JTable. I have achieved this in different ways already, but none of them allow me to do what this question is about: executing some code after I right click on the table, but the popup menu is on screen.

When the popup menu is displayed and I right click outside of it, it opens up again on a different location. So there must be a listener inside it that tells it to do that, but I can't find it. Ideally, I would @Override it and execute the same code I'm executing when I right click on the JTable.

So, to sum up the question, how do I trigger some code when I right click outside the menu (or on a table cell, for example) when the menu still has focus (or is displaying)?

EDIT: After looking more carefully, I identified the problem, but not the solution. the JPopupMenu is not the issue, but the UIManager. Here is the code of my test form that displays the issue I'm describing:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;

public class PopupTesting extends javax.swing.JFrame {

    public PopupTesting() {
        initComponents();
    }

    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jTable1.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override
            public void mousePressed(java.awt.event.MouseEvent evt) {
                jTable1MousePressed(evt);
            }
        });
        jScrollPane1.setViewportView(jTable1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(61, 61, 61)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(83, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(69, 69, 69)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 267, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(121, Short.MAX_VALUE))
        );
        pack();
    }                      

    private void jTable1MousePressed(java.awt.event.MouseEvent evt) {                                     
        if (SwingUtilities.isRightMouseButton(evt)) {
            jTable1.setRowSelectionInterval(jTable1.rowAtPoint(evt.getPoint()), jTable1.rowAtPoint(evt.getPoint()));
            getPopup().show(jTable1, evt.getX(), evt.getY());
        }
    }                                    

    public static void main(String args[]) {
        try {
            javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            Logger.getLogger(PopupTesting.class.getName()).log(Level.SEVERE, null, ex);
        }
        java.awt.EventQueue.invokeLater(() -> {
            new PopupTesting().setVisible(true);
        });
    }

    private JPopupMenu getPopup() {
        if (jTable1.getSelectedRow() > -1) {
            JPopupMenu popup = new JPopupMenu();

            MouseListener listener = (new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    System.out.println("I'm a menu Item.");
                }
            });

            JMenuItem m = new JMenuItem("Regular Menu Item");
            m.addMouseListener(listener);
            popup.add(m);

            return popup;
        } return null;
    }

    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;               
}

If you remove the line:

javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());

and the surrounding try and catch, it works flawlessly.

RaKXeR
  • 152
  • 2
  • 16
  • The "normal" way to show a popup is through [`JComponent#setComponentPopupMenu`](https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#setComponentPopupMenu(javax.swing.JPopupMenu)), you can use a `PopupMenuListener` to monitor the state of the popup, but I'm not entirely sure that can help you. You want to take some action when the user attempts to right click somewhere else while the popup is still visible? – MadProgrammer Apr 08 '16 at 09:18
  • @MadProgrammer if you play around with the popup menus that Windows uses, when you click somewhere else and the menu is on screen, it both closes and triggers the click event where you tried to click. JPopupMenu doesn't do that, it just closes and doesn't trigger. I want it to behave like the Windows popup window. – RaKXeR Apr 08 '16 at 09:21

2 Answers2

1

or on top of the table

The table header is not part of the table so it will not respond to the mouse event. You need to add a separate listener to the table header.

when you click somewhere else and the menu is on screen, it both closes and triggers the click event where you tried to click

Works fine for me using JDK8 on Windows 7 if you click on a cell of the table.

If you continue to have problems then post a SSCCE that demonstrates the problem.

Edit:

As MadProgrammer suggested in his comment, the setComponentPoupMenu(...) method should be used. This is a newer API. I don't know what the difference is between the two approaches, but using that method works for me on both LAF's that I tested.

Edit2:

Once again it still works fine for me. Note you should NOT be adding a MouseListener to a menu item. You use an ActionListener to handle the clicking of the menu item:

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

public class TableRightClick extends JFrame implements ActionListener
{
    JPopupMenu popup;

    public TableRightClick()
    {
        popup = new JPopupMenu();
        popup.add( new JMenuItem("Do Something1") );
        JMenuItem menuItem = new JMenuItem("ActionPerformed");
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.ALT_MASK));

        menuItem.addActionListener( this );
        popup.add( menuItem );

        JTable table = new JTable(10, 5);

        table.addMouseListener( new MouseAdapter()
        {
            public void mousePressed(MouseEvent e)
            {
                JTable source = (JTable)e.getSource();
                int row = source.rowAtPoint( e.getPoint() );
                int column = source.columnAtPoint( e.getPoint() );

                if (! source.isRowSelected(row))
                    source.changeSelection(row, column, false, false);
            }
        });

        table.setComponentPopupMenu( popup );

        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        getContentPane().add( new JScrollPane(table) );

        JMenuBar menuBar = new JMenuBar();
        setJMenuBar( menuBar );
        menuBar.add( popup );
    }

    public void actionPerformed(ActionEvent e)
    {
        Component c = (Component)e.getSource();
        JPopupMenu popup = (JPopupMenu)c.getParent();
        JTable table = (JTable)popup.getInvoker();
        System.out.println(table.getSelectedRow() + " : " + table.getSelectedColumn());
    }

    public static void main(String[] args)
    {
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception ex) { System.out.println(ex); }

        TableRightClick frame = new TableRightClick();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • by "on top of the table" I meant in as if I was viewing my screen from the top, so "in front of the table". – RaKXeR Apr 08 '16 at 15:39
  • also could you maybe show me the code you used that didn't encounter this issue? – RaKXeR Apr 08 '16 at 15:40
  • You first. You were asked to post a `SSCCE` that demonstrates the problem. Part of problem solving is learning how to simplify the problem. Chances are you are doing something strange. I have no idea what you mean by "in front of the table", which is why you need to post your demo code. – camickr Apr 08 '16 at 15:53
  • there we go. Sorry for that, you were right in respects that it should work by itself, but the question is now different, and I have added the entire class code :) – RaKXeR Apr 08 '16 at 17:26
  • thank you. I will be testing that again, but the issue with that is that it doesn't allow me much control. or maybe it does and I don't know how to use it... are you able to achieve exactly the same effect as in my code with setComponentPopupMenu ? – RaKXeR Apr 08 '16 at 18:02
  • @RaKXeR, I don't know what it doesn't do that you want to control. – camickr Apr 08 '16 at 18:11
  • no problem then, I will explain. on the code above, when I right click on my table, it selects the cell below the mouse, and then displays the popup menu. by using setComponentPopupMenu, when I right click on my table it selects the cell, adds the popup menu, displays it (all things I wanted) but then the next time I right click it only opens the popup menu, and doesn't select the cell. get what I mean? I'm currently trying to find a fix for this but haven't found it yet. – RaKXeR Apr 08 '16 at 18:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/108659/discussion-between-rakxer-and-camickr). – RaKXeR Apr 08 '16 at 19:07
  • @RaKXeR, Our suggestion was that you use the `setComponentPopupMenu()` to display the popup. We never said anything about the row selection. If you want row selection then leave that code in the MouseListener but get rid of the popup logic. – camickr Apr 08 '16 at 20:49
  • you do realize that the `MouseListener` wont trigger when I try to open another menu and the menu is already being displayed, right? by using `setComponentPopupMenu()`, if the menu is closed and opens, the mouse listener on the table triggers. if the menu is already opened, it moves it to another location and doesn't trigger the mouse listener, because I clicked the menu and not the table. – RaKXeR Apr 09 '16 at 11:43
  • my comment above only applies (of course) when using the damn `UIManager`. – RaKXeR Apr 09 '16 at 12:19
  • are you absolutely positive this code is meant to do the same as my code? are you using an operating system that is not windows (which is the one I'm currently testing in). because my issue is still happening on this code, because clicking anywhere while the menu is on screen doesnt leave the menu and click, it just leaves the menu... – RaKXeR Apr 09 '16 at 14:53
  • `and click,` - I don't know what you mean by "click". If I click on a new row, that row is highlighted and the menu is moved to the mouse click position. I already stated my platform in my original answer. – camickr Apr 09 '16 at 15:33
  • well last question, and then I'm a bit lost as to why we have different results with the same code: the question is, if "I click on a new row, that row is highlighted and the menu is moved to the mouse click position" works not only while the menu isn't being displayed, as well as while it is. Because my issue only occurs after the menu is displayed the first time – RaKXeR Apr 09 '16 at 15:42
  • @RaKXeR, well this is embarrassing. I was positive I tested this several times over 2 days, however I did just retest it and the row is not highlighted as you are correct the table no longer receives mouse events when the popup is visible. – camickr Apr 09 '16 at 19:43
  • it's alright, I was starting to think I had spent all this time trying to reach a solution for nothing xD if you find a way to improve my solution I would appreciate it, since I cant hold any mouse button when comming out of the `JPopupMenu` – RaKXeR Apr 09 '16 at 22:25
  • 1
    @RaKXeR, I can't think of anything better. I was trying to have the table request focus after the popup was display but it didn't work. I was then trying to add a MouseListener to the popup so I could redispatch the MouseEvents to the table. The popup didn't even respond to the MouseEvent. So I'm out of guesses. – camickr Apr 09 '16 at 23:45
  • thanks for your help ^^. unless you know how to turn an `AncestorEvent` into a `MouseEvent`, looks like this will have to do. – RaKXeR Apr 10 '16 at 00:30
1

Well, after many hours of trying to fix the issue, I ended up making a dodgy fix. The code that does what I requested is here:

import java.awt.AWTException;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

public class PopupTesting extends javax.swing.JFrame {

    public PopupTesting() {
        initComponents();
        jTable.setComponentPopupMenu(getPopup());
    }

    volatile int lastMouse = 1;

    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTable = new javax.swing.JTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jTable.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override
            public void mousePressed(java.awt.event.MouseEvent evt) {
                jTableMousePressed(evt);
            }
        });

        jScrollPane1.setViewportView(jTable);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(61, 61, 61)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(83, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(69, 69, 69)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 267, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(121, Short.MAX_VALUE))
        );
        pack();
    }

    private void jTableMousePressed(java.awt.event.MouseEvent evt) {                                    
        lastMouse = evt.getButton();
        if (lastMouse == 3) lastMouse--;
        lastMouse = InputEvent.getMaskForButton(lastMouse);
    }                                 


    public static void main(String args[]) {
        try {
            javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            Logger.getLogger(PopupTesting.class.getName()).log(Level.SEVERE, null, ex);
        }
        java.awt.EventQueue.invokeLater(() -> {
            new PopupTesting().setVisible(true);
        });
    }

    private JPopupMenu getPopup() {
        JPopupMenu popup = new JPopupMenu();

        JMenuItem m = new JMenuItem("Regular Menu Item");
        m.addActionListener((ActionEvent e) -> {
            System.out.println("I'm a menu Item.");
        });
        popup.add(m);
        PopupTesting frame = this;
        popup.addAncestorListener( new AncestorListener() {
            @Override
            public void ancestorAdded(AncestorEvent event) {
                Point mousePoint = MouseInfo.getPointerInfo().getLocation();
                mousePoint = SwingUtilities.convertPoint(frame, parseInt(mousePoint.getX() - getX()), parseInt(mousePoint.getY() - getY()), jTable);
                jTable.setRowSelectionInterval(jTable.rowAtPoint(mousePoint), jTable.rowAtPoint(mousePoint));
            }
            @Override public void ancestorRemoved(AncestorEvent event) {
                try {
                    Robot bot = new Robot();
                    bot.mousePress(lastMouse);
                    bot.mouseRelease(lastMouse);
                } catch (AWTException ex) {
                    Logger.getLogger(PopupTesting.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            @Override public void ancestorMoved(AncestorEvent event) {}
        });
        return popup;
    }


    private int parseInt(Double val) {
        return val.intValue();
    }

    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable;               
}

Despite setComponentPopupMenu() displaying the popup again while it is still visible, it still wont trigger MouseListeners.

The way I "fixed" it, was by using an AncestorListener, that would trigger ancestorAdded when the popup menu was displayed, and ancestorRemoved when it exited the screen.

With the ancestorAdded, I simply made it so that whenever the menu was displayed, it also selected the cell behind it (took a lot of time to figure out how to get the same position from MouseInfo as I would get from MouseEvent, but I got there).

With the ancestorRemoved, I had to "cheat". Basically, every time I click (MousePressed) on the table, I store which button was used to do it. Then, once ancestorRemoved gets triggered, it creates a Robot that repeats the last mouse click (if it was mouse 1 it triggers mouse 1, mouse 2 triggers mouse 2, etc).

This solution works, however it does not allow me to hold down a mouse button when exiting the JPopupMenu. If anyone believes to know the solution, please let me know, but for now, problem solved.

RaKXeR
  • 152
  • 2
  • 16
  • `it still wont trigger MouseListeners` - maybe that is the problem. You should NOT be using a MouseListener on a menu item. You should be using an ActionListener. I updated my answer. As best as I can tell it works the same as your code does. Also, you really should learn how to create a GUI without using the IDE so there is less code to look at. – camickr Apr 09 '16 at 14:45
  • @camickr I use the Netbeans GUI editor (which is kinda bad but works), but I find it easier to use a simple class for an example here – RaKXeR Apr 09 '16 at 14:49
  • @camickr edited so it uses an ActionListener instead of MouseListener. I had used MouseListener because before this iteration of the code I had a much more complicated version, and once I simplified it I no longer needed to capture mouse clicks, but forgot to change it. – RaKXeR Apr 09 '16 at 15:01