Performance-Problem: JTextArea als Logging-Window

  • Themenstarter Themenstarter Sven Z.
  • Beginndatum Beginndatum
Status
Nicht offen für weitere Antworten.
S

Sven Z.

Gast
Hallo *.*,

ich habe mir eine kleine Swing-Anwendung geschrieben, die viele Statusmeldungen ausgibt. Damit das möglichst komfortabel und schön ist, habe ich mir dazu eine von JFrame abgeleitete Klasse geschrieben:

Code:
public class WndLogging extends JFrame implements java.lang.Runnable
{	
	private javax.swing.JPanel jContentPane = null;

	private javax.swing.JScrollPane jScrollPane = null;
	private javax.swing.JTextArea taLogging = null;

	public WndLogging()
	{
		super();

		setVisible(false);
		setResizable(false);
		setTitle("Log-Window");
		setLocation(545,100);
		setSize(545, 328);

		taLogging = new javax.swing.JTextArea();
		jScrollPane = new javax.swing.JScrollPane();
		jContentPane = new javax.swing.JPanel();

		jScrollPane.setViewportView(taLogging);
		jScrollPane.setBounds(0, 0, 537, 303);

		jContentPane.setLayout(null);
		jContentPane.add(jScrollPane, null);
		setContentPane(jContentPane);
	}

	private String getFullFormat(String str)
	{
		String erg = null;
		if (str.trim().length() == 1)
		{
			erg = new String("0"+str);
		}
		else
			erg = new String(str);
		return erg;		
	}

	private String getDateTime()
	{
		StringBuffer sb = new StringBuffer("[ ");
		java.util.Calendar rightNow = java.util.Calendar.getInstance();
		String year = new String(""+rightNow.get(java.util.Calendar.YEAR));
		String month = getFullFormat(new String(""+rightNow.get(java.util.Calendar.MONTH)));
		String day = getFullFormat(new String(""+rightNow.get(java.util.Calendar.DAY_OF_MONTH)));
		String hour = getFullFormat(new String(""+rightNow.get(java.util.Calendar.HOUR_OF_DAY)));
		String minute = getFullFormat(new String(""+rightNow.get(java.util.Calendar.MINUTE)));
		String second = getFullFormat(new String(""+rightNow.get(java.util.Calendar.SECOND)));
		
		return sb.append(year).append("-").append(month).append("-").append(day).append(" ").append(hour).append(":").append(minute).append(":").append(second).append(" ] ").toString();
	}
	
	public void addMessage(String str)
	{
		StringBuffer sb = new StringBuffer(getDateTime()); 

		if (str.indexOf("\n") == -1)
		{
			sb.append(str);
			sb.append("\n");
			
			taLogging.insert(sb.toString(),taLogging.getText().length());
			return;
		}

		java.util.StringTokenizer st = new java.util.StringTokenizer(str,"\n");
		if (st.countTokens()>0)
		{
			while (st.hasMoreTokens()) {
				taLogging.insert(getDateTime()+st.nextToken()+"\n",taLogging.getText().length());
			}
		}
	}

	public void run() {
		this.setVisible(true);
	}
     
}

In meiner Applikation starte initialtisiere ich das Fenster folgendermaßen:
Code:
		frmLogging = new WndLogging();
		new Thread(frmLogging).start();

Anschließend knall ich im ms-Takt Nachrichten in mein Log-Fenster
Code:
frmLogging.addMessage("bla");

Leider wird die JTextArea erst gefüllt, nachdem das ganze Programm durchgelaufen ist. Ich verstehe leider nicht warum das so ist, und vorallem, wie ich das verhindern könnte. Ich möchte nämlich das ich die Statusmeldungen sehe als bald ich sie zugefügt habe.

Ich hoffe ihr versteht mich und könnt mir irgendwie helfen! Ich würde mich darüber jedenfalls sehr freuen! 🙂
 
Fügst du den nur String hinzu?? Wenn ja dann geht das auch so..

Code:
JTextarea myTextarea = new Textarea();
// Das \n ist wichtig da sonst immer neben einander geschrieben werden würde.
myTextarea.setText(myTextarea.getText() + "Hallo\n");

mfg mizus
 
Nein, ich füge noch einen Timestamp zu, und muss das auch bei Zeilenumbrüchen machen.

Aber steht ja alles in meinem Code, vielleicht sollte man sich den doch mal angucken bevor man antwortet. 😉
 
So, ich habe die Klasse nochmal neu geschrieben, das alte konnte so mit den Threads nicht funktionieren.

Jetzt läuft zwar die Protokollierung als eigener Thread, aber die GUI updated sich trotzdem nicht wie gewünscht bei jedem Einfügen einer Statusmeldung, sondern erst wenn das Programm fertig ist. In der Konsole funktioniert das Logging wie gewünscht...

Code:
public class WndLogging extends JFrame implements java.lang.Runnable
{	
	private javax.swing.JPanel jContentPane = null;
	private javax.swing.JScrollPane jScrollPane = null;
	private javax.swing.JTextArea taLogging = null;

	private Vector messages = null;

