背景

首先本文是一个系列的文章,起名为《Java Swing GUi从小白到大神》,主要介绍Java Swing相关概念和实践应用。目前JavaSwingGUI很少作为界面的开发框架,但是对于学习Java体系,以及新手将来学习后端之后,能快速从前后端整体去理解一套系统是非常有帮助的,所以是很有必要学习和掌握的。本篇是系列博文的第四篇,若想详细学习请点击首篇博文开始,让我们开始吧。

文章概览

  1. 如何使用表格组件
  2. 如何使用树组件
  3. 如何使Swing观感器

12. 如何使用表格组件

  1. 创建表格并添加到容器:了解如何使用 JTable 创建表格,以及如何将表格嵌入 JScrollPane 等容器中以支持滚动查看。
  2. 调整表格列宽:通过 TableColumn 类设置表格的列宽以优化显示效果。
  3. 定义表格模型:使用 DefaultTableModel 或自定义模型以动态管理表格数据和列定义。
  4. 监听数据变化:通过为表格模型添加 TableModelListener 监听器,实时响应数据更改事件。
  5. 使用表格选择器:配置表格的选择模式,管理行或单元格的选择行为。
  6. 表格编辑器与渲染器:学习如何使用默认的单元格编辑器和渲染器来控制用户输入和单元格显示。
  7. 自定义渲染器:通过扩展 DefaultTableCellRenderer 实现自定义的单元格外观。
  8. 添加文字说明:为表格和表头提供说明性文字,增强数据展示的可读性和上下文信息。
  9. 实现排序与过滤:使用 TableRowSorter 为表格数据提供排序和过滤功能。
  10. 组合框作为编辑器:通过设置自定义单元格编辑器为表格单元格添加 JComboBox 选择器。
  11. 其他编辑器的使用:将按钮、复选框或日期选择器作为表格单元格编辑器,提升交互体验。
  12. 验证编辑器输入:通过实现输入验证逻辑,确保用户输入的表格数据符合要求。
  13. 表格打印:使用 JTable.print() 方法轻松打印表格内容,同时支持分页和格式化设置。

组件调用关系示意图:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
JFrame
 └── JScrollPane
      └── JTable
           ├── TableModel
           │    └── DefaultTableModel
           ├── TableColumnModel
           │    └── TableColumn
           │        ├── DefaultTableCellRenderer
           │        └── DefaultCellEditor
           ├── ListSelectionModel
           │    └── ListSelectionListener
           ├── TableRowSorter
           │    └── RowFilter (optional)
           └── EventListeners (TableModelListener, ListSelectionListener)
 ├── JToolBar
 │    └── Buttons (Add, Delete, Export, Print)
 └── JPopupMenu (optional)

关于表格组件的所有功能,参考如下代码即可。

