TreeTable: Unterschied zwischen den Versionen
K |
|||
(16 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
Hier ist eine einfache Implementation einer TreeTable (gem. Alexandru Toth), | Hier ist eine einfache Implementation einer TreeTable (gem. Alexandru Toth), | ||
− | die ein gemeinsames Model für JTree und JTable benutzt. | + | die ein gemeinsames [[Model]] für {{JAPI|JTree}} und {{JAPI|JTable}} benutzt. |
− | Es ist dann kein Problem mehr, dynamisch neue Zeilen hinzuzufügen | + | Es ist dann kein Problem mehr, dynamisch neue Zeilen hinzuzufügen. |
− | <code=java>package treetable; | + | Die TreeTable ist so gedacht, daß in der ersten Spalte (JTree) die Kurzbezeichnung steht und in der zweiten Spalte die vollständige Bezeichnung. Die erste Spalte gehört zwar nicht zur JTable, aber man kann sie trotzdem in der Breite verändern, indem man den Divider vom {{JAPI|JSplitPane}} verschiebt. |
+ | |||
+ | "Table" ordnet die [[Renderer]] den entsprechenden Datentypen zu. Man kann weitere Renderer vorsehen, | ||
+ | wenn man will. Dort ist auch die Methode <code>shouldCombineCells()</code>, die wir gegebenenfalls anpassen | ||
+ | müssen. Sie wird in <code>getCellRect()</code> auffgerufen, wo das eigentliche Verbinden der Zellen stattfindet (für die vollständige Bezeichnung bei geschlossenem TreeNode). | ||
+ | |||
+ | "TableAdapter" enthält die Spaltenüberschriften der JTable. | ||
+ | |||
+ | "TableNode" enthält die Daten für die JTable (dataRow), sowie die Kurzbezeichnung, die im JTree erscheint (label). | ||
+ | |||
+ | "TreeTablePanel" verbindet alles zu einer Einheit (naja, der {{JAPI|JButton}} "btAdd" gehört da vielleicht nicht | ||
+ | rein). Dort muss man eigentlich nichts mehr anpassen (außer vielleicht Sachen, wie die RowHeight in der Methode <code>customizeTable()</code>). | ||
+ | |||
+ | Die "TreeTable" ist übrigens kein fertiges Endprodukt mit allen möglichen Ansprüchen, sondern nur ein Codeschnipsel als Ansatz für Entwickler. Fühl dich frei, so viele Spalten und Zugriffsmethoden hinzuzufügen, wie du brauchst. | ||
+ | Das TreeTablePanel-Array in der TreeTablePanel-Klasse ist nicht unbedingt nötig. Ich habe es hinzugefügt, für den Fall wo mehrere identische TreeTables angezeigt werden sollen. | ||
+ | |||
+ | [[Datei:TreeTableDemo.png]] | ||
+ | |||
+ | <syntaxhighlight lang="java">package treetable; | ||
import java.awt.Color; | import java.awt.Color; | ||
+ | import java.awt.Dimension; | ||
+ | import java.awt.Rectangle; | ||
+ | import java.util.Enumeration; | ||
import javax.swing.*; | import javax.swing.*; | ||
import javax.swing.table.*; | import javax.swing.table.*; | ||
+ | import javax.swing.tree.DefaultMutableTreeNode; | ||
/** JTable that has different renderers depending on the classes in TreeModel */ | /** JTable that has different renderers depending on the classes in TreeModel */ | ||
Zeile 14: | Zeile 36: | ||
void registerRenderers() { | void registerRenderers() { | ||
setDefaultRenderer(Color.class, new TableColorRenderer(Color.WHITE)); | setDefaultRenderer(Color.class, new TableColorRenderer(Color.WHITE)); | ||
+ | setUI(new UIMultiSpan()); | ||
} | } | ||
Zeile 43: | Zeile 66: | ||
return tcr; | return tcr; | ||
} | } | ||
− | |||
− | |||
− | |||
+ | @Override | ||
+ | public Rectangle getCellRect(int row, int column, boolean includeSpacing) { | ||
+ | Rectangle sRect = super.getCellRect(row, column, includeSpacing); | ||
+ | if ((row < 0) || (column < 0) || | ||
+ | (getRowCount() <= row) || (getColumnCount() <= column)) { | ||
+ | return sRect; | ||
+ | } | ||
+ | int index = 0; | ||
+ | int columnMargin = getColumnModel().getColumnMargin(); | ||
+ | Rectangle cellFrame = new Rectangle(); | ||
+ | if (column > 0 && shouldCombineCells(row)) { | ||
+ | return cellFrame; | ||
+ | } | ||
+ | int aCellHeight = rowHeight; | ||
+ | cellFrame.y = row * aCellHeight; | ||
+ | cellFrame.height = aCellHeight; | ||
+ | |||
+ | Enumeration enumeration = getColumnModel().getColumns(); | ||
+ | while (enumeration.hasMoreElements()) { | ||
+ | TableColumn aColumn = (TableColumn) enumeration.nextElement(); | ||
+ | cellFrame.width = aColumn.getWidth() + columnMargin; | ||
+ | if (index == column) { | ||
+ | break; | ||
+ | } | ||
+ | cellFrame.x += cellFrame.width; | ||
+ | index++; | ||
+ | } | ||
+ | while (enumeration.hasMoreElements() && shouldCombineCells(row)) { | ||
+ | TableColumn aColumn = (TableColumn) enumeration.nextElement(); | ||
+ | cellFrame.width += aColumn.getWidth() + columnMargin; | ||
+ | } | ||
+ | if (!includeSpacing) { | ||
+ | Dimension spacing = getIntercellSpacing(); | ||
+ | cellFrame.setBounds(cellFrame.x + spacing.width / 2, | ||
+ | cellFrame.y + spacing.height / 2, | ||
+ | cellFrame.width - spacing.width, | ||
+ | cellFrame.height - spacing.height); | ||
+ | } | ||
+ | return cellFrame; | ||
+ | } | ||
+ | |||
+ | private boolean shouldCombineCells(int row) { | ||
+ | TableAdapter model = (TableAdapter) getModel(); | ||
+ | JTree tree = model.getTree(); | ||
+ | DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getPathForRow(row).getLastPathComponent(); | ||
+ | return node.getLevel() < 2; | ||
+ | } | ||
+ | }</syntaxhighlight> | ||
+ | -------------------------------------------------- | ||
+ | <syntaxhighlight lang="java">package treetable; | ||
+ | /* | ||
+ | JTree already has a good data model (DefaultTreeModel), and it's easier | ||
+ | to implement an adapter that simply puts a TableModel on top of the TreeModel, | ||
+ | rather than to implement JTree renderers in a JTable and work around the API | ||
+ | to achieve correct displays. | ||
+ | |||
+ | The class "TableAdapter" puts an AbstractTableModel on top of DefaultTreeModel | ||
+ | which is suggested by Swing for a JTree. The TableModel consists of the | ||
+ | dataRow vector from "TableNode". The TableAdapter is also responsible for | ||
+ | firing updates on the table data in case the JTree is collapsed or expanded. | ||
+ | */ | ||
import javax.swing.*; | import javax.swing.*; | ||
import javax.swing.event.*; | import javax.swing.event.*; | ||
Zeile 92: | Zeile 173: | ||
int row = table.getSelectedRow(); | int row = table.getSelectedRow(); | ||
tree.setSelectionRow(row); | tree.setSelectionRow(row); | ||
+ | table.repaint(); | ||
} | } | ||
}; | }; | ||
table.getSelectionModel().addListSelectionListener(selectionListener); | table.getSelectionModel().addListSelectionListener(selectionListener); | ||
+ | } | ||
+ | |||
+ | public JTree getTree() { | ||
+ | return tree; | ||
} | } | ||
Zeile 151: | Zeile 237: | ||
@Override | @Override | ||
public boolean isCellEditable(int rowIndex, int columnIndex) { | public boolean isCellEditable(int rowIndex, int columnIndex) { | ||
− | return | + | return false; |
} | } | ||
− | }</ | + | }</syntaxhighlight> |
− | + | -------------------------------------------------- | |
− | < | + | <syntaxhighlight lang="java">package treetable; |
import java.awt.*; | import java.awt.*; | ||
Zeile 199: | Zeile 285: | ||
return this; | return this; | ||
} | } | ||
− | } | + | } |
− | |||
− | |||
+ | </syntaxhighlight> | ||
+ | -------------------------------------------------- | ||
+ | <syntaxhighlight lang="java">package treetable; | ||
+ | /* | ||
+ | TreeModels contain DefaultMutableTreeNodes. Each DefaultMutableTreeNode | ||
+ | contains a UserObject for which we can easily create a class "TableNode" | ||
+ | having a String to be displayed in the JTree, and a vector of objects | ||
+ | to be displayed in the JTable. | ||
+ | */ | ||
import java.util.*; | import java.util.*; | ||
Zeile 225: | Zeile 318: | ||
return dataRow; | return dataRow; | ||
} | } | ||
− | }</ | + | }</syntaxhighlight> |
− | + | -------------------------------------------------- | |
− | < | + | <syntaxhighlight lang="java">package treetable; |
import java.awt.*; | import java.awt.*; | ||
Zeile 238: | Zeile 331: | ||
public class TreeTable extends JFrame { | public class TreeTable extends JFrame { | ||
− | |||
− | |||
− | |||
− | |||
− | |||
private DefaultMutableTreeNode root; | private DefaultMutableTreeNode root; | ||
− | + | TreeTablePanel[] treeTablePanels; | |
− | |||
− | |||
public TreeTable() { | public TreeTable() { | ||
super("Tree Table Demo"); | super("Tree Table Demo"); | ||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | ||
− | + | DefaultTreeModel model = new DefaultTreeModel(createTree()); | |
− | + | setLayout(new GridLayout(0, 1)); | |
− | + | treeTablePanels = new TreeTablePanel[1]; | |
− | + | for (int i = 0; i < treeTablePanels.length; i++) { | |
− | + | treeTablePanels[i] = new TreeTablePanel(model, treeTablePanels); | |
− | + | add(treeTablePanels[i]); | |
− | + | } | |
− | + | setSize(1000, 300); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | setSize( | ||
setLocationRelativeTo(null); | setLocationRelativeTo(null); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
private DefaultMutableTreeNode createTree() { | private DefaultMutableTreeNode createTree() { | ||
// create root node and add more nodes | // create root node and add more nodes | ||
− | root = new DefaultMutableTreeNode(new TableNode(" | + | root = new DefaultMutableTreeNode(new TableNode("Erde", "Die Erde ist mit einer durchschnittlichen Sonnenentfernung von 149,6 Millionen km von der Sonne aus der dritte Planet im Sonnensystem.", Color.YELLOW)); |
− | DefaultMutableTreeNode n = new DefaultMutableTreeNode(new TableNode(" | + | DefaultMutableTreeNode n = new DefaultMutableTreeNode(new TableNode("Europa", "Europa (griechisch Εὐρώπη, Eurṓpē) ist das westliche Fünftel der eurasischen Landmasse. ", Color.GREEN)); |
root.add(n); | root.add(n); | ||
− | DefaultMutableTreeNode n1 = new DefaultMutableTreeNode(new TableNode(" | + | DefaultMutableTreeNode n1 = new DefaultMutableTreeNode(new TableNode("Finnland", "5,3 Millionen Einwohner", Color.ORANGE)); |
n.add(n1); | n.add(n1); | ||
− | n1 = new DefaultMutableTreeNode(new TableNode(" | + | n1 = new DefaultMutableTreeNode(new TableNode("Rumänien", "21,5 Millionen Einwohner", Color.RED)); |
n.add(n1); | n.add(n1); | ||
− | n1 = new DefaultMutableTreeNode(new TableNode(" | + | n1 = new DefaultMutableTreeNode(new TableNode("Deutschland", "82 Millionen Einwohner", Color.BLUE)); |
n.add(n1); | n.add(n1); | ||
− | n = new DefaultMutableTreeNode(new TableNode(" | + | n = new DefaultMutableTreeNode(new TableNode("Amerika", "Der amerikanische Doppelkontinent erstreckt sich in seiner Nord-Südachse vom 83. Breitengrad Nord (Kap Columbia) bis zum 56. Breitengrad Süd (Kap Hoorn).", Color.WHITE)); |
root.add(n); | root.add(n); | ||
− | n = new DefaultMutableTreeNode(new TableNode(" | + | n = new DefaultMutableTreeNode(new TableNode("Asien", "Asien ist mit rund 44,5 Mio. km² der größte Kontinent und Teil von Eurasien.", Color.MAGENTA)); |
root.add(n); | root.add(n); | ||
Zeile 326: | Zeile 373: | ||
} | } | ||
− | + | public static void main(final String[] args) { | |
− | + | Runnable gui = new Runnable() { | |
− | |||
− | |||
− | |||
− | |||
@Override | @Override | ||
− | public void | + | public void run() { |
− | + | try { | |
− | + | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); | |
+ | } catch (Exception ex) { | ||
+ | ex.printStackTrace(); | ||
+ | } | ||
+ | new TreeTable().setVisible(true); | ||
} | } | ||
− | }); | + | }; |
+ | //GUI must start on EventDispatchThread: | ||
+ | SwingUtilities.invokeLater(gui); | ||
+ | } | ||
+ | |||
+ | class TreeTablePanel extends JPanel implements TableModelListener { | ||
+ | |||
+ | private JTree tree; | ||
+ | private Rectangle selectedRowBounds; | ||
+ | private Color selectionColor; | ||
+ | private TableAdapter adapter; | ||
+ | private Table table; | ||
+ | private JSplitPane splitPane; | ||
+ | private JTableHeader tableHeader; | ||
+ | private JButton btAdd; | ||
+ | private TreeTablePanel[] treeTablePanels; | ||
+ | |||
+ | public TreeTablePanel(DefaultTreeModel model, TreeTablePanel[] treeTablePanels) { | ||
+ | super(new BorderLayout()); | ||
+ | this.treeTablePanels = treeTablePanels; | ||
+ | tree = new JTree(model) { | ||
+ | //Override paintComponent of JTree: | ||
+ | |||
+ | @Override | ||
+ | public void paintComponent(final Graphics g) { | ||
+ | if (selectedRowBounds != null) { | ||
+ | //Set selection Color: | ||
+ | g.setColor(selectionColor); | ||
+ | //Draw selection rectangle using the width of JTree: | ||
+ | g.fillRect(0, (int) selectedRowBounds.getY(), getWidth(), (int) selectedRowBounds.getHeight()); | ||
+ | } | ||
+ | super.paintComponent(g); | ||
+ | } | ||
+ | }; | ||
+ | adapter = new TableAdapter(tree); | ||
+ | table = new Table(adapter); | ||
+ | customizeTable(table); | ||
+ | customizeTree(tree, table); | ||
+ | customizeSplitpane(tree, table); | ||
+ | adapter.addTableModelListener(this); | ||
+ | // | ||
+ | JScrollPane scrollPane = new JScrollPane(splitPane); | ||
+ | add(scrollPane, BorderLayout.CENTER); | ||
+ | JPanel header = new JPanel(null); | ||
+ | tableHeader = table.getTableHeader(); | ||
+ | header.add(tableHeader); | ||
+ | header.setPreferredSize(new Dimension(0, 16)); | ||
+ | add(header, BorderLayout.NORTH); | ||
+ | |||
+ | btAdd = new JButton(); | ||
+ | add(btAdd, BorderLayout.SOUTH); | ||
+ | btAdd.setAction(new AbstractAction("Add") { | ||
+ | |||
+ | public void actionPerformed(ActionEvent e) { | ||
+ | DefaultMutableTreeNode n = new DefaultMutableTreeNode(new TableNode("NEW", "", Color.GREEN)); | ||
+ | DefaultMutableTreeNode parent = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); | ||
+ | if (parent != null) { | ||
+ | parent.add(n); | ||
+ | ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(parent); | ||
+ | table.repaint(); | ||
+ | validate(); | ||
+ | } | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | private void customizeTable(final JTable table) { | ||
+ | table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
+ | //connect Selection listeners | ||
+ | adapter.addTable(table); | ||
+ | table.setRowHeight(18); | ||
+ | table.addComponentListener(new ComponentAdapter() { | ||
− | + | @Override | |
+ | public void componentResized(ComponentEvent e) { | ||
+ | Point loc = table.getLocationOnScreen(); | ||
+ | tableHeader.setBounds(new Rectangle(loc.x - TreeTable.this.getLocation().x - 3, 0, 1100, 18)); | ||
+ | tableHeader.setReorderingAllowed(false); | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | } | ||
− | + | private void customizeTree(final JTree tree, final JTable table) { | |
− | + | tree.setRowHeight(table.getRowHeight()); | |
− | + | tree.setFocusable(false); | |
− | + | selectionColor = table.getSelectionBackground(); | |
− | + | tree.putClientProperty("JTree.lineStyle", "Horizontal"); | |
− | + | tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); | |
− | + | tree.setOpaque(false); | |
− | + | //Adapt the default selection colors: | |
− | + | DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tree.getCellRenderer(); | |
− | + | renderer.setBorderSelectionColor(Color.WHITE); | |
− | + | renderer.setBackgroundSelectionColor(new Color(0, 0, 0, 0)); | |
− | + | //Add the TreeSelectionListener: | |
− | + | TreeSelectionModel selectionModel = tree.getSelectionModel(); | |
− | + | selectionModel.addTreeSelectionListener(new TreeSelectionListener() { | |
− | + | public void valueChanged(final TreeSelectionEvent e) { | |
− | + | paintSelectionRect(); | |
− | + | } | |
− | + | }); | |
− | + | //Add the TreeExpansionListener: | |
− | + | tree.addTreeExpansionListener(new TreeExpansionListener() { | |
− | + | public void treeCollapsed(final TreeExpansionEvent event) { | |
− | + | paintSelectionRect(); | |
− | + | } | |
− | + | public void treeExpanded(final TreeExpansionEvent event) { | |
− | + | paintSelectionRect(); | |
− | + | } | |
− | + | }); | |
− | + | //Add MouseListener if you want to listen to whole line width: | |
− | + | tree.addMouseListener(new MouseAdapter() { | |
− | + | @Override | |
− | + | public void mousePressed(final MouseEvent e) { | |
− | + | int row = tree.getClosestRowForLocation(e.getX(), e.getY()); | |
− | + | if (e.getClickCount() == 2) { | |
− | + | if (tree.isCollapsed(row)) { | |
− | tree. | + | tree.expandRow(row); |
+ | } else { | ||
+ | tree.collapseRow(row); | ||
+ | } | ||
} else { | } else { | ||
− | tree. | + | tree.setSelectionRow(row); |
} | } | ||
− | |||
− | |||
} | } | ||
+ | }); | ||
+ | |||
+ | } | ||
+ | |||
+ | private void customizeSplitpane(final JTree tree, final JTable table) { | ||
+ | splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tree, table); | ||
+ | splitPane.setDividerLocation(150); | ||
+ | splitPane.setBackground(Color.WHITE); | ||
+ | splitPane.setContinuousLayout(true); | ||
+ | } | ||
+ | //This method is called in valueChanged, treeCollapsed and treeExpanded | ||
+ | //to paint the selection rectangle: | ||
+ | |||
+ | private void paintSelectionRect() { | ||
+ | //Get selected row bounds: | ||
+ | int[] rows = tree.getSelectionRows(); | ||
+ | if (rows == null) { | ||
+ | selectedRowBounds = null; | ||
+ | return; | ||
} | } | ||
− | }); | + | selectedRowBounds = tree.getRowBounds(rows[0]); |
+ | //Repaint the JTree: | ||
+ | tree.repaint(); | ||
+ | } | ||
+ | |||
+ | public void tableChanged(TableModelEvent e) { | ||
+ | if (treeTablePanels != null) { | ||
+ | for (TreeTablePanel treeTablePanel : treeTablePanels) { | ||
+ | treeTablePanel.getTable().repaint(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | public Table getTable() { | ||
+ | return table; | ||
+ | } | ||
} | } | ||
+ | }</syntaxhighlight> | ||
+ | -------------------------------------------------- | ||
+ | <syntaxhighlight lang="java">package treetable; | ||
+ | /* | ||
+ | * UIMultiSpan.java | ||
+ | * | ||
+ | */ | ||
+ | |||
+ | import java.awt.*; | ||
+ | import javax.swing.*; | ||
+ | import javax.swing.table.*; | ||
+ | import javax.swing.plaf.basic.*; | ||
+ | |||
+ | public class UIMultiSpan extends BasicTableUI { | ||
+ | |||
+ | @Override | ||
+ | public void paint(Graphics g, JComponent c) { | ||
+ | Rectangle oldClipBounds = g.getClipBounds(); | ||
+ | Rectangle clipBounds = new Rectangle(oldClipBounds); | ||
+ | int tableWidth = table.getColumnModel().getTotalColumnWidth(); | ||
+ | clipBounds.width = Math.min(clipBounds.width, tableWidth); | ||
+ | g.setClip(clipBounds); | ||
+ | |||
+ | int firstIndex = table.rowAtPoint(new Point(0, clipBounds.y)); | ||
+ | int lastIndex = table.getRowCount() - 1; | ||
− | + | Rectangle rowRect = new Rectangle(0, 0, | |
− | + | tableWidth, table.getRowHeight() + table.getRowMargin()); | |
− | + | rowRect.y = firstIndex * rowRect.height; | |
− | |||
− | |||
− | |||
− | + | for (int index = firstIndex; index <= lastIndex; index++) { | |
− | + | if (rowRect.intersects(clipBounds)) { | |
− | + | //System.out.println(); // debug | |
− | + | //System.out.print("" + index +": "); // row | |
− | + | paintRow(g, index); | |
− | + | } | |
− | + | rowRect.y += rowRect.height; | |
− | |||
} | } | ||
− | + | g.setClip(oldClipBounds); | |
− | |||
− | |||
} | } | ||
− | + | private void paintRow(Graphics g, int row) { | |
− | + | Rectangle rect = g.getClipBounds(); | |
+ | boolean drawn = false; | ||
+ | |||
+ | int numColumns = table.getColumnCount(); | ||
− | + | for (int column = 0; column < numColumns; column++) { | |
− | + | Rectangle cellRect = table.getCellRect(row, column, true); | |
− | + | int cellRow, cellColumn; | |
− | + | cellRow = row; | |
− | } | + | cellColumn = column; |
− | + | if (cellRect.intersects(rect)) { | |
+ | drawn = true; | ||
+ | paintCell(g, cellRect, cellRow, cellColumn); | ||
+ | } else { | ||
+ | if (drawn) { | ||
+ | break; | ||
} | } | ||
− | |||
} | } | ||
− | }; | + | } |
− | + | ||
− | + | } | |
+ | |||
+ | private void paintCell(Graphics g, Rectangle cellRect, int row, int column) { | ||
+ | TableCellRenderer renderer = table.getCellRenderer(row, column); | ||
+ | if (renderer == null) { | ||
+ | return; | ||
+ | } | ||
+ | Component component = table.prepareRenderer(renderer, row, column); | ||
+ | if (component.getParent() == null) { | ||
+ | rendererPane.add(component); | ||
+ | } | ||
+ | rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y, | ||
+ | cellRect.width - 1, cellRect.height - 1, true); | ||
+ | Color c = g.getColor(); | ||
+ | g.setColor(table.getGridColor()); | ||
+ | g.drawRect(cellRect.x - 1, cellRect.y - 1, cellRect.width, cellRect.height); | ||
+ | g.setColor(c); | ||
+ | |||
} | } | ||
− | }</ | + | }</syntaxhighlight> |
− | + | ||
[[Kategorie:Tutorials (Java)]] | [[Kategorie:Tutorials (Java)]] | ||
[[Kategorie:Swing]] | [[Kategorie:Swing]] | ||
[[Kategorie:Java-Codeschnipsel]] | [[Kategorie:Java-Codeschnipsel]] | ||
+ | [[Kategorie:JTable]] |
Aktuelle Version vom 2. April 2018, 16:27 Uhr
Hier ist eine einfache Implementation einer TreeTable (gem. Alexandru Toth),
die ein gemeinsames Model für JTree
und JTable
benutzt.
Es ist dann kein Problem mehr, dynamisch neue Zeilen hinzuzufügen.
Die TreeTable ist so gedacht, daß in der ersten Spalte (JTree) die Kurzbezeichnung steht und in der zweiten Spalte die vollständige Bezeichnung. Die erste Spalte gehört zwar nicht zur JTable, aber man kann sie trotzdem in der Breite verändern, indem man den Divider vom JSplitPane
verschiebt.
"Table" ordnet die Renderer den entsprechenden Datentypen zu. Man kann weitere Renderer vorsehen,
wenn man will. Dort ist auch die Methode shouldCombineCells()
, die wir gegebenenfalls anpassen
müssen. Sie wird in getCellRect()
auffgerufen, wo das eigentliche Verbinden der Zellen stattfindet (für die vollständige Bezeichnung bei geschlossenem TreeNode).
"TableAdapter" enthält die Spaltenüberschriften der JTable.
"TableNode" enthält die Daten für die JTable (dataRow), sowie die Kurzbezeichnung, die im JTree erscheint (label).
"TreeTablePanel" verbindet alles zu einer Einheit (naja, der JButton
"btAdd" gehört da vielleicht nicht
rein). Dort muss man eigentlich nichts mehr anpassen (außer vielleicht Sachen, wie die RowHeight in der Methode customizeTable()
).
Die "TreeTable" ist übrigens kein fertiges Endprodukt mit allen möglichen Ansprüchen, sondern nur ein Codeschnipsel als Ansatz für Entwickler. Fühl dich frei, so viele Spalten und Zugriffsmethoden hinzuzufügen, wie du brauchst. Das TreeTablePanel-Array in der TreeTablePanel-Klasse ist nicht unbedingt nötig. Ich habe es hinzugefügt, für den Fall wo mehrere identische TreeTables angezeigt werden sollen.
package treetable;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.Enumeration;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.tree.DefaultMutableTreeNode;
/** JTable that has different renderers depending on the classes in TreeModel */
public class Table extends JTable {
void registerRenderers() {
setDefaultRenderer(Color.class, new TableColorRenderer(Color.WHITE));
setUI(new UIMultiSpan());
}
public Table() {
super();
registerRenderers();
}
public Table(TableModel dm) {
super(dm);
registerRenderers();
}
public Table(TableModel dm, TableColumnModel cm) {
super(dm, cm);
registerRenderers();
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
Object value = getModel().getValueAt(row, column);
/** enable to have custom renderers depending on class of cell */
TableCellRenderer tcr = getDefaultRenderer(value.getClass());
return tcr;
}
@Override
public Rectangle getCellRect(int row, int column, boolean includeSpacing) {
Rectangle sRect = super.getCellRect(row, column, includeSpacing);
if ((row < 0) || (column < 0) ||
(getRowCount() <= row) || (getColumnCount() <= column)) {
return sRect;
}
int index = 0;
int columnMargin = getColumnModel().getColumnMargin();
Rectangle cellFrame = new Rectangle();
if (column > 0 && shouldCombineCells(row)) {
return cellFrame;
}
int aCellHeight = rowHeight;
cellFrame.y = row * aCellHeight;
cellFrame.height = aCellHeight;
Enumeration enumeration = getColumnModel().getColumns();
while (enumeration.hasMoreElements()) {
TableColumn aColumn = (TableColumn) enumeration.nextElement();
cellFrame.width = aColumn.getWidth() + columnMargin;
if (index == column) {
break;
}
cellFrame.x += cellFrame.width;
index++;
}
while (enumeration.hasMoreElements() && shouldCombineCells(row)) {
TableColumn aColumn = (TableColumn) enumeration.nextElement();
cellFrame.width += aColumn.getWidth() + columnMargin;
}
if (!includeSpacing) {
Dimension spacing = getIntercellSpacing();
cellFrame.setBounds(cellFrame.x + spacing.width / 2,
cellFrame.y + spacing.height / 2,
cellFrame.width - spacing.width,
cellFrame.height - spacing.height);
}
return cellFrame;
}
private boolean shouldCombineCells(int row) {
TableAdapter model = (TableAdapter) getModel();
JTree tree = model.getTree();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getPathForRow(row).getLastPathComponent();
return node.getLevel() < 2;
}
}
package treetable;
/*
JTree already has a good data model (DefaultTreeModel), and it's easier
to implement an adapter that simply puts a TableModel on top of the TreeModel,
rather than to implement JTree renderers in a JTable and work around the API
to achieve correct displays.
The class "TableAdapter" puts an AbstractTableModel on top of DefaultTreeModel
which is suggested by Swing for a JTree. The TableModel consists of the
dataRow vector from "TableNode". The TableAdapter is also responsible for
firing updates on the table data in case the JTree is collapsed or expanded.
*/
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import javax.swing.table.*;
import java.util.Vector;
public class TableAdapter extends AbstractTableModel {
private JTree tree;
private JTable table;
private int[] sel_rows;
private ListSelectionListener selectionListener;
public TableAdapter(JTree t) {
tree = t;
tree.addTreeExpansionListener(new TreeExpansionListener() {
public void treeExpanded(TreeExpansionEvent event) {
fireTableDataChanged();
select();
}
public void treeCollapsed(TreeExpansionEvent event) {
fireTableDataChanged();
select();
}
});
}
public void addTable(JTable t) {
table = t;
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
sel_rows = tree.getSelectionRows();
select();
}
});
selectionListener = new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
int row = table.getSelectedRow();
tree.setSelectionRow(row);
table.repaint();
}
};
table.getSelectionModel().addListSelectionListener(selectionListener);
}
public JTree getTree() {
return tree;
}
private void select() {
if (sel_rows != null) {
table.getSelectionModel().setSelectionInterval(sel_rows[0], sel_rows[0]);
}
}
//assumes all nodes have same number of items as root node
public int getColumnCount() {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
TableNode n = (TableNode) root.getUserObject();
return n.getDataRow().size();
}
public int getRowCount() {
return tree.getRowCount();
}
@Override
public String getColumnName(int col) {
return new String("col" + col);
}
public Object getValueAt(int row, int col) {
TreePath tPath = tree.getPathForRow(row);
Object[] oPath = tPath.getPath();
int len = oPath.length;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) oPath[len - 1];
TableNode treeNode = (TableNode) node.getUserObject();
Vector dataRow = treeNode.getDataRow();
return dataRow.elementAt(col);
}
@Override
public void setValueAt(Object value, int row, int col) {
TreePath tPath = tree.getPathForRow(row);
Object[] oPath = tPath.getPath();
int len = oPath.length;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) oPath[len - 1];
TableNode treeNode = (TableNode) node.getUserObject();
Vector dataRow = treeNode.getDataRow();
dataRow.setElementAt(value, col);
}
@Override
public Class getColumnClass(int c) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
TableNode n = (TableNode) root.getUserObject();
return n.getDataRow().elementAt(c).getClass();
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
}
package treetable;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.border.*;
public class TableColorRenderer extends JLabel implements TableCellRenderer {
Border unselectedBorder = null;
Border selectedBorder = null;
boolean isBordered = true;
public TableColorRenderer(Color color) {
super();
setOpaque(true);
setBackground(color);
}
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
if (isSelected) {
if (selectedBorder == null) {
selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
table.getSelectionBackground());
}
setBorder(selectedBorder);
} else {
if (unselectedBorder == null) {
unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
table.getBackground());
}
setBorder(unselectedBorder);
}
if (value.getClass() == Color.class) {
setBackground((Color) value);
} else {
setBackground(Color.WHITE);
}
return this;
}
}
package treetable;
/*
TreeModels contain DefaultMutableTreeNodes. Each DefaultMutableTreeNode
contains a UserObject for which we can easily create a class "TableNode"
having a String to be displayed in the JTree, and a vector of objects
to be displayed in the JTable.
*/
import java.util.*;
public class TableNode {
private String label;
private Vector dataRow;
public TableNode(String s, Object x, Object y) {
label = s;
dataRow = new Vector();
dataRow.add(x);
dataRow.add(y);
}
@Override
public String toString() {
return label;
}
public Vector getDataRow() {
return dataRow;
}
}
package treetable;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import javax.swing.table.*;
public class TreeTable extends JFrame {
private DefaultMutableTreeNode root;
TreeTablePanel[] treeTablePanels;
public TreeTable() {
super("Tree Table Demo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DefaultTreeModel model = new DefaultTreeModel(createTree());
setLayout(new GridLayout(0, 1));
treeTablePanels = new TreeTablePanel[1];
for (int i = 0; i < treeTablePanels.length; i++) {
treeTablePanels[i] = new TreeTablePanel(model, treeTablePanels);
add(treeTablePanels[i]);
}
setSize(1000, 300);
setLocationRelativeTo(null);
}
private DefaultMutableTreeNode createTree() {
// create root node and add more nodes
root = new DefaultMutableTreeNode(new TableNode("Erde", "Die Erde ist mit einer durchschnittlichen Sonnenentfernung von 149,6 Millionen km von der Sonne aus der dritte Planet im Sonnensystem.", Color.YELLOW));
DefaultMutableTreeNode n = new DefaultMutableTreeNode(new TableNode("Europa", "Europa (griechisch Εὐρώπη, Eurṓpē) ist das westliche Fünftel der eurasischen Landmasse. ", Color.GREEN));
root.add(n);
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode(new TableNode("Finnland", "5,3 Millionen Einwohner", Color.ORANGE));
n.add(n1);
n1 = new DefaultMutableTreeNode(new TableNode("Rumänien", "21,5 Millionen Einwohner", Color.RED));
n.add(n1);
n1 = new DefaultMutableTreeNode(new TableNode("Deutschland", "82 Millionen Einwohner", Color.BLUE));
n.add(n1);
n = new DefaultMutableTreeNode(new TableNode("Amerika", "Der amerikanische Doppelkontinent erstreckt sich in seiner Nord-Südachse vom 83. Breitengrad Nord (Kap Columbia) bis zum 56. Breitengrad Süd (Kap Hoorn).", Color.WHITE));
root.add(n);
n = new DefaultMutableTreeNode(new TableNode("Asien", "Asien ist mit rund 44,5 Mio. km² der größte Kontinent und Teil von Eurasien.", Color.MAGENTA));
root.add(n);
return root;
}
public static void main(final String[] args) {
Runnable gui = new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
ex.printStackTrace();
}
new TreeTable().setVisible(true);
}
};
//GUI must start on EventDispatchThread:
SwingUtilities.invokeLater(gui);
}
class TreeTablePanel extends JPanel implements TableModelListener {
private JTree tree;
private Rectangle selectedRowBounds;
private Color selectionColor;
private TableAdapter adapter;
private Table table;
private JSplitPane splitPane;
private JTableHeader tableHeader;
private JButton btAdd;
private TreeTablePanel[] treeTablePanels;
public TreeTablePanel(DefaultTreeModel model, TreeTablePanel[] treeTablePanels) {
super(new BorderLayout());
this.treeTablePanels = treeTablePanels;
tree = new JTree(model) {
//Override paintComponent of JTree:
@Override
public void paintComponent(final Graphics g) {
if (selectedRowBounds != null) {
//Set selection Color:
g.setColor(selectionColor);
//Draw selection rectangle using the width of JTree:
g.fillRect(0, (int) selectedRowBounds.getY(), getWidth(), (int) selectedRowBounds.getHeight());
}
super.paintComponent(g);
}
};
adapter = new TableAdapter(tree);
table = new Table(adapter);
customizeTable(table);
customizeTree(tree, table);
customizeSplitpane(tree, table);
adapter.addTableModelListener(this);
//
JScrollPane scrollPane = new JScrollPane(splitPane);
add(scrollPane, BorderLayout.CENTER);
JPanel header = new JPanel(null);
tableHeader = table.getTableHeader();
header.add(tableHeader);
header.setPreferredSize(new Dimension(0, 16));
add(header, BorderLayout.NORTH);
btAdd = new JButton();
add(btAdd, BorderLayout.SOUTH);
btAdd.setAction(new AbstractAction("Add") {
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeNode n = new DefaultMutableTreeNode(new TableNode("NEW", "", Color.GREEN));
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (parent != null) {
parent.add(n);
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(parent);
table.repaint();
validate();
}
}
});
}
private void customizeTable(final JTable table) {
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
//connect Selection listeners
adapter.addTable(table);
table.setRowHeight(18);
table.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
Point loc = table.getLocationOnScreen();
tableHeader.setBounds(new Rectangle(loc.x - TreeTable.this.getLocation().x - 3, 0, 1100, 18));
tableHeader.setReorderingAllowed(false);
}
});
}
private void customizeTree(final JTree tree, final JTable table) {
tree.setRowHeight(table.getRowHeight());
tree.setFocusable(false);
selectionColor = table.getSelectionBackground();
tree.putClientProperty("JTree.lineStyle", "Horizontal");
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setOpaque(false);
//Adapt the default selection colors:
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tree.getCellRenderer();
renderer.setBorderSelectionColor(Color.WHITE);
renderer.setBackgroundSelectionColor(new Color(0, 0, 0, 0));
//Add the TreeSelectionListener:
TreeSelectionModel selectionModel = tree.getSelectionModel();
selectionModel.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(final TreeSelectionEvent e) {
paintSelectionRect();
}
});
//Add the TreeExpansionListener:
tree.addTreeExpansionListener(new TreeExpansionListener() {
public void treeCollapsed(final TreeExpansionEvent event) {
paintSelectionRect();
}
public void treeExpanded(final TreeExpansionEvent event) {
paintSelectionRect();
}
});
//Add MouseListener if you want to listen to whole line width:
tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
int row = tree.getClosestRowForLocation(e.getX(), e.getY());
if (e.getClickCount() == 2) {
if (tree.isCollapsed(row)) {
tree.expandRow(row);
} else {
tree.collapseRow(row);
}
} else {
tree.setSelectionRow(row);
}
}
});
}
private void customizeSplitpane(final JTree tree, final JTable table) {
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tree, table);
splitPane.setDividerLocation(150);
splitPane.setBackground(Color.WHITE);
splitPane.setContinuousLayout(true);
}
//This method is called in valueChanged, treeCollapsed and treeExpanded
//to paint the selection rectangle:
private void paintSelectionRect() {
//Get selected row bounds:
int[] rows = tree.getSelectionRows();
if (rows == null) {
selectedRowBounds = null;
return;
}
selectedRowBounds = tree.getRowBounds(rows[0]);
//Repaint the JTree:
tree.repaint();
}
public void tableChanged(TableModelEvent e) {
if (treeTablePanels != null) {
for (TreeTablePanel treeTablePanel : treeTablePanels) {
treeTablePanel.getTable().repaint();
}
}
}
public Table getTable() {
return table;
}
}
}
package treetable;
/*
* UIMultiSpan.java
*
*/
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.plaf.basic.*;
public class UIMultiSpan extends BasicTableUI {
@Override
public void paint(Graphics g, JComponent c) {
Rectangle oldClipBounds = g.getClipBounds();
Rectangle clipBounds = new Rectangle(oldClipBounds);
int tableWidth = table.getColumnModel().getTotalColumnWidth();
clipBounds.width = Math.min(clipBounds.width, tableWidth);
g.setClip(clipBounds);
int firstIndex = table.rowAtPoint(new Point(0, clipBounds.y));
int lastIndex = table.getRowCount() - 1;
Rectangle rowRect = new Rectangle(0, 0,
tableWidth, table.getRowHeight() + table.getRowMargin());
rowRect.y = firstIndex * rowRect.height;
for (int index = firstIndex; index <= lastIndex; index++) {
if (rowRect.intersects(clipBounds)) {
//System.out.println(); // debug
//System.out.print("" + index +": "); // row
paintRow(g, index);
}
rowRect.y += rowRect.height;
}
g.setClip(oldClipBounds);
}
private void paintRow(Graphics g, int row) {
Rectangle rect = g.getClipBounds();
boolean drawn = false;
int numColumns = table.getColumnCount();
for (int column = 0; column < numColumns; column++) {
Rectangle cellRect = table.getCellRect(row, column, true);
int cellRow, cellColumn;
cellRow = row;
cellColumn = column;
if (cellRect.intersects(rect)) {
drawn = true;
paintCell(g, cellRect, cellRow, cellColumn);
} else {
if (drawn) {
break;
}
}
}
}
private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
TableCellRenderer renderer = table.getCellRenderer(row, column);
if (renderer == null) {
return;
}
Component component = table.prepareRenderer(renderer, row, column);
if (component.getParent() == null) {
rendererPane.add(component);
}
rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
cellRect.width - 1, cellRect.height - 1, true);
Color c = g.getColor();
g.setColor(table.getGridColor());
g.drawRect(cellRect.x - 1, cellRect.y - 1, cellRect.width, cellRect.height);
g.setColor(c);
}
}