Java实现自定义table宽高的示例代码

2025-06-22 17:50

本文主要是介绍Java实现自定义table宽高的示例代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing...

一、项目背景详细介绍

在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,实现不同场景下的宽度与高度自适应或定制化展示。例如:

  • 仪表盘与监控面板:实时数据显示区,往往需要让表格填满容器或保持固定比例,以便与图表、指标板并排展示。
  • 编辑与录入表单:作为表格控件的扩展,要求表格行高增大、列宽更宽,以便放置可编辑组件(如文本框、下拉框)。
  • 多视图切换:在同一应用中,可能需要不同风格的表格——紧凑型列表、详细型列表、卡片式列表等,需动态调整行高、列宽、滚动策略等。
  • 打印与导出:将表格导出为 PDF/Excel 时,需要基于页面尺寸或纸张布局自定义行高列宽,以保证打印效果。

Java Swing 的 JTable 默认行高和列宽均采用系统或 L&F 的默认值,仅通过 setRowHeightsetPreferredwidth 等方法做静态设置。要满足上述多样化需求,需要一套灵活、可配置且易扩展的“自定义表格宽高”方案。本项目将全面覆盖从需求分析、技术选型、架构设计,到核心实现、接口设计与性能优化的全过程,帮助开发者在任意 Swing 应用中快速集成并管理表格的宽度与高度。

二、项目需求详细介绍

行高自定义

  • 支持全局设置:为整张表一次性指定行高;
  • 支持按行设置:根据模型数据或行索引,动态调整某几行的高度(如带图片、富文本的行更高);

列宽自定义

  • 支持默认宽度:根据列数据类型或列名,在初始化时为所有列分配合理宽度;
  • 支持按列设置:动态调整单列或多列宽度;
  • 支持自适应宽度:根据内容(Header 与可见数据)自动计算最优宽度;

响应容器变化

  • 当表格所在滚动面板或父容器大小变化时,根据策略自动调整“可伸缩”列宽;
  • 支持总宽度固定或随容器拉伸而改变两种模式;

动态接口

setGlobalRowHeight(int height);
setRowHeight(int row, int height);
setColumnWidth(int column, int width);
fitColumnToContent(int column, int sampleRows);
setFillViewportWidth(boolean fill);
  • 支持批量设置与恢复默认;

持久化与用户偏好

  • 当用户手动拖拽列宽或通过 API 调整后,能够将设置保存(本地文件或数据库),下次启动自动恢复;
  • 支持多个表格场景的配置隔离;

性能与体验

  • 在数据量大(万行以上)或列数多(几十列)时,自动计算与更新操作应在后台 完成,避免阻塞 EDT;
  • 拖拽或接口调整时,界面响应流畅;

可扩展与定制

  • 可与表格排序、过滤、分组、编辑功能并行工作;
  • 可针对富文本、图表、按钮等自定义渲染单元格的特殊行/列,动态设置宽高;
  • 提供钩子接口,允许业务层对宽高变化做额外处理(如日志、动画效果);

三、相关技术详细介绍

JTable 行高设置

  • table.setRowHeight(int rowHeight):一行行高统一设置;
  • table.setRowHeight(int row, int rowHeight)(Java 1.7+):针对单行设置高度;
  • 自动增长行高:通过 table.getRowSorter() 在排序或过滤后重新计算行高。

TableColumn 与列宽控制

  • TableColumn 对象提供 setPreferredWidthsetMinWidthsetMaxWidth 方法;
  • table.getColumnModel().getColumn(int index) 获取目标列;
  • table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF/ALL_COLUMNS/LAST_COLUMN/…) 控制拖拽与自动填充行为;

自适应宽度计算

  • 通过渲染器测量:
TableCellRenderer headerR = table.getTableHeader().getDefaultRenderer();
Component comp = headerR.getTableCellRendererComponent(...);
int headerWidth = comp.getPreferredSize().width;
for (int i = 0; i < sampleRows; i++) {
    TableCellRenderer cellR = table.getCellRenderer(i, col);
    comp = cellR.getTableCellRendererComponent(table, table.getValueAt(i,col), ...);
    maxWidth = Math.max(maxWidth, comp.getPreferredSize().width);
}
  • 只对可见行或抽样行做测量,控制性能;

