【问题标题】:Java - Custom JDialog dispose method called more than once - it is not intended to do soJava - 多次调用自定义 JDialog dispose 方法 - 不打算这样做
【发布时间】:2016-07-22 02:03:01
【问题描述】:

我正在编写一个脚本,其中涉及连续弹出 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();
        }
    }
}

如果我运行应用程序然后打开和关闭/处理对话框,问题就很明显了。通过在 BaseDialog 的“dispose”方法下放置 Exception e,我在通过窗口的“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$2.run(WaitDispatchSupport.java:184)
    at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:229)
    at java.awt.WaitDispatchSupport$4.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.Window$1DisposeAction.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)

还请注意,如果您运行应用程序并重复“打开 - 关闭”对话框,则该方法调用会以 1 递增(如果您注释掉关于异常堆栈跟踪打印的覆盖的“处置”方法下的前两行,则最明显并观察输出)。

如果您想使用/查看,这是一个硬剥离版本,首先感谢您!

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 类),则正确调用 dispose 方法(使用 super.dispose())。
  3. 有点不相关,但“黑客”是在 BaseDialog 类下创建一个私有字段:

-> 私有布尔处理 = false;

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

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

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

【问题讨论】:

  • 我无法回复,但现在我在这里。我重新格式化了代码(昨天我做得不够快,因为我不得不起床并且厌倦了尝试解决问题)并上传了一个简短的工作示例。现在应该不会像以前那样混乱了。
  • 从 dispose 方法中打印出 hashCode() 以查看正在调用的实例。每次新实例被调用两次,每个旧实例被调用一次。
  • 你可能需要深入研究源代码来解决这个问题,因为我还看不出它发生的原因。
  • 目前正在尝试通过它,到目前为止我很不走运。我试图在不同的地方使用大量断点来定位罪魁祸首,但和以前一样,还没有运气。有人可能会提到,如果我省略“super.dispose()”,覆盖的方法会被调用一次,所以应用程序本身应该消除代码本身的缺陷,我开始怀疑是否存在核心库某处的问题...
  • 我的意思是浏览 Java 核心库源代码

标签: java dispose jdialog


【解决方案1】:

父窗口持有对对话框的引用,并且会在对话框本身被释放时重新释放对话框。

您的问题是您没有为对话框分配父窗口,因此由于某种原因,当对话框被处理时,它会重新处理先前所做的所有对话框引用,就好像基类包含对子类的引用一样窗户。您可以通过将父窗口传递给对话框的超级构造函数来解决此问题,如下所示:

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

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

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

}

再次注意,父 JFrame 包含对创建的所有对话框的引用,无论它们是否已被释放,并在关闭时重新释放它们。

【讨论】:

  • 是的,现在看来确实可以很好地解决问题。另外,作为一个附带问题,当涉及到按顺序显示对话框时,您建议以什么方式进行研究?例如通过不同的步骤进行用户输入并管理父对话框的可见性?或者这可能是针对不同的线程?不管怎样,谢谢你的帮助!
  • 我想到的一个简单案例是说我有一个带按钮的对话框。单击按钮后,我希望它在隐藏第一个对话框的同时打开第二个对话框(因此它不会在视觉上妨碍),当我关闭第二个对话框以再次显示第一个对话框而不让第一个对话框范围之外的执行继续时介于两者之间?同样,如果您相信的话,这可能最好使用单独的线程。
  • @Smokesick:因为第一个对话框不可见,所以基本 JFrame 可能是本示例的父窗口。但我自己可能会显示一个对话框并通过 CardLayout 交换视图。
  • 是的,听起来和我的情况类似。所以最后一个问题 - 你认为拥有一个主(不可见)框架,仅用于一系列不同的框架是一个很好的方法吗?每一帧都会被实例化(不可见),当一帧关闭时,使用 WindowListener 决定下一个打开的帧(包括中间的必要计算,如设置重要变量)?还是最好还是继续使用 CardLayout?
【解决方案2】:

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

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

注意:此构造函数不允许您创建无主的 JDialog。要创建一个无主的 JDialog,您必须使用带有 null 参数的 JDialog(Window) 或 JDialog(Dialog) 构造函数。

所以我简单地尝试使用JDialog(Window)JDialog(Dialog) 并且成功了!

我只需要编写这段代码

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-19
    • 2019-01-25
    • 2017-11-30
    • 1970-01-01
    • 2011-11-07
    • 1970-01-01
    • 2019-11-14
    相关资源
    最近更新 更多