Java - JTable inside a JTable Cell

How to Create a JTable Inside Another JTable In Java Netbeans





In this Java Tutorial we will see How To Create a JTable With a column containing nested tables representing sons and their ages In Netbeans.

What We Are Gonna Use In This Project:

- Java Programming Language.
- NetBeans Editor.
- FlatLaf (optional - a modern open-source cross-platform Look and Feel for Java Swing).

What This Project Do:

To visualize the nested tables within the main table, custom cell renderers (NestedTableRenderer) and editors (NestedTableEditor) are implemented. 
The NestedTableRenderer renders the cell with a JScrollPane containing a nested JTable when applicable, while the NestedTableEditor provides a JScrollPane with an editable nested JTable when editing is initiated.





Project Source Code:





package new_tutorials;

import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;
import com.formdev.flatlaf.FlatDarculaLaf;
import javax.swing.UIManager;

/**
 *
 * @author 1BestCsharp
 */
public class Nested_JTable_Form extends JFrame {

    public Nested_JTable_Form(){
        setTitle("Nested JTable");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(600,600);
        setLocationRelativeTo(null);
        /*
        try{ UIManager.setLookAndFeel(new FlatDarculaLaf()); }
        catch(Exception ex){ System.err.println(ex.getMessage()); }
        */
        initializeGUI();
    }
    
    private void initializeGUI(){
        
        JTable table = new JTable(new ParentTableModel());
        table.setRowHeight(100);
        table.setShowHorizontalLines(true);
        table.setShowVerticalLines(true);
        table.getColumnModel().getColumn(2).setCellRenderer(new NestedTableRenderer());
        table.getColumnModel().getColumn(2).setCellEditor(new NestedTableEditor());
        
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
    }
    
    public static void main(String[] args) {
        new Nested_JTable_Form().setVisible(true);
    }
    
    
    // Define the main table model
    private class ParentTableModel extends AbstractTableModel{

        private final String[] columnNames = {"Name","Age","Sons"};
        private final Object[][] data = {
                {"John", 30, createSonTableModel("Son1", "Son2")},
                {"Jane", 25, createSonTableModel("Son3", "Son4", "Son5")},
                {"Michael", 35, createSonTableModel("Son6")},
                {"Emily", 28, createSonTableModel("Son7", "Son8")},
                {"Robert", 42, createSonTableModel("Son9", "Son10", "Son11","Son9", "Son10", "Son11","Son9", "Son10", "Son11")},
                {"John", 30, createSonTableModel("Son1", "Son2")},
                {"Jane", 25, createSonTableModel("Son3", "Son4", "Son5")},
                {"Michael", 35, createSonTableModel("Son6")},
                {"Emily", 28, createSonTableModel("Son7", "Son8")},
                {"Robert", 42, createSonTableModel("Son9", "Son10", "Son11","Son9", "Son10", "Son11","Son9", "Son10", "Son11")}
        };
        
        
        @Override
        public int getRowCount() { return data.length; }

        @Override
        public int getColumnCount() { return columnNames.length; }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            
            return data[rowIndex][columnIndex];
        }
        
        @Override
        public String getColumnName(int columnIndex){
            
            return columnNames[columnIndex];
        }
        
        
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex){
            return columnIndex == 2;
        }


        @Override
        public void setValueAt(Object value, int rowIndex, int columnIndex){
            
            data[rowIndex][columnIndex] = value;
            fireTableCellUpdated(rowIndex, columnIndex);
        }
        
       private SonTableModel createSonTableModel(String... sons){
       
           return new SonTableModel(sons);
       }
        
    }
    
    
    // Define the nested table model for sons
    private class SonTableModel extends AbstractTableModel{

        private final String[] columnNames = {"Name","Age"};
        private final Object[][] data;
        
        // The String... sons parameter allows you to pass an array of String arguments
        // or simply a comma-separated list of String values. 
        public SonTableModel(String... sons){
            // Populate the nested table with son data
            data = new Object[sons.length][columnNames.length];
            for(int i = 0; i < sons.length; i++){
                data[i][0] = sons[i];
                data[i][1] = (int)(Math.random() * 10 + 1);// Random age
            }
        }
        
        @Override
        public int getRowCount() { return data.length; }

        @Override
        public int getColumnCount() { return columnNames.length; }

        @Override
        public String getColumnName(int columnIndex){
            
            return columnNames[columnIndex];
        }
        
        
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            
            return data[rowIndex][columnIndex];
        }
        
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex)
        {
            return false;
        }
        
    }
    
    
    // Define the cell renderer for the nested table
    private class NestedTableRenderer extends DefaultTableCellRenderer{
        
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column){
        
            // Create and format the nested table
            if(value instanceof TableModel tableModel)
            {
                JTable nestedJTable = new JTable(tableModel);
                nestedJTable.setRowHeight(20);
                nestedJTable.setShowHorizontalLines(true);
                nestedJTable.setShowVerticalLines(true);
                return new JScrollPane(nestedJTable);
            }
            else
            {
                // Return the default cell renderer component
                return  super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            }
            
        }
        
    }
    
    
    // Define the cell editor for the nested table
    private class NestedTableEditor extends AbstractCellEditor implements TableCellEditor{

        private final JScrollPane scrollPane;
        private final JTable nestedJTable;
        
        public NestedTableEditor()
        {
            // Create the initial nested table and scroll pane
            nestedJTable = new JTable(new SonTableModel());
            nestedJTable.setRowHeight(20);
            nestedJTable.setShowHorizontalLines(true);
            nestedJTable.setShowVerticalLines(true);
            scrollPane = new JScrollPane(nestedJTable);
        }
        
        @Override // Return the model of the nested table
        public Object getCellEditorValue() { return  nestedJTable.getModel(); }

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
         // Update the nested table model with the current value
            if(value instanceof TableModel){
                nestedJTable.setModel((SonTableModel)value);
            }
            return scrollPane;
        }
        
    }
    
    
    
}


The Final Result: