JTree in a JTable cell, few issues

1.2k views Asked by At

Right I have a JTable where one of the columns is a JTree, it looks like this

enter image description here

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?

1

There are 1 answers

0
PDStat On

Ok it looks like the issue was in the renderer itself, here's the solution

      @Override
  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    //Using the Renderers JTree was the mistake    
    //tree.setModel(((JTree) value).getModel());
    tree = (JTree)value;
    table.setRowHeight(row, tree.getPreferredSize().height+50);    
    return tree;
  }