商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > JavaSwing实现自定义TablePanel组件(附完整代码)

JavaSwing实现自定义TablePanel组件(附完整代码)

  发布于2026-07-27 阅读(0)

扫一扫,手机访问

一、背景

前面两篇分别介绍了 CusTable 和表格的渲染器/编辑器机制,但实际项目中,表格通常不是孤立存在的,还需要配合:

Ja vaSwing实现自定义TablePanel组件(附完整代码)

  • 查询条件面板(搜索框、下拉框、日期选择器等)
  • 工具栏(新增、删除、导出等操作按钮)
  • 分页组件

每次写一个表格页面都要重复搭建这些组件,代码冗余且容易出错。

TablePanel 的作用就是:将查询面板、工具栏、表格、分页整合为一体,提供开箱即用的完整表格页面解决方案。

二、整体架构

┌──────────────────────────────────────────────────────┐

│ TablePanel │

│ ┌────────────────────────────────────────────────┐ │

│ │ 查询面板(QueryPanel) │ │

│ │ 条件1 [输入框] 条件2 [下拉框] [查询] [重置] │ │

│ └────────────────────────────────────────────────┘ │

│ ┌────────────────────────────────────────────────┐ │

│ │ 工具栏(ToolBar) │ │

│ │ [新增] [修改] [删除] [导出] │ │

│ └────────────────────────────────────────────────┘ │

│ ┌────────────────────────────────────────────────┐ │

│ │ 表格(CusTable) │ │

│ │ ┌────┬──────┬──────┬──────┬──────────────┐ │ │

│ │ │ ☑ │ ID │ 姓名 │ 状态 │ 操作 │ │ │

│ │ │ ☐ │ 1 │ 张三 │ ON │ [编辑][删除] │ │ │

│ │ │ ☐ │ 2 │ 李四 │ OFF │ [编辑][删除] │ │ │

│ │ └────┴──────┴──────┴──────┴──────────────┘ │ │

│ └────────────────────────────────────────────────┘ │

│ ┌────────────────────────────────────────────────┐ │

│ │ 分页工具栏 │ │

│ │ 共100条,第1页 [上一页] [1] [下一页] 共5页 │ │

│ └────────────────────────────────────────────────┘ │

└──────────────────────────────────────────────────────┘

三、依赖组件

TablePanel 依赖以下组件(均已在前序篇章介绍):

组件来源篇章
CusTable第13篇
ButtonUtils第4篇
CusScrollPane第11篇
DatePickerUtils第5篇
ComponentUtils第3篇
CallbackProcessor第2篇

四、TablePanel 用到的按钮类

TablePanel 中使用了三个按钮配置类,用于统一管理不同场景下的按钮配置:

QueryBtn — 查询按钮类

用于查询面板中的按钮(搜索、重置、导出等自定义按钮)

import lombok.Getter;

import ja vax.swing.*;
import ja va.io.Serializable;
import ja va.util.function.Consumer;

/**
 * 查询按钮类
 * 用于查询面板中的按钮配置
 */
@Getter
public class QueryBtn implements Serializable {
    private static final long serialVersionUID = 5063820073104962669L;
    private final String text;
    private final ImageIcon imageIcon;
    private final String bgColorHex;
    private final String fontColorHex;
    private final Runnable mouseClick;
    private final Consumer mouseEntered;
    private final Consumer mouseExited;

    public QueryBtn(String text, ImageIcon imageIcon, Runnable mouseClick) {
        this(text, imageIcon, null, null, mouseClick, null, null);
    }

    public QueryBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex, Runnable mouseClick) {
        this(text, imageIcon, bgColorHex, fontColorHex, mouseClick, null, null);
    }

    public QueryBtn(String text, ImageIcon imageIcon, Runnable mouseClick,
                    Consumer mouseEntered, Consumer mouseExited) {
        this(text, imageIcon, null, null, mouseClick, mouseEntered, mouseExited);
    }

    public QueryBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex,
                    Runnable mouseClick, Consumer mouseEntered, Consumer mouseExited) {
        this.text = text;
        this.imageIcon = imageIcon;
        this.bgColorHex = bgColorHex;
        this.fontColorHex = fontColorHex;
        this.mouseClick = mouseClick;
        this.mouseEntered = mouseEntered;
        this.mouseExited = mouseExited;
    }
}

