PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Java] Swing+Threads


Talion
2004-03-26, 19:23:14
Hi,
ich komme gerade bei Java nicht weiter, es geht um Swing und Threads. Ich möchte in einem JTextfield einen Fortschrittangabe (Prozent geladen) ausgeben, wie weit eine Datei geladen ist. Meine Anweisungen an die gui werden aber erst ausgegeben, sobald die Datei komplett geladen ist (was ja nicht sehr sinnvoll ist).

Hier mal in etwa was ich mache:
ZipFile file = new ZipFile(zipdatei);
ZipEntry entry = file.getEntry(datei_im_ziparchiv);
InputStream in = file.getInputStream( entry );

// nun hole ich mir die dateigröße und gebe sie an die gui aus (AUSGABE 1!)

int l=0; // zahl der gelesenen bytes
while( l != -1) // end of file
{
// hier gebe ich eine Prozentangabe aus, wie viel von der Datei schon geladen ist (AUSGABE 2!)
byte[] myText = new byte[Buffer];
l = in.read(myText, 0, Buffer );
// byte-array in string umwandeln, speichern etc.
}

Wenn ich an den mit AUSGABE! markierten Stellen per System.out.println etwas ausgebe, funktioniert alles bestens. Wenn ich es aber an mein Textfeld ausgebe, ist der für die gui zuständige Thread wohl noch blockiert und gibt alles erst aus, wenn die Datei fertig geladen ist. Ich weiß, dass Swing nicht threadsicher ist und als Ersatzlösung invokeLater etc. angeboten wird, aber ich verstehe noch nicht, wie ich diese verwende. Muss ich nun das Einlesen der Datei in einen neuen, normalen Thread packen und von da aus über invokeLater meine Anweisungen an die gui geben, oder wie soll das aussehen?
Bin im Moment trotz Java-Dokus etwas hilflos, es wäre nett wenn mir da jemand mal n Tipp oder ein Codebeispiel gibt.

Talion

El Fantastico
2004-03-26, 20:42:36
Hi!

Führst Du den oben aufgeführten Code aus dem Kontext des Event-Handler Threads aus (also in einem Listener oder so)?

wg_mithrandir
2004-03-26, 20:42:36
In der Regel wird deine Schleife sehr schnell abgearbeitet werden, weshalb Swing kaum Gelegenheit haben wird, den gesetzten Text anzuzeigen.

Ich würde in der Schleife folgendes vorschlagen (Code ist getestet):

import java.awt.*;
import javax.swing.*;

public class Test extends JFrame
{
protected JTextField jTextField = null;
protected int i = 0;

public static void main( String[] args )
{
Test frame = new Test();

frame.show();


frame.doSomething();
}

public Test()
{
jTextField = new JTextField( "Test" );

getContentPane().add( jTextField, BorderLayout.CENTER );

pack();
}

private void doSomething()
{
for( i = 0 ; i < 1000; i++ )
{
try
{
SwingUtilities.invokeAndWait( new Thread()
{
public void run()
{
jTextField.setText( Integer.toString( i ) );
}
} );
}
catch( Exception iex )
{
iex.printStackTrace();
}
}
}
}

hth, mith

mithrandir
2004-03-26, 20:45:40
Hi,

Vor allem zu beachten ist die Zeile, wo invokeAndWait verwendet wird, um so lange zu warten, bis die Gui die Aufgabe (setText()) erfolgreich erledigt hat.

Sorry, war vorhin nicht eingelogg...

bye, mith

Talion
2004-03-26, 23:19:43
@ El Fantastico: Der obenstehende Code wird nach Bestätigung einer Eingabe (Enter) ausgeführt, aber in einer eigenen Klasse.

@ mith:
Danke, dein Code läuft bei mir und ich versteh es auch soweit.
Nun habe ich aber das Problem, das wenn ich deinen Code bei meinem Prog verwende ein Fehler auftritt. Einzige Änderungen die ich durchgeführt habe: statt JTextfield.setText() habe ich meine Methode setStatus() verwendet, die sonst auch funktioniert. Wenn ich nur die for-Schleife ohne invoke nehme, geht es auch immer noch. Andernfalls bekomme ich einen Fehler, der vom catch nicht aufgefangen wird. Heißt das, dass der Fehler an einer ganz anderen Stelle entsteht?
Ich sehe den Fehler nicht vollständig, weil ich in der Konsole nicht hochscrollen kann.

