基于Swing实现Access风格二级可折叠JTable的技术求助
Hey there! I totally get why the JTree + JTable approach wasn't working for you—it's clunky, breaks editing, and never quite matches the clean Access-style behavior you're after. Let's build this directly on top of JTable with custom logic that gives you the collapsible rows, proper editing, and the look you want.
Core Approach
We'll create a custom data model to track parent/child rows, a custom renderer for the collapse icons, and handle click events to toggle expansion. The key is to keep a flat "display list" for JTable (since JTable expects linear data) that updates when rows are expanded/collapsed.
Step 1: Define a Collapsible Row Data Structure
First, create a class to hold each row's state and data:
public class CollapsibleRow { private boolean isParent; private boolean isExpanded; private List<Object> rowData; private List<CollapsibleRow> children; // Constructor for parent rows public CollapsibleRow(List<Object> rowData, List<CollapsibleRow> children) { this.isParent = true; this.isExpanded = false; // Start collapsed by default this.rowData = rowData; this.children = children; } // Constructor for child rows public CollapsibleRow(List<Object> rowData) { this.isParent = false; this.isExpanded = false; this.rowData = rowData; this.children = null; } // Getters and setters for all fields public boolean isParent() { return isParent; } public boolean isExpanded() { return isExpanded; } public void setExpanded(boolean expanded) { this.isExpanded = expanded; } public List<Object> getRowData() { return rowData; } public List<CollapsibleRow> getChildren() { return children; } }
Step 2: Build a Custom Table Model
This model will manage both the hierarchical data and the flat display list that JTable uses:
public class CollapsibleTableModel extends AbstractTableModel { private List<String> columnNames; private List<CollapsibleRow> hierarchicalData; private List<CollapsibleRow> displayData; // Flat list for JTable public CollapsibleTableModel(List<String> columnNames, List<CollapsibleRow> hierarchicalData) { this.columnNames = columnNames; this.hierarchicalData = hierarchicalData; this.displayData = new ArrayList<>(); updateDisplayList(); // Initialize with collapsed rows } // Refresh the flat display list based on expanded state public void updateDisplayList() { displayData.clear(); for (CollapsibleRow row : hierarchicalData) { displayData.add(row); if (row.isParent() && row.isExpanded()) { displayData.addAll(row.getChildren()); } } } @Override public int getRowCount() { return displayData.size(); } @Override public int getColumnCount() { return columnNames.size(); } @Override public String getColumnName(int column) { return columnNames.get(column); } @Override public Object getValueAt(int rowIndex, int columnIndex) { return displayData.get(rowIndex).getRowData().get(columnIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { // Prevent editing the collapse icon column (first column) for parent rows CollapsibleRow row = displayData.get(rowIndex); return !(row.isParent() && columnIndex == 0); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { CollapsibleRow row = displayData.get(rowIndex); row.getRowData().set(columnIndex, aValue); fireTableCellUpdated(rowIndex, columnIndex); } // Helper to get the display row for renderer/click handling public CollapsibleRow getDisplayRow(int rowIndex) { return displayData.get(rowIndex); } }
Step 3: Custom Renderer for Collapse Icons & Indentation
This renderer will draw the +/- icons for parent rows and indent child rows:
public class CollapseCellRenderer extends DefaultTableCellRenderer { private final Icon expandIcon = UIManager.getIcon("Tree.expandedIcon"); private final Icon collapseIcon = UIManager.getIcon("Tree.collapsedIcon"); private final Border childIndentBorder = BorderFactory.createEmptyBorder(0, 20, 0, 0); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); CollapsibleTableModel model = (CollapsibleTableModel) table.getModel(); CollapsibleRow rowData = model.getDisplayRow(row); if (column == 0) { if (rowData.isParent()) { setIcon(rowData.isExpanded() ? expandIcon : collapseIcon); setBorder(null); // No indent for parents } else { setIcon(null); setBorder(childIndentBorder); // Indent child rows } } else { setIcon(null); setBorder(null); } return this; } }
Step 4: Add Click Handling for Collapse/Expand
Add a mouse listener to the table to toggle expansion when the collapse icon is clicked:
JTable table = new JTable(model); table.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { Point clickPoint = e.getPoint(); int column = table.columnAtPoint(clickPoint); int row = table.rowAtPoint(clickPoint); if (column == 0 && row != -1) { CollapsibleTableModel model = (CollapsibleTableModel) table.getModel(); CollapsibleRow rowData = model.getDisplayRow(row); if (rowData.isParent()) { rowData.setExpanded(!rowData.isExpanded()); model.updateDisplayList(); model.fireTableDataChanged(); } } } });
Step 5: Match Access-Style Header
Tweak the table header to match Access's bold, shaded style:
JTableHeader header = table.getTableHeader(); header.setBackground(new Color(215, 225, 235)); header.setFont(header.getFont().deriveFont(Font.BOLD)); header.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); table.setRowHeight(25); // Adjust row height to fit icons
Step 6: Wire It All Together
Here's how to initialize the table with sample data:
// Sample column names List<String> columns = Arrays.asList("Item", "Value", "Notes"); // Sample child rows CollapsibleRow child1 = new CollapsibleRow(Arrays.asList("Subitem 1", "100", "First child")); CollapsibleRow child2 = new CollapsibleRow(Arrays.asList("Subitem 2", "200", "Second child")); // Sample parent row CollapsibleRow parent = new CollapsibleRow(Arrays.asList("Parent Item", "300", "Parent notes"), Arrays.asList(child1, child2)); // Build model and table CollapsibleTableModel model = new CollapsibleTableModel(columns, Arrays.asList(parent)); JTable table = new JTable(model); // Apply renderer to first column table.getColumnModel().getColumn(0).setCellRenderer(new CollapseCellRenderer()); // Add to scroll pane JScrollPane scrollPane = new JScrollPane(table); // Add scrollPane to your frame/panel
Key Notes
- Editing Support: The
isCellEditablemethod ensures parent rows' first column (icon) isn't editable, but all other cells (parent and child) are. - Performance: For large datasets, consider optimizing
updateDisplayListto avoid full rebuilds, but for most use cases, this approach is fast enough. - Customization: Swap out the
Tree.expandedIcon/Tree.collapsedIconwith custom icons if you want to match Access's exact look.
内容的提问来源于stack exchange,提问作者Learning from masters

