Einfrierendes Fenster

In meinem aktuellen Projekt ist folgendes Problem aufgetreten: Nach umfassenden Änderungen an meiner Gui (Swing), die anderweitig sehr vieles verbessert und erst ermöglicht haben, ist nun ein seltsamer Seiteneffekt aufgetreten. Zunächst der grundlegende Programmaufbau:

[ul]
[li] Programmoberfläche (A) des Hauptprogramms in einem JFrame mit Button zum Öffnen von B.
[/li] [li] Dialog (B) in einem JDialog mit dem eingebetteten Panel C
[/li] [li] Panel ©, ein JPanel mit verschiedenen Fenster und Buttons. Einer davon öffnet den Fortschrittsdialog D
[/li] [li] Fortschrittsdialog D in einem JDialog (seit 1,5 Jahren unverändert).
[/li][/ul]

Wenn ich den Button, der D startet, nicht betätige, verhält sich das Programm völlig normal. Drücke ich diesen Button, so fällt zunächst nichts weiter auf. D wird angezeigt und am Ende des Ablaufs wieder geschlossen, ich kann B ganz normal beenden, doch dann “friert” mir A ein in der Weise, dass es in den Hintergrund geschickt wird. Hole ich das Fenster wieder hervor, kann ich weder irgendeines der selbst eingefügten Bedienelemente betätigen, noch reagiert es auf den kleinen Knopf [x] oben rechts zum Beenden.

So ein Verhalten ist mir völlig neu.

Mit Herumprobieren, Auskommentieren und Testen habe ich herausgefunden, dass der Fehler nicht auftritt, wenn ich den Fortschrittsdialog D nicht anzeige. Dieser weiß aber rein gar nichts von irgendwelchen anderen Gui-Elementen, er wird nur über den Fortschritt der im Hintergrund ablaufenden Arbeit informiert und schließt sich dann wieder. Auch habe ich ihn seit anderthalb Jahren gar nicht verändert gehabt.

Seine Verwendung muss also irgendeinen Seiteneffekt auslösen, der mir völlig rätselhaft ist.

Das zugrundeliegende System ist ein Windows 7. Der Fehler tritt auch auf einem anderen Rechner (ebenfalls Windows 7) auf und es ist auch kein Unterschied, ob ich aus Eclipse oder dem Jar-File starte.

Ich weiß, das ist alles furchtbar wage, aber hatte jemand schon mal etwas vergleichbares? Ich tappe völlig im Dunkeln, weil offensichtlich der gefundene Auslöser nicht die Fehlerursache ist.

Es wäre vielleicht noch anzumerken, dass weder B, C noch D die Programmoberfläche A in irgendeiner Form kennen. Direkt kann also gar nicht auf A eingewirkt werden, wobei ich auch überhaupt nicht wüsste, wie sich das beobachtete Verhalten bewusst herbeiführen ließe.

Wenn man den Fehler macht, lange Prozesse direkt im EDT zu starten, friert das Fenster zwar auch ein in dem Sinne, dass es nicht aktualisiert wird, aber der von Windows bereitgestellte Rahmen (mit dem [x] zum Schließen) funktioniert dann noch.

Einfrierende Gui hängt sehr oft damit zusammen, dass du irgendwo den EDT blockierst, bzw dass du GUI elemente ausserhalb des EDT veränderst.

Wenn das der Fall ist, könnte es sein, dass durch eine Änderung des JRE (Java Update) nun so ein Verhalten auslöst.

Zum blockierten EDT schrieb ich ja schon etwas. Dann kann ich aber immer noch das Fenster bewegen und es schließen. Dieses Fenster lässt sich nicht mal mehr verschieben.

JRE ist unverändert die gleiche.

