Swing GUI wird nicht zum richtigen Zeitpunkt neu gezeichnet

Fleur de Soleil

Aktives Mitglied
Hallo zusammen,

ich hab mal wieder ein Problem. Ich hab eine GUI mit mehreren JTextFields in die man Eingaben tätigen muss. Klickt man anschließend auf den Submit-Button, wird überprüft ob alle notwendigen Daten vorhanden und falls ja werden die Daten verschlüsselt und übermittelt. Mein Problem ist jetzt, dass ich im ActionListener des SubmitButtons auch was in einem JTextPane aus der GUI ausgeben will. Die Aktualisierung klappt zwar, aber immer erst nach der Verschlüsselung und dem Senden, und dass kann seine Zeit dauern.

Mein ActionListener:
Java:
    class SubmitListener implements ActionListener{

    	public void actionPerformed(ActionEvent evt){
			if(evt.getSource()==view.getSubmitButton()){
				try {
					if(checkInput()){
						setUp();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
    }

Methode checkInput:
Java:
    private Boolean checkInput() {
    	complete = false;
		if(!(sourceFile == null)){
			if(!(propertyFile == null)){
				if(!(targetFile == null)){
					if(!(view.getPin().equals(""))){
						view.newPositivStatus("Alle notwendigen Eingaben für die Datenübermittlung wurden getätigt!"); <-------Passiert erst nach dem setUp
						complete = true;
					}
				}
			}
		}
		if(!complete){
			view.newNegativStatus("Es wurden nicht alle notwendigen Eingaben gemacht!");
		}
		return complete;
	}

Methode setUp:
Java:
 private void setUp() throws Exception{ 
Builder builder = new Builder(); 
view.newPositivStatus("Builder erstellt!"); <-------Passiert erst nach dem setUp
		builder.createAndSendXmlString(senden);

view.repaint(); view.Pack(); hab ich schon ausprobiert klappt nicht. Ich hoffe ihr könnt mir helfen.

LG Fleur
 
solange der ActionListener, vom GUI-Thread aus gestartet, arbeitet, ist der GUI-Thread fürs Zeichnen oder Benutzereingaben blockiert,
du musst längerfristige Aktionen in einen nebenläufigen neuen Thread ausführen
 
Der FAQ-Eintrag dazu ist immernoch pending 🙁 setUp blockiert den EDT, der für Neuzeichnen usw. verantwortlich ist. setUp muss in einem eigenen Thread gemacht werden. Die Klasse SwingWorker ist für sowas gedacht
 
Hey, Danke für die schnelle Hilfe.

Aber das ist genau das was ich vermeiden wollte, kenne mich mit Threads nicht aus, obwohl ich wirklich versucht habe mich damit zu befassen. Eine andere Möglichkeit gibt es anscheinend nicht. Mal sehen wie ich es lösen werde
 
Okay ich hab mich mal ein wenig mit SwingWorker auseinander gesetzt und festgestellt, dass das für mich viel verständlicher ist als Threads. Bin gerade dabei ein SwingWorker für meine Problem zu bauen, jedoch stoße ich schnell auf ein Problem. Mit folgendem Code erstelle ich ja einen SwingWorker. Problem bei meinem Beispiel ist, dass meine Methode die im Background ausgeführt werden soll ja keinen Rückgabewert hat. Mit void funktioniert es nicht. Jemand einen Tipp für mich??

Java:
swingWorker = new SwingWorker<String, Void>(){

       @Override
	protected String doInBackground(){
		setUp();
	}
}
 
Stimmt auch wieder *g*

Läuft jetzt soweit durch. Hab nur noch eine Sache die Methode done des SwingWorkers wird ja nach dem doInBackground ausgeführt. Gibt es auch eine Methode die vorher was ausführt? Oder kann ich die Berechnung in die done-Methode setzen und die Ausgabe im View in den Background setzen, damit zuerst was ausgegeben wird??

Java:
class SubmitListener implements ActionListener{

    	public void actionPerformed(ActionEvent evt){
			if(evt.getSource()==view.getSubmitButton()){
					if(checkInput()){
						swingWorker = new SwingWorker<String, Void>(){

							@Override
							protected String doInBackground(){
								try {
									setUp();
								} catch (Exception e) {
									e.printStackTrace();
								}
								return null;
							}

							protected void done(){
								view.newPositivStatus("Alle notwendigen Eingaben für die Datenübermittlung wurden getätigt!");
							}
						};
						swingWorker.execute();
					}
					else{
					view.newNegativStatus("Es wurden nicht alle notwendigen Eingaben gemacht!");
					}
			}
		}
    }
 
Guten Morgen zusammen,

das mit dem SwingWorker klappt jetzt echt super. Steh jetzt aber vor einem weitaus schwierigeren Problem, zumindest aus meiner Sicht. Ich hab mal zur Verdeutlichung mein Problem auf das wesentliche reduziert und den Quellcode unten angehängt. Was ich jetzt gerne möchte ist, dass meine JProgressBar den Fortschritt der Schleife in der Methode machWas in der Klasse Berechnung anzeigt. Problem dabei ist, die Klasse Berechnung soll von der View nicht allzu viel wissen, sodass die View so gut es geht von der Berechnung losgelöst ist. Versucht habe ich es schon mit einem PropertyChangeListener, aber leider hat das nicht so funktioniert wie ich mir das vorgestellt habe. Ich hoffe ihr könnt mir helfen und mir direkt ab meinem Code ein Beispiel zeigen wie es funktionieren kann.

LG Fleur

View:
Java:
 package testen;

import java.awt.Dimension;

import javax.swing.*;

import testen.Controller.ButtonListener;


public class View extends JFrame{

	private JButton button;
	private JTextField text;
	private JProgressBar bar;

	View(){
		JPanel panel = new JPanel();
		add(panel);

		text = new JTextField(10);
		panel.add(text);

		button = new JButton("Test");
		panel.add(button);

		bar = new JProgressBar(0, 100);
		panel.add(bar);
		bar.setStringPainted(true);
        bar.setPreferredSize(new Dimension(350, 20));

        setDefaultCloseOperation(EXIT_ON_CLOSE);
		pack();
		setLocationRelativeTo(null);
	}

	public void addButtonListener(ButtonListener buttonListener) {
		button.addActionListener(buttonListener);
	}

	public void setTextText(String string){
		text.setText(string);
	}

	public void setValue(int i) {
		bar.setValue(i);
	}
}

Controller:
Java:
 package testen;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.*;

import javax.swing.SwingWorker;


public class Controller {

	private View view;
	private SwingWorker<String, Void> worker;

	public Controller(View view) {
		this.view  = view;

		view.addButtonListener(new ButtonListener());
	}

	class ButtonListener implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			worker = new SwingWorker<String, Void>(){

				@Override
				protected String doInBackground(){
					try {
						setUp();
					} catch (Exception e) {
						e.printStackTrace();
					}
					return null;
				}

				protected void done(){
					view.setTextText("fertig");
				}
			};
			worker.execute();
		}
	}

	private void setUp() {
		Berechnung b = new Berechnung("Test1");
		b.machWas();
	}
}

Start:
Java:
 package testen;
public class Start {
	public static void main(String[] args){
		View view = new View();
		Controller controller = new Controller(view);
		view.setVisible(true);
	}
}

Berechnung:
Java:
 package testen;

import java.util.Hashtable;

public class Berechnung {

	private String name;
	private String[] tester;
	private int i;

	Berechnung(String name){
		this.name = name;
		tester = new String[100];
		for(i = 0; i < 100; i++){
			tester[i] = "test" + i;
		}
	}

	public void machWas() {
		for (String test: tester){
			//mach ganz viel
		}
	}
}
 
Berechnung muss ab und zu, z.B. in der machWas()-Schleife, irgendeinen Zustand ändern,
ob direkt ein Klassenattribut oder PropertyChangeListener ist zweitranging, hauptsache es passiert etwas,
sei es eine einfache int-Variable von 0 bis 100,

irgendjemand, z.B. ein zweiter Thread/ ein zweiter SwingWorker, fragt diesen Wert ab und zu ab und setzt den ProgressBar-Value der GUI
 
Wow das ging aber schnell Danke schon mal dafür.

Das mit der Variable habe ich mit gedacht, weil die einzelnen Strings aus dem Array kann ich wohl kaum als Berechnungsgrundlage für die ProgressBar nehmen. Könntest du mir ein bisschen Code zum SwingWorker geben, wie er überprüfen soll ob sich was geändert hat und wie ich das dann im Controller einbauen kann?? Wäre echt hilfreich, weil ich versuch schon seid Tage die Progressbar zum laufen zu bekommen. Und selbst bei einem einfachen Beispiel will sie bei mir nicht laufen.
 
du hast doch schon einen SwingWorker, der eine
> for (String test: tester){
Schleife ausführt,
was bringt es dann dass ich ein anderes SwingWorker-Beispiel mit einer anderen Schleife poste?
nicht zielführend, zumal ich selber SwingWorker gar nicht benutze

baue einen SwingWorker mit Schleife, wobei jetzt while(anderer Thread noch läuft) Schleife,
dort den Wert abfragen und in die GUI setzen, das für sich ist eine Zeile

natürlich muss der zweise SwingWorker/Thread den ersten kennen, also Variablen übergeben oder so,
und irgendwie muss das Ende festgestellt werden, das könnte ein bestimmter ProgressBar-Wert sein oder es gibt bestimmt auch Methoden im SwingWorker,
wichtig ist dann, dass auch der zweite SwingWorker seine Schleife beendet,
 
Ja das Ding ist ja es ist keine andere Schleife sondern genau die, die schon durch den SwingWorker ausgeführt wird. Diese Schleife soll mit den Fortschittsbalken verändern.

Kannst du am besten noch sagen wo der neue SwingWorker ansetzen soll?? Ist er so an der richtigen Stelle?? Wie überprüfe ich, ob der andere SwingWorker noch läuft??

Java:
class ButtonListener implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			worker = new SwingWorker<String, Void>(){

				@Override
				protected String doInBackground(){
					try {
						setUp();
					} catch (Exception e) {
						e.printStackTrace();
					}
					return null;
				}

				protected void done(){
					view.setTextText("fertig");
				}
			};
			worker.execute();
			worker2 = new SwingWorker<String, Void>(){
				while()
				@Override
				protected String doInBackground() throws Exception {
					int wert = b.zaehler; 
					return null;
				}
				
			};
		}
	}
 
> Wie überprüfe ich, ob der andere SwingWorker noch läuft??
ich selbst wußte es bis vor 1 Min. auch nicht (und werde es in 5 Min. wieder vergessen),
habe dann aber vor 45 sec in der API geschaut und weiß es nun,
versuche das bitte auch
 
Sry, aber ich schlag mich schon eine ganze Weile mit den gleichen Zeilen Code rum und könnte mittlerweile echt aus der Haut fahren. Hab jetzt nochmal mein ButtonListener angehängt. Problem ist, so wie ich es jetzt da stehen hab ist der erste SwingWorker ja immer fertig wenn er die while-Schleife angeht. Wo genau muss ich denn den zweiten SingWorker ansetzen??

Wie es mit scheint, arbeitest du nicht mit SwingWorker, sondern mit Threads. Gibt es da auch eine Lösung für mein Problem?

Java:
	class ButtonListener implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			worker = new SwingWorker<String, Void>(){

				@Override
				protected String doInBackground(){
					try {
						setUp();
					} catch (Exception e) {
						e.printStackTrace();
					}
					return null;
				}

				protected void done(){
					view.setTextText("fertig");
				}
			};
			worker.execute();
			while(!worker.isDone()){
				worker2 = new SwingWorker<String, Void>(){

					@Override
					protected String doInBackground() throws Exception {
						int wert = b.zaehler;
						
						view.bar.setValue(wert);
						return null;
					}
				};
			}
		}
	}
 
