I want to detect when selection changes inside a JPopupMenu
. Not when a menu item is clicked, but when a menu item is selected (armed). With simpler words, I want to detect this:
The thing that should work is to add a ChangeListener
to its SelectionModel
, but it doesn't seem to respond to selection events:
public class PopupSelection extends JFrame {
private static final long serialVersionUID = 363879723515243543L;
public PopupSelection() {
super("something");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel label = new JLabel("right click me");
JPopupMenu menu = new JPopupMenu();
menu.getSelectionModel().addChangeListener(System.out::println);
JMenuItem menuItem1 = new JMenuItem("Item1");
JMenuItem menuItem2 = new JMenuItem("Item2");
JMenuItem menuItem3 = new JMenuItem("Item3");
menu.add(menuItem1);
menu.add(menuItem2);
menu.add(menuItem3);
label.setComponentPopupMenu(menu);
getContentPane().add(label);
setSize(400, 400);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new PopupSelection().setVisible(true));
}
}
Second thing I tried is with a PropertyChangeListener
, but it does not work (does not detect this specific event) either:
menu.addPropertyChangeListener(System.out::println);
I know there is the alternative of adding a ChangeListener
to each JMenuItem
and each time iterate all components of the JPopupMenu
in order to find which one is selected, but this is not a solution I want to follow since it will add unwanted complexity in my code.
So, is there a way to detect the selection?
In case of a XY problem, my final goal is to increase/decrease this scrollbar properly when user changes menu's selection with arrow buttons: ā ā
Use change listener on button model of your items. Here is the solution:
import java.awt.Component;
import java.awt.FlowLayout;
import java.util.stream.Stream;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* <code>PopupSelection</code>.
*/
public class PopupSelection extends JFrame {
private static final long serialVersionUID = 363879723515243543L;
public PopupSelection() {
super("something");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel label = new JLabel("right click me");
JPopupMenu menu = new MyPopupMenu();
menu.getSelectionModel().addChangeListener(System.out::println);
JMenuItem menuItem1 = new JMenuItem("Item1");
JMenuItem menuItem2 = new JMenuItem("Item2");
JMenuItem menuItem3 = new JMenuItem("Item3");
menu.add(menuItem1);
menu.add(menuItem2);
menu.add(menuItem3);
label.setComponentPopupMenu(menu);
getContentPane().add(label);
setSize(400, 400);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new PopupSelection().setVisible(true));
}
private static class MyPopupMenu extends JPopupMenu {
private final ChangeListener listener = this::changed;
@Override
protected void addImpl(Component comp, Object constraints, int index) {
super.addImpl(comp, constraints, index);
if (comp instanceof AbstractButton) {
((AbstractButton) comp).getModel().addChangeListener(listener);
}
}
@Override
public void remove(int index) {
Component comp = getComponent(index);
if (comp instanceof AbstractButton) {
((AbstractButton) comp).getModel().removeChangeListener(listener);
}
super.remove(index);
}
private void changed(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
AbstractButton selected = Stream.of(getComponents()).filter(AbstractButton.class::isInstance)
.map(AbstractButton.class::cast)
.filter(b -> b.getModel().isArmed() && b.getModel() == model).findAny().orElse(null);
setSelected(selected);
}
}
}