Swing gleiche Einträge in JTable farblich markieren

Curry

Aktives Mitglied
Moin Moin!

Ich würde gerne in einer JTable den fokussierten Eintrag auslesen und gleiche Einträge in dieser Tabelle farblich markieren. Dies habe ich mit einem TableCellRenderer versucht, was aber leider nur bedingt funktioniert.

Scheinbar werden nicht immer alle Zellen neue gezeichnet und komischerweise kann ich auch die erste Zeile nicht markieren.

Hat jemand wie ich es lösen kann?

Hier ein kleines funktionsfähiges Beispiel:
Java:
public class Main extends JFrame {

	public Main() {
		this.setLayout(new FlowLayout());


		String[][] rowData = {
			{"Tag 1", "1", "2", "3", "4", "5"},
			{"Tag 2", "2", "1", "5", "4", "4"},
			{"Tag 3", "1", "2", "3", "4", "3"},
			{"Tag 4", "1", "2", "3", "3", "5"},
			{"Tag 5", "1", "2", "2", "4", "5"},
			{"Tag 6", "1", "1", "3", "4", "5"}
		};

		String[] columnNames = {
			"Tag", "Aktion 1", "Aktion 2", "Aktion 3", "Aktion 4", "Aktion 5"
		};

		JTable table = new JTable(rowData, columnNames);

		TableColumnModel columnModel = table.getColumnModel();
		columnModel.getColumn(1).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(2).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(3).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(4).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(5).setCellRenderer(new TestCellRenderer());
		add(new JScrollPane(table));
		pack();
	}

	public static void main(String[] args) {
		Main test = new Main();
		test.setVisible(true);
	}

	public class TestCellRenderer extends JLabel implements TableCellRenderer {

		private Color colorEqual = new Color(255, 0, 0);
		private Color colorFocused = new Color(0, 255, 0);
		private Color colorDefault = new Color(255, 255, 255);

		public TestCellRenderer() {
			this.setOpaque(true);
			this.setHorizontalAlignment(JLabel.CENTER);
		}

		// ##################################
		// Implementierung: TableCellRenderer
		// ##################################
		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

			String str = String.valueOf(value);
			this.setText(str);

			int selectedColumn = table.getSelectedColumn();
			int selectedRow = table.getSelectedRow();

			System.out.println(row + "\t\t" + column + "\t\t" + selectedRow + "\t\t" + selectedColumn);

			if (selectedRow > 0 && selectedColumn > 0) {

				String compare = String.valueOf(table.getValueAt(selectedRow, selectedColumn));

				if (hasFocus) {
					this.setBackground(colorFocused);
				} else if (str.equals(compare)) {
					this.setBackground(colorEqual);
				} else {
					this.setBackground(colorDefault);
				}

			} else {
				this.setBackground(colorDefault);
			}

			return this;

		}
	}
}
 
Wie Du bereits festgestellt hast ist der TableCellRenderer recht passiv und dazu nicht gerade intelligent ;-) Man kann mit ihm zwar über den Tellerrand einer Zelle hinausschauen, mehr aber nicht.

Der Weg das über den TableCellRenderer zu lösen ist m.M. der falsche Ansatz und führt wahrscheinlich nicht zum Erfolg.

Ich würde mir zu nächst einmal ein Objekt defnieren, welches die selben "Einträge" repräsentiert.
Mittels MouseListener und TableCellRenderer wird dann die "Selektion" und die Markierung gesteuert.

Hier mal ein auf die Schnelle hingeschriebener quick & dirty Ansatz, der das mal veranschaulichen soll.
Hat noch einiges an Optimierungspotential und ein paar unschöne Behelfslösungen:
Java:
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

