Right I have a JTable where one of the columns is a JTree, it looks like this
When I click on a JTree cell I want it to expand/collapse the JTree and adjust the height of the table row appropriately. I can't seem to get this to work, there are a couple of issues
a) The path of the JTree seems to be expanded by default b) Clicking on the JTree doesn't expand/collapse the JTree or adjust the height of the table row.
Here's the code
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.util.Arrays;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
public class TestTableCellSelection {
private String[] columnNames = {"JobID", "Status"};
private DefaultTableModel tableModel = new DefaultTableModel(null, columnNames) {
private static final long serialVersionUID = 1L;
@Override
public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
};
private JTable table = new JTable(tableModel);
public static void main(String... args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestTableCellSelection().makeUI());
frame.setSize(635, 566);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private Component makeUI() {
TableColumn statusColumn = table.getColumnModel().getColumn(1);
statusColumn.setCellRenderer(new JTableJTreeRenderer());
//Setup row/column selection listeners
table.setRowSelectionAllowed(true);
TableCellSelectionListener cellSelectionListener = new TableCellSelectionListener(table);
//table.getSelectionModel().addListSelectionListener(cellSelectionListener);
table.getColumnModel().getSelectionModel().addListSelectionListener(cellSelectionListener);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
startTask(new Object[][]{{"000001"}, {"complete"}});
startTask(new Object[][]{{"000002"}, {"processing","rendering pdf"}});
startTask(new Object[][]{{"000003"}, {"processing","rendering pdf","rendering afp"}});
startTask(new Object[][]{{"000004"}, {"processing","rendering pdf","rendering afp","rendering postscript"}});
startTask(new Object[][]{{"000005"}, {"processing","normalsing","enhancing","sorting","rendering"}});
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
private void startTask(Object[][] row) {
final String jobID = (String) row[0][0];
final DefaultMutableTreeNode statusRoot = new DefaultMutableTreeNode((String)row[1][0]);
final DefaultTreeModel statusTreeModel = new DefaultTreeModel(statusRoot);
final JTree statusTree = new JTree(statusTreeModel);
if(row[1].length > 1) {
initStatusTree(statusRoot, Arrays.copyOfRange(row[1], 1, row[1].length, String[].class));
}
tableModel.addRow(new Object[]{jobID, statusTree});
}
private void initStatusTree(DefaultMutableTreeNode root, String[] statusList) {
for(String s: statusList) {
root.add(new DefaultMutableTreeNode(s));
}
}
}
class TableCellSelectionListener implements ListSelectionListener {
private JTable _t;
public TableCellSelectionListener(JTable t) {
_t = t;
}
@Override
public void valueChanged(ListSelectionEvent arg0) {
int rowSelected = _t.getSelectedRow();
int colSelected = _t.getSelectedColumn();
if(rowSelected > -1 && colSelected > -1) {
System.out.println("Row selected: " +rowSelected);
System.out.println("Column selected: " +colSelected);
TableCellRenderer renderer = _t.getCellRenderer(rowSelected, colSelected);
Object obj = _t.getValueAt(rowSelected, colSelected);
Component c = renderer.getTableCellRendererComponent(_t, obj, false, false, rowSelected, colSelected);
if(c instanceof JTree) {
TreePath path = ((JTree)c).getPathForRow(0);
System.out.println("Path expanded: " +((JTree) c).isExpanded(path));
if(((JTree) c).isExpanded(path)) {
((JTree)c).collapsePath(path);
((JTree) c).fireTreeCollapsed(path);
} else {
((JTree)c).expandPath(path);
((JTree) c).fireTreeExpanded(path);
}
System.out.println("Path expanded: " +((JTree) c).isExpanded(path));
_t.setRowHeight(rowSelected, c.getPreferredSize().height+50);
((DefaultTableModel)_t.getModel()).fireTableDataChanged();
}
}
}
}
@SuppressWarnings("serial")
class JTableJTreeRenderer extends DefaultTableCellRenderer {
private JTree tree = new JTree();
private TreeCellRenderer renderer = new DefaultTreeCellRenderer();
public JTableJTreeRenderer() {
tree.setCellRenderer(renderer);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
tree.setModel(((JTree) value).getModel());
table.setRowHeight(row, tree.getPreferredSize().height+50);
return tree;
}
}
I can see from the following println code
if(rowSelected > -1 && colSelected > -1) {
System.out.println("Row selected: " +rowSelected);
System.out.println("Column selected: " +colSelected);
TableCellRenderer renderer = _t.getCellRenderer(rowSelected, colSelected);
Object obj = _t.getValueAt(rowSelected, colSelected);
Component c = renderer.getTableCellRendererComponent(_t, obj, false, false, rowSelected, colSelected);
if(c instanceof JTree) {
TreePath path = ((JTree)c).getPathForRow(0);
System.out.println("Path expanded: " +((JTree) c).isExpanded(path));
if(((JTree) c).isExpanded(path)) {
((JTree)c).collapsePath(path);
((JTree) c).fireTreeCollapsed(path);
} else {
((JTree)c).expandPath(path);
((JTree) c).fireTreeExpanded(path);
}
System.out.println("Path expanded: " +((JTree) c).isExpanded(path));
_t.setRowHeight(rowSelected, c.getPreferredSize().height+50);
((DefaultTableModel)_t.getModel()).fireTableDataChanged();
}
}
That the results from clicking on a particular cell twice are as follows
Row selected: 2
Column selected: 1
Path expanded: true
Path expanded: false
Row selected: 2
Column selected: 1
Path expanded: true
Path expanded: false
As you can see the path reverts to its previous state. Any ideas?
Ok it looks like the issue was in the renderer itself, here's the solution