最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Selecting JTree node on popup - Stack Overflow

programmeradmin0浏览0评论

Here's a task. We need a custom popup menu for our JTree extension. Right click should trigger not only the popup, but also selection of the tree's appropriate node. The analyst was explicit: clicking a node and then right-clicking to trigger a popup won't do, it should all happen on a single right click.

Important: selection should beat the popup as the menu composition depends on which tree node is clicked.

Manually showing the popup in the MouseListener code is not a good idea: we have a complex component composition, many of our components have their own popups, and sometimes we need to make sure a component delegates popup trigger handling to its parent component. While it's possible by setting inheritsPopupMenu to true if the popup is set conventionally, regular MouseListeners do not respect that property.

Sadly, PopupEvent has no knowledge of x, y coordinates of the click. For that reason, I also can't select a tree node in the javax.swing.event.PopupMenuListener#popupMenuWillBecomeVisible method. It makes sense, if you think about it, as popups may be triggered in a number of ways, not all of them involve mouse clicks.

Here's a demo that attaches a separate MouseListener to select a node. However, as you may see, no node is ever selected. It's because the event is consumed by a popup trigger handler (see javax.swing.plaf.basic.BasicLookAndFeel.AWTEventHelper#eventDispatched).

How do I achieve my goal?

import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.WindowConstants;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class TreePopupDemo {

    private static JTree tree;

    public static void main(String[] args) {
        Container mainPanel = createMainPanel();
        JFrame frame = new JFrame("Tree Popup Demo");
        frame.setContentPane(mainPanel);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static JPanel createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(createTree());
        return panel;
    }

    private static JTree createTree() {
        tree = new JTree();
        tree.setComponentPopupMenu(createTreePopup());
        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                onTreeClick(e);
            }
        });
        return tree;
    }

    private static JPopupMenu createTreePopup() {
        JPopupMenu popup = new JPopupMenu();
        PopupMenuListener listener = new PopupMenuAdapter() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                beforePopupAppears();
            }
        };
        popup.addPopupMenuListener(listener);
        popup.add(createTestPopupItem());
        return popup;
    }

    private static void beforePopupAppears() {
        int[] selectionRows = tree.getSelectionRows();
        int selectionRow = isEmpty(selectionRows) ? -1 : selectionRows[0];
        System.out.println("Selected tree row: " + selectionRow);
    }

    private static boolean isEmpty(int[] selectionRows) {
        return selectionRows == null || selectionRows.length == 0;
    }

    private static JMenuItem createTestPopupItem() {
        JMenuItem menuItem = new JMenuItem("Test popup item");
        return menuItem;
    }

    private static void onTreeClick(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        int row = tree.getRowForLocation(x, y);
        if (row >= 0) tree.setSelectionRow(row);
    }

    private static class PopupMenuAdapter implements PopupMenuListener {

        @Override
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        }

        @Override
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        }

        @Override
        public void popupMenuCanceled(PopupMenuEvent e) {
        }
    }
}

Here's a task. We need a custom popup menu for our JTree extension. Right click should trigger not only the popup, but also selection of the tree's appropriate node. The analyst was explicit: clicking a node and then right-clicking to trigger a popup won't do, it should all happen on a single right click.

Important: selection should beat the popup as the menu composition depends on which tree node is clicked.

Manually showing the popup in the MouseListener code is not a good idea: we have a complex component composition, many of our components have their own popups, and sometimes we need to make sure a component delegates popup trigger handling to its parent component. While it's possible by setting inheritsPopupMenu to true if the popup is set conventionally, regular MouseListeners do not respect that property.

Sadly, PopupEvent has no knowledge of x, y coordinates of the click. For that reason, I also can't select a tree node in the javax.swing.event.PopupMenuListener#popupMenuWillBecomeVisible method. It makes sense, if you think about it, as popups may be triggered in a number of ways, not all of them involve mouse clicks.