public class TableMarkDemo extends JFrame {
	public TableMarkDemo() {
		MyObject[] objects = new MyObject[5];
		for (int i = 0; i < objects.length; i++)
			objects[i] = new MyObject("Objekt " + i);
		final DefaultTableModel model = new DefaultTableModel(new Object[][] { { "Tag 1", objects[0], objects[1], objects[2], objects[3] },
				{ "Tag 2", objects[1], objects[2], objects[0], objects[1] }, { "Tag 3", objects[4], objects[4], objects[3], objects[2] },
				{ "Tag 4", objects[1], objects[0], objects[1], objects[4] } },
				new String[] { "Tag", "Objekt", "Objekt", "Objekt", "Objekt" }) {
			
			public Class<?> getColumnClass(int columnIndex) {
				if (columnIndex > 0)
					return MyObject.class;
				return super.getColumnClass(columnIndex);
			}
		};
		
		final JTable table = new JTable(model);
		this.getContentPane().add(new JScrollPane(table));

		table.setDefaultRenderer(MyObject.class, new MyCellRenderer());

		table.addMouseListener(new MouseAdapter() {
			private MyObject lastSelection;

			public void mouseClicked(MouseEvent e) {
				int row = table.rowAtPoint(e.getPoint());
				int column = table.columnAtPoint(e.getPoint());
				if (lastSelection != null)
					lastSelection.setHighlighted(false);
				Object value = table.getValueAt(row, column);
				if (value instanceof MyObject) {
					lastSelection = (MyObject) value;
					lastSelection.setHighlighted(true);
				} else
					lastSelection = null;
				model.fireTableDataChanged();
			}
		});
	}

	class MyCellRenderer extends DefaultTableCellRenderer {
		private Color highlight = Color.ORANGE;

		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			if (value instanceof MyObject) {
				MyObject mo = (MyObject) value;
				this.setText(mo.getValue());
				if (mo.isHighlighted())
					this.setBackground(highlight);
				else
					this.setBackground(table.getBackground());
			}
			return this;
		}
	}

	class MyObject {
		private String value;
		private boolean highlighted;

		public MyObject(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}

		public void setHighlighted(boolean b) {
			this.highlighted = b;
		}

		public boolean isHighlighted() {
			return highlighted;
		}
	}

	public static void main(String[] args) {
		JFrame frame = new TableMarkDemo();
		frame.setBounds(0, 0, 500, 300);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
}
 
So, ich habe jetzt mal eine Art Quirl aus meinem ersten und deinem Beispiel gemacht. Das funktioniert schon sehr schön. Ich verwende jetzt einen
Code:
ListSelectionListener
mit seiner Methode
Code:
valueChanged(ListSelectionEvent e)
. In dieser lese ich die fokussierte Zelle aus und hinterlege dessen Wert im TableModel (mittels
Code:
setSelected(String selected)
), das daraufhin
Code:
fireTableDataChanged()
aufruft. Dadurch werden alle Zellen neu gezeichnet und alles wird richtig angezeigt.

Allerdings habe ich den ListSelectionListener noch nicht richtig unter Kontrolle (oder vielleicht ist der Fehler auch woanders). Auf jeden Fall bleibt die fokussierte Zelle nicht fokussiert, sondern verlieht den Fokus wieder. Dadurch wird sie wie die anderen auch rot und nicht grün (siehe
Code:
TestCellRenderer
) angezeigt.

Wenn ich nacheinander auf zwei Zellen klicke, dann produziert der
Code:
ListSelectionListener
mit seiner
Code:
valueChanged(ListSelectionEvent e)
Methode die folgende Konsolenausgabe:
Code:
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged(1) -> tableModel.setSelected()
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged(5) -> tableModel.setSelected()
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged() -> getValueIsAdjusting = true
Main.valueChanged() -> getValueIsAdjusting = true

Woran liegt das und wie kann ich das besser machen?


Java:
public class Main extends JFrame implements ListSelectionListener {

	JTable table = null;
	MyTableModel tableModel = null;