	public WndLogging()
	{
		super();

		setVisible(false);
		setResizable(false);
		setTitle("Log-Window");
		setLocation(545,100);
		setSize(545, 328);

		taLogging    = new javax.swing.JTextArea();
		jScrollPane  = new javax.swing.JScrollPane();
		jContentPane = new javax.swing.JPanel();

		jScrollPane.setViewportView(taLogging);
		jScrollPane.setBounds(0, 0, 537, 303);

		jContentPane.setLayout(null);
		jContentPane.add(jScrollPane, null);
		setContentPane(jContentPane);
		
		messages = new Vector(); 
	}

	private String getFullFormat(String str)
	{
		String erg = null;
		if (str.trim().length() == 1)
		{
			erg = new String("0"+str);
		}
		else
			erg = new String(str);
		return erg;		
	}

	private String getDateTime()
	{
		StringBuffer sb = new StringBuffer("[ ");
		java.util.Calendar rightNow = java.util.Calendar.getInstance();
		String year = new String(""+rightNow.get(java.util.Calendar.YEAR));
		String month = getFullFormat(new String(""+rightNow.get(java.util.Calendar.MONTH)));
		String day = getFullFormat(new String(""+rightNow.get(java.util.Calendar.DAY_OF_MONTH)));
		String hour = getFullFormat(new String(""+rightNow.get(java.util.Calendar.HOUR_OF_DAY)));
		String minute = getFullFormat(new String(""+rightNow.get(java.util.Calendar.MINUTE)));
		String second = getFullFormat(new String(""+rightNow.get(java.util.Calendar.SECOND)));
		
		return sb.append(year).append("-").append(month).append("-").append(day).append(" ").append(hour).append(":").append(minute).append(":").append(second).append(" ] ").toString();
	}
	
	public void addMessage(String str)
	{
		if (str.indexOf("\n") == -1)
		{
			messages.addElement(new String(getDateTime()+str+"\n"));
		}
		else
		{
			java.util.StringTokenizer st = new java.util.StringTokenizer(str,"\n");
			if (st.countTokens()>0)
			{
				while (st.hasMoreTokens()) {
					messages.addElement(new String(getDateTime()+st.nextToken()+"\n"));
				}
			}
		}

		synchronized (this)
		{
			notifyAll();
		}
	}

	public void run() {
		this.setVisible(true);
		
		while (true)
		{
			synchronized(this)
			{
				try
				{
					wait();

					while (messages.size()>0)
					{
						taLogging.insert((String)messages.elementAt(0),taLogging.getText().length());
						System.out.println((String)messages.elementAt(0));
						messages.removeElementAt(0);
					}
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
	}
     
}
 
Hallo,

Du darfst niemals die GUI außerhalb des Dispatcher-Threads ändern.
Im Dispatcher-Thread ist man z.B., wenn man ein Eventhandler ist (also ein Listener) und gerade benachrichtigt
wird.
Hintergrund: Swing packt alle Events (Tastendruck, Textänderung, Mouse, ...) in eine Queue. Der Dispatcher arbeitet sie der Reihe nach ab. Wenn Du nun in einem solchen Event eine Aktion abarbeitest, bist Du im Dispatcher-Thread.
Folglich kann dieser seiner Aufgabe nicht nachkommen. Die Events bleiben liegen, bis Deine Aufgabe fertig ist.

Ich würde Dir empfehlen, zunächst Deinen Thread für das GUI-Update zu entfernen. Du brauchst nicht wirklich eine Queue, genausogut kannst Du die Infos in die TextArea direkt einfügen. Das mit wait() und notify() ist zwar sehr
schön, aber wozu einen Thread zwischenschalten, wenn man das nicht muss?

Als nächstes musst Du dafür sorgen, dass Deine eigentliche Aktion (Erzeuger der Logmeldungen) in einem eigenen
Thread läuft. Vermutlich ist Dein Problem bisher, dass der Anwender irgendwo auf einen Button drückt, woraufhin die
Aktion synchron (also im Dispatcher-Thread läuft). Während dessen kann natürlich die GUI nicht neu gezeichnet werden.
Kapsele daher den Aufruf starteAktion() (oder wie immer auch das heißt) in einen Thread und starte den.
Damit kann der Dispatcher (parallel) seiner eigentlichen Arbeit nachgehen - Events ausliefern.

Die Sache hat allerdings einen Haken, Updates darf wieder nur der DispatcherThread machen.
Dafür sieht Swing die Möglichkeit vor, Aktionen innerhalb der EventQueue laufen zu lassen.
Immer wenn Du also die GUI änderst, in Deinem Fall die TextArea, musst Du die Änderung in ein
Runnable r verpacken und mit SwingUtilities.invokeLater(r) aufrufen.
Damit wird die Änderung (wie jedes Event) durch den EventDispatcher-Thread ausgeführt - Die GUI lebt.

Hinweis: Wenn man Swing threaded, muss man sich um die wilden Eingaben des Benutzers natürlich mehr kümmern!

Hier noch Links:

http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html

http://java.sun.com/developer/technicalArticles/Threads/swing/?frontpage-jdc

Gruß,

Karl
 
Hallo Karl,

vielen Dank für Deinen Beitrag. Dank Deiner ausführlichen Hilfe (und den Links) konnte ich dieses wirklich lästige Problem lösen. 🙂:toll:

Gruß,
Sven
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben