Java Anhand von Informationen bestimmte Zellen in JTable färben

darton

Lt. Junior Grade
Registriert
Okt. 2004
Beiträge
282
Hallo!
Ich habe mehrere Objekte, deren ganzen Daten ich in einer JTable dargestellt habe. Wenn manche Daten allerdings fehlerhaft oder einfach gar nicht vorhanden sind, soll die entsprechende Zelle in der Tabelle, wo die fehlerhaften Daten eingefügt werden, eingefärbt werden.
Um zu wissen welche Zellen betroffen sind, habe ich mir gedacht, dass ich ein int-Array für jedes Objekt erstelle, was dann z.B. so aussieht: [0,0,1,0,1]. Eine Eins bedeutet, dass die Information falsch ist. Die erste Eins steht beim Index 2, also gilt für die Zelle in Spalte 2 (von 0 ausgehend), dass sie eingefärbt werden soll. Mein Problem ist nun, wie ich diese Arrays an den CellRenderer weitergeben soll. Es handelt sich dabei ja um eine Objekteigenschaft. Das TableModel bekommt ja noch die int-Arrays, aber wie kriege ich sie von da zum CellRenderer, sodass er die entsprechenden Zellen einfärben kann?
 
Der CellRenderer kann direkt auf die Objektdaten zugreifen, da muss nichts extra übergeben werden. Du definierst einen eigenen Renderer für Deine Objekte und änderst die Hintergrundfarbe, wenn indiziert.

Code:
table.setDefaultRenderer(ModelObject.class, new ModelObjectRenderer());

private static class ModelObjectRenderer extends DefaultTableCellRenderer {
    public Component getTableCellRendererComponent(
            JTable rTable, Object rObject, boolean rSelected,
            boolean rFocused, int rRow, int rColumn) {
        super.getTableCellRendererComponent(rTable, rObject, rSelected,
                                            rFocused, rRow, rColumn);
    
        ModelObject object = (ModelObject)rObject;

        if (object.isDataError() || object.isDataMissing()) {
            setBackground(Color.RED);
            setForeground(Color.WHITE);
        }
    
        return this;
    }
}

Eventuell muss man den Renderer noch opaque machen. Das weiß ich adhoc nicht. Man könnte auch noch auf Fokus und Selektierstatus reagieren und die Farben entsprechend ändern. Oder einen Tooltip setzen, der spezifische Informationen über den Fehler anzeigt etc.
 
OK, das sieht jetzt bei mir so aus:
Code:
public class MyTableRenderer extends DefaultTableCellRenderer {	
	
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {

		super.getTableCellRendererComponent(table, value, isSelected,
                hasFocus, row, column);	
		Person p = (Person)value;

                if (!p.getOrt().equals("bla")) {
        	      System.out.println("jo");
                }       
                return this;		
	}
}

//In der GUI-Klasse
table.setDefaultRenderer(Person.class, new MyTableRenderer());
Und das funktioniert nicht. Hab zur Einfachheit jetzt mal den Ort einer Person genommen und der ist garantiert nirgendwo "bla", d.h. in der Konsole müsste die ganze Zeit "jo" erscheinen, das tut es aber nicht.
 
Das Beispiel war wohl zu verkürzt. Das Problem wird das verwendete Datenmodell sein. Poste mal ein lauffähiges Beispiel.
 
OK, ich habe jetzt mal ein stark vereinfachtes Beispiel erstellt. Dort werden nur zwei Personen in die Tabelle eingefügt und bei der zweiten fehlt bei der Straßenangabe die Hausnummer. Sowas müsste dann z.B. rot markiert werden.
 

Anhänge

Wie vermutet liegt es daran, dass im Modell weder Person Objekte vorhanden, noch verlangt werden. Deswegen wird immer der Standard-Renderer benutzt (java.lang.Object).

Du fügst dem Model lediglich Strings hinzu. Das ist hier schlecht, da unspezifisch. Mach Objekte daraus, z.B. Street, und füge diese dem Modell hinzu. Das Modell muss dann noch je nach Index die passende Klasse liefern, damit der spezifische Renderer genommen wird. Hierzu #getColumnClass(int) überschreiben und für die dritte Spalte z.B. Street.class liefern. Dort hat es dann das Flag, das der Renderer abfragen kann.