ToolBarBtn — 工具栏按钮类

用于表格上方工具栏中的按钮配置,扩展了 name 字段用于标识

import lombok.Getter;

import ja vax.swing.*;
import ja va.util.function.Consumer;

/**
 * 表格工具栏按钮类
 * 用于工具栏中的按钮配置,扩展了 name 字段
 */
public class ToolBarBtn extends QueryBtn {
    private static final long serialVersionUID = -3322742242512220910L;
    @Getter
    private final String name;

    public ToolBarBtn(String text, ImageIcon imageIcon, Runnable mouseClick) {
        super(text, imageIcon, null, null, mouseClick, null, null);
        this.name = "";
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, String name, Runnable mouseClick) {
        super(text, imageIcon, null, null, mouseClick, null, null);
        this.name = name;
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex, Runnable mouseClick) {
        super(text, imageIcon, bgColorHex, fontColorHex, mouseClick, null, null);
        this.name = "";
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex, String name, Runnable mouseClick) {
        super(text, imageIcon, bgColorHex, fontColorHex, mouseClick, null, null);
        this.name = name;
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, Runnable mouseClick,
                      Consumer mouseEntered, Consumer mouseExited) {
        super(text, imageIcon, null, null, mouseClick, mouseEntered, mouseExited);
        this.name = "";
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, String name, Runnable mouseClick,
                      Consumer mouseEntered, Consumer mouseExited) {
        super(text, imageIcon, null, null, mouseClick, mouseEntered, mouseExited);
        this.name = name;
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex,
                      Runnable mouseClick, Consumer mouseEntered, Consumer mouseExited) {
        super(text, imageIcon, bgColorHex, fontColorHex, mouseClick, mouseEntered, mouseExited);
        this.name = "";
    }

    public ToolBarBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex, String name,
                      Runnable mouseClick, Consumer mouseEntered, Consumer mouseExited) {
        super(text, imageIcon, bgColorHex, fontColorHex, mouseClick, mouseEntered, mouseExited);
        this.name = name;
    }
}

OperateBtn — 操作列按钮类

用于表格操作列中的按钮配置,扩展了 width 字段控制按钮宽度

import lombok.Getter;

import ja vax.swing.*;

/**
 * 操作列按钮类
 * 用于表格操作列中的按钮配置,扩展了 width 字段
 */
public class OperateBtn extends QueryBtn {
    private static final long serialVersionUID = 7964671454622940429L;
    @Getter
    private int width = 60;

    public OperateBtn(String text, Runnable mouseClick) {
        super(text, null, mouseClick);
    }

    public OperateBtn(String text, int width, Runnable mouseClick) {
        super(text, null, mouseClick);
        this.width = width;
    }

    public OperateBtn(String text, String bgColorHex, String fontColorHex, Runnable mouseClick) {
        super(text, null, bgColorHex, fontColorHex, mouseClick);
    }

    public OperateBtn(String text, int width, String bgColorHex, String fontColorHex, Runnable mouseClick) {
        super(text, null, bgColorHex, fontColorHex, mouseClick);
        this.width = width;
    }

    public OperateBtn(String text, ImageIcon imageIcon, Runnable mouseClick) {
        super(text, imageIcon, mouseClick);
    }

    public OperateBtn(String text, int width, ImageIcon imageIcon, Runnable mouseClick) {
        super(text, imageIcon, mouseClick);
        this.width = width;
    }

    public OperateBtn(String text, ImageIcon imageIcon, String bgColorHex, String fontColorHex, Runnable mouseClick) {
        super(text, imageIcon, bgColorHex, fontColorHex, mouseClick);
    }

    public OperateBtn(String text, int width, ImageIcon imageIcon, String bgColorHex, String fontColorHex, Runnable mouseClick) {
        super(text, imageIcon, bgColorHex, fontColorHex, mouseClick);
        this.width = width;
    }
}

三个按钮类的职责划分

使用场景特有字段说明
QueryBtn查询面板用于搜索、重置、导出等查询面板中的按钮
ToolBarBtn工具栏name用于表格上方工具栏中的操作按钮
OperateBtn操作列width用于表格每行的操作列按钮,支持自定义宽度

