Swing GUI und weiter Thread gibt Problem

Niles

Grünschnabel
Hi,
Ich wollte Daten aus meiner MySQL DB in meine GUI einlesen. Das funktioniert soweit auch super.
Allerdings habe ich nun ein Problem.
Ich habe das auslesen der Daten aus der DB in einen eigenen Thread geschrieben, der in einer while(true) ständig läuft, da sich die Dateninhalte in der DB ständig ändern (z.B.: der Status). Die Daten werden an eine set-Methode übergeben und die GUI holt sich die Daten über die Get-Methode.
Nun bekomme ich aber immer eine ArrayIndexOutOfBoundsException und die Darstellung der Tabelle in der GUI flackert ständig.

Vielleicht weiß jemand Rat.

Hier mal der Code:


code in der GUI-Klasse:
Code:
public GUI(){ 
       
      : 
      : 

      // Thread zum anzeigen des Tabelleninhaltes starten 
      TableData tableDataThread = new TableData( m_server ); 
      tableDataThread.start(); 
       
      slaveStatusTable.setModel( tableDataThread.getTableData() ); 
      slaveStatusTableSP = new JScrollPane( slaveStatusTable,ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 
      ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED ); 
      : 
      : 
}


und hier der Thread
Code:
import java.sql.*; 
import java.net.*; 
import java.io.*; 
import java.util.*; 
import java.security.*; 
import java.lang.*; 
import javax.swing.table.DefaultTableModel; 


    

class TableData extends Thread { 
    
   private Rttserver server;          
   private  DefaultTableModel dtm ;          


   TableData( Rttserver server ) {          
      this.server = server;                      
      dtm = new DefaultTableModel();    
   } 
    

    
    
    
   public void run() { 
       
      ResultSet rsSelect;                
      ResultSetMetaData rsmd;             
      int columnCount;                
      int rowCount; 
      Object [][] oData;          
      Object []   columnHeaders;                
      while( true ) { 
       
      DBconnect db = new DBconnect(); 
      Connection connection = db.DBconnect( server );                         
          
          
         try { 
             
            Statement select = connection.createStatement(); 
            rsSelect         = select.executeQuery( "SELECT * FROM hardware;" ); 
             
             
            rsmd = null;      // ein ResultSet 
            columnCount = -1;   // beinhaltet die gezählten columns 
       
             
            try { 
               rsmd        = rsSelect.getMetaData(); 
               columnCount = rsmd.getColumnCount(); 
            } 
            catch( SQLException e ) { e.printStackTrace(); } 
             
             
             
            // Prüfen, ob rsmd und columnCount Werte bekommen haben 
            if ( rsmd == null || columnCount == -1 ) throw new RuntimeException( "ResultSetMetaData is null" ); 
             
             
            try { 
               rsSelect.first(); 
               rsSelect.last(); 
               rowCount        = rsSelect.getRow(); 
               boolean hasdata = rsSelect.first(); 
                
                
               // die Anzahl der rows und columns dem Datenobjekt zuweisen 
               oData         = new Object[rowCount][columnCount]; 
               columnHeaders = new Object[columnCount]; 
                
                
               // Anzahl und Name der Header herrausfinden 
               for ( int i = 1; i <= columnCount; i++ ) { 
                  columnHeaders[i-1] = rsmd.getColumnName(i); 
                  System.out.println(i); 
               } 
                
                
               int row   = 0; 
                
               // Zeilenweise auslesen 
               while( hasdata ) { 
                   
                  // Anzahl Zeilen und deren Inhalte 
                  for( int i = 1; i <= columnCount; i++ ) { 
                     System.out.print( rsSelect.getString(i) + " " ); 
                     oData[row][i-1] = rsSelect.getString(i); 
                  } 
                   
                  row++; 
                  hasdata=rsSelect.next(); 
               } 
                               
               if (row!=0) { setTableData( oData, columnHeaders ); } 
                               
            } 
            catch( SQLException e ) { e.printStackTrace(); } 
             
             
       
            // Datenbankverbindung schließen 
            connection.close(); 
             
            // einfach x Sekunden warten 
            long time = System.currentTimeMillis();                // Systemzeit 
            long wait = 2000;                                // wartezeit 
            while ( System.currentTimeMillis() <= (time + wait) ) {} // warteschleife 
          
         } 
         catch( SQLException sqle ){} 
       
      } 
             
   } 
    
    
    