监听容器大小变化

  • 通过 ComponentListener 监听 componentResized,在窗口、JSPlitPaneJInternalFrame 等大小变化后触发列宽重分配;

后台计算与 EDT 更新

  • 使用 SwingWorker< Map<Integer,Integer>, Void> 在后台线程计算多列宽度映射;
  • 在 done() 中调用 SwingUtilities.invokeLater 应用设置;

持久化方案

  • 简易:Java Preferences API 或 .properties
  • 复杂:基于数据库的配置表,支持多用户多表持久化;

四、实现思路详细介绍

模块划分

  • ResizableTablePanel(视图层):封装 JTable 与列宽、行高设置逻辑,暴露接口;
  • DimensionController(控制层):处理自动计算、自适应、持久化加载与保存;
  • DimensionConfig(模型层):存储用户偏好配置,支持文件或数据库读写。

初始化流程

  • 构造 ResizableTablePanel 时,载入 DimensionConfig(读取持久化配置);
  • 根据配置调用 setRowHeightsetColumnWidth 等接口恢复上次设置;
  • 若无配置或需要自动自适应,调用 autoAdjustAllColumns php与 setGlobalRowHeight

自动调整算法

  • 选择合适的抽样行数(如前 50 行或所有可见行),并在后台线程中测量所需宽度;
  • 考虑列最小最大宽度约束,并合并 Header 与内容宽度;
  • 根据 AUTO_RESIZE_MODE 决定是否在剩余空间平分或保持总宽度;

手动拖拽与监听

  • 利用 JTableHeader 的拖拽行为,无需额外监听;
  • 在 TableColumnModelListener.columnMarginChanged 中捕获列宽变化,并延迟(防抖)调用 DimensionController.saveConfig

动态接口调用

  • 外部业务可通过 ResizableTablePanel 的 fitColumn(int column)resetToDefaults() 等方法在人为触发自适应或恢复;

容器变化响应

  • ResizableTablePanel 注册自身父级容器的 ComponentListener,在大小变化后根据模式执行整体列宽分配逻辑;

五、完整实现代码

// ===== 文件:ColumnWidthConfig.java =====
package com.example.resizetable;
 
import java.util.Map;
import java.util.prefs.Preferences;
 
/**
 * 持久化列宽配置:使用 Java Preferences API 存储用户列宽偏好
 */
public class ColumnWidthConfig {
    private static final String NODE = "/com/example/resizetable/columnwidth";
    private final Preferences prefs = Preferences.userRoot().node(NODE);
    private final String tableKey;
 
    public ColumnWidthConfig(String tableKey) {
        this.tableKey = tableKey;
    }
 
    /** 保存单列宽度 */
    public void saveWidth(int colIndex, int width) python{
        prefs.putInt(tableKey + ".col." + colIndex, width);
    }
 
    /** 加载单列宽度,若无配置则返回 -1 */
    public int loadWidth(int colIndex) {
        return prefs.getInt(tableKey + ".col." + colIndex, -1);
    }
 