Es ist trotzdem ein Fehler im EDT, da du diesen entweder mit dem Code der von A aus B öffnet nach dem Schließen von B blockierst oder irgendwo anders ein Event ausgelöst wird was sich in einem loop festkrallt. Dass das Frame gar nicht mehr reagiert deutet daraufhin dass du es irgendwie geschafft hast das event-accept vom WDM-Bus zu blockieren.
Poste doch mal den (ggf. reduzierten) Code.
Bau auch mal entsprechende aus- und wieder-ein-kopplungen (sry grad am Handy, müsste mal schnell wer anders Beispiel-Code posten).
Ggf. auch mal mit einem Profiler und SysOuts nachgucken wo genau er “sich aufhängt”.

Eine Reihe von Fragen, womöglich etwas redundant, meine Vermutungen in Klammern

Ist D ein JDialog? (ja)

Wer ist der Owner von D? (A)

Ist D modal? (ja)

Wie wird D geschlossen? (setVisible(false))

Welches ist die DefaultCloseOperation von D? (HIDE_ON_CLOSE, besser vielleicht DISPOSE_ON_CLOSE)

Evtl. kann es helfen nicht setVisible(false) zu nutzen sondern ein dispose().

Das wäre auch so ungefähr die einzige logische Erklärung.

[QUOTE=ionutbaiu;125223]Eine Reihe von Fragen, womöglich etwas redundant, meine Vermutungen in Klammern

Ist D ein JDialog? (ja)

Wer ist der Owner von D? (A)

Ist D modal? (ja)

Wie wird D geschlossen? (setVisible(false))

Welches ist die DefaultCloseOperation von D? (HIDE_ON_CLOSE, besser vielleicht DISPOSE_ON_CLOSE)

Evtl. kann es helfen nicht setVisible(false) zu nutzen sondern ein dispose().[/QUOTE]

Zu deinen Fragen:

Ist D ein JDialog? (ja)

Fortschrittsdialog D in einem JDialog (seit 1,5 Jahren unverändert).

Also ja.

Wer ist der Owner von D? (A)

Wie ich schrieb, öffnet C den Dialog D. Der Owner ist dann also C.

Wie wird D geschlossen? (setVisible(false))

Mit

        dialog.addWindowListener(new ClosingWindowListener(new Quitter() {
            @Override
            public void quit() {
                end();
            }}));

und

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                dialog.setVisible(false);
                dialog.dispose();
                stopWatchLabel.stop();
            }
        });
    }

Welches ist die DefaultCloseOperation von D? (HIDE_ON_CLOSE, besser vielleicht DISPOSE_ON_CLOSE)

Siehe letzte Antort: DO_NOTHING_ON_CLOSE und eigene Behandlung.

Moin,

ich schmeise ja gerne mit Task um mich rum - aber Deine Close-Operation hatte ich direkt in den Listener rein gepackt ohne SwingUtils.invokeLater(). Vom Bauchgefühl her würde ich stopWatchLabel.stop() zwei Zeilen nach oben verschieben. Das dialog.dispose() sollte das stopWatchLabel mit zerstören (sofern es auf den Dialog liegt).

Ich habe eben festgestellt, wenn ich statt des Dialoges D irgendeinen anderen Dialog anzeige, tritt das Problem genauso auf. Etwa per

Kommentiere ich die Zeile aus (also zeige gar keinen Dialog an), verhält sich das Programm fehlerfrei.


end() wird auch von anderer Stelle aufgerufen, und das aus einem anderen Thread. Daher das invokeLater. Beendet wird er auch auf diesem Wege.
Ich hatte die Zeile auch schon nach oben verschoben, macht keinen Unterschied. Ich habe testweise das Label mit der Stoppuhr ganz ausgebaut, kein Unterschied.

Standardtipp der sicher bekannt ist, aber irgendwann einfach genannt werden MUSS:
baue ein vollständiges Testprogramm, was du hier guten Gewissens posten kannst,

zwar wird es wahrscheinlich bei anderen laufen, deinen Angaben nach ja kein allgemeiner Fehler,
aber vielleicht fallen trotzdem paar vermeidbare Konstrukte auf,