代码如何使用表格组件
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
public class TableFeaturesDemo {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(TableFeaturesDemo::createAndShowGUI);
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("JTable Features Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);

        // Table data and columns
        String[] columns = { "ID", "Name", "Age", "Department", "Role" };
        Object[][] data = {
                { 1, "Alice", 30, "HR", "Manager" },
                { 2, "Bob", 24, "IT", "Developer" },
                { 3, "Charlie", 28, "Finance", "Analyst" },
                { 4, "Diana", 35, "IT", "Architect" },
        };

        DefaultTableModel tableModel = new DefaultTableModel(data, columns) {
            @Override
            public boolean isCellEditable(int row, int column) {
                return column != 0; // Make ID column non-editable
            }
        };

        JTable table = new JTable(tableModel);

        // Add row sorter
        TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(tableModel);
        table.setRowSorter(sorter);

        // Set column widths
        table.getColumnModel().getColumn(0).setPreferredWidth(50);
        table.getColumnModel().getColumn(1).setPreferredWidth(150);
        table.getColumnModel().getColumn(2).setPreferredWidth(50);
        table.getColumnModel().getColumn(3).setPreferredWidth(150);
        table.getColumnModel().getColumn(4).setPreferredWidth(150);

        // Add table to scroll pane
        JScrollPane scrollPane = new JScrollPane(table);

        // Add TableModelListener
        tableModel.addTableModelListener(e -> {
            if (e.getType() == TableModelEvent.UPDATE) {
                int row = e.getFirstRow();
                int column = e.getColumn();
                Object updatedValue = tableModel.getValueAt(row, column);
                System.out.println("Updated value at row " + row + ", column " + column + ": " + updatedValue);
            }
        });

        // Define a JComboBox for the "Department" column
        String[] departments = { "HR", "IT", "Finance", "Marketing" };
        JComboBox<String> departmentComboBox = new JComboBox<>(departments);
        DefaultCellEditor departmentEditor = new DefaultCellEditor(departmentComboBox);

        // Set the custom editor for the "Department" column
        TableColumn departmentColumn = table.getColumnModel().getColumn(3);
        departmentColumn.setCellEditor(departmentEditor);

        // Add tooltips
        table.setToolTipText("This is a JTable with various features");
        table.getTableHeader().setToolTipText("This is a JTableHeader ");

        // Add custom cell renderer
        table.getColumnModel().getColumn(2).setCellRenderer(new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                    boolean hasFocus, int row, int column) {
                Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                if (value instanceof Integer && (Integer) value > 30) {
                    cell.setForeground(Color.RED);
                } else {
                    cell.setForeground(Color.BLACK);
                }
                return cell;
            }
        });

        // Add buttons for adding and deleting rows
        JPanel buttonPanel = new JPanel(new BorderLayout());
        JPanel controlPanel = new JPanel();
        JPanel labelPanel = new JPanel();
        JLabel selectionLabel = new JLabel("Selected Data: None");
        labelPanel.add(selectionLabel);

        JButton addRowButton = new JButton("Add Row");
        JButton deleteRowButton = new JButton("Delete Row");

        addRowButton.addActionListener(e -> {
            Vector<Object> newRow = new Vector<>();
            newRow.add(tableModel.getRowCount() + 1); // Auto ID
            newRow.add("New Employee");
            newRow.add(25);
            newRow.add("Department");
            newRow.add("Role");
            tableModel.addRow(newRow);
        });

        deleteRowButton.addActionListener(e -> {
            int selectedRow = table.getSelectedRow();
            if (selectedRow != -1) {
                tableModel.removeRow(table.convertRowIndexToModel(selectedRow));
            } else {
                JOptionPane.showMessageDialog(frame, "Please select a row to delete.", "Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        });

        // Add selection mode buttons
        JButton singleSelectionButton = new JButton("Single Selection");
        JButton contiguousSelectionButton = new JButton("Contiguous Selection");
        JButton multipleSelectionButton = new JButton("Multiple Selection");

        JLabel modeLabel = new JLabel("Selection Mode: Single");
        controlPanel.add(modeLabel);

        singleSelectionButton.addActionListener(e -> {
            table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            modeLabel.setText("Selection Mode: Single");
        });

        contiguousSelectionButton.addActionListener(e -> {
            table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
            modeLabel.setText("Selection Mode: Contiguous");
        });

        multipleSelectionButton.addActionListener(e -> {
            table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
            modeLabel.setText("Selection Mode: Multiple");
        });

        // Add selection listener to update label
        table.getSelectionModel().addListSelectionListener(e -> {
            if (!e.getValueIsAdjusting()) {
                int[] selectedRows = table.getSelectedRows();
                int[] selectedColumns = table.getSelectedColumns();
                StringBuilder selectedData = new StringBuilder();

                for (int row : selectedRows) {
                    for (int column : selectedColumns) {
                        selectedData.append(table.getValueAt(row, column)).append(" ");
                    }
                }

                selectionLabel.setText("Selected Data: " + (selectedData.isEmpty() ? "None" : selectedData.toString()));
            }
        });

        // Add a Print Table button
        JButton printTableButton = new JButton("Print Table");
        printTableButton.addActionListener(e -> {
            try {
                if (table.print(JTable.PrintMode.FIT_WIDTH)) {
                    JOptionPane.showMessageDialog(frame, "Table printed successfully.", "Print",
                            JOptionPane.INFORMATION_MESSAGE);
                } else {
                    JOptionPane.showMessageDialog(frame, "Table print cancelled.", "Print",
                            JOptionPane.WARNING_MESSAGE);
                }
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(frame, "Error occurred while printing: " + ex.getMessage(), "Print Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        });

        controlPanel.add(addRowButton);
        controlPanel.add(deleteRowButton);
        controlPanel.add(singleSelectionButton);
        controlPanel.add(contiguousSelectionButton);
        controlPanel.add(multipleSelectionButton);
        controlPanel.add(printTableButton);

        buttonPanel.add(controlPanel, BorderLayout.NORTH);
        buttonPanel.add(labelPanel, BorderLayout.SOUTH);

        // Add components to frame
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.add(buttonPanel, BorderLayout.SOUTH);

        frame.setVisible(true);
    }
}

13. 如何使用树组件

  1. 如何创建树
  2. 如何创建数据模型
  3. 如何处理节点事件
  4. 如何定义个性化树

组件之间的关系说明:

  1. JTree

    • 用于显示树形结构的数据。
    • 依赖于TreeModel来获取树的数据。
    • 使用TreeCellRenderer来渲染每个节点。
    • 使用TreeCellEditor来编辑节点。
    • 依赖于TreeSelectionModel来管理选择的节点。
    • 使用TreeExpansionListener来处理节点的展开和折叠事件。
    • 使用TreeSelectionListener来监听树的选择变化事件。
  2. TreeModel

    • JTree通过它与树形数据进行交互。
    • TreeModel是一个接口,通常由DefaultTreeModel实现。
  3. DefaultTreeModel

    • 实现了TreeModel接口,管理树的数据结构。
    • 依赖于TreeNode来表示树节点。
  4. TreeNode

    • TreeModel中树节点的基本接口。
    • TreeNode的子类(如DefaultMutableTreeNode)提供了具体的节点行为。
  5. DefaultMutableTreeNode

    • 实现了TreeNode接口,提供了树节点的可变性,可以修改节点内容和子节点。
  6. TreeCellRenderer

    • 负责渲染树的每个节点。
    • JTree使用TreeCellRenderer来决定每个节点的外观。
    • 默认的实现是DefaultTreeCellRenderer
  7. DefaultTreeCellRenderer

    • TreeCellRenderer的默认实现,提供节点的基本渲染方式,可以自定义节点的外观(例如,改变颜色、字体等)。
  8. TreeCellEditor

    • 负责编辑树节点的内容。
    • JTree使用TreeCellEditor来允许用户修改节点的内容。
    • 默认的实现是DefaultTreeCellEditor
  9. DefaultTreeCellEditor

    • TreeCellEditor的默认实现,提供节点的编辑界面,如文本框。
  10. TreeSelectionModel

    • 管理树中选中的节点。
    • JTree通过TreeSelectionModel来跟踪哪些节点被选中。
  11. TreeExpansionListener

    • 监听树节点展开和折叠事件。
    • 用于响应树的展开和折叠行为,通常用来触发自定义的动画或行为。
    • 是用于处理树节点展开相关的事件,TreeExpansionListener是节点展开后的监听器。
  12. TreeSelectionListener

    • 监听树节点的选择事件。
    • 通过该监听器,JTree可以响应用户选择的变化,比如在选择节点时执行某些操作。
    • 通过TreeSelectionListener,可以获取选择的节点、取消选择的节点等信息。
  13. TreeWillExpandListener

    • 监听树节点即将展开的事件。
    • TreeWillExpandListener提供了一些方法来在节点展开之前处理一些任务,比如验证展开动作。
    • 是用于处理树节点展开相关的事件,但TreeWillExpandListener是节点展开之前的监听器。
  14. TreePath

    • 表示树中的路径,通常由一系列节点组成。
    • TreePath在树中查找路径时非常有用,用于描述节点的层级结构,特别是在复杂的树结构中查找特定节点时。
  15. TreeUI

    • TreeUI是一个抽象类,TreeUI的自定义能力可以让你更精细地控制树的外观和交互行为。
    • BasicTreeUI是其默认实现,负责处理树的呈现、布局等。
    • 你可以自定义TreeUI来改变树的视觉效果和交互方式。
  16. JTreeAccessibility

    • 用于增强JTree组件的可访问性,确保它符合屏幕阅读器等辅助技术的要求。
    • JTreeAccessibility确保JTree在辅助功能方面有更好的支持。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
JTree
 ├── TreeModel
 │    └── DefaultTreeModel
 │         └── TreeNode
 │              └── DefaultMutableTreeNode
 ├── TreeCellRenderer
 │    └── DefaultTreeCellRenderer
 ├── TreeCellEditor
 │    └── DefaultTreeCellEditor
 ├── TreeSelectionModel
 ├── TreeExpansionListener
 ├── TreeSelectionListener
 ├── TreeWillExpandListener
 ├── TreePath
 ├── TreeUI
 │    └── BasicTreeUI
 ├── JTreeAccessibility

关于树组件的所有功能,参考如下代码即可。

代码如何使用树组件
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
public class CustomSwingApp {

    private JFrame frame;
    private JTree tree;
    private JTextArea nodeEditor;
    private JTextArea introductionTextArea;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode selectedNode;
    private JTabbedPane tabbedPane;

    public CustomSwingApp() {
        frame = new JFrame("Custom Swing App");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);

        // Left Panel
        JPanel leftPanel = new JPanel(new GridLayout(2, 1));

        // Left Top: JTree
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        DefaultMutableTreeNode vegetables = new DefaultMutableTreeNode("Vegetables");
        vegetables.add(new DefaultMutableTreeNode("Carrot"));
        vegetables.add(new DefaultMutableTreeNode("Potato"));
        DefaultMutableTreeNode fruits = new DefaultMutableTreeNode("Fruits");
        fruits.add(new DefaultMutableTreeNode("Apple"));
        fruits.add(new DefaultMutableTreeNode("Banana"));
        DefaultMutableTreeNode gifts = new DefaultMutableTreeNode("Gifts");
        gifts.add(new DefaultMutableTreeNode("Toy"));
        gifts.add(new DefaultMutableTreeNode("Book"));
        DefaultMutableTreeNode homeItems = new DefaultMutableTreeNode("Home Items");
        homeItems.add(new DefaultMutableTreeNode("Candle"));
        homeItems.add(new DefaultMutableTreeNode("Vase"));

        root.add(vegetables);
        root.add(fruits);
        root.add(gifts);
        root.add(homeItems);

        treeModel = new DefaultTreeModel(root);
        tree = new JTree(treeModel);
        tree.setEditable(false);
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); // 获取选中的节点
                if (selectedNode != null) {
                    updateTabbedPane(selectedNode.toString());
                }
            }
        });
        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2 && selectedNode != null) {
                    String newValue = JOptionPane.showInputDialog(frame, "Edit Node", selectedNode.toString());
                    if (newValue != null && !newValue.trim().isEmpty()) {
                        selectedNode.setUserObject(newValue);
                        treeModel.nodeChanged(selectedNode);
                        nodeEditor.append("Node Edited: " + newValue + "\n");
                    }
                } else if (e.getClickCount() == 1 && selectedNode != null) {
                    introductionTextArea.setText("Introduction for: " + selectedNode.toString());
                }
            }
        });

        JScrollPane treeScrollPane = new JScrollPane(tree);
        leftPanel.add(treeScrollPane);

        // Left Bottom: Node Editor
        nodeEditor = new JTextArea("Node Modification Area: " + "\n");
        nodeEditor.setEditable(false);
        JScrollPane editorScrollPane = new JScrollPane(nodeEditor);
        leftPanel.add(editorScrollPane);

        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, treeScrollPane, editorScrollPane);
        leftSplitPane.setDividerLocation(300);

        // Right Panel
        tabbedPane = new JTabbedPane();

        // Tab 1: Introduction
        introductionTextArea = new JTextArea("Introduction content goes here.");
        introductionTextArea.setEditable(false);
        tabbedPane.addTab("Introduction", new JScrollPane(introductionTextArea));

        // Other tabs
        JTextArea usageTextArea = new JTextArea("Usage content goes here.");
        usageTextArea.setEditable(false);
        tabbedPane.addTab("Usage", new JScrollPane(usageTextArea));

        JTextArea comparisonTextArea = new JTextArea("Comparisons content goes here.");
        comparisonTextArea.setEditable(false);
        tabbedPane.addTab("Comparisons", new JScrollPane(comparisonTextArea));

        JTextArea sourceTextArea = new JTextArea("Sources content goes here.");
        sourceTextArea.setEditable(false);
        tabbedPane.addTab("Sources", new JScrollPane(sourceTextArea));

        JTextArea notesTextArea = new JTextArea("Notes content goes here.");
        notesTextArea.setEditable(false);
        tabbedPane.addTab("Notes", new JScrollPane(notesTextArea));

        JPanel buttonPanel = new JPanel();
        JButton addButton = new JButton("Add Node");
        JButton deleteButton = new JButton("Delete Node");

        addButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (selectedNode != null) {
                    String newNodeName = JOptionPane.showInputDialog(frame, "Enter Node Name");
                    if (newNodeName != null && !newNodeName.trim().isEmpty()) {
                        DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(newNodeName);
                        selectedNode.add(newNode);
                        treeModel.reload(selectedNode);
                        nodeEditor.append("Node Added: " + newNodeName + "\n");
                    }
                }
            }
        });

        deleteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (selectedNode != null && selectedNode.getParent() != null) {
                    DefaultMutableTreeNode parent = (DefaultMutableTreeNode) selectedNode.getParent();
                    nodeEditor.append("Node Deleted: " + selectedNode.toString() + "\n");
                    parent.remove(selectedNode);
                    treeModel.reload(parent);
                }
            }
        });

        buttonPanel.add(addButton);
        buttonPanel.add(deleteButton);

        JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tabbedPane, buttonPanel);
        rightSplitPane.setDividerLocation(400);

        // Main SplitPane
        JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSplitPane, rightSplitPane);
        mainSplitPane.setDividerLocation(400);

        frame.add(mainSplitPane);
        frame.setVisible(true);
    }

    private void updateTabbedPane(String nodeName) {
        introductionTextArea.setText("Introduction for: " + nodeName);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(CustomSwingApp::new);
    }
}

