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 MouseListener
s 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 MouseListener
s 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
2 Answers
Reset to default 0You 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;
}