表格核心 --- 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 等分页组件配合使用。
七、注意事项
- 泛型 :
CusTable<T>支持泛型,确保数据类型安全 - 数据转换 :
rowBase负责将T转换为Object[],需与cols顺序一致 - 右键复制 :复制功能默认启用,不需要可移除
createPopupMenu()调用 - 可编辑列 :通过
editableColumnIndex控制,-1 表示全部不可编辑 - 空数据处理 :传入
null或空列表时,表格显示空行 - 图片列 :自动识别
ImageIcon类型并正确渲染
八、小结
CusTable 封装了 JTable 的常用功能,简化了表格的使用流程:
- 泛型支持,避免类型转换
- 数据绑定与刷新一行搞定
- 内置右键复制功能
- 灵活的编辑控制
结合后续介绍的渲染器/编辑器,可以实现更丰富的表格交互。