JMenuItem 的 ActionListener 不工作

The ActionListener of the JMenuItem is not working

我正在做一个个人项目,按钮的部分功能是右键单击它们时会显示 PopMenu。该代码在昨天之前一直有效,但今天我试图让它变得更 OO 而现在,虽然菜单确实显示了,但当我单击 MenuItems 时没有任何反应。不幸的是,我没有版本控制,所以我没有旧版本的副本。

代码如下:

这是PopUpMenuclass

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));
    }

}

这是实际的 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;
    }

}

这是MenuItem

ActionListener
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) {
                }
            }
        }
    }

}

这是PopUpMenu

ActionListener
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;
    }

}

这里是 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;
    }

}

经过一些测试,我发现 ActionListener 实际上是由按下的键激活的,而不是由鼠标的任何按钮激活的,(通过在调试过程中删除 if 发现)这不是很有帮助,因为正如您在 IListener 中看到的那样 class 我不想接受来自键盘的任何事件。

IListener 也是我在我的程序中使用的所有其他 ActionListeners 的基础,它似乎对他们来说工作得很好。

所以基本上我的问题是:我需要修复什么才能通过鼠标点击激活 MenuItemListener

PopUpMenu

中删除 getParent 方法
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));
    }

}

此方法将覆盖 java.awt.Component.getParent() 中定义的 getParent。我想这会导致意外行为。

编辑

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.

您可以覆盖该方法,但必须确保 Component.getParent method's contract.

PopUpMenu 不是容器 parent 的子容器。我的意思是如果 PopUpMenu returns 容器 parent 容器也应该知道 PopUpMenu 是它的子容器。例如。 Container.getCompnents() 应该包含 PopUpMenu。这就是合同。

但这对您的情况没有帮助,因为实际上并不想创建组件 parent/child 关系。您只想保留对要调用的某些对象的引用 method.invoke(container);.

此示例基于您的代码以及我在上面建议的修复。我将所有内容都放在一个编译单元中以提供 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;
    }

}

这是相同逻辑的重构版本,不需要像 PopUPMenuMenuItem.

这样的大量专门(扩展)类
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());
        }
    }

}