Java Swing 自定义组件库分享(十三)

表格核心 --- CusTable

一、背景

Swing 原生 JTable 使用起来比较繁琐,每次都需要手动创建 DefaultTableModel、设置渲染器、处理事件等。而且原生表格不支持直接绑定数据对象,需要将对象转换为 Object 数组。

CusTable 的作用就是:封装 JTable 的常用操作,支持泛型数据绑定、右键菜单复制、单元格编辑控制等功能,减少重复代码。

二、类源码

java 复制代码
import cn.hutool.core.collection.CollUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义表格
 * 支持泛型数据绑定、右键菜单复制、单元格编辑控制
 *
 * 使用示例:
 * CusTable<User> table = new CusTable<>(new String[]{"ID", "姓名", "年龄"}, user -> new Object[]{user.getId(), user.getName(), user.getAge()});
 * table.loadData(userList);
 */
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class CusTable<T> extends JTable {
    /** 列名数组 */
    private String[] cols = null;
    /** 行数据转换器,将数据对象转换为 Object 数组 */
    private JTableRowBase<T> rowBase = null;
    /** 当前表格数据列表 */
    private List<T> dataList = null;
    /** 右键菜单 */
    private JPopupMenu popupMenu = null;
    /** 右键点击位置 */
    private Point popupMenuPoint = null;
    /** 可编辑列索引,-1 表示所有列不可编辑 */
    private int editableColumnIndex = -1;

    /**
     * 加载数据
     * @param dataList 数据源
     */
    public void loadData(List<T> dataList) {
        this.dataList = dataList;
        DefaultTableModel model = getTableModel(dataList);
        setTable(model);
        this.validate();
        this.updateUI();
    }

    /**
     * 设置表格模型及样式
     * @param model 表格模型
     */
    public void setTable(DefaultTableModel model) {
        this.setModel(model);
        // 内容居中对齐
        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
        renderer.setHorizontalAlignment(SwingConstants.CENTER);
        this.setDefaultRenderer(Object.class, renderer);
        // 单选模式
        this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        // 显示网格线
        this.setShowHorizontalLines(true);
        this.setShowVerticalLines(true);
        // 单元格间距
        this.setIntercellSpacing(new Dimension(2, 2));
        // 创建右键菜单
        createPopupMenu();
    }

    /**
     * 根据数据列表生成 TableModel
     * @param dataList 数据源
     * @return DefaultTableModel
     */
    public DefaultTableModel getTableModel(List<T> dataList) {
        DefaultTableModel model = new DefaultTableModel(cols, 0) {
            @Override
            public boolean isCellEditable(int row, int column) {
                return editableColumnIndex >= 0 && column == editableColumnIndex;
            }

            @Override
            public Class<?> getColumnClass(int column) {
                if (getRowCount() > 0 && getValueAt(0, column) instanceof ImageIcon) {
                    return ImageIcon.class;
                }
                return super.getColumnClass(column);
            }
        };
        dataList = CollUtil.isEmpty(dataList) ? new ArrayList<>() : dataList;
        this.dataList = dataList;
        dataList.forEach(t -> model.addRow(rowBase.getRow(t)));
        return model;
    }

    /**
     * 获取指定行的数据对象
     * @param rowIndex 行索引
     * @return 数据对象
     */
    public T getData(int rowIndex) {
        return dataList.get(rowIndex);
    }

    /**
     * 获取当前选中行的数据对象
     * @return 数据对象,未选中时返回 null
     */
    public T getSelectedData() {
        int selectedRowIndex = this.getSelectedRow();
        if (selectedRowIndex == -1) {
            return null;
        }
        return dataList.get(selectedRowIndex);
    }

    /**
     * 构造函数(使用已有 TableModel)
     * @param model 表格模型
     */
    public CusTable(DefaultTableModel model) {
        super(model);
    }

    /**
     * 构造函数
     * @param cols 列名数组
     * @param rowBase 行数据转换器
     */
    public CusTable(String[] cols, JTableRowBase<T> rowBase) {
        this.cols = cols;
        this.rowBase = rowBase;
        loadData(null);
    }

    // ==================== 右键菜单 ====================

    /**
     * 创建右键菜单(复制功能)
     */
    private void createPopupMenu() {
        popupMenu = new JPopupMenu();
        JMenuItem copyItem = new JMenuItem("复制");
        popupMenu.add(copyItem);
        copyItem.addActionListener(e -> copySelectedCell());

        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                showPopupMenu(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                showPopupMenu(e);
            }
        });
    }

    /**
     * 复制选中单元格内容到剪贴板
     */
    private void copySelectedCell() {
        if (null == popupMenuPoint) return;
        int row = this.rowAtPoint(popupMenuPoint);
        int column = this.columnAtPoint(popupMenuPoint);
        if (row >= 0 && column >= 0) {
            Object value = this.getValueAt(row, column);
            if (value != null) {
                StringSelection stringSelection = new StringSelection(value.toString());
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null);
            }
        }
    }

    /**
     * 显示右键菜单
     * @param e 鼠标事件
     */
    private void showPopupMenu(MouseEvent e) {
        if (!e.isPopupTrigger()) return;
        popupMenuPoint = e.getPoint();
        int row = this.rowAtPoint(popupMenuPoint);
        int column = this.columnAtPoint(popupMenuPoint);
        if (row >= 0 && column >= 0) {
            popupMenu.show(this, e.getX(), e.getY());
        }
    }
}

