You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Swing实现Access风格二级可折叠JTable的技术求助

Solution for Access-Style Two-Level Collapsible 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 isCellEditable method ensures parent rows' first column (icon) isn't editable, but all other cells (parent and child) are.
  • Performance: For large datasets, consider optimizing updateDisplayList to avoid full rebuilds, but for most use cases, this approach is fast enough.
  • Customization: Swap out the Tree.expandedIcon/Tree.collapsedIcon with custom icons if you want to match Access's exact look.

内容的提问来源于stack exchange,提问作者Learning from masters

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:52:11