	public Main() {
		this.setLayout(new FlowLayout());
		
		String[][] rowData = {
			{"Tag 1", "1", "2", "3", "4", "5"},
			{"Tag 2", "2", "1", "5", "4", "4"},
			{"Tag 3", "1", "2", "3", "4", "3"},
			{"Tag 4", "1", "2", "3", "3", "5"},
			{"Tag 5", "1", "2", "2", "4", "5"},
			{"Tag 6", "1", "1", "3", "4", "5"}
		};

		String[] columnNames = {"Tag", "Aktion 1", "Aktion 2", "Aktion 3", "Aktion 4", "Aktion 5"};

		tableModel = new MyTableModel();
		tableModel.setDataVector(rowData, columnNames);
		table = new JTable(tableModel);
		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		table.getSelectionModel().addListSelectionListener(this);
		tableModel.addTableModelListener(table);

		TableColumnModel columnModel = table.getColumnModel();
		columnModel.getColumn(1).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(2).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(3).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(4).setCellRenderer(new TestCellRenderer());
		columnModel.getColumn(5).setCellRenderer(new TestCellRenderer());
		add(new JScrollPane(table));
		pack();
	}

	public static void main(String[] args) {
		Main test = new Main();
		test.setVisible(true);
	}

	@Override
	public void valueChanged(ListSelectionEvent e) {
		String selected = "";
		int selectedRow = table.getSelectedRow();
		int selectedColumn = table.getSelectedColumn();

		if (e.getValueIsAdjusting()) {
			System.out.println("ExamVersionPanel.valueChanged(" + selected + ") -> getValueIsAdjusting = true");
			return;
		}

		if (selectedRow >= 0 && selectedColumn >= 0) {
			selected = (String) table.getValueAt(selectedRow, selectedColumn);
		}

		if (selected.equals("") == false) {
			System.out.println("ExamVersionPanel.valueChanged(" + selected + ") -> tableModel.setSelected()");
			tableModel.setSelected(selected);
		}
	}

	public class MyTableModel extends DefaultTableModel {

		String selected = "";

		public void setSelected(String selected) {
			this.selected = selected;
			fireTableDataChanged();
		}

		public String getSelected() {
			return selected;
		}
	}

	public class TestCellRenderer extends JLabel implements TableCellRenderer {

		private Color colorEqual = new Color(255, 0, 0);
		private Color colorFocused = new Color(0, 255, 0);
		private Color colorDefault = new Color(255, 255, 255);
		private MatteBorder borderFocused = BorderFactory.createMatteBorder(2, 2, 2, 2, new Color(0, 150, 0));
		

		public TestCellRenderer() {
			this.setOpaque(true);
			this.setHorizontalAlignment(JLabel.CENTER);
		}

		// ##################################
		// Implementierung: TableCellRenderer
		// ##################################
		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

			String str = String.valueOf(value);
			this.setText(str);

			String compare = tableModel.getSelected();

			if (hasFocus) {
				this.setBackground(colorFocused);
				this.setBorder(borderFocused);
			} else if (str.equals(compare)) {
				this.setBackground(colorEqual);
				this.setBorder(null);
			} else {
				this.setBackground(colorDefault);
				this.setBorder(null);
			}

			return this;

		}
	}
}
 
Zusatz:

Wenn man die Zuweisung des ListSelectionListener auskommentiert, dann bleibt die Zelle nach dem anklicken fokussiert und es kann nach dem ersten fokussieren einer Zelle mit den Pfeiltasten in der Tabelle navigiert werden.

Java:
//		table.getSelectionModel().addListSelectionListener(this);

Es muss also irgendwie an dem ListSelectionListener liegen. Kann man das irgendwie unterbinden?
 
das mit dem
Code:
fireTableDataChanged()
bewirkt eine Deselektion der JTable und somit nochmaliges Auslösen des Listeners.

Hier mal eine Variante mit ListSelectionListener (wobei dieser nur auf Zeilenwechsel reagiert):
Java:
import java.awt.Color;
import java.awt.Component;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

