当前位置: 代码网 > it编程>编程语言>Java > Java Swing实现自定义TablePanel组件(附完整代码)

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

2026年07月27日 Java 我要评论
一、背景前面两篇分别介绍了custable和表格的渲染器/编辑器机制,但实际项目中,表格通常不是孤立存在的,还需要配合:查询条件面板(搜索框、下拉框、日期选择器等)工具栏(新增、删除、导出等操作按钮)

一、背景

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

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

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

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 javax.swing.*;
import java.io.serializable;
import java.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<jcomponent> mouseentered;
    private final consumer<jcomponent> 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<jcomponent> mouseentered, consumer<jcomponent> mouseexited) {
        this(text, imageicon, null, null, mouseclick, mouseentered, mouseexited);
    }

    public querybtn(string text, imageicon imageicon, string bgcolorhex, string fontcolorhex,
                    runnable mouseclick, consumer<jcomponent> mouseentered, consumer<jcomponent> 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 javax.swing.*;
import java.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<jcomponent> mouseentered, consumer<jcomponent> mouseexited) {
        super(text, imageicon, null, null, mouseclick, mouseentered, mouseexited);
        this.name = "";
    }

    public toolbarbtn(string text, imageicon imageicon, string name, runnable mouseclick,
                      consumer<jcomponent> mouseentered, consumer<jcomponent> 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<jcomponent> mouseentered, consumer<jcomponent> 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<jcomponent> mouseentered, consumer<jcomponent> mouseexited) {
        super(text, imageicon, bgcolorhex, fontcolorhex, mouseclick, mouseentered, mouseexited);
        this.name = name;
    }
}

operatebtn — 操作列按钮类

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

import lombok.getter;

import javax.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 javax.swing.*;
import javax.swing.table.defaulttablecellrenderer;
import javax.swing.table.tablecellrenderer;
import javax.swing.table.tablecolumn;
import javax.swing.text.jtextcomponent;
import java.awt.*;
import java.awt.event.mouseadapter;
import java.awt.event.mouseevent;
import java.awt.event.mousemotionadapter;
import java.time.localdate;
import java.util.*;
import java.util.list;
import java.util.function.bifunction;
import java.util.function.function;
import java.util.function.supplier;

/**
 * 完整表格面板
 * 整合查询面板、工具栏、表格、分页于一体
 *
 * 使用示例:
 * tablepanel<user> 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<user> rowbase = user -> new object[]{user.getid(), user.getname(), user.getage(), user.isactive()};
 *
 * // 初始化
 * panel.initialize(queries, columns, rowbase, this::loaddata, toolbarbtns);
 */
public class tablepanel<t> 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<t> custable;
    @getter
    private pagetoolbar pagetoolbar;
    @setter
    private int pagesize = 20;
    @getter
    private page<t> page;
    private boolean withcb = false;
    @getter
    private jpanel outerpanel;
    private list<integer> hidetipcolumns = new arraylist<>();
    private integer switchcolumn;
    private final map<integer, bifunction<integer, boolean, boolean>> switchactions = new hashmap<>();
    private integer operatecolumn;
    private function<t, operatebtn[]> 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<t> tablerowbase) {
        custable = new custable<t>(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<page<t>> loaddata) {
        pagetoolbar = new pagetoolbar(params -> {
            page.setcurrent(params[0]);
            page<t> 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<t> tablerowbase,
                           supplier<page<t>> 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<t> tablerowbase,
                           supplier<page<t>> 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<t> getselecteddata() {
        if (!withcb) {
            log.warn("表格未启用复选框功能");
            return new arraylist<>();
        }
        list<t> 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<integer, boolean, boolean> 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<integer, boolean, boolean> switchaction = switchactions.get(column);
        tablecolumn.setcelleditor(new switchcelleditor(custable, switchaction));
    }

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

    public void handleoperatecolumn(int column, function<t, operatebtn[]> actionprovider) {
        this.operatecolumn = column;
        this.actionprovider = actionprovider;
        if (null != custable && custable.getcolumncount() > column) {
            renderoperatecolumn(column, actionprovider);
        }
    }

    private void renderoperatecolumn(int column, function<t, operatebtn[]> 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<t, operatebtn[]> 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<t> processtablerowbase(jtablerowbase<t> 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<user> 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<user> rowbase = user -> new object[]{
    user.getid(), user.getname(), user.getage(), user.isactive()
};

// 数据加载方法
supplier<page<user>> loaddata = () -> userservice.querypage(page);

// 初始化
panel.initialize(queries, columns, rowbase, loaddata, toolbarbtns);
add(panel);

启用复选框

panel.initialize(true, queries, columns, rowbase, loaddata, toolbarbtns);
list<user> 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);

八、注意事项

泛型一致性tablepanel<t>custable<t>jtablerowbase<t> 的泛型类型需保持一致

数据加载loaddata 方法需返回 page<t> 对象,包含数据和分页信息

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

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

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

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

九、小结

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

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

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

以上就是java swing实现自定义tablepanel组件(附完整代码)的详细内容,更多关于java swing自定义组件的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com