Java - 多次调用自定义 JDialog dispose 方法 - 它不打算这样做

Java - Custom JDialog dispose method called more than once - it is not intended to do so

我正在编写一个脚本,其中涉及连续弹出 JDialogs 以要求用户捕获屏幕上的信息(例如菜单周围的框等),以便程序随后自动单击我指定的位置。

我目前遇到的问题是,当我关闭自定义 JDialog 时,dispose 方法似乎被调用了多次,虽然我不知道为什么。

这是该问题的完整工作示例:

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Main {

    // Creates the main frame (MainForm extends JFrame)
    private static MainForm mainForm = new MainForm();

    public static void main(String[] args) {
        mainForm.setVisible(true);
    }

    static class BaseDialog extends JDialog {
        BaseDialog() {
            super();
            setModal(true);
        }

        // Overrides and calls (super)dispose method of JDialog - Nothing unusual
        @Override
        public void dispose() {
            Exception e = new Exception();
            e.printStackTrace();
            System.out.println("disposing");
            super.dispose();
        }
    }

    static class CaptureDialog extends BaseDialog implements ActionListener {
        CaptureDialog() {
            super();
            JButton btnInventory = new JButton("Close Me");
            btnInventory.addActionListener(this);
            add(btnInventory);
            setTitle("Recapture");
            setModalityType(ModalityType.APPLICATION_MODAL);
            setLocationRelativeTo(null);
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            setResizable(false);
            setSize(200, 80);
            setVisible(true);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Clicked the button!");
            dispose();
        }
    }

    static class MainForm extends JFrame implements ActionListener {
        MainForm() {
            super("Example");
            JButton btnCapture = new JButton();
            btnCapture.setText("Capture");
            btnCapture.addActionListener(this);
            add(btnCapture);
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            setLocationRelativeTo(null);
            setSize(200, 80);
        }

        // Only one button is added to action listener ('if' not necessary)
        @Override
        public void actionPerformed(ActionEvent e){
            new CaptureDialog();
        }
    }
}

如果我 运行 应用程序然后打开 close/dispose 对话框,问题就很明显了。通过 Exception eBaseDialog 的 "dispose" 方法下的放置,我在通过 [=59= 关闭时得到以下输出]的'X'按钮(省略内部调用):

java.lang.Exception
    at Main$BaseDialog.dispose(Main.java:24)
    at javax.swing.JDialog.processWindowEvent(JDialog.java:691)
    at java.awt.Window.processEvent(Window.java:2017)
    at java.awt.WaitDispatchSupport.run(WaitDispatchSupport.java:184)
    at java.awt.WaitDispatchSupport.run(WaitDispatchSupport.java:229)
    at java.awt.WaitDispatchSupport.run(WaitDispatchSupport.java:227)
    at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:227)
    at java.awt.Dialog.show(Dialog.java:1084)
    at java.awt.Component.show(Component.java:1673)
    at java.awt.Component.setVisible(Component.java:1625)
    at java.awt.Window.setVisible(Window.java:1014)
    at java.awt.Dialog.setVisible(Dialog.java:1005)
    at Main$CaptureDialog.<init>(Main.java:46)
    at Main$MainForm.actionPerformed(Main.java:71)
    at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
disposing
disposing
java.lang.Exception
    at Main$BaseDialog.dispose(Main.java:24)
    at java.awt.Window.disposeImpl(Window.java:1161)
    at java.awt.WindowDisposeAction.run(Window.java:1189)
    at java.awt.Window.doDispose(Window.java:1210)
    at java.awt.Window.dispose(Window.java:1151)
    at javax.swing.SwingUtilities$SharedOwnerFrame.dispose(SwingUtilities.java:1814)
    at javax.swing.SwingUtilities$SharedOwnerFrame.windowClosed(SwingUtilities.java:1792)
    at java.awt.Window.processWindowEvent(Window.java:2061)
    at javax.swing.JDialog.processWindowEvent(JDialog.java:683)
    at java.awt.Window.processEvent(Window.java:2017)

另请注意,如果您重复 运行 应用程序和 'open - close' 对话框,则该方法调用递增 1(如果您注释掉覆盖 'dispose' 关于异常堆栈跟踪打印的方法并观察输出)。

如果你想use/look这里有一个精简版,首先谢谢你!

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Main {
    public static void main(String[] args) {
        new MainForm();
    }

    static class BaseDialog extends JDialog {
        BaseDialog() {
            super();
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            setVisible(true);
        }

        @Override
        public void dispose() {
            new Exception().printStackTrace();
            System.out.println("disposing");
            super.dispose();
        }
    }

    static class MainForm extends JFrame implements ActionListener {
        MainForm() {
            super();
            JButton btnCapture = new JButton();
            btnCapture.setText("Capture");
            btnCapture.addActionListener(this);
            add(btnCapture);
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            setSize(200, 80);
            setVisible(true);
        }

        @Override
        public void actionPerformed(ActionEvent e){
            new BaseDialog();
        }
    }
}

可能有用的信息:

  1. 省略 'super.dispose()' 会导致 'dispose' 仅调用一次 - 应用程序代码不应该是问题所在。
  2. 如果我扩展 JFrame 而不是 JDialog(关于 BaseDialog class),则会正确调用 dispose 方法(使用 super.dispose())。
  3. 有点无关紧要,但是 'hack' 是在 BaseDialog class 下创建一个私有字段 class:

-> private boolean disposed = false;

然后在调用dispose方法时进行检查:

@Override
public void dispose() {
    if (!disposed) {
        disposed = true;
        super.dispose();
    }
}

虽然它可能会解决问题,但这远不是一个好的答案,而且由于核心没有正确修复,未来很容易出现问题。

parent window 持有对对话框的引用,并将 re-dispose 在对话框本身被处置时。

你的问题是你没有为对话框分配 parent window,因此由于某种原因,当对话框被处理时它 re-dosposes 以前所做的所有对话框引用,就好像基数 class 包含对 child window 的引用。您可以通过将 parent window 传递到对话框的超级构造函数中来解决问题,如下所示:

import javax.swing.*;

import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Main {

    // Creates the main frame (MainForm extends JFrame)
    private static MainForm mainForm = new MainForm();

    public static void main(String[] args) {
        mainForm.setVisible(true);
    }

    static class BaseDialog extends JDialog {
        BaseDialog(Window win) {
            super(win);
            setModal(true);
        }

        // Overrides and calls (super)dispose method of JDialog - Nothing
        // unusual
        @Override
        public void dispose() {
            Exception e = new Exception();
            // e.printStackTrace();
            String text = String.format("Disposing. This hashCode: %08X", hashCode());
            System.out.println(text);
            super.dispose();
        }
    }

    static class CaptureDialog extends BaseDialog implements ActionListener {
        CaptureDialog(Window win) {
            super(win);
            JButton btnInventory = new JButton("Close Me");
            btnInventory.addActionListener(this);
            add(btnInventory);
            setTitle("Recapture");
            setModalityType(ModalityType.APPLICATION_MODAL);
            setLocationRelativeTo(null);
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            setResizable(false);
            setSize(200, 80);
            setVisible(true);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Clicked the button!");
            dispose();
        }
    }

    static class MainForm extends JFrame implements ActionListener {
        MainForm() {
            super("Example");
            JButton btnCapture = new JButton();
            btnCapture.setText("Capture");
            btnCapture.addActionListener(this);
            add(btnCapture);
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            setLocationRelativeTo(null);
            setSize(200, 80);
        }

        // Only one button is added to action listener ('if' not necessary)
        @Override
        public void actionPerformed(ActionEvent e) {
            new CaptureDialog(MainForm.this);
        }
    }
}

我自己,如果我认为我可能需要它,我会避免 re-creating 对话框,这样可以防止不必要的引用积累。请参阅下面的代码并注释和取消注释指示的部分以了解我的意思:

import java.awt.Window;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.*;

public class MainSimple extends JPanel {
    private JDialog dialog;

    public MainSimple() {
        add(new JButton(new OpenDialogAction("Open Dialog", KeyEvent.VK_O)));
        add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
    }

    private class OpenDialogAction extends AbstractAction {
        public OpenDialogAction(String name, int mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            boolean test = true;
            // test = (dialog == null); // ***** comment or uncomment this line *****
            if (test) {
                Window win = SwingUtilities.getWindowAncestor(MainSimple.this);
                dialog = new MyDialog(win);
                dialog.pack();
                dialog.setLocationRelativeTo(win);
            }
            dialog.setVisible(true);            
        }
    }

    private class MyDialog extends JDialog {

        public MyDialog(Window win) {
            super(win, "My Dialog", ModalityType.APPLICATION_MODAL);
            add(new JButton(new DisposeAction("Close", KeyEvent.VK_C)));
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        }

        @Override
        public void dispose() {
            String text = String.format("Disposing. This hashCode: %08X", hashCode());
            System.out.println(text);
            super.dispose();
        }
    }

    private class DisposeAction extends AbstractAction {

        public DisposeAction(String name, int mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Component source = (Component) e.getSource();
            Window win = SwingUtilities.getWindowAncestor(source);
            win.dispose();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui() {
        MainSimple mainPanel = new MainSimple();
        JFrame frame = new JFrame("Main");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

}

再次注意,parent JFrame 保存对所有创建的对话框的引用,无论它们是否已被释放,并且 re-dispose 在关闭时将它们全部删除。

有点晚了,但我仍然认为它会对某人有所帮助。

我遇到了同样的问题并检查了文档中的 JDialog 我发现没有参数的构造函数文档说

NOTE: This constructor does not allow you to create an unowned JDialog. To create an unowned JDialog you must use either the JDialog(Window) or JDialog(Dialog) constructor with an argument of null.

所以我只是尝试使用 JDialog(Window) or JDialog(Dialog) 并且成功了!

我只需要写这段代码

public class MyDialog extends JDialog {
    public MyDialog() {
        super((Window)null);
    }
}

如果您无法访问应该是父级的 JFrame(这是我的情况),这可能会有所帮助