public class TableMarkDemo extends JFrame {
	public TableMarkDemo() {
		MyObject[] objects = new MyObject[5];
		for (int i = 0; i < objects.length; i++)
			objects[i] = new MyObject("Objekt " + i);
		final DefaultTableModel model = new DefaultTableModel(new Object[][] { { "Tag 1", objects[0], objects[1], objects[2], objects[3] },
				{ "Tag 2", objects[1], objects[2], objects[0], objects[1] }, { "Tag 3", objects[4], objects[4], objects[3], objects[2] },
				{ "Tag 4", objects[1], objects[0], objects[1], objects[4] } },
				new String[] { "Tag", "Objekt", "Objekt", "Objekt", "Objekt" }) {

			public Class<?> getColumnClass(int columnIndex) {
				if (columnIndex > 0)
					return MyObject.class;
				return super.getColumnClass(columnIndex);
			}
		};

		final JTable table = new JTable(model);
		this.getContentPane().add(new JScrollPane(table));
		table.setDefaultRenderer(MyObject.class, new MyCellRenderer());

		table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			private MyObject lastSelection;

			public void valueChanged(ListSelectionEvent e) {
				if (!e.getValueIsAdjusting()) {
					int row = table.getSelectedRow();
					int column = table.getSelectedColumn();

					System.out.println(row + " : " + column);

					if (lastSelection != null)
						lastSelection.setHighlighted(false);
					Object value = table.getValueAt(row, column);
					if (value instanceof MyObject) {
						lastSelection = (MyObject) value;
						lastSelection.setHighlighted(true);
					} else
						lastSelection = null;
					model.fireTableRowsUpdated(0, model.getRowCount() - 1);
				}
			}
		});
	}

	class MyCellRenderer extends DefaultTableCellRenderer {
		private Color highlight = Color.ORANGE;

		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			if (value instanceof MyObject) {
				MyObject mo = (MyObject) value;
				this.setText(mo.getValue());
				if (hasFocus || isSelected)
					return this;
				if (mo.isHighlighted())
					this.setBackground(highlight);
				else
					this.setBackground(table.getBackground());
			}
			return this;
		}
	}

	class MyObject {
		private String value;
		private boolean highlighted;

		public MyObject(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}

		public void setHighlighted(boolean b) {
			this.highlighted = b;
		}

		public boolean isHighlighted() {
			return highlighted;
		}
	}

	public static void main(String[] args) {
		JFrame frame = new TableMarkDemo();
		frame.setBounds(0, 0, 500, 300);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
}
 
Vielen Dank für deinen Hinweis zu
Code:
fireTableDataChanged()
!

Aber deine Lösung mit der MyObjekt-Klasse passt bei mir im Programm nicht so gut. Drum habe ich nach einer anderen Möglichkeit gesucht.

Ich habe es jetzt mit folgendem Vorgehen hinbekommen:
Damit der
Code:
ListSelectionListener
sowohl bei Zeilen- als auch bei Spaltenwechsel aktiv wird, habe ich ihn bei beiden Listenern registriert.
Java:
table.getSelectionModel().addListSelectionListener(this);
table.getColumnModel().getSelectionModel().addListSelectionListener(this);
Und um das Problem mit dem Deselektieren nach
Code:
fireTableDataChanged()
zu umgehen, rufe ich diese Methode gar nicht auf. Stattdessen verwende ich
Code:
table.repaint();
in
Code:
valueChanged()
.
Java:
@Override
public void valueChanged(ListSelectionEvent e) {

	if (!e.getValueIsAdjusting()) {
		String selected = "";
		int selectedRow = table.getSelectedRow();
		int selectedColumn = table.getSelectedColumn();

		if (selectedRow >= 0 && selectedColumn >= 0) {
			selected = (String) table.getValueAt(selectedRow, selectedColumn);
		}

		if (selected.equals("") == false) {
			tableModel.setSelected(selected);
			table.repaint();
		}
	}
}

Wie würdet ihr dieses Vorgehen bewerten? Sauber, oder habe ich etwas wichtiges nicht übersehen?
 

Neue Themen


Zurück
Oben