14. 如何使Swing观感器

  • 通过UIManager.setLookAndFeel()方法来设置切换观感
  • DefaultTreeModel.reload() 动态刷新树的显示
  • SwingUtilities.updateComponentTreeUI() 刷新整个界面以应用新的观感
  • 常见观感器名称: Nimbus, Metal, System, CDE/Motif
代码如何使Swing观感器
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
public class CustomSwingApp {

    private JFrame frame;
    private JTree tree;
    private JTextArea nodeEditor;
    private JTextArea introductionTextArea;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode selectedNode;
    private JTabbedPane tabbedPane;

    public CustomSwingApp() {
        frame = new JFrame("Custom Swing App");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);

        // Left Panel
        JPanel leftPanel = new JPanel(new GridLayout(2, 1));

        // Left Top: JTree
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        DefaultMutableTreeNode vegetables = new DefaultMutableTreeNode("Vegetables");
        vegetables.add(new DefaultMutableTreeNode("Carrot"));
        vegetables.add(new DefaultMutableTreeNode("Potato"));
        DefaultMutableTreeNode fruits = new DefaultMutableTreeNode("Fruits");
        fruits.add(new DefaultMutableTreeNode("Apple"));
        fruits.add(new DefaultMutableTreeNode("Banana"));
        DefaultMutableTreeNode gifts = new DefaultMutableTreeNode("Gifts");
        gifts.add(new DefaultMutableTreeNode("Toy"));
        gifts.add(new DefaultMutableTreeNode("Book"));
        DefaultMutableTreeNode homeItems = new DefaultMutableTreeNode("Home Items");
        homeItems.add(new DefaultMutableTreeNode("Candle"));
        homeItems.add(new DefaultMutableTreeNode("Vase"));