Nein ich hab eine for-Schleife die im SwingWorker aufgerufen wird. Diese for-Schleife soll mir jetzt auch den Progressbar verändern während der erste SwingWorker noch läuft, oder eben so lange bis der erste fertig ist
 
Zeile 21-31 steht derzeit gar nicht in einem SwingWorker, sondern direkt in einem ActionListener,
das ist nicht gut, diese 10 Zeilen müssen in einen SwingWorker, so schwer das auch zu glauben ist

edit:
halt, da ist ja schon einer, nur die
> while(!worker.isDone()){
Schleife muss eben da hinein, in doInBackground(), nicht außen rum (?!)
 
Hey Danke. Endlich bewegt sich mal was im ProgressBar. Jetzt habe ich aber noch ein Problem. In meiner Schleife in der Klasse Berechnung wird beim durchgehen durch das Array gleichzeitig pro Durchlauf ein Zähler hochgezählt, mit Hilfe dessen ich dann die Prgressbar aktualisiere. Ab und zu wirft er mir dann im Thread der mir die Progressbar aktualisiert eine NullPointerException, weil er denke ich den zaehler nicht finden kann. Außerdem glaube ich, dass ich den SwingWorker jetzt mit Thread vermischt habe und das eigentlich einfacher gehen würde. Könnt ihr bitte nochmal über meinen ActionListener drüberschauen?

Java:
	class ButtonListener implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			worker = new SwingWorker<String, Void>(){

				@Override
				protected String doInBackground(){
					try {
						setUp();
					} catch (Exception e) {
						e.printStackTrace();
					}
					return null;
				}

				protected void done(){
					view.setTextText("fertig");
				}
			};
			worker.execute();
			worker2 = new SwingWorker<String, Void>(){

				@Override
				protected String doInBackground() throws Exception {
					while(!worker.isDone()){
						new Thread(){
							public void run(){
								view.bar.setValue(b.zaehler);
							}
						}.start();								
					}
					return null;
				}
			};
			worker2.execute();
		}
	}
 
Java:
			worker2 = new SwingWorker<String, Void>(){

				@Override
				protected String doInBackground() throws Exception {
					while(!worker.isDone()){
						Thread.sleep(300); // nicht 1 Mio. mal pro Sekunde, try/catch fehlt noch
                        // SwingUtilities.invokeLater(new Runnable() { public void run() {
                          view.bar.setValue(b.zaehler);
                        // } });
					}
					return null;
				}
			};
			worker2.execute();
die beiden auskommentierten Zeilen bewirken, dass die GUI-Änderung vom GUI-Thread ausgeführt wird,
ist hier wahrscheinlich zu empfehlen, hilft gegen NPE beim Zeichnen,
auch wenn die jetzt vielleicht seltener auftreten, da mit dem sleep() nur noch 3.3x pro Sekunde aktualisiert wird statt nahezu unendlich oft,

wobei du bisher einen unnötigen zusätzlichen Thread hast, das verlangsamt auch deutlich, so oft war das bisher vielleicht gar nicht..
 
Den unnötigen Thread hast du ja mit deinem Beispiel entfernt, richtig??

Laufen tut es jetzt wirklich flüssig. Muss das jetzt nur noch an mein komplexes Beispiel anpassen. Aber das bekomme ich alleine hin. Vielen Dank auf jeden Fall für die nette Hilfe.

