Search code examples
javaswingjtableclipboardjpopup

Copy cell value of JTable with right click


I am showing some results in a JTable that consists of 2 columns.

File - Result

I implemented a JPopupMenu which displays a copy entry, and I try to copy the value of the cell, where I right-clicked.

filelistTable.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
         if(SwingUtilities.isRightMouseButton(e))
         {
             TablePopupMenu popup = new TablePopupMenu(filelistTable, e.getPoint());
             filelistTable.setComponentPopupMenu(popup);
         }
    }
});

--

    public TablePopupMenu(JTable table, Point p) {

        this.table = table;
        this.p = p;

        JMenuItem mntmKopieren = new JMenuItem("Kopieren");
        mntmKopieren.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                copyCellToClipboard();
            }
        });
        add(mntmKopieren);
    }

    public void copyCellToClipboard()
    {
        int r = table.rowAtPoint(p);
        int c = table.columnAtPoint(p);
        System.out.println(table.getValueAt(table.convertRowIndexToView(r), 
                table.convertRowIndexToView(c)));
        StringSelection entry = new StringSelection(table.getValueAt(table.convertRowIndexToView(r), 
                table.convertRowIndexToView(c)).toString());
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents( entry, this );

    }

Anyhow, this only works for a small number of tests. Did I do something wrong or something missing? It looks to me, as if the cell will not even get choosen correctly.


Solution

  • Two thingies are slightly off:

    • setting the componentPopup in the clicked is too late in the sequence of mouseEvents (popups are typically triggered on pressed or released which happen before the click)
    • the value is taken from the incorrect cell: all coordinates in a JTable are in view coordinate system, converting them to view coordinates will be completely off

    That said: getting cell-coordinate related context is poorly supported. Often, the best bet is to (code snippet below)

    • override getPopupLocation(MouseEvent) and store the location somewhere
    • implement a popup/action to access the location

    Fails if (as should be done in a well-behaved application), the popup could be triggered by keyboard: if that's the case, you'll need to provide some other marker (f.i. the focused cell) to act on.

    final String popupLocation = "table.popupLocation";
    final JTable table = new JXTable(new AncientSwingTeam()) {
    
        @Override
        public Point getPopupLocation(MouseEvent event) {
            // event may be null if triggered by keyboard, f.i.
            // thanks to @Mad for the heads up!
            ((JComponent) event.getComponent()).putClientProperty(
                    popupLocation, event != null ? event.getPoint() : null);
            return super.getPopupLocation(event);
        }
    
    };
    JPopupMenu popup = new JPopupMenu();
    Action printLocation = new AbstractAction("print cell") {
    
        @Override
        public void actionPerformed(ActionEvent e) {
           Point p = (Point) table.getClientProperty(popupLocation);
           if (p != null) { // popup triggered by mouse
               int row = table.rowAtPoint(p);
               int column = table.columnAtPoint(p);
               LOG.info("" + table.getValueAt(row, column)); 
           } else { // popup triggered otherwise
               // could choose f.i. by leadRow/ColumnSelection
               ...
           }
        }
    
    };
    popup.add(printLocation);
    table.setComponentPopupMenu(popup);
    

    Edit (triggered by Mad's comment):

    You should be checking MouseEvent.isPopupTrigger as the trigger point is platform dependent. This does mean you need to monitor mousePressed, mouseReleased and mouseClicked

    No, that's not needed (just checked :-): the mechanism that shows the componentPopup in response to a mouseEvent - happens in BasicLookAndFeel.AWTEventHelper - only does so if it is a popupTrigger.

    By reading the api doc (should have done yesterday ;-) again, it turns out that the method is called always before showing the componentPopup, that is also if triggered by other means, f.i. keyboard. In that case the event param is null - and the original code would blow. On the bright side, with that guarantee, all the logic of finding the target cell/s could be moved into that method. Didn't try though, so it might not be feasable (f.i. if then the location should be based on the leadRow/ColumnSelection that might not yet be fully handled at that time)