    /** 清除所有列宽配置 */
    public void clear() {
        try {
            for (String key : prefs.keys()) {
                if (key.startsWith(tableKey + ".col.")) {
                    prefs.remove(key);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /** 保存多列宽度 */
    public void saveAll(Map<Integer, Integer> widths) {
        widths.forEach(this::saveWidth);
    }
}
 
// ===== 文件:DimensionController.java =====
package com.example.resizetable;
 
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
imjavascriptport java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ExecutionException;
 
/**
 * 列宽控制器:自动/手动调整列宽,响应容器变化,并持久化配置
 */
public class DimensionController {
    private final JTable table;
    private final ColumnWidthConfig config;
    private final int sampleRows;
    private Timer saveTimer;
 
    public DimensionController(JTable table, ColumnWidthConfig config, int sampleRows) {
        this.table = table;
        this.config = config;
        this.sampleRows = sampleRows;
        initSaveDebounce();
        installModelListener();
    }
 
    /** 初始化防抖定时器,等待用户停止拖拽后再保存 */
    private void initSaveDebounce() {
        saveTimer = new Timer(500, e -> saveConfig());
        saveTimer.setRepeats(false);
    }
 
    /** 安装列宽变化监听,触发防抖保存 */
    private void installModelListener() {
        table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
            @Override public void columnMarginChanged(ChangeEvent e) {
                saveTimer.restart();
            }
            @Override public void columnMoved(TableColumnModelEvent e) {}
            @Override public void columnAdded(TableColumnModelEvent e) {}
            @Override public void columnRemoved(TableColumnModelEvent e) {}
            @Override public void columnSelectionChanged(ListSelectionEvent e) {}
        });
    }
 
    /** 自动调整所有列宽(后台线程) */
    public void autoAdjustAll() {
        new SwingWorker<Map<Integer, Integer>, Void>() {
            @Override
            protected Map<Integer, Integer> doInBackground() {
                Map<Integer, Integer> result = new HashMap<>();
                TableColumnModel cm = table.getColumnModel();
                for (int col = 0; col < cm.getColumnCount(); col++) {
                    int width = measureColumn(col);
                    result.put(col, width);
                }
                return result;
            }
            @Override
            protected void done() {
                try {
                    Map<Integer, Integer> widths = get();
                    widths.forEach((col, w) -> table.getColumnModel()
                            .getColumn(col).setPreferredWidth(w));
                    saveConfig();
                } catch (InterruptedException | ExecutionException ex) {
                    ex.printStackTrace();
                }
            }
        }.execute();
    }
 
    /** 测量单列所需宽度 */
    private int measureColumn(int col) {
        int max = 0;
        TableColumn tc = table.getColumnModel().getColumn(col);
        // header
        TableCellRenderer hr = tc.getHeaderRenderer();
        if (hr == null) hr = table.getTableHeader().getDefaultRenderer();
        Component c = hr.getTableCellRendererComponent(
                table, tc.getHeaderValue(), false, false, -1, col);
        max = c.getPreferredSize().width;
        // sample rows
        int rowCount = Math.min(sampleRows, table.getRowCount());
        for (int row = 0; row < rowCount; row++) {
            TableCellRenderer cr = table.getCellRenderer(row, col);
            c = cr.getTableCellRendererComponent(
                    table, table.getValueAt(row, col),
                    false, false, row, col);
            max = Math.max(max, c.getPreferredSize().width);
        }
        // 加入一点缓冲
        return max + 10;
    }
 
    /** 恢复持久化配置的列宽 */
    public void restoreConfig() {
        TableColumnModel cm = table.getColumnModel();
        for (int col = 0; col < cm.getColumnCount(); col++) {
            int w = config.loadWidth(col);
            if (w > 0) cm.getColumn(col).setPreferredWidth(w);
        }
    }
 
    /** 保存当前列宽到配置 */
    public void saveConfig() {
        TableColumnModel cm = table.getColumnModel();
        Map<Integer, Integer> widths = new HashMap<>();
        for (int col = 0; col < cm.getColumnCount(); col++) {
            widths.put(col, cm.getColumn(col).getWidth());
        }
        config.saveAll(widths);
    }
 
    /** 清除所有持久化并恢复默认 */
    public编程 void clearAndDefault() {
        config.clear();
        autoAdjustAll();
    }
 
    /** 编程方式设置单列宽度 */
    public void setColumnWidth(int col, int width) {
        table.getColumnModel().getColumn(col).setPreferredWidth(width);
        saveConfig();
    }
 
    /** 获取单列当前宽度 */
    public int getColumnWidth(int col) {
        return table.getColumnModel().getColumn(col).getWidth();
    }
}
 
// ===== 文件:ResizableTablePanel.java =====
package com.example.resizetable;
 
import javax.swing.*;
import java.awt.*;
 
/**
 * 自适应表格面板:封装 JTable、滚动条和宽度控制
 */
public class ResizableTablePanel extends JPanel {
    private final JTable table;
    private final DimensionController controller;
 
    public ResizableTablePanel(Object[][] data, Object[] columns, String tableKey) {
        super(new BorderLayout());
        table = new JTable(data, columns);
        ColumnWidthConfig config = new ColumnWidthConfig(tableKey);
        controller = new DimensionController(table, config, 50);
        // 恢复历史配置,若无则自动调整
        controller.restoreConfig();
        if (config.loadWidth(0) < 0) {
            controller.autoAdjustAll();
        }
        add(new jscrollPane(table), BorderLayout.CENTER);
    }
 
    // 对外 API
    public void fitAllColumns() { controller.autoAdjustAll(); }
    public void resetWidths() { controller.clearAndDefault(); }
    public void shttp://www.chinasem.cnetColumnWidth(int col, int w) { controller.setColumnWidth(col, w); }
    public int getColumnWidth(int col) { return controller.getColumnWidth(col); }
}

六、代码详细解读

ColumnWidthConfig.java

  • 使用 Java Preferences API(userRoot 节点)存储以 tableKey.col.<index> 为键的列宽整数;
  • 提供单列保存/加载、批量保存及清除所有配置的方法,实现与平台无关的轻量持久化。

DimensionController.java

  • 构造时接收 JTable、ColumnWidthConfig 及采样行数 sampleRows;
  • 自动调整 (autoAdjustAll):使用 SwingWorker 在后台测量每列所需宽度,考虑表头和前 sampleRows 行内容,完成后在 EDT 中批量应用并保存;
  • 测量算法 (measureColumn):分别测量表头和可见单元格的 Component.getPreferredSize().width,取最大值并加缓冲;
  • 持久化保存:监听 columnMarginChanged 事件,使用防抖 Timer 延迟 500ms 后调用 saveConfig,避免拖拽过程中频繁写入;
  • 恢复配置 (restoreConfig):在初始化时读取并应用上次保存的列宽;
  • API 可编程调用:提供 setColumnWidth、getColumnWidth、clearAndDefault 等方法,满足业务动态调整需求。

ResizableTablePanel.java

  • 将 JTable 与滚动面板封装在 JPanel 中,并创建 DimensionController;
  • 初始化时先调用 restoreConfig 恢复上次配置,再判断是否存在历史配置,否则调用 autoAdjustAll 自动自适应;
  • 对外暴露 fitAllColumns、resetWidths、setColumnWidth、getColumnWidth 等简洁 API,便于集成。

七、项目详细总结

本项目提供了一套完整的 Java Swing JTable 列宽自动/手动调整与持久化方案:

  • 利用渲染器测量与后台线程异步计算,确保在大数据场景下快速、平滑地完成自适应;
  • 通过 Preferences API 实现轻量且跨平台的列宽持久化,用户下次启动即可恢复上次自定义设置;
  • 采用防抖 Timer 与 TableColumnModelListener,保障拖拽过程中不频繁写入,提升性能与响应;
  • 封装 ResizableTablePanel 与 DimensionController,对外提供简洁、可编程的 API,便于在各种 Swing 应用中复用。

八、项目常见问题及解答

Q:为何自动调整后列宽仍被截断?
A:请检查 sampleRows 是否足够大,如果数据分布不均,可增大采样行数或改为遍历可见行。

Q:持久化配置找不到或未生效?
A:tableKey 应唯一标识不同表格,避免冲突;可使用类名或业务名称作为 tableKey。

Q:拖拽调整列宽卡顿?
A:拖拽过程仅读取内存并更新 UI,不应进行 IO;若仍卡顿,请确认没有在监听器中执行耗时操作。

Q:如何在窗口大小变化时按比例分配宽度?
A:可在外层容器 ComponentListener 中调用自定义逻辑,例如获取增量并均匀分配给未锁定列。

Q:如何支持行高自适应?
A:可仿照列宽实现,在 DimensionController 中增加 autoAdjustRowHeights(),测量行内容高度并调用 table.setRowHeight(row, height)。

九、扩展方向与性能优化

行高自适应

  • 在 DimensionController 中添加行高测量与设置功能,定制多行/富文本行高。

配置持久化多选方案

  • 支持 .json、.XML 等多种存储格式,可导入/导出配置文件;

容器大小响应策略

  • 提供“保持总宽度”与“填满可用宽度”两种自动模式,结合滑块 UI 让用户可视化切换;

缓存与性能

  • 对列宽测量结果做 LRU 缓存,避免在同一列上多次重复测量;
  • 在测量时仅对前 N 列或活跃区域执行,提高初始加载速度。

插件化与钩子

  • 在 DimensionController 中提供监听接口,如 addDimensionChangeListener,让业务逻辑在宽高变化时执行自定义操作(动画、日志等)。

以上就是Java实现自定义table宽高的示例代码的详细内容,更多关于Java自定义table宽高的资料请关注China编程(www.chinasem.cn)其它相关文章!

这篇关于Java实现自定义table宽高的示例代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155175

相关文章

SpringBoot中4种数据水平分片策略

《SpringBoot中4种数据水平分片策略》数据水平分片作为一种水平扩展策略,通过将数据分散到多个物理节点上,有效解决了存储容量和性能瓶颈问题,下面小编就来和大家分享4种数据分片策略吧... 目录一、前言二、哈希分片2.1 原理2.2 SpringBoot实现2.3 优缺点分析2.4 适用场景三、范围分片

一文详解Java Stream的sorted自定义排序

《一文详解JavaStream的sorted自定义排序》Javastream中的sorted方法是用于对流中的元素进行排序的方法,它可以接受一个comparator参数,用于指定排序规则,sorte... 目录一、sorted 操作的基础原理二、自定义排序的实现方式1. Comparator 接口的 Lam

SpringBoot开发中十大常见陷阱深度解析与避坑指南

《SpringBoot开发中十大常见陷阱深度解析与避坑指南》在SpringBoot的开发过程中,即使是经验丰富的开发者也难免会遇到各种棘手的问题,本文将针对SpringBoot开发中十大常见的“坑... 目录引言一、配置总出错?是不是同时用了.properties和.yml?二、换个位置配置就失效?搞清楚加

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

利用Python脚本实现批量将图片转换为WebP格式

《利用Python脚本实现批量将图片转换为WebP格式》Python语言的简洁语法和库支持使其成为图像处理的理想选择,本文将介绍如何利用Python实现批量将图片转换为WebP格式的脚本,WebP作为... 目录简介1. python在图像处理中的应用2. WebP格式的原理和优势2.1 WebP格式与传统

SpringBoot集成LiteFlow工作流引擎的完整指南

《SpringBoot集成LiteFlow工作流引擎的完整指南》LiteFlow作为一款国产轻量级规则引擎/流程引擎,以其零学习成本、高可扩展性和极致性能成为微服务架构下的理想选择,本文将详细讲解Sp... 目录一、LiteFlow核心优势二、SpringBoot集成实战三、高级特性应用1. 异步并行执行2

python如何调用java的jar包

《python如何调用java的jar包》这篇文章主要为大家详细介绍了python如何调用java的jar包,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以参考一下... 目录一、安装包二、使用步骤三、代码演示四、自己写一个jar包五、打包步骤六、方法补充一、安装包pip3 install

怎么用idea创建一个SpringBoot项目

《怎么用idea创建一个SpringBoot项目》本文介绍了在IDEA中创建SpringBoot项目的步骤,包括环境准备(JDK1.8+、Maven3.2.5+)、使用SpringInitializr... 目录如何在idea中创建一个SpringBoot项目环境准备1.1打开IDEA,点击New新建一个项

C++ 检测文件大小和文件传输的方法示例详解

《C++检测文件大小和文件传输的方法示例详解》文章介绍了在C/C++中获取文件大小的三种方法,推荐使用stat()函数,并详细说明了如何设计一次性发送压缩包的结构体及传输流程,包含CRC校验和自动解... 目录检测文件的大小✅ 方法一:使用 stat() 函数(推荐)✅ 用法示例:✅ 方法二:使用 fsee