LG Fleur
 
Sry für den Doppelpost, aber anscheinend bekomme ich die Umsetzung in meine bestehendes Projekt doch nicht alleine hin. So sieht die ActionListener Klasse aus. Dabei habe ich festgestellt, dass die Zeile (!swingWorker.isDone()) immer false ergibt und der zweite SwingWorker somit nichts macht. Woran kann das liegen?? Zum testen habe ich in den zweiten SwingWorker auch die done Methode geschrieben. Die Ausgabe kommt sofort nach dem Klick auf den Button. Dann wird gearbeitet und am Ende kommen die Ausgaben vom ersten SwingWorker. Bitte helft mir nochmal.

Java:
    class SubmitListener implements ActionListener{

    	public void actionPerformed(ActionEvent evt){
			if(evt.getSource()==view.getSubmitButton()){
				if(checkInput()){
					swingWorker = new SwingWorker<String, Void>(){

						@Override
						protected String doInBackground(){
							try {
								view.setWaitCursor();
								setUp();
							} catch (Exception e) {
								view.newNegativStatus("Der Pin stimmt nicht mit dem Zertifikatspin überein!");
							}
							return null;
						}

						protected void done(){
							view.setDefaultCursor();
						}
					};
					swingWorkerBar = new SwingWorker<String, Void>(){

						@Override
						protected String doInBackground() throws Exception{
							while(!swingWorker.isDone()){
								Thread.sleep(10);
								view.setBarValue(builder.getProgressZaehler());
							}
							return null;
						}
						
						protected void done(){
							view.newNegativStatus("Test");
						}
					};
					swingWorker.execute();
					swingWorkerBar.execute();
				}
				else{
					view.newNegativStatus("Es wurden nicht alle notwendigen Eingaben gemacht!");
				}
			}
		}
    }
 
