`WindowListener` acting up, perpetual firing
Asked Answered
D

2

5

I have an application with an abstract class that extends JDialog. The class as an abstract void onClose(), and, in the class's constructor, the following code is added:

addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosed(WindowEvent e) {
        onClose();
    }
}

The event is fired when expected, but then a strange thing happens. When a concrete extension of this class has code to create a new JDialog in the onClose() method, and this JDialog's defaultCloseOperation is JDialog.DISPOSE_ON_CLOSE, the event is fired continuously, until I force quit the operation.

I have isolated the code to the following SSCCE:

// package removed
// imports removed
public class SSCCE extends JDialog {
    public static void main(String[] args) {
        SSCCE s = new SSCCE();
        s.pack();
        s.setVisible(true);
    }
    public SSCCE() {
        setLayout(new GridLayout(1, 0, 0, 0));
        JButton btn = new JButton("click me");
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                dispose();
            }
        });
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                System.out
                        .println("SSCCE.SSCCE().new WindowAdapter() {...}.windowClosed()");
                onClose();
            }
        });
        add(btn);
    }

    public void onClose() {
        JDialog dialog = new JDialog();
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setVisible(true);
    }

}

Upon clicking the "click me" button, the blank JDialog appears and SSCCE.SSCCE().new WindowAdapter() {...}.windowClosed() appears in the console window. When I close the blank dialog, it reappears again and the text appears again.

Another really interesting thing is that when I change the initialization line from

JDialog dialog = new JDialog();

to

JDialog dialog = new JDialog() {
        @Override
        public synchronized void addWindowListener(WindowListener l) {
            super.addWindowListener(l);
            System.out
                    .println("SSCCE.onClose().new JDialog() {...}.addWindowListener()");
        }
    };

I get the following output in the console:

When clicking the "click me" button:

SSCCE.SSCCE().new WindowAdapter() {...}.windowClosed()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()

At the first closing of the dialog:

SSCCE.SSCCE().new WindowAdapter() {...}.windowClosed()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()

At the second closing of the dialog:

SSCCE.SSCCE().new WindowAdapter() {...}.windowClosed()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()
SSCCE.onClose().new JDialog() {...}.addWindowListener()

Each time I close the dialog, addWindowListener(WindowListener l) is called an additional time, even though it I am not intentionally calling it.

I don't really want any WindowListeners to be registered on the dialog, but I think that simply overriding the addWindowListener(...) method and not calling super.addWindowListener(...) would be too sloppy.

I'm running Java 1.6.0_31 on Mac OS X 10.6.8, using Eclipse Indigo (with WindowBuilder, if it matters).

Does anyone have any ideas?

Thanks!

Dryad answered 19/4, 2012 at 21:26 Comment(0)
B
7

As per the Java Dialog tutorial,

Every dialog is dependent on a Frame component. When that Frame is destroyed, so are its dependent Dialogs.

When you use the JDialog constructor without any arguments, it

Creates a modeless dialog without a title and without a specified Frame owner. A shared, hidden frame will be set as the owner of the dialog.

That shared hidden frame is SwingUtilities$SharedOwnerFrame, and on initialization it registers a WindowListener to all owned windows.

When you close your dialog, the SharedOwnerFrame's windowClosed method is called, which checks for all windows it owns (at this point is the original SSCCE dialog and the new one), it finds none are visible, and so it disposes itself. This has the impact of disposing all of the dialogs it owns, which posts a window close event to each. This calls windowClosed in your listener, opening a new dialog. And round we go again :-). Regarding your last comments, you get additional log lines each time because you get one per dialog the SharedOwnerFrame owns.

If you make the SSCCE dialog own the new dialog (by passing this into its constructor) then you don't end up with shared ownership and it works fine:

 public void onClose() {
     JDialog dialog = new JDialog(this);
     dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
     dialog.setVisible(true);
 }
Beryl answered 20/4, 2012 at 0:5 Comment(1)
Thanks! That makes perfect sense.Dryad
B
4
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;

public class SSCCE extends JDialog {
    public static void main(String[] args) {
        SSCCE s = new SSCCE();
        s.pack();
        s.setVisible(true);
    }

    private WindowAdapter wa = new WindowAdapter() {
        @Override
        public void windowClosed(WindowEvent e) {
            System.out
                    .println("SSCCE.SSCCE().new WindowAdapter() {...}.windowClosed()");
            onClose();
        } 
    };

    public SSCCE() {
        setLayout(new GridLayout(1, 0, 0, 0));
        JButton btn = new JButton("click me");
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                dispose();
            }
        });
        addWindowListener(
        wa);
        add(btn);
    }

    public void onClose() {
        removeWindowListener(wa);
        JDialog dialog = new JDialog();
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setVisible(true);

    }

}
Brackely answered 19/4, 2012 at 21:42 Comment(6)
I see what you've done, and I see why it works, but I'm really wondering why the listener (wa) is getting added to dialog in the first place. Could you explain to me why?Dryad
I wish I had a good answer for you. I think it may have to do with the fact that your class extends JDialog.Brackely
user1291492 with the fact that your class extends JDialog +++++ 1k, exactlyRow
@mKorbel: Can you elaborate? How does this contribute to the error?Dryad
+!. Note that the pre/code type of tags used when posting that code do not produce context formatting. For best code formatting, select the code and click the {} button above the message posting/editing form. :)Constellation
@WChargin my answer answers you question of 'why?', and offers (IMHO) a simpler solution.Beryl

© 2022 - 2024 — McMap. All rights reserved.