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

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 的常用功能,简化了表格的使用流程:

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

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

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
小羊没烦恼!3 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
一隅论数智3 天前
给AI一张“业务概念地图“:本体如何从哲学走向企业智能
大数据·人工智能·经验分享·笔记·学习·学习方法·政务
俊昭喜喜里3 天前
java中的继承和多态的区别
java
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商