        root.add(vegetables);
        root.add(fruits);
        root.add(gifts);
        root.add(homeItems);

        treeModel = new DefaultTreeModel(root);
        tree = new JTree(treeModel);
        tree.setEditable(false);
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); // 获取选中的节点
                if (selectedNode != null) {
                    updateTabbedPane(selectedNode.toString());
                }
            }
        });
        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2 && selectedNode != null) {
                    String newValue = JOptionPane.showInputDialog(frame, "Edit Node", selectedNode.toString());
                    if (newValue != null && !newValue.trim().isEmpty()) {
                        selectedNode.setUserObject(newValue);
                        treeModel.nodeChanged(selectedNode);
                        nodeEditor.append("Node Edited: " + newValue + "\n");
                    }
                } else if (e.getClickCount() == 1 && selectedNode != null) {
                    introductionTextArea.setText("Introduction for: " + selectedNode.toString());
                }
            }
        });

        JScrollPane treeScrollPane = new JScrollPane(tree);
        leftPanel.add(treeScrollPane);

        // Left Bottom: Node Editor
        nodeEditor = new JTextArea("Node Modification Area: " + "\n");
        nodeEditor.setEditable(false);
        JScrollPane editorScrollPane = new JScrollPane(nodeEditor);
        leftPanel.add(editorScrollPane);

        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, treeScrollPane, editorScrollPane);
        leftSplitPane.setDividerLocation(300);

        // Right Panel
        tabbedPane = new JTabbedPane();

        // Tab 1: Introduction
        introductionTextArea = new JTextArea("Introduction content goes here.");
        introductionTextArea.setEditable(false);
        tabbedPane.addTab("Introduction", new JScrollPane(introductionTextArea));

        // Other tabs
        JTextArea usageTextArea = new JTextArea("Usage content goes here.");
        usageTextArea.setEditable(false);
        tabbedPane.addTab("Usage", new JScrollPane(usageTextArea));

        JTextArea comparisonTextArea = new JTextArea("Comparisons content goes here.");
        comparisonTextArea.setEditable(false);
        tabbedPane.addTab("Comparisons", new JScrollPane(comparisonTextArea));

        JTextArea sourceTextArea = new JTextArea("Sources content goes here.");
        sourceTextArea.setEditable(false);
        tabbedPane.addTab("Sources", new JScrollPane(sourceTextArea));

        JTextArea notesTextArea = new JTextArea("Notes content goes here.");
        notesTextArea.setEditable(false);
        tabbedPane.addTab("Notes", new JScrollPane(notesTextArea));

        JPanel buttonPanel = new JPanel();
        JButton addButton = new JButton("Add Node");
        JButton deleteButton = new JButton("Delete Node");

        addButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (selectedNode != null) {
                    String newNodeName = JOptionPane.showInputDialog(frame, "Enter Node Name");
                    if (newNodeName != null && !newNodeName.trim().isEmpty()) {
                        DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(newNodeName);
                        selectedNode.add(newNode);
                        treeModel.reload(selectedNode);
                        nodeEditor.append("Node Added: " + newNodeName + "\n");
                    }
                }
            }
        });

        deleteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (selectedNode != null && selectedNode.getParent() != null) {
                    DefaultMutableTreeNode parent = (DefaultMutableTreeNode) selectedNode.getParent();
                    nodeEditor.append("Node Deleted: " + selectedNode.toString() + "\n");
                    parent.remove(selectedNode);
                    treeModel.reload(parent);
                }
            }
        });

        buttonPanel.add(addButton);
        buttonPanel.add(deleteButton);

        JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tabbedPane, buttonPanel);
        rightSplitPane.setDividerLocation(400);

        // Main SplitPane
        JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSplitPane, rightSplitPane);
        mainSplitPane.setDividerLocation(400);

        frame.add(mainSplitPane);
        frame.setVisible(true);
    }

    private void updateTabbedPane(String nodeName) {
        introductionTextArea.setText("Introduction for: " + nodeName);
    }

    private void setCustomLookAndFeel() {
        try {
            // 使用默认的 Nimbus Look and Feel(自带观感器)
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }

            // 自定义某些 UI 组件的外观
            UIManager.put("Tree.selectionBackground", new Color(184, 207, 229));
            UIManager.put("Tree.selectionBorderColor", new Color(99, 130, 191));
            UIManager.put("Tree.textBackground", Color.WHITE);
            UIManager.put("Tree.textForeground", Color.BLACK);
            UIManager.put("Tree.rendererFillBackground", true);

            UIManager.put("Button.background", new Color(102, 205, 170)); // 按钮背景
            UIManager.put("Button.foreground", Color.BLACK); // 按钮字体颜色
            UIManager.put("TabbedPane.selected", new Color(240, 230, 140)); // 选中标签背景色
        } catch (Exception e) {
            System.err.println("Failed to set custom look and feel: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new CustomSwingApp().setCustomLookAndFeel(); // 设置自定义观感器
            new CustomSwingApp();
        });
    }

}

总结

本章重点,表格组件、树组件、观感器组件。这么多内容一次记住很难,不过通过例子来理解,就比较容易了。