PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Java] Probleme mit Swing


Lipwigzer
2008-03-18, 10:02:27
Grüßt euch,
hab' mich hier gerade angemeldet, daher erstmal 'nen freundliches 'Hallo' an alle (Mit-)Leser. :)

Folgendes Problem...
Hab' mich in den letzten Wochen Studiumbedingt in Java OOP eingearbeitet und will nun nach der Klausur nicht alles wieder verwerfen - also wird weitergeproggt.
Nun hab' ich hier aber "kleine" Unregelmäßigkeiten mit Swing.
Und zwar läuft mein Programm beim ersten Start (aus der Konsole) wunderbar - Swing stellt die GUI so dar, wie ichs möchte und alles läuft.
Wenn ich dann das Fenster schließe beendet es sich auch (auch inder Konsole zu sehen). Wenn ich es dann aber gleich wieder starte, bekomme ich nur ein graues leeres Fenester von Swing gezeichnet.
Wenn ichs allerdings beende und Strg+C in der Konsole drücke, klappt es beim nächsten Start wieder ohne Probleme. Es scheint also, als würde irgendeine Instanz nicht korrekt beendet werden und verhindert dann beim zweiten Start das Zeichnen der GUI durch Swing?!

Hier mein Code...



import javax.swing.*; // Java Package Swing GUI importieren
import java.awt.event.*; // Java Package Action Listener importieren
import java.awt.*;

public class myNewBox {

JFrame frame;
JLabel charLabel;
JLabel weaponLabel;
JPanel panel;

public static void main(String[] args) {
myNewBox theBox = new myNewBox();
theBox.makeGUI();
theBox.fillMyBag();
}

public void fillMyBag() { ... }

public String printChars() {
StringBuffer myString = new StringBuffer();
for(int i=0;i<myCharacter.theCharacters.size();i++) {
myString.append(myCharacter.theCharacters.get(i).toString()+"<p/>");
}
return myString.toString();
}

public String printWeapons() {
StringBuffer myString = new StringBuffer();
for(int i=0;i<weapon.theWeapons.size();i++) {
myString.append(weapon.theWeapons.get(i).toString()+"<p/>");
}
return myString.toString();
}

public void makeGUI() {
frame = new JFrame(); // Frame (Fenster) erstellen
charLabel = new JLabel("Das Label 1!"); // erstes Label erstellen
weaponLabel = new JLabel("Das Label 2!"); // zweietes Label erstellen
JButton charButton = new JButton("CHARACTER!"); // ersten Button erstellen
JButton weaponButton = new JButton("WEAPON!"); // zweiten Button erstellen
panel = new JPanel(); // Panel erstellen

// Panel einrichten...
panel.setBackground(Color.white);
panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
// Frame einrichten...
frame.setSize(600,600);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Buttons mit ActionListener versehen...
charButton.addActionListener(new characterListener());
weaponButton.addActionListener(new weaponListener());
// Elemente mit dem Frame verknüpfen...
frame.add(BorderLayout.CENTER, panel);
// Elemente mit dem Panel verknüpfen...
panel.add(charButton);
panel.add(charLabel);
panel.add(weaponButton);
panel.add(weaponLabel);
}

// Innere Klasse mit ActionListener für Button "charButton"
class characterListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
charLabel.setText("<html><p/>"+printChars()+"<p/></html>"); // NOCH IMPLEMENTIEREN !!!!
}
}

// Innere Klasse mit ActionListener für Button "weaponButton"
class weaponListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
weaponLabel.setText("<html><p/>"+printWeapons()+"<p/></html>"); // NOCH IMPLEMENTIEREN !!!!
}
}
}



Ich dachte, dass ich mit frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); alles nötige gemacht hätte... dem scheint aber irgendwie nicht so zu sein?!
In fillMyBag() werden Instanzen zwei anderer Klasse erzeugt (myCharacter und weapon) und diese in zwei generische, statische Arrays geladen. Den Teil hab ich mal weggelassen, da das ja nichts mit Swing zutun hat...

Hoffe, dass mir einer helfen kann! :)

EDIT: (falls es hilft) hier zwei Bilder...
Beim ersten Starten - alles 1A.
http://www5.picfront.org/picture/CCsp2Vjbfx3/img/javaSwing1.jpg

Beim zweiten Starten - nur noch graue Fläche. (behebbar, wenn ich nach dem ersten Beenden in der Konsole Strg+C drücke)
http://www5.picfront.org/picture/RFtihsra/img/javaSwing2.jpg

Beste Grüße und Danke schon jetzt.
Lipwigzer

Gast
2008-03-18, 11:34:00
Nimm C++.

instinct
2008-03-18, 12:04:39
Nimm C++.

welche geistreicher kommentar ...

An den TS:
Versuch mal anstelle des EXIT_ON_CLOSE, eine frame.HIDE_ON_CLOSE.

astro
2008-03-18, 13:19:23
Das
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
muss in die Deine main-methode.

Also so:
theBox.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
kannst Du weglassen.

Lipwigzer
2008-03-18, 13:55:47
Grüßt euch.