außerdem zwingt es dich dazu, schon selber weitgehend aufzuräumen,
simples JOptionPane statt ‘Dialog D’ ist ja schon interessanter Anfang, so dass Besonderheiten zu D nicht mehr zu bedenken ist,

womöglich läuft es bereits bei dir ab einem bestimmten Rein-Stadium, dann schrittweise zurück und die Grenze finden, ab der Probleme,
falls alles gut reproduzierbar ist, Fehler immer auftritt

Ich habe auf dem Weg zur Vereinfachung erst einmal B und C verschmolzen. Ich habe nichts anders gemacht, als dass die Dinge rund um den Panel nun nicht mehr ausgelagert sind. Und der Fehler ist weg. Ich finde es zwar etwas unbefriedigend, nicht zu wissen, was das Problem war, aber nun gut…

Der Fehler muss also irgendwo in den vier Klassen liegen, die nun zu zweien wurden. Wobei ich im Wesentlichen einfach nur die Methoden kopiert habe. Ich habe nichts an Modalitäten, Verwendung des Dialogs oder sonstigem geändert. kopfkratz

Es ist nicht logisch… aber es geht immerhin.

Wenn es mich genug wurmt, werde ich ein Konstrukt aus den vier Klassen so weit vereinfachen, wie der Fehler noch auftritt.

*** Edit ***

Ich bin schon dabei, daraus ein möglichst kleines Beispiel zu bauen. Ich will einfach gerne wissen, was ich falsch gemacht habe…

So, ich habe das Problem auf ein minimales Beispiel zusammengekocht und ich weiß nun, an welcher Stelle das Problem auftritt und womit es grob zu tun hat. Allerdings “verstehe” ich es trotzdem nicht wirklich.

Das Ganze ist nun auf drei kurze Klassen begrenzt:


import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class AGui {

    public AGui() {
        JFrame frame = new JFrame("A");
        frame.setLayout(new BorderLayout());

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.add(createLowerPart(), BorderLayout.CENTER);

        frame.setPreferredSize(new Dimension(300, 200));
        frame.setLocation(100,  75);
        frame.pack();

        frame.setVisible(true);
    }

    private Component createLowerPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        JButton button = new JButton("tu was");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                BGui gui = new BGui();
                gui.setVisible(true);
            }
        });
        panel.add(button, BorderLayout.CENTER);

        return panel;
    }

    public static void main(String[] args) {
        new ui.AGui();
    }

}```

```package ui;

import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.EventQueue;

import javax.swing.JDialog;
import javax.swing.JPanel;

public class BGui {

    private final JDialog dialog;
    private final CGui cGui;

    public BGui() {
        cGui = new CGui();

        dialog = new JDialog();

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui();
            }
        });
    }

    private void createGui() {
        dialog.setTitle("B");
        dialog.setLayout(new BorderLayout());
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

        dialog.add(createSuiteTestPanel(), BorderLayout.CENTER);

        dialog.setPreferredSize(new Dimension(300, 200));
        dialog.setLocation(200, 175);
        dialog.pack();
    }

    private JPanel createSuiteTestPanel() {
        return cGui.getPanel();
    }

    public void setVisible(boolean bool) {
        dialog.setVisible(bool);
    }

}```

```package ui;

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

import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class CGui {

    private final JPanel panel;

    public CGui() {
        panel = new JPanel();
        panel.setLayout(new BorderLayout());

        JButton startButton = new JButton("Start");
        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(null, "D", "Irgendein Dialog ...",
                        JOptionPane.WARNING_MESSAGE);
            }
        });
        panel.add(startButton, BorderLayout.CENTER);
    }

    public JPanel getPanel() {
        return panel;
    }

}```

Entstehung des Freeze:
1) auf "tu was" drücken
2) auf "Start" drücken
3) auf "OK" drücken
4) Dialog B mit Klick auf das [x] oben rechts schließen
Nun ist A bei mir eingefroren.


Es gibt zwei Möglichkeiten, den Freeze aufzulösen:

1) Im Konstruktor von `BGui `wird auf `EventQueue.invokeLater(...)` verzichtet und statt dessen direkt `createGui()` aufgerufen.

2) In `setVisible()` in `BGui` wird um das `dialog.setVisible(bool);` herum ebenfalls `EventQueue.invokeLater(...)` verwendet.

Das Problem entsteht wohl dadurch, dass die Oberfläche BGui angezeigt wird, bevor sie aufgebaut wird. Warum das allerdings `AGui` einfriert, kann ich mir nicht wirklich erklären.

Problem besteht bei mir auch,
ich habe zumindest das Testprogramm noch etwas verkürzt auf nur main-Methode + zweite für den B-Dialog,
etwas weniger unnötiges Layout, auf invokeLater kann verzichtet werden, zumal ja eh bei ActionListener im EventThread,

und das Problem läßt sich zurückführen auf setVisible(true) für Dialog vor oder nach setModalityType(Dialog.ModalityType.APPLICATION_MODAL),
aber was wohl dahintersteckt?..
bisher auch nirgendwo was zu finden zu APPLICATION_MODAL und diesem Problem

    public static void main(String[] args)  {
        JFrame frame = new JFrame("A");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JButton button = new JButton("tu was");
        button.addActionListener(new ActionListener()  {
                @Override
                public void actionPerformed(ActionEvent e)  {
                    createB();
                }
            });
        frame.add(button);

        frame.setSize(new Dimension(300, 200));
        frame.setLocation(100, 75);
        frame.setVisible(true);
    }

    static void createB() {
        final JDialog d = new JDialog();
        d.setTitle("B");
        d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);

        JButton startButton = new JButton("Start");
        startButton.addActionListener(new ActionListener()   {
                @Override
                public void actionPerformed(ActionEvent e)    {
                    JOptionPane.showMessageDialog(null, "D", "Irgendein Dialog ...", JOptionPane.WARNING_MESSAGE);
                }
            });
        d.add(startButton);

        d.setSize(new Dimension(300, 200));
        d.setLocation(200, 175);

        d.setVisible(true);
        d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
    }
}

edit: die Kette JFrame, JDialog, JOptionPane (mit intern ja auch irgendwie JDialog…) ist auch komplett nötig,

lustig kurz wäre sonst nur ein Dialog in der main-Methode der im Listener per erneuter main-Methode den nächsten Dialog erzeugt,
aber

  • nur das JFrame wird blockiert, nicht andere Dialoge, soweit in der Kürze getestet,
  • nur der eigene Dialog kann auf APPLICATION_MODAL gesetzt werden, anderes freilich vielleicht auch als Ursache möglich
  • nur mit dem JOptionPane wirkt das ganze dann auch erst noch, soweit in der Kürze getestet, jedenfalls nicht mit noch einem B-Dialog

Ich hatte auch überlegt, ob ich es in statische Methoden packen sollte, habe dann aber noch die Klassenform erhalten. Ich hatte schon so wahnsinnig viel Code aus den Klassen geschmissen, dass mir die weitere Vereinfachung dann nicht mehr eingefallen ist. Es ist schön, ein noch kürzeres Programm zu sehen, dass das Problem zeigt.

Ob diese Abhängigkeit von der Reihenfolge von setVisible(true) vor oder nach setModalityType(Dialog.ModalityType.APPLICATION_MODAL) wohl auf einen internen Bug hindeutet?

Oder es ist einfach ein Benutzerfehler, da man setVisible(true) eben ganz zuletzt aufrufen sollte. Bei mir war das noch ein wenig versteckter, da der Aufruf nur durch das einseitige EventQueue.invokeLater(...) verdreht stattfand.

vermutlich

ich persönlich übergebe dem Konstruktor schon ob der Dialog modal sein soll oder nicht - setVisible rufe ich immer zum Schluss auf