Inhalt der JTextArea wird während Speichervorgang nicht aktualisiert

B

ByeBye 148134

Hallo zusammen,
ich habe nun das Forum durchforstet, fand aber keine Lösung, darum das neue Thema.

Kurzes Briefing:
Ich habe ein Programm geschrieben, das sämtliche Bilder einer Webseite in einen Ordner speichert.
Ablauf:
  1. HTML Quelltext wird eingelesen
  2. Quelltext wird nach "href" Tags durchsucht, jeder Link wird in einer Arraylist gespeichert
  3. Mittels Schleife wird durch das Array gegangen und jedes Bild gespeichert

Die Klassen:
PicDownloader: Hier ist keine Logik vorhanden, lediglich das Frame, dessen Aufbau und Actionhandling der Komponenten
URLScanner = Hier ist die Logik enthalten, zu Beginn wird ein Objekt davon erstellt.

Hier der relevante Teil des PicDownloader:

Code:
public class FourChanPicDownloader {

	//Texte
	final String URL_INVALID="Bitte geben Sie eine gültige URL ein!\nBeispiel: https://boards.4chan.org/s/res/13512782";
	final String FOUND_PICS="Es wurden [picSum] Bilder gefunden.\nKlicken Sie auf den Button unten, um den Download zu starten.";
	// GUI Komponenten
	JFrame frame;
	JLabel label;
	JTextField urlEingabe;
	JButton bilderHolen;
	JButton speichern;
	JPanel hauptPanel;
	JTextArea infoFeld;

	// Variablen
	ArrayList<String> links = new ArrayList<String>();
	URLScanner scanner = new URLScanner();

	public static void main(String[] args) {
		new FourChanPicDownloader().go();
	}

	public void go() {
		frame = new JFrame("4chan Pic Downloader");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(350, 300);
		frame.setResizable(false);
		frame.setLocationRelativeTo(null);

		urlEingabe = new JTextField(30);
		
		infoFeld = new JTextArea(5, 30);
		infoFeld.setEditable(true);
		infoFeld.setText("Bitte URL kopieren und Bilder holen");
		
		JScrollPane scroller = new JScrollPane(infoFeld);
		scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

		bilderHolen = new JButton("Anzahl Bilder holen");
		bilderHolen.addActionListener(new BilderHolenListener());

		speichern = new JButton("Bilder speichern");
		speichern.addActionListener(new SpeichernListener());
		speichern.setEnabled(false);

		hauptPanel = new JPanel();
		//hauptPanel.setLayout(new BoxLayout(hauptPanel, BoxLayout.Y_AXIS));
		hauptPanel.add(urlEingabe);
		hauptPanel.add(bilderHolen);
		hauptPanel.add(scroller);
		hauptPanel.add(speichern);
		frame.add(hauptPanel);
		frame.setVisible(true);
	}

	public class BilderHolenListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			if (urlEingabe.getText().isEmpty()) {
				infoFeld.setText(URL_INVALID);
			} else {
				links = scanner.getLinks(urlEingabe.getText().trim());
				infoFeld.setText(FOUND_PICS.replace("[picSum]", String.valueOf(links.size())));
				speichern.setEnabled(true);
				
			}

		}
	}

	public class SpeichernListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			scanner.savePicsInDir(links, frame, infoFeld);

		}
	}
}

Und hier nun der relevante Teil der Logikklasse

Code:
public class URLScanner {

public void savePicsInDir(ArrayList<String> pics, JFrame frame, JTextArea infoFeld){
		JFileChooser fchooser = new JFileChooser();
		infoFeld.setText("");
		//Ein Ordner muss als Speicherort ausgewählt werden
		fchooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		fchooser.showSaveDialog(frame);
		try{
			int counter = 1;
			int anzahlBilder = pics.size();
			for (String aktuellesBild:pics){
				String fileName = convert(counter, 4);
				
				File file = new File(fchooser.getSelectedFile().getAbsolutePath()+"\\"+fileName+"."+getFileEnding(aktuellesBild));
				// aus dem String eine URL machen
				URL url = new URL(aktuellesBild);
				// eine HTTPS-Connection erstellen
				HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
				// einen Browser "nachahmen"
				con.setRequestProperty("user-agent",
						"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)");
	            FileOutputStream out = new FileOutputStream(file);
	            InputStream in = con.getInputStream();
	            int n;
	            byte[] buf = new byte[4096];
	            while( (n = in.read(buf)) > 0)
	            {
	                out.write(buf, 0, n);
	            }
	            in.close();
	            out.close();
				
	            infoFeld.append("Bild "+counter+" von "+anzahlBilder+" gespeichert.\n");
	            //TODO: Feld wird nicht nach jedem Speichern geupdatet...
	            counter++;
			}

			
			
			System.out.println("Speichern erfolgreich");
		} catch (Exception e){
			e.printStackTrace();
		}
	}
	
}