at java.awt.Component.processEvent(Unknown Source)
at java.awt.Container.processEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(Unknown Source)

at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Window.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(Unknown Source)

at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)


In "Java ist auch eine Insel" ist folgender Code angegeben, um die Fehlerausgabe in ein Fenster umzulenken:
PrintStream p = new PrintStream() {
new OutputStream() {
public void write( int b ) {
ta.append ( (char)b );
}
}
}
System.setErr( p );
System.setOut( p );
ta soll ein Textarea-Objekt sein, aber der Compiler scheitert schon vorher, so dass ich gerade nicht weiß wie ich an die gesamte Fehlermeldung kommen soll.

HellHorse
2004-03-27, 01:39:53
Original geschrieben von Talion
Ich weiß, dass Swing nicht threadsicher ist
Wer hat dir denn das erzählt?
Das Problem ist wohl eher, dass das GUI nicht neu gezeichnet wird, solange der Event-Thread nicht abgeschlossen ist.

Dieses Beispiel soll das veranschaulichen:

public class ThreadFrame extends JFrame {

public ThreadFrame() throws HeadlessException {
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.initLayout();
this.pack();
this.setResizable(false);
}

private void initLayout() {
JComponent contentPane = (JComponent) this.getContentPane();
contentPane.setLayout(new GridLayout(3, 1, 5, 5));

//JTextField progressBar = new JTextField( "ThreadTest" );
JProgressBar progressBar = new JProgressBar(0, 9);
contentPane.add(progressBar);

JCheckBox checkBox = new JCheckBox("run in new Thread");
NewThreadListener newThreadListener = new NewThreadListener();
checkBox.addActionListener(newThreadListener);
contentPane.add(checkBox);

JButton button = new JButton("start thread");
LoaderListener loaderListener = new LoaderListener(progressBar, 0, 9);
button.addActionListener(loaderListener);
contentPane.add(button);
newThreadListener.setLoaderListener(loaderListener);

Border outsideBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED);
Border insideBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
Border compoundBoder = BorderFactory.createCompoundBorder(outsideBorder, insideBorder);
contentPane.setBorder(compoundBoder);
}

public static void main( String[] args ) {
ThreadFrame frame = new ThreadFrame();
frame.setVisible(true);
}

private class NewThreadListener implements ActionListener {
private LoaderListener loaderListener;

public void actionPerformed(ActionEvent e) {
AbstractButton source = (AbstractButton) e.getSource();
this.loaderListener.setUseNewThread(source.isSelected());
}

public void setLoaderListener(LoaderListener loaderListener) {
this.loaderListener = loaderListener;
}
}

private class LoaderListener implements ActionListener {

private JProgressBar progressBar;

private boolean useNewThread;

int min;

int max;

public LoaderListener(JProgressBar progressBar, int min, int max) {
if (min >= max) {
throw new IllegalArgumentException("min must be smaller than max");
}
this.progressBar = progressBar;
this.useNewThread = false;
this.min = min;
this.max = max;
}

public void setUseNewThread(boolean useNewThread) {
this.useNewThread = useNewThread;
}

private void count() {
try {
for(int i = this.min ; i <= this.max; i++ ) {
progressBar.setValue(i);

Thread.sleep(500);
}
Thread.sleep(500);
progressBar.setValue(min);
}
catch (InterruptedException ie) {
ie.printStackTrace();
System.exit(1);
}
}

public void actionPerformed(ActionEvent ae) {
final AbstractButton source = (AbstractButton) ae.getSource();
source.setEnabled(false);

if (this.useNewThread) {
Thread t = new Thread( new Runnable() {
public void run() {
count();
source.setEnabled(true);
}}
);
t.start();
}
else {
count();
source.setEnabled(true);
}
}
}
}

Wenn wir einen neuen Thread öffnen, wird das GUI neu gezeichnet, wann immer wir inkrementieren.
Wenn nicht, wird es bloss einmal neu gezeichnet, nämlich wenn wir mit zählen fertig sind.

Wie sieht denn die Fehlermeldung des Compilers aus?

von JTextField auf JProgressBar umgestiegen

El Fantastico
2004-03-27, 09:10:46
Original geschrieben von Talion
@ El Fantastico: Der obenstehende Code wird nach Bestätigung einer Eingabe (Enter) ausgeführt, aber in einer eigenen Klasse.

