Reputation: 7285
I am working on a personal project and part of the functionality of the buttons is that they have a PopMenu
show up when they are right-clicked. The code used to work until yesterday, but today I tried to make it more OO and now, although the menu does show up nothing happens when I click the MenuItems
. I unfortunately do not have version control so I do not have a copy of the older version.
The code is listed below:
This is the PopUpMenu
class
public class PopUpMenu extends JPopupMenu {
private Container parent;
public PopUpMenu(MenuItem[] menuItems) {
super();
for (MenuItem item : menuItems) {
add(item);
}
}
public Container getParent() {
return parent;
}
public void setParent(Container parent) {
this.parent = parent;
parent.addMouseListener(new PopUpListener(this));
}
}
This is the actual MenuItem
.
public class MenuItem extends JMenuItem {
private String methodName;
public MenuItem(String methodName, String text) {
super(text);
setMethodName(methodName);
setFocusable(true);
addActionListener(new MenuItemListener());
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
}
This is the ActionListener
of the MenuItem
public class MenuItemListener extends IListener {
protected void action(ActionEvent event) {
Object source = event.getSource();
if (source instanceof MenuItem) {
MenuItem item = (MenuItem) source;
Container parent = item.getParent();
if (parent instanceof PopUpMenu) {
PopUpMenu menu = (PopUpMenu) parent;
Container container = menu.getParent();
try {
String name = item.getMethodName();
Method method = container.getClass().getMethod(name);
method.invoke(container);
} catch (Exception e) {
}
}
}
}
}
This the ActionListener
of the PopUpMenu
public class PopUpListener extends MouseAdapter {
private PopUpMenu menu;
public PopUpListener(PopUpMenu menu) {
setMenu(menu);
}
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger()) {
menu.show(event.getComponent(), event.getX(), event.getY());
}
}
public PopUpMenu getMenu() {
return menu;
}
public void setMenu(PopUpMenu menu) {
this.menu = menu;
}
}
Here is the abstract class IListener
.
public abstract class IListener implements ActionListener {
private boolean keyboardSensitive;
public IListener() {
setKeyboardSensitive(false);
}
@Override
public void actionPerformed(ActionEvent event) {
if ((event.getModifiers() != 0) || isKeyboardSensitive()) {
action(event);
}
}
protected abstract void action(ActionEvent event);
public boolean isKeyboardSensitive() {
return keyboardSensitive;
}
public void setKeyboardSensitive(boolean keyboardSensitive) {
this.keyboardSensitive = keyboardSensitive;
}
}
After a bit of testing I found out that the ActionListener
was actually activated by keys pressed but not by any button of the mouse, (found out by removing if during debuging)which is not very helpful since as you can see in the IListener class I do not want to accept any event coming from the keyboard.
The IListener
is also the base for all the other ActionListeners
I use in my program and it seems to work fine for them.
So essentually my question is: what do I need to fix so that the MenuItemListener
is activated by mouse clicks?
Upvotes: 2
Views: 1149
Reputation: 51333
Remove the getParent
Method from PopUpMenu
public class PopUpMenu extends JPopupMenu {
private Container parent;
public PopUpMenu(MenuItem[] menuItems) {
super();
for (MenuItem item : menuItems) {
add(item);
}
}
public void setParent(Container parent) {
this.parent = parent;
parent.addMouseListener(new PopUpListener(this));
}
}
This method will override the getParent
defined in java.awt.Component.getParent()
. I guess this leads to the unexpected behavior.
EDIT
I'm overriding that method on purpose. But I still tried to remove it to see if that would fix the problem. Unfortunately, it did not.
You can override the method but you must ensure the Component.getParent
method's contract.
The PopUpMenu
is not a child of the container parent
. I mean that if the PopUpMenu
returns the container parent
the container should also know that the PopUpMenu
is it's child. E.g. Container.getCompnents()
should contain the PopUpMenu
. That's the contract.
But it will not help inyour situation since what don't really want to create a component parent/child relationship. You just want to hold a reference to some Object that you want to invoke some method.invoke(container);
.
This example is based on your code with the fix I suggessted above. I've put everything in one compilation unit to provide a MVCE:
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
JFrame frame = createFrame();
MenuItem menuItem1 = new MenuItem("getForeground", "Foreground Color");
MenuItem menuItem2 = new MenuItem("getBackground", "Background Color");
PopUpMenu popUpMenu = new PopUpMenu(new MenuItem[] { menuItem1, menuItem2 });
popUpMenu.setParent(frame);
frame.setVisible(true);
}
private static JFrame createFrame() {
JFrame frame = new JFrame();
frame.setSize(1000, 800);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
return frame;
}
}
class PopUpMenu extends JPopupMenu {
private Container parent;
public PopUpMenu(MenuItem[] menuItems) {
super();
for (MenuItem item : menuItems) {
add(item);
}
}
public Container getParentComponent() {
// another name because I don't want to override getParent()
// Try to rename this method to getParent to see
// that it will not work
return parent;
}
public void setParent(Container parent) {
this.parent = parent;
parent.addMouseListener(new PopUpListener(this));
}
}
class MenuItemListener extends IListener {
protected void action(ActionEvent event) {
Object source = event.getSource();
if (source instanceof MenuItem) {
MenuItem item = (MenuItem) source;
Container parent = item.getParent();
if (parent instanceof PopUpMenu) {
PopUpMenu menu = (PopUpMenu) parent;
Container container = menu.getParentComponent();
try {
String name = item.getMethodName();
Method method = container.getClass().getMethod(name);
Object invoke = method.invoke(container);
JOptionPane.showMessageDialog(container, invoke);
} catch (Exception e) {
}
}
}
}
}
abstract class IListener implements ActionListener {
private boolean keyboardSensitive;
public IListener() {
setKeyboardSensitive(false);
}
@Override
public void actionPerformed(ActionEvent event) {
if ((event.getModifiers() != 0) || isKeyboardSensitive()) {
action(event);
}
}
protected abstract void action(ActionEvent event);
public boolean isKeyboardSensitive() {
return keyboardSensitive;
}
public void setKeyboardSensitive(boolean keyboardSensitive) {
this.keyboardSensitive = keyboardSensitive;
}
}
class MenuItem extends JMenuItem {
private String methodName;
public MenuItem(String methodName, String text) {
super(text);
setMethodName(methodName);
setFocusable(true);
addActionListener(new MenuItemListener());
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
}
class PopUpListener extends MouseAdapter {
private PopUpMenu menu;
public PopUpListener(PopUpMenu menu) {
setMenu(menu);
}
@Override
public void mousePressed(MouseEvent event) {
if (event.isPopupTrigger()) {
menu.show(event.getComponent(), event.getX(), event.getY());
}
}
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger()) {
menu.show(event.getComponent(), event.getX(), event.getY());
}
}
public PopUpMenu getMenu() {
return menu;
}
public void setMenu(PopUpMenu menu) {
this.menu = menu;
}
}
Here is a refactored version of the same logic that doesn't need a lot of specialized (extended) classes like PopUPMenu
or MenuItem
.
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.*;
import java.text.MessageFormat;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
JFrame frame = createFrame();
JMenuItem foregroundMenuItem = createMenuItem(frame, "getForeground", "Foreground Color");
JMenuItem backgroundMenuItem = createMenuItem(frame, "getBackground", "Background Color");
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(foregroundMenuItem);
popupMenu.add(backgroundMenuItem);
PopUpListener popUpListener = new PopUpListener(popupMenu);
frame.addMouseListener(popUpListener);
frame.setVisible(true);
}
private static JMenuItem createMenuItem(Object invocationTarget, String methodName, String actionName) {
MethodInvocationAction methodInvocationAction = new MethodInvocationAction(invocationTarget, methodName);
methodInvocationAction.putValue(Action.NAME, actionName);
JMenuItem menuItem = new JMenuItem(methodInvocationAction);
return menuItem;
}
private static JFrame createFrame() {
JFrame frame = new JFrame();
frame.setSize(1000, 800);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
return frame;
}
}
class MethodInvocationAction extends AbstractAction {
private Object targetObj;
private Method targetMethod;
private boolean keyboardSensitive;
public MethodInvocationAction(Object targetObj, String methodName) {
this.targetObj = targetObj;
try {
targetMethod = targetObj.getClass().getMethod(methodName);
} catch (NoSuchMethodException | SecurityException e) {
String msg = MessageFormat.format("{0} does not have a method named {1}", targetObj, methodName);
throw new RuntimeException(msg, e);
}
}
public boolean isKeyboardSensitive() {
return keyboardSensitive;
}
public void setKeyboardSensitive(boolean keyboardSensitive) {
this.keyboardSensitive = keyboardSensitive;
}
@Override
public void actionPerformed(ActionEvent event) {
if ((event.getModifiers() != 0) || isKeyboardSensitive()) {
performAction(event);
}
}
public void performAction(ActionEvent e) {
try {
Object invoke = targetMethod.invoke(targetObj);
JOptionPane.showMessageDialog(null, invoke);
} catch (Exception exception) {
showException(exception);
}
}
private void showException(Exception e1) {
StringWriter exceptionStackTraceWriter = new StringWriter();
e1.printStackTrace(new PrintWriter(exceptionStackTraceWriter));
String exceptionStackTrace = exceptionStackTraceWriter.toString();
JTextArea exceptionStackTraceTextComponent = new JTextArea();
exceptionStackTraceTextComponent.setText(exceptionStackTrace);
JScrollPane scrollPane = new JScrollPane(exceptionStackTraceTextComponent);
scrollPane.setPreferredSize(new Dimension(800, 600));
JOptionPane.showMessageDialog(null, scrollPane, e1.getLocalizedMessage(), JOptionPane.ERROR_MESSAGE);
}
}
class PopUpListener extends MouseAdapter {
private JPopupMenu menu;
public PopUpListener(JPopupMenu menu) {
this.menu = menu;
}
public void mousePressed(MouseEvent event) {
handlePopupEvent(event);
}
public void mouseReleased(MouseEvent event) {
handlePopupEvent(event);
}
private void handlePopupEvent(MouseEvent event){
if (event.isPopupTrigger()) {
menu.show(event.getComponent(), event.getX(), event.getY());
}
}
}
Upvotes: 2