五、TablePanel 源码

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.lgooddatepicker.components.DatePicker;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ja vax.swing.*;
import ja vax.swing.table.DefaultTableCellRenderer;
import ja vax.swing.table.TableCellRenderer;
import ja vax.swing.table.TableColumn;
import ja vax.swing.text.JTextComponent;
import ja va.awt.*;
import ja va.awt.event.MouseAdapter;
import ja va.awt.event.MouseEvent;
import ja va.awt.event.MouseMotionAdapter;
import ja va.time.LocalDate;
import ja va.util.*;
import ja va.util.List;
import ja va.util.function.BiFunction;
import ja va.util.function.Function;
import ja va.util.function.Supplier;

/**
 * 完整表格面板
 * 整合查询面板、工具栏、表格、分页于一体
 *
 * 使用示例:
 * TablePanel panel = new TablePanel<>();
 *
 * // 查询条件
 * TablePanel.Query[] queries = {
 *     new TablePanel.Query("姓名:", new JTextField(10)),
 *     new TablePanel.Query("状态:", new JComboBox<>(new String[]{"全部", "启用", "禁用"}))
 * };
 *
 * // 工具栏按钮
 * ToolBarBtn[] toolBarBtns = {
 *     new ToolBarBtn("新增", addIcon, this::handleAdd),
 *     new ToolBarBtn("删除", delIcon, this::handleDelete)
 * };
 *
 * // 配置表格
 * String[] columns = {"ID", "姓名", "年龄", "状态"};
 * JTableRowBase rowBase = user -> new Object[]{user.getId(), user.getName(), user.getAge(), user.isActive()};
 *
 * // 初始化
 * panel.initialize(queries, columns, rowBase, this::loadData, toolBarBtns);
 */
public class TablePanel extends JPanel {
    private static final Logger log = LoggerFactory.getLogger(TablePanel.class);
    private static final int DEFAULT_OPERATE_COLUMN_WIDTH = 200;
    private static final int COLUMN_PADDING = 10;
    private static final int CHECK_BOX_WIDTH = 40;
    private static final int BUTTON_GAP = 10;

    public DatePicker startDatePicker = DatePickerUtils.datePicker();
    public DatePicker endDatePicker = DatePickerUtils.datePicker();

    @Getter
    private JPanel queryPanel;
    private Query[] queryList;
    private Runnable loadData;
    @Getter
    private CusTable cusTable;
    @Getter
    private PageToolBar pageToolBar;
    @Setter
    private int pageSize = 20;
    @Getter
    private Page page;
    private boolean withCb = false;
    @Getter
    private JPanel outerPanel;
    private List hideTipColumns = new ArrayList<>();
    private Integer switchColumn;
    private final Map> switchActions = new HashMap<>();
    private Integer operateColumn;
    private Function actionProvider;

    public TablePanel() {
        setLayout(new BorderLayout());
    }

    // ==================== 查询面板 ====================

    /**
     * 创建查询条件面板
     * @param queryList 查询条件列表
     * @param search 查询方法
     * @param actionBtns 除搜索、重置以外的按钮
     * @return 查询面板
     */
    public JPanel createQuery(Query[] queryList, Runnable search, QueryBtn... actionBtns) {
        this.queryList = queryList;
        this.loadData = search;
        JPanel queryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
        queryPanel.putClientProperty("FlatLaf.style", "arc: 30;");
        queryPanel.setBackground(Color.WHITE);
        queryPanel.setBorder(BorderFactory.createEmptyBorder(5, 50, 5, 10));

        for (Query query : queryList) {
            JLabel label = new JLabel(query.getLabel());
            label.setFont(new Font(MetConstants.FONT_NAME, Font.BOLD, 16));
            queryPanel.add(label);
            queryPanel.add(query.getComponent());
        }

        ButtonUtils.createSearchBtn(queryPanel, this::loadAndRender);
        ButtonUtils.createResetBtn(queryPanel, this::reloadData);

        for (QueryBtn btn : actionBtns) {
            ButtonUtils.createActionBtn(queryPanel, btn.getText(), btn.getImageIcon(),
                    btn.getBgColorHex(), btn.getFontColorHex(),
                    btn.getMouseClick(), btn.getMouseEntered(), btn.getMouseExited());
        }
        return queryPanel;
    }