Here's a demo that attaches a separate MouseListener to select a node. However, as you may see, no node is ever selected. It's because the event is consumed by a popup trigger handler (see javax.swing.plaf.basic.BasicLookAndFeel.AWTEventHelper#eventDispatched).

How do I achieve my goal?

import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.WindowConstants;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class TreePopupDemo {

    private static JTree tree;

    public static void main(String[] args) {
        Container mainPanel = createMainPanel();
        JFrame frame = new JFrame("Tree Popup Demo");
        frame.setContentPane(mainPanel);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static JPanel createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(createTree());
        return panel;
    }

    private static JTree createTree() {
        tree = new JTree();
        tree.setComponentPopupMenu(createTreePopup());
        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                onTreeClick(e);
            }
        });
        return tree;
    }

    private static JPopupMenu createTreePopup() {
        JPopupMenu popup = new JPopupMenu();
        PopupMenuListener listener = new PopupMenuAdapter() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                beforePopupAppears();
            }
        };
        popup.addPopupMenuListener(listener);
        popup.add(createTestPopupItem());
        return popup;
    }

    private static void beforePopupAppears() {
        int[] selectionRows = tree.getSelectionRows();
        int selectionRow = isEmpty(selectionRows) ? -1 : selectionRows[0];
        System.out.println("Selected tree row: " + selectionRow);
    }

    private static boolean isEmpty(int[] selectionRows) {
        return selectionRows == null || selectionRows.length == 0;
    }

    private static JMenuItem createTestPopupItem() {
        JMenuItem menuItem = new JMenuItem("Test popup item");
        return menuItem;
    }

    private static void onTreeClick(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        int row = tree.getRowForLocation(x, y);
        if (row >= 0) tree.setSelectionRow(row);
    }

    private static class PopupMenuAdapter implements PopupMenuListener {

        @Override
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        }

        @Override
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        }

        @Override
        public void popupMenuCanceled(PopupMenuEvent e) {
        }
    }
}
Share Improve this question edited Mar 28 at 10:33 Sergey Zolotarev asked Mar 28 at 8:12 Sergey ZolotarevSergey Zolotarev 1,8691 gold badge11 silver badges31 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 0

You can try and select the node on mouse press thereby circumventing the InputEvent#consume invocation by a popup trigger handler. It doesn't come into play before the button is released.

tree.addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent e) {
        onTreePress(e); // renamed onTreeClick(e)
    }
});

Whether it's an acceptable compromise for your application depends on your analyst's expectations.

Also, make sure the "PopupMenu.consumeEventOnClose" property is set to false unless you are comfortable with your selection not working when the popup is visible. The property's default value depends on L&F.

It is not possible to (cleanly) achieve what you want while using setComponentPopupMenu.

You will have to show the context menu manually, like we used to do before Java 1.5:

private static JTree createTree() {
    tree = new JTree();

    JPopupMenu menu = createTreePopup();
    tree.addMouseListener(new MouseAdapter() {
        private void showMenu(MouseEvent e) {
            if (menu.isPopupTrigger(e)) {
                int x = e.getX();
                int y = e.getY();
                int row = tree.getClosestRowForLocation(x, y);
                if (row >= 0 && !tree.isRowSelected(row)) {
                    tree.setSelectionRows(new int[] { row });
                }
                EventQueue.invokeLater(() -> menu.show(tree, x, y));
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            showMenu(e);
        }

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

        @Override
        public void mouseReleased(MouseEvent e) {
            showMenu(e);
        }
    });
    String showMenuID = "showPopupMenu";
    tree.getActionMap().put(showMenuID, new AbstractAction() {
        @Serial
        private static final long serialVersionUID = 1;

        @Override
        public void actionPerformed(ActionEvent event) {
            Point menuLocation;
            int row = tree.getLeadSelectionRow();
            if (row >= 0) {
                Rectangle bounds = tree.getRowBounds(row);
                menuLocation = new Point(bounds.x + bounds.width / 2,
                                         bounds.y + bounds.height / 2);
            } else {
                menuLocation = tree.getMousePosition();
                if (menuLocation == null) {
                    menuLocation = new Point();
                }
            }
            menu.show(tree, menuLocation.x, menuLocation.y);
        }
    });
    tree.getInputMap().put(
        KeyStroke.getKeyStroke("CONTEXT_MENU"), showMenuID);
    tree.getInputMap().put(
        KeyStroke.getKeyStroke("shift F10"), showMenuID);

    return tree;
}
发布评论

评论列表(0)

  1. 暂无评论