   /** 
    * Set-Methode für dtm 
    * 
    *********************************************************************************/ 
   public synchronized void setTableData( Object[][] oData, Object[] columnHeaders) { 

      dtm.setDataVector( oData, columnHeaders ); 
   } 
    
    
   /** 
    * Get-Methode für dtm. Die GUI holt sich dieses Object. 
    * 
    *****************************************************/ 
   public synchronized DefaultTableModel getTableData() { 
      return this.dtm; 
   } 

}

Vielleicht findet jemand den Fehler, oder kann mir sagen, was ich machen muß.
Danke
Gruß Niles
 
Hab mal weiter gesucht.
Ich glaube es liegt daran, dass Swing nicht Thread-save ist.
Ich glaube ich muß irgendwo invokeLater() einbauen.
Weiß aber leider nicht wo.

Kann mir da jemand weiterhelfen?
Danke Gruß Niles
 
Hallo Niles,
also zunächst einmal, zu Deinem Thread.
Es ist erheblich besser den Thread am Ende Deiner Verarbeitung schlafen zu lassen also mit der Routine sleep(...), da du sonst unnötig viel CPU-Zeit benötigst.
Eine Alternative zu Deiner Schleife, ich habe sie jedoch nur überflogen, wäre einen Timer zu verwenden.
es gibt dabei 2 Timerklassen:
die eine ist in java.util.* und arbeitet ganz nett. Bei einer geischeiten Koexistenz mit Swing muss jedoch dort, das Update auf Deine Swingkomponente in einem invokeLater() oder alternativ dazu in ein invokeAndWait() gesteckt werden, damit dies im Event-Queue behandelnden Thread läuft. Somit wird auch Dein Flackern aufhören.

die zweite, aus meiner Sicht empfehlenswertere, Timerklasse befindet sich im Packet java.swing.* . Dieser Timer führt zu ActionEvents, die von sich aus im Event-Queue behandelnden Thread ablaufen. Dazu musst Du lediglich Deine Abfrage (ohne Schleife) in einem Runnable Object implementieren und diese dem Timer-Object vorgeben. Du kannst frei nach Beleiben den Timer zuyklisch einen ActionEvent auslösen lassen oder auch nur ein einzigesmal. Du kannst den Timer stopopen und auch restarten, so wie Du es in Deiner Anwendung brauchst.

viel Glück

Takidoso
 
da fällt mir gerade noch was auf....
Wenn der Zugriff auf Deine DB bzw das bereitstellen der Daten eine etwas längere Angelegenheit sein sollte, empfiehlt sich doch den Timer aus dem java.util-Packet zu nehmen, da man den Thread der für die Event-Queue zuständig ist nicht mit langen Tasks belasten soll, damit der Anwender einigermaßen ungestört sein GUI benutzen kann.
Du muß also lediglich Deine Routine setTableData() als ein Runnable-Objekt implementieren und eine der Routinen (invokeLater() oder invokeAndWait()) gefüttert mit Deinem Runnable aufrufen.
die while(true) Schleife ist zu entfernen!

viel Glück

Takidoso
 
Hallo takidoso,
danke erstmal für deine Hilfe.
Leider weiß ich nicht genau, wo ich das invokeLater() eintragen soll.
Wie sieht denn soetwas aus?
Muß ich den aufruf aus der GUI so gestallten?:
Code:
EventQueue.invokeLater( new Runnable() { 
    public void run() { 
        slaveStatusTable.setModel( tableDataThread.getTableData() ); 
}
} );
und wo kommt das invokeLater() hin?

Dake für deine Hilfe
Gruß Niles
 
Hi,
leider bekomme ich immer noch eine ArrayIndexOutOfBoundsException.
Komme einfach nicht klar.
Bitte um Hilfe
Gruß Niles
 
Grüß Dich,
also Dein invokeLater(Runnable) ist eine Routine aus der Klasse SwingUtilities.
Du setzt sie unmittelbar nach dem zusammenstellen Deiner darzustellenden Daten (s. Dein Thread) ein.

daraus ergibt sich in etwa folgendes Schema:
================================================================
Datenzusammenstellende Thread- oder Runnable-Klasse:
----------------------------------------------------------------------------------------------------------
lese Datenzeilen aus DB und verbringe sie in DataVector
"SwingUtilities.invokeLater(Thread der Daten dem Tablemodel übergibt)";
----------------------------------------------------------------------------------------------------------

