Swing JTextArea als TableCellRenderer in JTable - wie Zeilen selektieren?

handshake45

Bekanntes Mitglied
Versucht hatte ich Folgendes
Java:
	private static class MultiLineTableCellRenderer extends JTextArea implements TableCellRenderer {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
				int row, int column) {
			if (value instanceof String[]) {
				String[] s = (String[]) value;
				StringBuilder sb = new StringBuilder();
				for (String string : s) {
					sb.append(string);
					sb.append("\n");
				}
				this.setText(sb.toString());
				if (isSelected) {
					// this.select(0, s[0].length());
					StringSelection selection = new StringSelection(s[0]);
					Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
					clipboard.setContents(selection, selection);
				}
			}
			return this;
		}
	}


		DefaultTableModel dtm = new DefaultTableModel(1, 5) {
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public Class<?> getColumnClass(int columnIndex) {
				return String[].class;
			}

		};
		JTable table = new JTable(dtm);
		table.setDefaultRenderer(String[].class, new MultiLineTableCellRenderer());
		table.setRowHeight(table.getRowHeight() * 4);
aber es lässt sich keine Zelle selektieren. Habt ihr vielleicht einen Workaround für mich parat?
 
Soweit ich weiß wird nur ein Bild von jeder Zelle erstellt und das Markieren einer Zeile/Zelle muss der Editor machen?

Prinzipiell sollen zwei Sachen geschehen, wenn man auf eine Zelle klickt, farbliche Hervorhebung und den Inhalt der ersten Zeile der Zelle in die Zwischenablage tun.
 
Soweit ich weiß wird nur ein Bild von jeder Zelle erstellt und das Markieren einer Zeile/Zelle muss der Editor machen?
Welcher Editor? Der CellRenderer stellt die Zelle dar inkl. der Markierung, ob sie selektiert ist (für den Border gibt es, soweit ich mich entsinne, eine extra Property im Look and Feel) bzw. sie den Fokus besitzt. Ein CellEditor wäre dann für die Bearbeitung zuständig.
 
Die Zwischenablage hab ich schon mal hinbekommen:
Java:
		table.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent mouseEvent) {
				JTable table = (JTable) mouseEvent.getSource();
				Point point = mouseEvent.getPoint();
				int row = table.rowAtPoint(point);
				int col = table.columnAtPoint(point);
				if (mouseEvent.getClickCount() == 2 && table.getSelectedRow() != -1) {
					StringSelection selection = new StringSelection(((String[]) dtm.getValueAt(row, col))[0]);
					Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
					clipboard.setContents(selection, selection);
				}
			}
		});

Aber bei der farblichen Markierung bin ich echt überfragt... Hast du nich nen Beispiel für mich?
 
Eine TextArea schaut nunmal nicht aus wie ein Label.
Ist mehrzeilige Anzeige dein einziges Kriterium?
Dann verwende den Standard-Renderer, für diese Spalte aber ersetze den anzuzigenden String: Setz "<html>" davor, füge am Ende "</html>". So werden JLabels mehrzeillig.
Wenn du sauber arbeiten willst, ersetze noch Zeilenumbrüche durch "<br>"

Edit: Ich weiß jetzt allerdings nicht, ob der JTable so die Zeilenhöhe automatisch erhöht.
 
Sehr schön es funktioniert mit setBackground:

e.png

wie man an dem Beispiel sehen kann ist die Mehrzeiligkeit absolut wichtig für eine kompakte Darstellung, da ich sonst 15 Spalten benötigen würde....

Jetzt würde ich gern noch jede zweite Zeile farblich hervorheben... Auch eine Idee?
 
Kurze Frage noch: Den abwechselnden Zeilenhintergrund und markierte Zeilen realisiere ich jetzt über eine zusätzliche Spalte die entweder 0 oder 1 beinhalten kann:
Java:
	private static class MultiLineTableCellRenderer extends JTextArea implements TableCellRenderer {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private int colorNumber = 0;
		private Color color = null;

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
				int row, int column) {
			if (value instanceof String[]) {
				String[] s = (String[]) value;
				StringBuilder sb = new StringBuilder();
				for (String string : s) {
					sb.append(string);
					sb.append("\n");
				}
				this.setText(sb.toString());
				if (column == 0) {
					colorNumber = Integer.parseInt(s[0]);
					if (colorNumber == 0) {
						color = Color.yellow;
					} else {
						color = Color.white;
					}
				}
				if (isSelected) {
					this.setBackground(Color.lightGray);
				} else {
					this.setBackground(color);
				}
			}
			return this;
		}
	}
(Es gibt also drei Farben, Weiß, Gelb und Grau.) Kann ich dem DefaultTableModel bei addRow irgendwelche zusätzlichen Info mitgeben über die Hintergrundfarbe mit der später die Zeilen gerendert werden sollen ohne dass ich alles umschmeißen muss?
 
Das DTM arbeitet ja mit Object, insofern könntest Du direkt ein Color-Objekt mitgeben. Ich würde allerdings zu einem vernünftigen Model und Renderer tendieren, damit die Kopplung nicht so eng.

Du könntest z. B. ein
Java:
public interface TableColorModel {
    Color getColor(JTable table, Object value, int row, int col);
}
implementieren und dem Renderer mitgeben.

Java:
        private TableColorModel colorModel; 

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
            if (value instanceof String[]) {
                String[] s = (String[]) value;
                StringBuilder sb = new StringBuilder();
                for (String string : s) {
                    sb.append(string);
                    sb.append("\n");
                }
                this.setText(sb.toString());

                if (isSelected) {
                    this.setBackground(Color.lightGray);
                } else {
                    Color color = colorModel.getColor(table, value, row, column);
                    this.setBackground(color);
                }
            }
            return this;
        }
    }
 
@mihe7 So?
Java:
	private static class MultiLineTableCellRenderer extends JTextArea implements TableCellRenderer {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private DefaultTableModel dtm = null;

		public MultiLineTableCellRenderer(DefaultTableModel dtm) {
			this.dtm = dtm;
		}

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
				int row, int column) {
			if (value instanceof String[]) {
				String[] s = (String[]) value;
				StringBuilder sb = new StringBuilder();
				for (String string : s) {
					sb.append(string);
					sb.append("\n");
				}
				this.setText(sb.toString());
				if (isSelected) {
					this.setBackground(Color.lightGray);
				} else {
					this.setBackground((Color) dtm.getValueAt(row, dtm.getColumnCount()));
				}
			}
			return this;
		}
	}
Java:
		DefaultTableModel dtm = new DefaultTableModel(1, 5) {
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;
			private List<Color> colors = new ArrayList<Color>();

			@Override
			public Class<?> getColumnClass(int columnIndex) {
				return String[].class;
			}

			@Override
			public void addRow(Object[] rowData) {
				colors.add((Color) rowData[rowData.length - 1]);
				Object[] rowData2 = new Object[rowData.length - 1];
				System.arraycopy(rowData, 0, rowData2, 0, rowData.length - 1);
				super.addRow(rowData2);
			}

			@Override
			public Object getValueAt(int row, int column) {
				if (column == this.getColumnCount()) {
					return colors.get(row);
				}
				return super.getValueAt(row, column);
			}

			@Override
			public void setRowCount(int rowCount) {
				if (rowCount == 0) {
					colors.clear();
				}
				super.setRowCount(rowCount);
			}
		};
		JTable table = new JTable(dtm);
		table.setDefaultRenderer(String[].class, new MultiLineTableCellRenderer(dtm));
		table.setRowHeight(table.getRowHeight() * 4);
Java:
				dtm.setRowCount(0);
				Color color = Color.yellow;
				for (MyTrade2 a : trades2) {
					MyTrade t = a.a;
					Object[] rowData = new Object[] { new String[] { ...geheime Daten... },
							color };
					dtm.addRow(rowData);
					if (a.b != null) {
						t = a.b;
						rowData = new Object[] { new String[] { ...geheime Daten... },
								color };
						dtm.addRow(rowData);
					}
					if (color.equals(Color.yellow)) {
						color = Color.white;
					} else {
						color = Color.yellow;
					}
				}
Ich hab nicht das Gefühl, dass das so richtig sei...
 
Nein, das DTM musst Du nicht übergeben. Das kannst Du via JTable erhalten. Die Implementierung von TableColorModel müsstest Du übergeben.

Wenn Du sowieso schon ein eigenes TableModel implementierst, könntest Du gleich von AbstractTableModel ableiten. Dann könnte Dein TableModel auch TableColorModel implementieren. Außerdem hättest Du dann auch Zugriff auf die Objekte,.
 
So, mal schnell ein Beispiel zusammengeschustert.

Java:
import java.awt.Component;
import java.awt.Color;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;

public class ColorRenderer implements TableCellRenderer {
    private JTextArea component = new JTextArea();
    private TableColorModel colorModel;

    public ColorRenderer(TableColorModel model) {
        colorModel = model;
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
        int row, int column) {
        String text = value == null ? "" : value.toString();
        component.setText(text);

        if (isSelected) {
            component.setBackground(Color.lightGray);
        } else {
            Color color = colorModel.getColor(table, value, row, column);
            component.setBackground(color == null ? table.getBackground() : color);
        }
        return component;
    }
}

Java:
import java.awt.Color;

public class Party {
    public String name;
    public Color color;