Dann könnte Dein Problem mit invokeAndWait() daher rühren. Zitat aus der javadoc (invokeAndWait):
"It should not be called from the EventDispatchThread."

Ich denke das Beste wäre in Deinem Fall, den Entzip-Code in einem neuen Thread zu starten (und so vom EventDispatchThread zu entkoppeln) und Deine GUI Updates mit invokeAndWait() auszuführen und innerhalb dieses Threads anzustossen.

@HellHorse:
Threadsicherheit ist wohl nicht der richtige Ausdruck. Siehe dazu auch:
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
Zitat:
"Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread."

Ich denke, genau das tust Du doch nicht mehr in Deinem Beispiel?

Talion
2004-03-27, 10:40:54
Original geschrieben von HellHorse
Wer hat dir denn das erzählt?
http://www.galileocomputing.de/openbook/javainsel3/javainsel_150033.htm#t2t32
Warum Swing nicht Thread-sicher ist
Die Tatsache, dass das Swing-Toolkit nicht Thread-sicher ist, erstaunt vielleicht auf den ersten Blick. Das AWT ist Thread-sicher, da AWT auf Plattform-Peer-Elemente vertraut. In einer List-Box unter dem AWT ist es problemlos möglich, ein Element einzufügen und parallel zu löschen. Doch auf die Synchronisation bei Swing wurde aus zwei Gründen verzichtet:
Untersuchungen mit anderen grafischen Bibliotheken haben ergeben, dass Operationen in Threads zu ärgerlichen Deadlock-Situationen führen können. Es ist eine zusätzliche Last, die auf dem Programmieren grafischer Oberflächen lastet, Monitore korrekt einzusetzen.
Als zweiter, sicherlich in Zukunft weniger wichtiger Punkt ist der Gewinn von Ausführungsgeschwindigkeit zu nennen. Das Swing-Toolkit kostet ohnehin viel Zeit, so dass auf die zusätzliche Synchronisation gut verzichtet werden kann.

Ich denke das Beste wäre in Deinem Fall, den Entzip-Code in einem neuen Thread zu starten (und so vom EventDispatchThread zu entkoppeln) und Deine GUI Updates mit invokeAndWait() auszuführen und innerhalb dieses Threads anzustossen.Mal sehen ob ich das mit dem Code von Hellhorse hinbekomme, gestern hatte es noch nicht geklappt. (Mit Java hab ich bisher nicht wirklich viel gemacht)



Wie sieht denn die Fehlermeldung des Compilers aus?F:\JAVA\gui.java:169: illegal start of type
new OutputStream() {
^
F:\JAVA\gui.java:174: <identifier> expected
}
^
F:\JAVA\gui.java:175: ';' expected
System.setErr( p );
^
3 errors

HellHorse
2004-03-27, 11:00:16
Original geschrieben von El Fantastico
...
Ich denke, genau das tust Du doch nicht mehr in Deinem Beispiel?
Ja, so besser?

public ThreadFrame() throws HeadlessException {
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.initLayout();
this.pack();
this.setResizable(false);
}

private void initLayout() {
JComponent contentPane = (JComponent) this.getContentPane();
contentPane.setLayout(new GridLayout(3, 1, 5, 5));

//JTextField progressBar = new JTextField( "ThreadTest" );
JProgressBar progressBar = new JProgressBar(0, 9);
contentPane.add(progressBar);

JCheckBox checkBox = new JCheckBox("run in new Thread");
NewThreadListener newThreadListener = new NewThreadListener();
checkBox.addActionListener(newThreadListener);
contentPane.add(checkBox);

JButton button = new JButton("start thread");
LoaderListener loaderListener = new LoaderListener(progressBar, 0, 9);
button.addActionListener(loaderListener);
contentPane.add(button);
newThreadListener.setLoaderListener(loaderListener);

Border outsideBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED);
Border insideBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
Border compoundBoder = BorderFactory.createCompoundBorder(outsideBorder, insideBorder);
contentPane.setBorder(compoundBoder);
}

public static void main( String[] args ) {
ThreadFrame frame = new ThreadFrame();
frame.setVisible(true);
}

private class NewThreadListener implements ActionListener {
private LoaderListener loaderListener;

public void actionPerformed(ActionEvent e) {
AbstractButton source = (AbstractButton) e.getSource();
this.loaderListener.setUseNewThread(source.isSelected());
}

public void setLoaderListener(LoaderListener loaderListener) {
this.loaderListener = loaderListener;
}
}