Thread-oder Runnable-Klasse die Daten dem Tablemodel übergibt:
----------------------------------------------------------------------------------------------------------
vorrausgesetzt Dein Tablemodel ist ein DefaultTableModel oder davon abgeleitet
DefaultTableModel.setDataVector(DataVector)
----------------------------------------------------------------------------------------------------------

java.util.Timer initialisieren dabei Datenzusammenstellendes Thread-Object zum Ausfürhren übergeben.
================================================================
Das ganze hätte dann zur Folge, das Dein Timer züklisch (so er richtig konfiguriert wurde)
Dein Runnable aufruft, welches Daten aus der DB holt, welches dann wiederum ein Runnable als Task in die Event-Queue des Event-Queue bearbeitenden Thread von Swing reinstellt, der die Daten in dein Tablemodel übergibt, welches dann seine Listener - in diesem Fall Deinem Table erzählt, dass es geändert wurde, worauf diese sich dann aktualisieren würden.

Bezüglich Deines ArrayIndex-Problems mußt Du schlichtweg mal tracen oder ähnliches
um es analysieren zu können. Ich würde an Deiner Stelle aber erstmal oben beschriebenes Schema implementieren und schauen ob Dein ArrayIndex-Problem noch anhält.

Takidoso
 
Hi Takidoso,
danke erstmal für deine Antwort. Allerdings habe ich schwierigkeiten beim Umsetzen deines Schemas.

Ich habe in meinem Thread, der die Daten aus der DB ausliest den Methodenaufruf setDataVector(DataVector) in die invokeLater()-Methode geschrieben.

Hier mal mein Thread:
Code:
import java.sql.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.security.*;
import java.lang.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.SwingUtilities;

	
class TableData extends Thread {
	
	private Rttserver server;						
	private DefaultTableModel dtm ;				// das TabellenModell

																		
	// Anlegen der Spaltennamen
	Object []   columnHeaders = { "  ","Prog / Port","Slave-IP","Model","Status"};	// Datenobjekt, das die Header enthält
	Object [][] oData;																	// Datenobjekt, beinhaltet die rows und die columns
	
			

	TableData( Rttserver server ) {							// übergibt dem Konstruktor den server
		this.server = server;								// keep the reference of the server
		dtm = new DefaultTableModel();						// Objekt anlegen
	}
	


	public void run() {
		
		setPriority( MIN_PRIORITY );		// Geringe Priorität
		
		
		ResultSet rsSelect;					// Meta daten des ResultSet
		ResultSetMetaData rsmd;				// ein ResultSet
		int columnCount;					// beinhaltet die gezählten columns
		int rowCount;						// beinhaltet die gezählten rows

	
		// Select der Daten aus der Datenbank
		// Datenbankverbindung aufmachen
		DBconnect db = new DBconnect();									// neue Instanz erzeugen von DBconnect
		Connection connection = db.DBconnect( server );					// empfang der variable connection und aufruf der Methode
					
		
		try {
			
			Statement select = connection.createStatement();		// SQL Statement erzeugen
			rsSelect         = select.executeQuery( "SELECT * FROM hardw_management;" );
							
			rsmd = null;		// ein ResultSet
			columnCount = -1;	// beinhaltet die gezählten columns
	

			// auslesen aus DB 
			try {
				rsmd        = rsSelect.getMetaData();
				columnCount = rsmd.getColumnCount();
				
				
				// Prüfen, ob rsmd und columnCount Werte bekommen haben
				if ( rsmd == null || columnCount == -1 ) throw new RuntimeException( "ResultSetMetaData is null" );
			

				List list = new ArrayList();							// Liste anlegen
									
				while (rsSelect.next()) {								// daten aus ResultSet lesen
					Object[] rowData = new Object[columnCount];			// Eindimensionales Objekt anlegen und die Anzahl der columns geben
					for (int i = 1; i <= columnCount; i++) {			// solange Strings lesen, wie columns da sind
						rowData[i - 1] = rsSelect.getString(i);			// Strings an eindimensionales Array rowData übergeben
					}
					list.add(rowData);									// rowData der Liste hinzufügen
				}
				rsSelect.close();										// ResultSet schließen
	
				oData = new Object[list.size()][columnCount];
				for (int i = 0; i < oData.length; i++) {
					oData[i] = (Object[]) list.get(i);
				}
				
				SwingUtilities.invokeLater( new Runnable() { 			
					public void run() {					
						setTableData( oData, columnHeaders );			
					}
				});
					
									
			} 
			catch (SQLException e) { e.printStackTrace(); }	
		
					
		
			// Datenbankverbindung schließen
			connection.close();
			
			try{sleep(1);}catch(InterruptedException ie){}
		}
		catch( SQLException sqle ){}
		
	}
	
	
	