三、核心功能说明

数据绑定:

  • loadData(List<T>):加载数据列表,自动刷新表格
  • getData(int):根据行索引获取数据对象
  • getSelectedData():获取当前选中行的数据对象

列配置:

  • cols:列名数组,定义表格列头
  • editableColumnIndex:可编辑列索引,-1 表示不可编辑

右键菜单:

  • 右键点击单元格弹出菜单
  • 支持复制单元格内容到系统剪贴板

样式:

  • 单元格内容居中对齐
  • 显示水平/垂直网格线
  • 单选模式

四、使用示例

4.1 基本用法

java 复制代码
// 定义列名
String[] cols = {"ID", "姓名", "年龄", "性别"};
// 定义行数据转换器
JTableRowBase<User> rowBase = user -> new Object[]{
    user.getId(),
    user.getName(),
    user.getAge(),
    user.getGender()
};
// 创建表格
CusTable<User> table = new CusTable<>(cols, rowBase);
// 加载数据
table.loadData(userList);
// 添加到滚动面板
JScrollPane scrollPane = new JScrollPane(table);
panel.add(scrollPane);

4.2 获取选中数据

java 复制代码
JButton btn = new JButton("查看详情");
btn.addActionListener(e -> {
    User selected = table.getSelectedData();
    if (selected != null) {
        System.out.println("选中:" + selected.getName());
    } else {
        JOptionPane.showMessageDialog(null, "请先选择一行");
    }
});

4.3 设置可编辑列

java 复制代码
table.setEditableColumnIndex(2);  // 只允许编辑第3列(年龄)

4.4 刷新表格

java 复制代码
// 数据变更后重新加载
table.loadData(newUserList);

4.5 右键复制

用户右键点击任意单元格,选择"复制",即可将单元格内容复制到剪贴板。

五、关于 JTableRowBase

JTableRowBase 是一个函数式接口,用于将数据对象转换为表格行数据:

java 复制代码
public interface JTableRowBase<T> {
    Object[] getRow(T t);
}

六、关于分页

CusTable 本身不包含分页功能。分页属于业务层逻辑,可根据项目需求自行实现,或使用 PageToolBar 等分页组件配合使用。

七、注意事项

  1. 泛型CusTable<T> 支持泛型,确保数据类型安全
  2. 数据转换rowBase 负责将 T 转换为 Object[],需与 cols 顺序一致
  3. 右键复制 :复制功能默认启用,不需要可移除 createPopupMenu() 调用
  4. 可编辑列 :通过 editableColumnIndex 控制,-1 表示全部不可编辑
  5. 空数据处理 :传入 null 或空列表时,表格显示空行
  6. 图片列 :自动识别 ImageIcon 类型并正确渲染

八、小结

CusTable 封装了 JTable 的常用功能,简化了表格的使用流程:

  • 泛型支持,避免类型转换
  • 数据绑定与刷新一行搞定
  • 内置右键复制功能
  • 灵活的编辑控制

结合后续介绍的渲染器/编辑器,可以实现更丰富的表格交互。

相关推荐
livemetee2 小时前
【关于Spring声明式事务】
java·后端·spring
倒流时光三十年2 小时前
Java 内存模型(JMM)通俗解释
java·开发语言
码兄科技3 小时前
Java AI智能体开发实战:从零构建企业级智能应用指南
java·开发语言·人工智能
2401_859506243 小时前
AIGC赋能大漆摆件设计:从痛点分析到技术架构与实战验证
java·大数据·人工智能
剑挑星河月3 小时前
54.螺旋矩阵
java·算法·leetcode·矩阵
Lhappy嘻嘻3 小时前
Java 并发编程(六)|并发进阶高频:CAS、锁升级
java·开发语言
techdashen3 小时前
Arborium:把 tree-sitter 语法高亮打包成 Rust 文档生态的基础设施
开发语言·后端·rust
要开心吖ZSH4 小时前
MVCC 进阶:快照读 vs 当前读、幻读与 Next-Key Lock
java·数据库·sql·mysql·mvcc
Profile排查笔记4 小时前
指纹浏览器环境异常排查:Fingerprint、Profile、Proxy、Session 和 Task Log 怎么看
前端·人工智能·后端·自动化