private class LoaderListener implements ActionListener {

private JProgressBar progressBar;

private boolean useNewThread;

int min;

int max;

public LoaderListener(JProgressBar progressBar, int min, int max) {
if (min >= max) {
throw new IllegalArgumentException("min must be smaller than max");
}
this.progressBar = progressBar;
this.useNewThread = false;
this.min = min;
this.max = max;
}

public void setUseNewThread(boolean useNewThread) {
this.useNewThread = useNewThread;
}

private void count() {
try {
for(int i = this.min ; i <= this.max; i++ ) {
progressBar.setValue(i);

Thread.sleep(500);
}
Thread.sleep(500);
progressBar.setValue(min);
}
catch (InterruptedException ie) {
ie.printStackTrace();
System.exit(1);
}
}

private void countSafe() {
try {
for(int i = this.min ; i <= this.max; i++ ) {
SwingUtilities.invokeAndWait(this.countRunnable(i));

Thread.sleep(500);
}
Thread.sleep(500);
SwingUtilities.invokeAndWait(this.countRunnable(min));
}
catch (InterruptedException ie) {
ie.printStackTrace();
System.exit(1);
} catch (InvocationTargetException ite) {
ite.printStackTrace();
}
}

private Runnable countRunnable(final int i) {
return new Runnable() {
public void run() {
progressBar.setValue(i);
}
};
}

public void actionPerformed(ActionEvent ae) {
final AbstractButton source = (AbstractButton) ae.getSource();
source.setEnabled(false);

if (this.useNewThread) {
Thread t = new Thread( new Runnable() {
public void run() {
countSafe();
source.setEnabled(true);
}}
);
t.start();
}
else {
// hier countSafe() aufzurufen würde zu einem
// Fehler führen
count();
source.setEnabled(true);
}
}
}
}
@Gast du

Original geschrieben von El Fantastico
Dann könnte Dein Problem mit invokeAndWait() daher rühren. Zitat aus der javadoc (invokeAndWait):
"It should not be called from the EventDispatchThread."
Das führt natürlich zu einem Fehler.

HellHorse
2004-03-27, 11:04:51
Original geschrieben von Talion

F:\JAVA\gui.java:175: ';' expected

PrintStream p = new PrintStream() {
new OutputStream() {
public void write( int b ) {
ta.append ( (char)b );
}//write
}//OutputStream
}; //PrintStream

Talion
2004-03-27, 11:25:01
Original geschrieben von HellHorse

PrintStream p = new PrintStream() {
new OutputStream() {
public void write( int b ) {
ta.append ( (char)b );
}//write
}//OutputStream
}; //PrintStream

Die anderen beiden Fehler bleiben dann aber immer noch, deshalb hatte ich mich um den noch nicht gekümmert. Hätte ja auch ein Folgefehler sein können

El Fantastico
2004-03-27, 11:36:54
Original geschrieben von Talion
Die anderen beiden Fehler bleiben dann aber immer noch, deshalb hatte ich mich um den noch nicht gekümmert. Hätte ja auch ein Folgefehler sein können
So geht`s:

PrintStream p = new PrintStream (
new OutputStream() {
public void write( int b ) {
ta.append ( (char)b );
}//write
}//OutputStream
); //PrintStream


@HellHorse: Ja so ist besser :)

Talion
2004-03-27, 11:51:28
Danke! (Kenne kein anderes Forum wo man so schnell mit kompetenter Hilfe rechnen kann :) )

Der Anfang der Fehlermeldung sagt jetzt allerdings nur dass was irgendwer hier schon geschrieben hatte:
java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread at java.awt.EventQueue.invokeAndWait(Unknown Source) at javax.swing.SwingUtilities.invokeAndWait(Unknown Source) at personendata.doSomething(personendata.java:246) [...]

Dann werd ich (sobald ich Zeit hab) mal weiterprobieren und hoffe dass es klappt.

HellHorse
2004-03-27, 11:51:46
Ja, du müsstest den laden-Vorgang in einem neuen Thread laufen lassen.
Du kannst das Problem nachstellen, indem du meinem zweiten Code.

// hier countSafe() aufzurufen würde zu einem
// Fehler führen
count();

mit

countSafe();

ersetzt.

Talion
2004-03-27, 15:58:30
Es geht jetzt alles, danke an alle die geholfen haben!