	/**
	 * Get-Methode für dtm. Die GUI holt sich dieses Object.
	 *****************************************************/
	public synchronized DefaultTableModel getTableData() {
		return this.dtm;
	}

}

Was meinst du mit:
Datenzusammenstellende Thread- oder Runnable-Klasse:
----------------------------------------------------------------------------------------------------------
lese Datenzeilen aus DB und verbringe sie in DataVector
"SwingUtilities.invokeLater(Thread der Daten dem Tablemodel übergibt)";
----------------------------------------------------------------------------------------------------------
Muß der ganze Code zum auslesen der Daten ebenfalls in einen invokeLater()-Thread?

Und wie implementiere ich den Timer in meiner GUI? (der muß doch in die GUI.Klasse, oder?)

Hier nochmal kurz meine GUI:
Code:
public GUI(){ 
       
      : 
      : 

      // Thread zum anzeigen des Tabelleninhaltes starten 
      TableData tableDataThread = new TableData( m_server ); 
      tableDataThread.start(); 
       
      slaveStatusTable.setModel( tableDataThread.getTableData() ); 
      slaveStatusTableSP = new JScrollPane( slaveStatusTable,ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 
      ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED ); 
      : 
      : 
}

Danke für deine Hilfe
Gruß Niles
 
Grüß Dich,
also Du soltest zunächst einmal die Dokumentation von java.util.Timer anlesen.

Deine Klasse TableData tut im wesentlichen schon das was ich in dem Schema mit Datenzusammenstellende Thread- oder Runnable-Klasse gemeint hatte, das kannst Du sicher identifizieren, mit dem was Du in Deiner Klasse codiert hast.
Du mußt generell diese Klasse dem Timer geben und diesen Timer starten.
Du solltest jedoch das sleep in Deinem TableData noch rausnehmen, da die Aktion nach getaner Arbeit sich verabschieden kann und vom Timer neu aufgerufen wird.
Und noch etwas: Du mußt in Deiner GUI-Klasse dafür sorgen, dass dein JTable das selbe Tablemodel (also das selbe Object) bekommt wie Dein TableData. Entweder, indem Du TableData das Model vom genutzten JTable-Object übergibst oder in dem Du, so wie Du es bisher mit einer eigenen Instanz im TableData generiert hast, dem Table das Model vom TableData übergibst. Auf diese weise arbetest Du dann mit einem "gesharten" Model, was sich positiv auf die Performance auswirken sollte.

Du solltest außerdem in der run()-Metode Deines Threads nicht ständig die Datenbank öffnen und schließen, ist so denke ich zu performance-lastig.

Wo du Deinen Timer letztlich konfigurierst und aufrufst ist lediglich eine Frage Deiner Architektur. Denkbar ist es im GUI selbst zu machen, ist aber nicht unbedingt notwendig.
Ich selbst habe mir angewöhnt Routinen, die Daten holen sollen letzlich in DatenModule oder, wie ich sie nenne, DataHolder zu zentrieren, um bei Änderungen von Datenquellen und Senken schneller analysieren zu können. Aber das ist jedem eigener Geschmack.

Du scheinst im setTableData (..) außerdem die Spalten des Deines JTables mitzu geben. die Frage die sich da mir aufdrängt ist ob, denn die Spalten in Deiner Anwendung ebenso dynamisch sein sollen wie die eigentlichen Inhalte, sprich die Zeilen.

ansonsten viel Glück

Takidoso
 
Hallo takidoso,
hab mir die Timer-klasse mal angesehen. Hab versucht mittels schedule( new TableData(), 1000) aus der GUI meinen Thread aufzurufen, leider ohne Erfolg. Ich krig es einfach nicht hin. Mein Thread ist jetzt nicht mehr um Thread sondern um TimerTask erweitert. Das war doch erstmal richtig oder?
Kannst du mir bitte das noch etwas genauer erklären? Vielleicht, kannst du (wenn es dir keine umstände bereitet) mal anhand meines geposteten codes dies veranschaulichen.
Ich sitz jetzt schon seit einigen Tagen an dem Problem, aber ich komme da einfach nicht richtig weiter.
Vielen Dank für deine Hilfe.
Gruß Niles

Übrigens:
Das sleep() habe ich entfernt. :)
 

Neue Beiträge

Zurück