    // ==================== 工具栏 ====================

    /**
     * 创建工具栏
     * @param toolBarBtns 工具栏按钮数组
     * @return 工具栏
     */
    public JToolBar createToolBar(ToolBarBtn[] toolBarBtns) {
        if (null == toolBarBtns || toolBarBtns.length == 0) {
            return null;
        }
        JToolBar toolBar = new JToolBar("操作工具栏", SwingConstants.HORIZONTAL);
        toolBar.addSeparator();
        toolBar.setOpaque(false);
        toolBar.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));

        for (ToolBarBtn btn : toolBarBtns) {
            ButtonUtils.createActionBtn(toolBar, btn.getText(), btn.getImageIcon(),
                    btn.getBgColorHex(), btn.getFontColorHex(),
                    btn.getMouseClick(), btn.getMouseEntered(), btn.getMouseExited());
        }
        return toolBar;
    }

    // ==================== 表格 ====================

    /**
     * 创建表格
     * @param columns 列名数组
     * @param tableRowBase 行数据转换器
     * @return 滚动面板
     */
    public JScrollPane createTable(String[] columns, JTableRowBase tableRowBase) {
        cusTable = new CusTable(columns, tableRowBase) {
            @Override
            public boolean isCellEditable(int row, int column) {
                if (switchActions.containsKey(column)) {
                    Object value = getValueAt(row, column);
                    return value instanceof Boolean;
                }
                return super.isCellEditable(row, column);
            }

            @Override
            public Class getColumnClass(int column) {
                if (withCb && column == 0) {
                    return Boolean.class;
                }
                return super.getColumnClass(column);
            }

            @Override
            public String getToolTipText(MouseEvent e) {
                int row = rowAtPoint(e.getPoint());
                int col = columnAtPoint(e.getPoint());
                if (row >= 0 && col >= 0 && !hideTipColumns.contains(col)) {
                    Object value = getValueAt(row, col);
                    if (null != value) {
                        String text = value.toString();
                        int colWidth = getColumnModel().getColumn(col).getWidth();
                        FontMetrics fm = getFontMetrics(getFont());
                        int textWidth = ComponentUtils.calculateTextWidth(text, fm);
                        if (textWidth > colWidth - COLUMN_PADDING) {
                            return text;
                        }
                    }
                }
                return null;
            }
        };

        cusTable.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                handleOperateMoved(e);
            }
        });
        cusTable.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                handleCheckboxPressed(e);
            }
            @Override
            public void mouseClicked(MouseEvent e) {
                handleOperateClicked(e);
            }
        });

        if (withCb) {
            configureCheckboxColumn();
        }

        cusTable.setRowHeight(40);
        cusTable.getTableHeader().setFont(new Font(MetConstants.FONT_NAME, Font.BOLD, 16));
        cusTable.setFont(new Font(MetConstants.FONT_NAME, Font.PLAIN, 16));
        cusTable.setSelectionBackground(new Color(cusTable.getBackground().getRGB()));
        cusTable.setSelectionForeground(new Color(cusTable.getSelectionForeground().getRGB()));

        CusScrollPane scrollPane = new CusScrollPane(cusTable);
        scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
        return scrollPane;
    }

    // ==================== 分页 ====================

    /**
     * 创建分页工具栏
     * @param loadData 加载数据的方法,返回Page
     */
    public void createPageToolBar(Supplier> loadData) {
        pageToolBar = new PageToolBar(params -> {
            page.setCurrent(params[0]);
            Page pageData = loadData.get();
            return new int[]{Math.toIntExact(pageData.getCurrent()), Math.toIntExact(pageData.getTotal())};
        });
        pageToolBar.setPageSize(pageSize);
        pageToolBar.setOpaque(false);
        page = new Page<>(pageToolBar.getPageIdx(), pageSize);
    }

    // ==================== 初始化 ====================

    /**
     * 初始化表格面板
     */
    public void initialize(JPanel queryPanel, JToolBar toolBar, JScrollPane tablePanel) {
        if (null != queryPanel) {
            add(queryPanel, BorderLayout.NORTH);
        }

        JPanel centerPanel = new JPanel(new BorderLayout());
        centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 40, 0, 40));
        centerPanel.putClientProperty("FlatLaf.style", "arc: 30;");
        centerPanel.setBackground(Color.WHITE);

        if (null != toolBar) {
            centerPanel.add(toolBar, BorderLayout.NORTH);
        }
        centerPanel.add(tablePanel, BorderLayout.CENTER);
        centerPanel.add(pageToolBar, BorderLayout.SOUTH);

        outerPanel = new JPanel(new BorderLayout());
        outerPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
        outerPanel.add(centerPanel, BorderLayout.CENTER);
        add(outerPanel, BorderLayout.CENTER);
    }

    /**
     * 初始化表格面板(完整参数)
     */
    public void initialize(Query[] queryList, String[] columns, JTableRowBase tableRowBase,
                           Supplier> loadData, ToolBarBtn[] toolBarBtns, QueryBtn... actionBtns) {
        this.loadData = loadData::get;
        queryPanel = null != queryList ? createQuery(queryList, this.loadData, actionBtns) : null;
        JToolBar toolBar = createToolBar(toolBarBtns);
        JScrollPane tablePanel = createTable(columns, tableRowBase);
        createPageToolBar(loadData);
        initialize(queryPanel, toolBar, tablePanel);
    }

    /**
     * 初始化表格面板(带复选框)
     */
    public void initialize(boolean withCb, Query[] queryList, String[] columns, JTableRowBase tableRowBase,
                           Supplier> loadData, ToolBarBtn[] toolBarBtns, QueryBtn... actionBtns) {
        this.withCb = withCb;
        initialize(queryList, processColumns(columns), processTableRowBase(tableRowBase), loadData, toolBarBtns, actionBtns);
        if (withCb) {
            TableColumn tableColumn = cusTable.getColumnModel().getColumn(0);
            tableColumn.setCellRenderer(cusTable.getDefaultRenderer(Boolean.class));
            tableColumn.setCellEditor(cusTable.getDefaultEditor(Boolean.class));
            setColumnWidth(0, CHECK_BOX_WIDTH);
        }
    }

    // ==================== 数据加载 ====================

    private void loadAndRender() {
        CallbackProcessor.run(loadData);
        renderColumnsAfterLoad();
    }

    /**
     * 重新加载数据(重置查询条件)
     */
    public void reloadData() {
        pageToolBar.setPageIdx(1);
        page.setCurrent(1);
        if (null != this.queryList) {
            for (Query query : this.queryList) {
                if (query.getComponent() instanceof JTextComponent) {
                    ((JTextComponent) query.getComponent()).setText("");
                }
                if (query.getComponent() instanceof JComboBox) {
                    ((JComboBox) query.getComponent()).setSelectedIndex(-1);
                }
                if (query.getComponent() instanceof DatePickerImpl) {
                    ((DatePickerImpl) query.getComponent()).getFormattedTextField().setText(null);
                }
                if (query.getComponent() instanceof DatePicker) {
                    ((DatePicker) query.getComponent()).setDate(null);
                    ((DatePicker) query.getComponent()).getSettings().setDateRangeLimits(null, null);
                }
            }
        }
        loadAndRender();
    }

    private void renderColumnsAfterLoad() {
        if (null == cusTable) return;
        if (withCb) {
            renderCheckboxColumn();
        }
        if (null != switchColumn && cusTable.getColumnCount() > switchColumn) {
            renderSwitchColumn(switchColumn);
        }
        if (null != operateColumn && null != actionProvider && cusTable.getColumnCount() > operateColumn) {
            renderOperateColumn(operateColumn, actionProvider);
        }
    }

    // ==================== 复选框支持 ====================

    public List getSelectedData() {
        if (!withCb) {
            log.warn("表格未启用复选框功能");
            return new ArrayList<>();
        }
        List selected = new ArrayList<>();
        for (int i = 0; i < cusTable.getRowCount(); i++) {
            Boolean isSelected = (Boolean) cusTable.getValueAt(i, 0);
            if (null != isSelected && isSelected) {
                selected.add(cusTable.getData(i));
            }
        }
        return selected;
    }

    private void renderCheckboxColumn() {
        TableColumn checkboxColumn = cusTable.getColumnModel().getColumn(0);
        cusTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        checkboxColumn.setCellRenderer(new CheckboxRenderer());
        checkboxColumn.setCellEditor(new CheckboxEditor());
        setColumnWidth(0, CHECK_BOX_WIDTH);
    }

    private void handleCheckboxPressed(MouseEvent e) {
        if (withCb && null != cusTable) {
            int row = cusTable.rowAtPoint(e.getPoint());
            int col = cusTable.columnAtPoint(e.getPoint());
            if (row >= 0 && col == 0) {
                if (cusTable.isEditing()) {
                    cusTable.getCellEditor().stopCellEditing();
                }
                Boolean current = (Boolean) cusTable.getValueAt(row, col);
                cusTable.setValueAt(null == current || !current, row, col);
            }
        }
    }

    private void configureCheckboxColumn() {
        TableColumn checkboxColumn = cusTable.getColumnModel().getColumn(0);
        cusTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        checkboxColumn.setCellRenderer(new CheckboxRenderer());
        checkboxColumn.setCellEditor(new CheckboxEditor());
    }

    // ==================== 开关列支持 ====================

    public void enableSwitchClick(int column, BiFunction switchAction) {
        switchColumn = column;
        switchActions.put(column, switchAction);
        if (null != cusTable && cusTable.getColumnCount() > column) {
            renderSwitchColumn(column);
        }
    }

    private void renderSwitchColumn(int column) {
        TableCellRenderer defaultRenderer = cusTable.getDefaultRenderer(Object.class);
        TableColumn tableColumn = cusTable.getColumnModel().getColumn(column);
        tableColumn.setCellRenderer(new SwitchCellRenderer(defaultRenderer));
        BiFunction switchAction = switchActions.get(column);
        tableColumn.setCellEditor(new SwitchCellEditor(cusTable, switchAction));
    }

    // ==================== 操作列支持 ====================

    public void handleOperateColumn(int column, Function actionProvider) {
        this.operateColumn = column;
        this.actionProvider = actionProvider;
        if (null != cusTable && cusTable.getColumnCount() > column) {
            renderOperateColumn(column, actionProvider);
        }
    }

    private void renderOperateColumn(int column, Function actionProvider) {
        TableColumn col = cusTable.getColumnModel().getColumn(column);
        col.setCellRenderer(new OperateColumnRenderer<>(actionProvider::apply, cusTable));
        col.setPreferredWidth(DEFAULT_OPERATE_COLUMN_WIDTH);
    }

    private void handleOperateMoved(MouseEvent e) {
        OperateBtn cursorBtn = getButtonAtPoint(actionProvider, e.getPoint());
        cusTable.setCursor(null != cursorBtn ? Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) : Cursor.getDefaultCursor());
    }

    private void handleOperateClicked(MouseEvent e) {
        OperateBtn btn = getButtonAtPoint(actionProvider, e.getPoint());
        if (null != btn) {
            Runnable action = btn.getMouseClick();
            if (null != action) {
                action.run();
            }
            e.consume();
        }
    }

    private OperateBtn getButtonAtPoint(Function actionProvider, Point p) {
        if (null == operateColumn || null == actionProvider || null == cusTable) {
            return null;
        }
        int rowView = cusTable.rowAtPoint(p);
        int colIndex = cusTable.columnAtPoint(p);
        if (rowView >= 0 && colIndex == operateColumn) {
            int rowModel = cusTable.convertRowIndexToModel(rowView);
            T rowData = cusTable.getData(rowModel);
            OperateBtn[] btns = actionProvider.apply(rowData);
            if (null != btns && btns.length > 0) {
                Rectangle cellRect = cusTable.getCellRect(rowView, colIndex, false);
                int currentX = calCurrentX(btns, cellRect);
                for (OperateBtn btn : btns) {
                    int btnWidth = null == btn ? MetConstants.BUTTON_WIDTH : Math.min(btn.getWidth(), cellRect.width / btns.length);
                    Rectangle btnRect = new Rectangle(currentX, cellRect.y, btnWidth, cusTable.getRowHeight());
                    if (null != btn && btnRect.contains(p)) {
                        return btn;
                    }
                    currentX += btnWidth + BUTTON_GAP;
                }
            }
        }
        return null;
    }

    private static int calCurrentX(OperateBtn[] btns, Rectangle cellRect) {
        int totalBtnWidth = 0;
        for (int i = 0; i < btns.length; i++) {
            int btnWidth = null == btns[i] ? MetConstants.BUTTON_WIDTH : Math.min(btns[i].getWidth(), cellRect.width / btns.length);
            totalBtnWidth += btnWidth;
            if (i < btns.length - 1) {
                totalBtnWidth += BUTTON_GAP;
            }
        }
        return cellRect.x + (cellRect.width - totalBtnWidth) / 2;
    }

    // ==================== 辅助方法 ====================

    private String[] processColumns(String[] columns) {
        if (!withCb) {
            return columns;
        }
        String[] newColumns = new String[columns.length + 1];
        newColumns[0] = "";
        System.arraycopy(columns, 0, newColumns, 1, columns.length);
        return newColumns;
    }

    private JTableRowBase processTableRowBase(JTableRowBase tableRowBase) {
        if (!withCb) {
            return tableRowBase;
        }
        return item -> {
            Object[] originalData = tableRowBase.getRow(item);
            Object[] newData = new Object[originalData.length + 1];
            newData[0] = false;
            System.arraycopy(originalData, 0, newData, 1, originalData.length);
            return newData;
        };
    }

    public void setColumnWidth(int columnIndex, int width) {
        if (columnIndex < 0 || columnIndex >= cusTable.getColumnCount() || width < 0) {
            log.warn("列索引[{}]越界或者宽度[{}]小于0", columnIndex, width);
            return;
        }
        TableColumn column = cusTable.getColumnModel().getColumn(columnIndex);
        column.setMinWidth(width);
        column.setMaxWidth(width);
        column.setPreferredWidth(width);
        column.setWidth(width);
        column.setResizable(false);
    }

    public void setColumnWidth(int width, int... columns) {
        if (null == columns) return;
        for (int columnIndex : columns) {
            setColumnWidth(columnIndex, width);
        }
    }

    public void showIndexCell() {
        int colIndex = withCb ? 1 : 0;
        TableColumn column = cusTable.getColumnModel().getColumn(colIndex);
        column.setMinWidth(80);
        column.setMaxWidth(80);
        column.setPreferredWidth(80);
        column.setWidth(80);
        column.setCellRenderer(new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value,
                                                           boolean isSelected, boolean hasFocus, int row, int column) {
                value = row + 1;
                Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                setHorizontalAlignment(SwingConstants.CENTER);
                return c;
            }
        });
        column.setResizable(false);
    }

    public void disableBgColor(int... columns) {
        TableCellRenderer defaultRenderer = cusTable.getDefaultRenderer(Object.class);
        for (int col : columns) {
            TableColumn column = cusTable.getColumnModel().getColumn(col);
            column.setCellRenderer(new DisableBgCellRenderer(defaultRenderer, col));
        }
    }

    public void hideToolTipColumns(Integer... columns) {
        hideTipColumns = Arrays.asList(columns);
    }

    public void dateChangeListener() {
        startDatePicker.addDateChangeListener(e -> {
            LocalDate start = e.getNewDate();
            if (null != start) {
                endDatePicker.getSettings().setDateRangeLimits(start, null);
                if (null != endDatePicker.getDate() && endDatePicker.getDate().isBefore(start)) {
                    endDatePicker.setDate(null);
                }
            }
        });
        endDatePicker.addDateChangeListener(e -> {
            LocalDate end = e.getNewDate();
            if (null != end) {
                startDatePicker.getSettings().setDateRangeLimits(null, end);
                if (null != startDatePicker.getDate() && startDatePicker.getDate().isAfter(end)) {
                    startDatePicker.setDate(null);
                }
            }
        });
    }

    // ==================== 查询条件内部类 ====================

    @Getter
    public static class Query {
        private final String label;
        private final JComponent component;

        public Query(String label, JComponent component) {
            this.label = label;
            this.component = component;
        }
    }
}