Der Fehler - bzw. das Problem, bei dem ich nicht weiterkomme ist bei der "TODO" Kennzeichnung. Eigentlich sollte das Feld infofeld bei jedem Schleifendurchlauf geupdatet werden, so dass man sieht, welches Bild gerade gespeichert wurde. Tatsächlich ist es aber so, dass sämtliche Bilder gespeichert werden und erst wenn dies beendet ist, wird der Inhalt von JTextArea aktualisiert. Das erweckt bei dem Speichervorgang natürlich den Eindruck, dass Programm tut nichts oder hat sich aufgehängt.
Probiert habe ich schon folgendes:
  • Sämtliche repaint() oder revalidate() Varianten
  • Einen eigenen Thread für das Updaten der JTextArea

Wobei ich mir bei letzterem Punkt nicht sicher bin, aber nicht weiß, wo der separate Thread hinmuss und was dieser tun soll: Speichern oder das infoFeld updaten?

Ich hoffe, ihr könnt mir bei meinem Problem helfen!

Viele Grüße und schonmal ein schönes Wochenende,

CrazyBread
 
Hi

repaint, revalidate etc.:
Die Fensterverwaltung von Java/vom Betriebssystem hat (pro Fenster) nicht mehrere Threads.
Du startest die ganze Aktion mit savePicsInDir usw. durch einen Buttonklick oder Ähnliches;
und die Fensterverwaltung wartet dabei, bis savePicsInDir fertig ist.
repaint speichert zwar irgendwo hin, dass neu gezeichnet werden soll,
gemacht wird es aber erst, wenn der "Buttonklick" fertig abgearbeitet ist.

Zum Thread: Thread ist schonmal gut.
Was da rein gehört: Alles. Die ganze Such-Download-Prozedur.
Dann gibts auch kein Problem mit dem Scheinbar-Aufhängen vom Programm.

Was dabei mindestens zu beachten ist:
a) Sicherstellen, was mit dem Thread passiert, wenn das Fenster geschlossen wird.
b) Um aus dem Thread das Fenster zu ändern muss mit invokeLater gearbeitet werden
(zur Anwendung findet man genug...)

Gruß

PS: 4chan :rolleyes:
 
Hey vielen Dank,
es hat funktioniert. Ich hab mich ein wenig mit InvokeLater auseinandergesetzt. Wieder was gelernt! :D
Hier noch meine Lösung:

FourChan - Klasse:
Code:
	public class SpeichernListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			Thread th = new Thread(new BilderSpeichern());
			th.start();
		}
	}
	
	public class BilderSpeichern implements Runnable{

		@Override
		public void run() {
			scanner.savePicsInDir(links, frame, infoFeld);
			
		}
		
	}

URLScanner-klasse:
Code:
(...)
	            in.close();
	            out.close();
	            updateText = updateText.concat("Bild "+counter+" von "+anzahlBilder+" gespeichert.\n");
	            SwingUtilities.invokeLater(new updateInfoFeld(infoFeld));
	            counter++;
			}

			
			
			System.out.println("Speichern erfolgreich");
			updateText = updateText.concat("Speichern erfolgreich");
			SwingUtilities.invokeLater(new updateInfoFeld(infoFeld));
		} catch (Exception e){
			e.printStackTrace();
		}
	}
(...)
	public class updateInfoFeld implements Runnable{
		JTextArea ta;
		public updateInfoFeld(JTextArea infoFeld) {
			ta = infoFeld;
		}
		@Override
		public void run() {
			ta.append(updateText);
		}
		
	}

Ich weiß nicht, ob es noch eine elegantere Lösung gibt, aber diese hier tut zumindest. Jetzt kann ich das Progrämmchen weiter verfeinern.
Vielen vielen Dank!

Greetz,
Dominik

P.S: Japp 4chan ;-) Ich weiß was du ansprechen willst, aber hauptsächlich interessieren mich dort die Comics, die hochgeladen werden ;)
 
Zuletzt bearbeitet von einem Moderator:

Neue Beiträge

Zurück