    public Party(String name, Color color) {
        this.name = name;
        this.color = color;
    }
}

Java:
import java.awt.Color;
import javax.swing.JTable;

public class PartyTableColorModel implements TableColorModel {
    private PartyTableModel model;

    public PartyTableColorModel(PartyTableModel model) {
        this.model = model;
    }

    public Color getColor(JTable table, Object value, int row, int col) {
        return model.getParty(row).color;
    }
}

Java:
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;

public class PartyTableModel extends AbstractTableModel {
    private List<Party> parties;

    public PartyTableModel(List<Party> parties) {
        this.parties = new ArrayList<>(parties);
    }

    public Party getParty(int index) {
        return parties.get(index);
    }

    public int getColumnCount() {
        return 1;
    }

    public int getRowCount() {
        return parties.size();
    }

    public String getColumnName(int col) {
        return "Partei";
    }

    public Object getValueAt(int row, int col) {
        return getParty(row).name;
    }
}

Java:
import java.awt.Color;
import javax.swing.JTable;

public interface TableColorModel {
    Color getColor(JTable table, Object value, int row, int col);
}

Java:
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

public class Test {

    public void run() {
        List<Party> parties = new ArrayList<>();
        parties.add(new Party("CDU", Color.BLACK));
        parties.add(new Party("SPD", Color.RED));
        parties.add(new Party("Bündnis 90", Color.GREEN));
        parties.add(new Party("FDP", Color.YELLOW));
        parties.add(new Party("Sonstige", null));

        PartyTableModel model = new PartyTableModel(parties);
        PartyTableColorModel colorModel = new PartyTableColorModel(model);
        JTable table = new JTable(model);
        table.setDefaultRenderer(Object.class, new ColorRenderer(colorModel));
      
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(new JScrollPane(table));
        frame.setSize(800, 600);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new Test().run());
    }
}
 
Nicht so tragisch: ColorRenderer -> TableColorModel. Implementierung von TableColorModel ist das PartyTableColorModel, das in diesem Fall ein PartyTableModel erhält. Du könntest auch das TableColorModel direkt im PartyTableModel implementieren. Dann hast Du aber im Model eine Abhängigkeit zu JTable.
 
@mihe7 Ich denke ,ich habe einen Mittelweg gefunden ,der mit weniger Zeilen auskommt...
Java:
	private static class MultiLineTableModel extends DefaultTableModel {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private MultiLineTableCellRenderer renderer;

		public MultiLineTableModel(MultiLineTableCellRenderer renderer) {
			super(1, 5);
			this.renderer = renderer;
		}

		@Override
		public Class<?> getColumnClass(int columnIndex) {
			return String[].class;
		}

		public void addRow(Object[] rowData, Color color) {
			renderer.colors.add(color);
			super.addRow(rowData);
		}

		@Override
		public void setRowCount(int rowCount) {
			if (rowCount == 0) {
				renderer.colors.clear();
			}
			super.setRowCount(rowCount);
		}
	}

	private static class MultiLineTableCellRenderer extends JTextArea implements TableCellRenderer {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		public List<Color> colors = new ArrayList<Color>();

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
				int row, int column) {
			if (value instanceof String[]) {
				String[] s = (String[]) value;
				StringBuilder sb = new StringBuilder();
				for (String string : s) {
					sb.append(string);
					sb.append("\n");
				}
				this.setText(sb.toString());
				if (isSelected) {
					this.setBackground(Color.lightGray);
				} else {
					this.setBackground(colors.get(row));
				}
			}
			return this;
		}
	}


		MultiLineTableCellRenderer renderer = new MultiLineTableCellRenderer();
		MultiLineTableModel model = new MultiLineTableModel(renderer);
		JTable table = new JTable(model);
		table.setDefaultRenderer(String[].class, renderer);
		table.setRowHeight(table.getRowHeight() * 4);
i.O.?
 
Ein Beispiel hast Du in #25 🙂 Der Unterschied wäre, dass Du auf TableColorModel verzichtest und im Renderer das Model, das Du von JTable erhältst, auf PartyTableModel castest:

Java:
import java.awt.Component;
import java.awt.Color;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;

public class ColorRenderer implements TableCellRenderer {
    private JTextArea component = new JTextArea();

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
        int row, int column) {
        String text = value == null ? "" : value.toString();
        component.setText(text);

        if (isSelected) {
            component.setBackground(Color.lightGray);
        } else {
            PartyTableModel model = (PartyTableModel) table.getModel();
            Color color = model.getParty(row).color;
            component.setBackground(color == null ? table.getBackground() : color);
        }
        return component;
    }
}
 
Das ist zwar nur ein Beispiel, aber ja, Du könntest auch vom DefaultTableModel (das für die Tonne ist) ableiten und auf das protected Feld dataVector zugreifen.
 