六、核心功能说明

查询面板:

  • 支持多个查询条件(文本框、下拉框、日期选择器等)
  • 自动生成"查询"和"重置"按钮
  • 支持额外的自定义按钮(通过 QueryBtn

工具栏:

  • 水平工具栏,支持多个操作按钮
  • 按钮样式统一(由 ButtonUtils + ToolBarBtn 提供)

表格:

  • 基于 CusTable,支持泛型数据绑定
  • 支持复选框列(多选)
  • 支持开关列(点击切换状态)
  • 支持操作列(每行多个操作按钮,通过 OperateBtn 配置)
  • 支持提示文本(内容过长时显示 ToolTip)
  • 支持序号列

分页:

  • 内置分页工具栏(页码跳转、上一页/下一页)
  • 可配置每页大小

七、使用示例

基本用法

TablePanel panel = new TablePanel<>();

// 查询条件
TablePanel.Query[] queries = {
    new TablePanel.Query("姓名:", new JTextField(10)),
    new TablePanel.Query("状态:", new JComboBox<>(new String[]{"全部", "启用", "禁用"}))
};

// 工具栏按钮
ToolBarBtn[] toolBarBtns = {
    new ToolBarBtn("新增", addIcon, "add", this::handleAdd),
    new ToolBarBtn("删除", delIcon, "delete", this::handleDelete)
};

// 列定义
String[] columns = {"ID", "姓名", "年龄", "状态"};

// 行数据转换
JTableRowBase rowBase = user -> new Object[]{
    user.getId(), user.getName(), user.getAge(), user.isActive()
};

// 数据加载方法
Supplier> loadData = () -> userService.queryPage(page);

// 初始化
panel.initialize(queries, columns, rowBase, loadData, toolBarBtns);
add(panel);

启用复选框

panel.initialize(true, queries, columns, rowBase, loadData, toolBarBtns);
List selected = panel.getSelectedData();

启用开关列

panel.enableSwitchClick(3, (row, newState) -> {
    User user = panel.getCusTable().getData(row);
    user.setActive(newState);
    return userService.update(user) > 0;
});

启用操作列

panel.handleOperateColumn(columns.length - 1, user -> new OperateBtn[]{
    new OperateBtn("编辑", 60, editIcon, "#409EFF", "#FFFFFF", () -> editUser(user)),
    new OperateBtn("删除", 60, delIcon, "#F56C6C", "#FFFFFF", () -> deleteUser(user))
});

显示序号

panel.showIndexCell();

设置列宽

panel.setColumnWidth(0, 60);
panel.setColumnWidth(1, 120);

自定义查询按钮(使用 QueryBtn)

// 导出按钮(带悬停效果)
QueryBtn exportBtn = new QueryBtn("导出", exportIcon, "#E6A23C", "#FFFFFF", this::handleExport,
    comp -> ((JButton) comp).setBackground(Color.decode("#D4891B")),
    comp -> ((JButton) comp).setBackground(Color.decode("#E6A23C"))
);

// 打印按钮
QueryBtn printBtn = new QueryBtn("打印", printIcon, "#909399", "#FFFFFF", this::handlePrint,
    comp -> ((JButton) comp).setBackground(Color.decode("#7A7A7A")),
    comp -> ((JButton) comp).setBackground(Color.decode("#909399"))
);

panel.initialize(queries, columns, rowBase, loadData, toolBarBtns, exportBtn, printBtn);

八、注意事项

泛型一致性TablePanelCusTableJTableRowBase 的泛型类型需保持一致

数据加载loadData 方法需返回 Page 对象,包含数据和分页信息

列索引:启用复选框后,数据列从索引1开始;开关列、操作列需根据实际列索引设置

操作列按钮:每个 OperateBtnwidth 控制按钮宽度,默认 60px

查询条件重置reloadData() 会清空所有查询条件并重新查询

提示文本:内容宽度超过列宽时自动显示 ToolTip

九、小结

TablePanel 是一个完整的表格页面解决方案,将查询、表格、分页三大模块整合为一体:

  • 查询面板 → 条件筛选
  • 工具栏 → 批量操作
  • 表格 → 数据展示 + 交互(复选框/开关/操作列)
  • 分页 → 大数据量分页加载

使用 TablePanel 可以大幅减少表格页面的重复代码,一套代码适配多种场景。

本文转载于:https://www.jb51.net/program/3681243u5.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注