Um es generisch zu machen, für alle Value-Objekte (Name, Plz, Strasse usw.) ein gemeinsames Interface für die Eigenschaft nutzen, dann braucht es nur einen Renderer, um auf diesen Zustand zu reagieren.

Um aber noch einmal auf die Eingangsfrage zurück zu kommen: Eine Alternative wäre die Indices der betroffenen Zellen in eine Datenstruktur zu packen und diese an die Tabelle zu hängen. Swing liefert hierfür einen einfachen Mechanismus: JComponent#putClientProperty/getClientProperty. Darüber könnte der CellRenderer auf die Datenstruktur zugreifen und je nachdem die Zelle entsprechend einfärben. Dann müsstest Du nur den Standard-Renderer für java.lang.Object setzen. Alles andere könnte unverändert bleiben.

Hast Du schon einmal mit Tabellen gearbeitet? Es empfiehlt sich am Anfang ein entsprechendes Buch/Tutorial durchzuarbeiten, um sich mit den Grundlagen vertraut zu machen:

http://download.oracle.com/javase/tutorial/uiswing/components/table.html
 
Zuletzt bearbeitet:
Hab deine erste Möglichkeit jetzt genommen, da ich ohnehin schon dabei war, alle Strings in entsprechende Objekte umzuwandeln. Der Hinweis mit dem Interface war sehr gut, jetzt scheint alles zu klappen.
Musste den CellRenderer allerdings noch ein wenig abändern.
Code:
public Component getTableCellRendererComponent(JTable table, Object value,
	boolean isSelected, boolean hasFocus, int row, int column) {
		super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);	
		setBackground(Color.WHITE);
                if (value instanceof Property) {
                    if (((Property) value).isInvalid()) {            	
            	        setBackground(Color.RED);
                    }        	    	
                }       
                return this;		
}
Habe noch ein "setBackground(Color.WHITE);" eingefügt, weil der Renderer irgendwie sonst ab der ersten fehlerhaften Zelle alles rot eingefärbt hat.
 
Ich halte den gewählten Ansatz auch für den besseren Weg. Wenn der CellRenderer lediglich für die Property-Klasse definiert wird, spart man sich den instanceof-Check und der Cast reicht. Da der Renderer nicht für jede Zelle neu erstellt wird, muss man den Status jeweils wie gewünscht anpassen, hier also den Hintergrund je nachdem auf weiß oder rot setzen.
 
Muss hier jetzt nochmal nachhaken. Ich hab für die Klassen die das Interface implementieren auch in dem TableModel für die Funktion getColumnClass() natürlich das Interface zurückgeben lassen. Also statt für die dritte Spalte Street.class und für die vierte Spalte Ort.class usw. steht da dann einfach Interfacename.class für mehrere Spalten.
Das Problem ist nun, dass ich all die Zellen, die zu diesen Spalten gehören, nicht editieren kann. Wenn ich statt des Interfacenamens in der getColumnClass() Funktion wieder Street.class usw. einsetze, funktioniert es. Woran liegt das?
 
Um nicht raten zu müssen: Poste am besten ein Code-Beispiel! Analog zum eigenen CellRenderer musst Du vermutlich auch einen eigenen CellEditor setzen.

Wie gesagt: arbeite einmal das genannte Tutorial durch. Dort sind die Grundlagen recht schön erklärt und entsprechender Code verfügbar.
 
Mit dem CellEditor hattest du Recht. Es hat sogar einfach gereicht, der JTable den DefaultCellEditor mitm JTextField im Konstruktor zu geben und dann funktionierte das Editieren der Felder auch schon, die mit dem Interface markiert sind. Sieht also so aus:
Code:
table.setDefaultEditor(InterfaceName.class, new DefaultCellEditor(new JTextField()));
Dachte eigentlich, dass die JTable den DefaultCellEditor verwendet, aber ich will mich nicht beklagen, es funktioniert ja jetzt. Danke für den Tipp.
 
Zurück
Oben