Hallo @mihe7 , aber das Problem wird sein dass ich dann zwei Collections haben müsste einmal für die Tabellen-Daten und einmal für die Zeilen-Farben und ich nicht beide zusammen sortieren kann also wenn ich die Tabellen-Daten sortiere, dann werden die Zeilen-Farben nicht mit sortiert und umgekehrt. Hättest du dafür auch eine Idee?

Und wieso ist das DefaultTableModel schlecht? TableModel hat einfach wahnsinnig viele Methoden die alle implementiert werden müssen. Iwie werde ich mit dem Table-Framework noch nicht so richtig warm. 🙁
 
aber das Problem wird sein dass ich dann zwei Collections haben müsste einmal für die Tabellen-Daten und einmal für die Zeilen-Farben und ich nicht beide zusammen sortieren kann also wenn ich die Tabellen-Daten sortiere, dann werden die Zeilen-Farben nicht mit sortiert und umgekehrt.
Verstehe ich nicht.

Und wieso ist das DefaultTableModel schlecht? TableModel hat einfach wahnsinnig viele Methoden die alle implementiert werden müssen.
Das DTM verwendet Vector (= synchronized) und Object. Das ist in der Regel nicht das, was man möchte. Das TableModel implementiert man am einfachsten durch Ableiten von AbstractTableModel. Dort müssen lediglich drei Methoden implementiert werden. Kommt natürlich immer auf den Zweck an.
 
@mihe7 , akzeptabel ?
Java:
	private static class MultiLineTableModel extends AbstractTableModel {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		public static class MTMElem {
			public String[][] data;
			public Color color;

			public MTMElem(String[][] data, Color color) {
				this.data = data;
				this.color = color;
			}
		}

		private ArrayList<MTMElem> tableData = new ArrayList<MTMElem>();

		@Override
		public Class<?> getColumnClass(int columnIndex) {
			return String[].class;
		}

		public void addRow(String[][] rowData, Color color) {
			tableData.add(new MTMElem(rowData, color));
		}

		@Override
		public int getRowCount() {
			return tableData.size();
		}

		@Override
		public int getColumnCount() {
			return 5;
		}

		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			return tableData.get(rowIndex).data[columnIndex];
		}

		public Color getColorAt(int rowIndex) {
			return tableData.get(rowIndex).color;
		}

		public void clear() {
			tableData.clear();
			this.fireTableDataChanged();
		}
	}

	private static class MultiLineTableCellRenderer extends JTextArea implements TableCellRenderer {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private MultiLineTableModel multiLineTableModel;

		public MultiLineTableCellRenderer(MultiLineTableModel multiLineTableModel) {
			this.multiLineTableModel = multiLineTableModel;
		}

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
				int row, int column) {
			String[] s = (String[]) value;
			StringBuilder sb = new StringBuilder();
			for (String string : s) {
				sb.append(string);
				sb.append("\n");
			}
			this.setText(sb.toString());
			if (isSelected) {
				this.setBackground(Color.lightGray);
			} else {
				this.setBackground(multiLineTableModel.getColorAt(row));
			}
			return this;
		}
	}


		MultiLineTableModel model = new MultiLineTableModel();
		MultiLineTableCellRenderer renderer = new MultiLineTableCellRenderer(model);
		JTable table = new JTable(model);
		table.setDefaultRenderer(String[].class, renderer);
		table.setRowHeight(table.getRowHeight() * 4);


				model.clear();
				Color color = Color.yellow;
				for (MyTrade2 a : trades2) {
					MyTrade t = a.a;
					String[][] rowData = new String[][] { new String[] { ...geheimeDaten... } };
					model.addRow(rowData, color);
					if (a.b != null) {
						t = a.b;
						rowData = new String[][] { new String[] { ...geheimeDaten... } };
						model.addRow(rowData, color);
					}
					if (color.equals(Color.yellow)) {
						color = Color.white;
					} else {
						color = Color.yellow;
					}
				}
Müsste ich in addRow() nicht auch i-ein Event aufrufen/auslösen - oder bekommt die Table die Änderung seltsamerweise auch ohne Event mit?
 
?!? Entweder wie oben mit einem separaten TableColorModel oder indem Du von TableModel (dem Interface) eines ableitest.
 
Naja, was ist denn speziell an Deinem TableModel? getColorAt, oder? Also kannst Du z. B. hergehen und
Java:
interface ColoredTableModel extends TableModel {
    Color getColorAt(int row);
}
schreiben. Dann ist Dein Renderer nur noch von diesem TableModel abhängig, aber nicht mehr von Deiner Implementierung.
 

Neue Themen


Zurück
Oben