@ instinct:
HIDE_ON_CLOSE macht aber nicht das, was ich erreichen will. Da wird einfach die GUI geschlossen/versteckt - das Programm läuft aber auf der Konsole weiter (klar).
Ich will aber das komplette Programm durch Drücken des X-Buttons schließen...

@ astro:
Syntaktisch leuchtet mir das zwar ein - es funktioniert aber leider nicht. Beim compilieren bricht er mit der Fehlermeldung "cannot find symbol" ab. Soweit ich weiss ist setDefaultCloseOperation() eine Swing-eigene Methode - auf eine Instanz von myNewBox kann ich die also gar nicht anwenden, oder?!

Grüße,
Lipwigzer

astro
2008-03-18, 15:18:00
@ astro:
Syntaktisch leuchtet mir das zwar ein - es funktioniert aber leider nicht. Beim compilieren bricht er mit der Fehlermeldung "cannot find symbol" ab. Soweit ich weiss ist setDefaultCloseOperation() eine Swing-eigene Methode - auf eine Instanz von myNewBox kann ich die also gar nicht anwenden, oder?!

Ah ok, ich hab was übersehen. Du kannst setDefaultCloseOperation() nicht in der main ausführen, da Deine myBox nicht vom JFrame erbt, somit ist setDefaultCloseOperation() nicht ausführbar. Daher der Compilerfehler.
Lass Deine Klasse einfach von JFrame erben, dann sollte es funktionieren:
public class myNewBox extends JFrame {

Viel Erfolg :up:

DocEW
2008-03-18, 15:50:36
Also bei mir musst du nur die Größe des Fensters ändern, dann erscheinen die Komponenten. Alternativ kannst du zunächst "panel.add(...)" machen, und danach "frame.add(..., panel)".

HellHorse
2008-03-18, 19:46:48
Hat vermutlich nichts mit deinem Problem zu tun aber du musst alle Interaktionen mit Swing Komponenten, insbesondere das Instanzieren und Hinzufügen im EDT laufen lassen -> SwingUtilities.invokeLater

AtTheDriveIn
2008-03-18, 20:12:28
Ich würde einfach einen WindowListener erstellen:

mit

public void windowClosing(WindowEvent e)
{
System.exit(0);
}

damit ist dein Programm ganz sicher komplett beendet.

Lipwigzer
2008-03-21, 15:33:42
Grüßt euch,
hatte nun endlich Zeit mich nochmal dran zu setzen... danke an alle für die hilfreichen Tipps! :)



frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent ev) {
System.exit(0);
}
});



Ist bisher noch die "funktionierendste" Variante. Allerdings ist es ab dem zweiten Start nur sichtbar, wenn ich das Fenster per Maus vergrößere/-kleinere... (wie DocEW schon angemerkt hat)

@ astro: Wenn ich myNewBox von JFrame erben lasse und dann in der main()-Methode theBox.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); schreibe, schließt er zwar das Fenster, aber das Programm läuft in der Konsole weiter und muss mit Strg+C beendet werden...

@ DocEW: Genau das hab ich ja eigentlich?!



// Elemente mit dem Panel verknüpfen...
panel.add(charButton);
panel.add(charLabel);
panel.add(weaponButton);
panel.add(weaponLabel);
// Elemente mit dem Frame verknüpfen...
frame.add(BorderLayout.CENTER, panel);


@HellHorse:
Erstmal danke für den Tipp! Aber ich weiss mit "EDT" absolut nichts anzufangen... vllt. ein- zwei Sätze dazu?! :)

@AtTheDriveIn:
Habs nun mit dem WindowListener gemacht (wie man oben sieht) - danke für den Tipp! :)
Klappt halt immer noch nicht hundertprozentig. =/

@all: Ich hab mal die drei Klassen angehängt, vllt. hab' ich ja irgendwas übersehen?!

Grüße und Danke,
Lipwigzer

Achill
2008-03-21, 17:13:39
Hallo

Wenn der Aufruf "frame.setVisible(true);" in deiner Methode "makeGUI()" zum Schluss ausgeführt wird, kommt es nicht zu dem Problem. Ich kenne mich leider zu wenig mit Swing aus, um genau sagen zu können, warum das so ist.