ich wollte jetzt schon dein Beispiel kopieren, aber auf meinem Java läuft SwingWorker nichteinmal, erst ab Java 1.6..

hier ein Beispiel mit einfacheren Threads, das funktioniert, mehr kann ich nicht beitragen
Java:
public class TestGUI  extends JFrame {
    public TestGUI()  {
        JButton b = new JButton("Go");
        add(b);
        b.addActionListener(new SubmitListener());
        setSize(950, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args)  {
        new TestGUI();
    }
}


class SubmitListener  implements ActionListener {

    public void actionPerformed(ActionEvent evt)  {
        final Thread t = new Thread()  {
                public void run()   {
                    try { Thread.sleep(2000); } catch (Exception e) {} // simulierte Arbeit
                    System.out.println("t fertig");
                }

            };
        Thread t2 = new Thread() {
                public void run()   {
                    while (t.isAlive())  {
                        System.out.println("t2 wartet auf t");
                        try { Thread.sleep(400); } catch (Exception e) {}
                    }
                    System.out.println("t2 fertig");
                }
            };
        t.start();
        t2.start();
    }
}


ach ja, noch eine Vermutung: swingWorker ist ein Klassenattribut, theoretisch kann das ständig durch neue Objekte überschrieben werden,
der Test
> while(!swingWorker.isDone()){
bezieht sich immer auf das aktuelleste Objekt unter dieser Variablen, nicht unbedingt genau der SwingWorker, der in dieser Methode definiert wurde,
da wäre eine (finale) lokale Variable sicherer, siehe mein Beispiel
 
Guten Morgen zusammen,

Dank dir SlaterB für dein super Beispiel-Code. In diesem Zusammenhang habe ich die Threadsache sogar verstanden und klappt jetzt alles genau so wie ich es mir vorgestellt habe. Damit ist das Thema hier erledigt, aber ihr werdet wieder von mir lesen 😉

LG Fleur
 
Ich muss das Thema doch noch mal aufmachen. Mein Code ist jetzt in etwa so aufgebaut wie der von SlaterB im vorletzten Post. Verstehe ich das jetzt richtig, dass da eigentlich 3 Threads offen sind? Einmal der setUp(), dann der der solange läuft bis der erste Thread beendet wird und dann noch der Thread aus der main-Methode?? Lässt sich das nicht so lösen, dass zum Beispiel das aktualisieren der View im main-Thread läuft?? Also so, dass es nur zwei Threads parallel laufen??

LG Fleur
 
das ist denkbar,
ansonsten ist der main-Thread auch recht bald beendet nachdem das GUI-Objekt erzeugt wurde,
bis du nach Sekunden irgendeine Aktion anstößt, müsste der main-Thread warten, dann die Informationen über den anderen Thread bekommen usw.,
machbar ist alles, aber in der Richtung musst du nicht unbedingt sparen

wenn dann verwende statt SwingWorker doch andere tolle Dinge wie ExecutorService,
das ist ein oder wenige Threads denen man im Laufe der Zeit beliebig viele Einzelaufgaben übergeben kann,

kann man mit bisschen Arbeit natürlich auch wieder selber bauen, ein Thread der wartet, bis ein Runnable oder sonstiges einfaches Objekt übergeben wird, dann dieses ausführen, dann wieder warten falls nicht noch mehr in der Warteliste steht usw.
 
Zuletzt bearbeitet von einem Moderator:

Zurück
Oben