为什么在调用 setVisible(false) 和 dispose() 时调用的 Window/Component 侦听器不同?

Why are the Window/Component Listeners invoked differently when setVisible(false) and dispose() are called?

我看到的差异是(运行 on JDK 1.7):

setVisible(false), invokes componentHidden but not windowClosed (The API states only on dispose() so it's OK even if it irritates me)

但是

dispose(), invokes windowClosed but not componentHidden

简短 运行 示例代码 (MCVE):

public class JDialogTest extends JDialog {
    private static final long serialVersionUID = 1L;

    public JDialogTest(JFrame owner){
        super(owner,ModalityType.APPLICATION_MODAL);
        init();
    }
    private void init() {
        this.getContentPane().setLayout(new GridLayout(1,2));
        JButton btnVisible = new JButton("Set visible false");
        btnVisible.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JDialogTest.this.setVisible(false);
            }
        });
        JButton btnDispose = new JButton("Dispose");
        btnDispose.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JDialogTest.this.dispose();
            }
        });
        this.getContentPane().add(btnVisible);
        this.getContentPane().add(btnDispose);
        this.pack();
    }

    public static void main(String[] args) {

        //A fake frame to test JDialog
        JFrame fakeFrame = new JFrame("Fake Frame");
        fakeFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        fakeFrame.getContentPane().setLayout(new BorderLayout());
        JButton btnOpen = new JButton("Open Dialog");
        fakeFrame.getContentPane().add(btnOpen,BorderLayout.CENTER);
        fakeFrame.pack();
        fakeFrame.setLocationRelativeTo(null);

        //Generate the test dialog
        final JDialogTest dialog = new JDialogTest(fakeFrame);
        dialog.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentShown(ComponentEvent e) {
                System.out.println("Component Shown");
            }
            @Override
            public void componentHidden(ComponentEvent e) {
                System.out.println("Component Hidden");
            }
        });

        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent e) {
                System.out.println("Window open");
            }
            @Override
            public void windowClosed(WindowEvent e) {
                System.out.println("Window closed");
            }
        });
        dialog.setLocationRelativeTo(null);

        btnOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dialog.setVisible(true);
            }
        });
        fakeFrame.setVisible(true);
    }
}

注意: 该示例具有 JDialog,但我在 JFrame 中看到相同的行为,以测试简单地将侦听器附加到 fakeFrame,并添加类似的按钮。 (避免在 MVCE 中保持它 M 最小)。

我考虑过这个post:

JDialog setVisible(false) vs dispose()

API 文档:

Window.setVisible(boolean b), Window.dispose(), ComponentListener.componentHidden(ComponentEvent e), WindowListener.windowClosed(WindowEvent e)

我为什么关心:自然是出于好奇,也是因为我使用按钮关闭window(调用dispose())和界面也可以通过 top/right window 关闭图标和 alt+F4 (调用 setVisible(false)! ?)。因此,不能使用上述任何一个侦听器。只有 HierarchyListener 会捕获它们,这似乎违反直觉。

编辑: 这个问题的范围是 "why is it like this"?目的是什么?”。我期待 dispose() 调用两者!我在 API 文档中找不到任何线索来说明为什么不这样做。

the interface can also be close by top/right window close icon alt+F4 (invoking setVisible(false)!?)

这是由默认关闭操作决定的。您可以使用 setDefaultCloseOperation 设置它。默认值为 HIDE_ON_CLOSE,这就是您获得 componentHidden 调用的原因。如果您将其设置为 DISPOSE_ON_CLOSE 那么您将获得 windowClosed 调用。设置为后者将允许您只注册那些事件类型。

无论如何,隐藏和处置做不同的事情。处置会释放使用的资源,而隐藏则不会。此外,隐藏最后一个 window 不会退出 JVM,而处理它会。

至于事件调度的技术方面,有很多错综复杂的地方。虽然处理 window 确实调用了它的隐藏方法,但事件的调度是在操作完成后完成的。这意味着 EDT 可以调度事件 "after the fact"。由于 window 已关闭,因此不会调度隐藏事件。

我在努力解决所描述的行为时遇到了这个问题。 componentHidden 的处理程序没有被调用,尽管 ComponentEvent(COMPONENT_HIDDEN)setVisible(false) 安排到对话框的事件队列中。

我的对话框是模态的,调用者在对话框关闭后显式调用 dispose 并从 setVisible(true) 调用 returns。这可能是对话框的事件队列和应用程序的事件队列之间的“竞争条件”。因此,我的解决方法是在调用方方面:

SwingUtilities.invokeLater(myDialog::dispose);

这应该推迟对话框的处理,直到其事件队列耗尽; componentHidden 在此设置中被调用。