public void makeGUI() {
frame = new JFrame(); // Frame (Fenster) erstellen
charLabel = new JLabel("Das Label 1!"); // erstes Label erstellen
weaponLabel = new JLabel("Das Label 2!"); // zweietes Label erstellen
JButton charButton = new JButton("CHARACTER!"); // ersten Button erstellen
JButton weaponButton = new JButton("WEAPON!"); // zweiten Button erstellen
panel = new JPanel(); // Panel erstellen

// Panel einrichten...
panel.setBackground(Color.white);
panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
// Elemente mit dem Panel verknüpfen...
panel.add(charButton);
panel.add(charLabel);
panel.add(weaponButton);
panel.add(weaponLabel);
// Frame einrichten...
frame.setSize(600,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Buttons mit ActionListener versehen...
charButton.addActionListener(new characterListener());
weaponButton.addActionListener(new weaponListener());
// Elemente mit dem Frame verknüpfen...
frame.add(BorderLayout.CENTER, panel);
frame.setVisible(true);
}


Meine Beobachtung sind folgende:

1. "setVisible()" macht dein Fenster überhaupt sichtbar und Swing wird auch erst das Fenster vom OS zeichnen lassen, wenn die Methode überhaupt aufgerufen wird. Wird der Aufruf weggelassen, dann beendet sich dein Programm auch gleich wieder.

2. Es werden mehrere Treads gestartet, auch wenn du das nicht direkt mitbekommst bzw. explizit programmiert hast, die Thread sind unter Linux und Java6 : Thread[Signal Dispatcher,9,system], Thread[Reference Handler,10,system], Thread[main,5,main], Thread[AWT-Shutdown,5,main], Thread[AWT-XAWT,6,main], Thread[Java2D Disposer,10,system], Thread[Finalizer,8,system], Thread[AWT-EventQueue-0,6,main]

3. Das Weglassen von dem Aufruf "frame.add(BorderLayout.CENTER, panel);" führt immer zu deinem beschrieben Fehler.

Nun die Vermutung:

Beim ersten Aufruf sind deine kompilierten Klassen noch nicht von deinem Dateisystem gecacht. Dein Main-Thread (Programm, dass gestartet wurde) schafft alle Anweisungen auf dem Frame-Object abzuarbeiten / Werte zu setzen, bevor deine GUI erzeugt wird (die anderen Threads).

Der zweite Aufruf und jeder folgende ohne neues Kompilieren führt zu den besagten Fehler, da die Klassen gecached sind und somit die Threads auch schneller starten (hier spielt sicher auch die JVM mit rein). Die GUI wird erzeugt und sichtbar gemacht mittels "setVisible(true)", bevor dein Main-Thread auf deiner Frame folgende Methode aufrufen kann "add(BorderLayout.CENTER, panel);".

Da im nachhinein das Frame nicht mitbekommt, dass das Layout geändert wurde (dazu muss ein entsprechendes Event gefeuert werden), sieht man ein leeres Frame.

Die Änderung der Größe des Fensters hilft nun, weil dies ein solches Event (Änderung des Context) darstellt und eine Neuzeichnug veranlast, wo nun auch dein Layout berücksichtigt wird.

Das ganze sollte ein typisches Problem der Race-Kondition mehrere Threads sein: http://de.wikipedia.org/wiki/Race_Condition

Aber lass dich davon nicht abhalten, die Lösung ist, dass du entweder "frame.setVisible(true);" ans Ende deiner Methode packst oder aber, dass du deinem Main-Thread einen exclusiven Zugriff auf das jFrame-Object gibst, dann müssen alle anderen Threads warten, bis der "synchronized-Block" von deinem Thread abgearbeitet ist:


public void makeGUI() {
frame = new JFrame(); // Frame (Fenster) erstellen

synchronized (frame) {

charLabel = new JLabel("Das Label 1!"); // erstes Label erstellen
weaponLabel = new JLabel("Das Label 2!"); // zweietes Label erstellen
JButton charButton = new JButton("CHARACTER!"); // ersten Button erstellen
JButton weaponButton = new JButton("WEAPON!"); // zweiten Button erstellen
panel = new JPanel(); // Panel erstellen

// Panel einrichten...
panel.setBackground(Color.white);
panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
// Elemente mit dem Panel verknüpfen...
panel.add(charButton);
panel.add(charLabel);
panel.add(weaponButton);
panel.add(weaponLabel);
// Frame einrichten...
frame.setSize(600,600);
frame.setVisible(true);
//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Buttons mit ActionListener versehen...
charButton.addActionListener(new characterListener());
weaponButton.addActionListener(new weaponListener());
// Elemente mit dem Frame verknüpfen...
frame.add(BorderLayout.CENTER, panel);

frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent ev) {
System.exit(0);
}
});
}
}

Lipwigzer
2008-03-21, 22:23:48
'n abend, Achill.
Vielen lieben Dank für deine ausführliche Erklärung! Bin also durch die Deplatzierung von setVisible() in 'ne böse Falle getappt.
Hab' es eben ausprobiert und mit setVisible() als letzte Anweisung in der main()-Methode klappt es ohne Probleme! Danke dafür! :)
Mit Threads wollte ich mich erstmal nicht rumärgern - erstmal müssen die "Basics" des OOP sitzen, denke ich.

Beste Grüße,
Lipwigzer

Trap
2008-03-21, 22:28:46
Man muss sich mit Threads nicht rumärgern, einfach eine main() benutzen die so aussieht:

public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
realMain(/*evtl mit args*/);
}
});
}
Dann hat man alles in einem Thread (dem EDT), zumindest solang man keine eigenen Threads erstellt.

EDT steht übrigens für Event-Dispatch-Thread, das ist der Thread in dem alle Aktionen durch den User verarbeitet werden und in dem üblicherweise auch der komplette GUI-Code laufen sollte.