TreeTable
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:
<code=java>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; }
}</code=java>
<code=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.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; }
}</code=java>
<code=java>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; }
}
</code=java>
<code=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.*;
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; }
}</code=java>
<code=java>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("Welt", "", 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(); System.out.println(loc.x + " " + getLocation().x); tableHeader.setBounds(new Rectangle(loc.x - TreeTable.this.getLocation().x - 3, 0, 1100, 18)); } });
}
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) { for (TreeTablePanel treeTablePanel : treeTablePanels) { treeTablePanel.getTable().repaint(); } }
public Table getTable() { return table; } }
}</code=java>
<code=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);
}
}</code=java>