Java實(shí)現(xiàn)自定義table寬高的示例代碼
一、項(xiàng)目背景詳細(xì)介紹
在桌面應(yīng)用、管理系統(tǒng)乃至報(bào)表工具中,表格(JTable)作為最常用的數(shù)據(jù)展示組件,不僅承載對(duì)數(shù)據(jù)的增刪改查,還需要配合布局與視覺需求,實(shí)現(xiàn)不同場(chǎng)景下的寬度與高度自適應(yīng)或定制化展示。例如:
- 儀表盤與監(jiān)控面板:實(shí)時(shí)數(shù)據(jù)顯示區(qū),往往需要讓表格填滿容器或保持固定比例,以便與圖表、指標(biāo)板并排展示。
- 編輯與錄入表單:作為表格控件的擴(kuò)展,要求表格行高增大、列寬更寬,以便放置可編輯組件(如文本框、下拉框)。
- 多視圖切換:在同一應(yīng)用中,可能需要不同風(fēng)格的表格——緊湊型列表、詳細(xì)型列表、卡片式列表等,需動(dòng)態(tài)調(diào)整行高、列寬、滾動(dòng)策略等。
- 打印與導(dǎo)出:將表格導(dǎo)出為 PDF/Excel 時(shí),需要基于頁面尺寸或紙張布局自定義行高列寬,以保證打印效果。
而 Java Swing 的 JTable 默認(rèn)行高和列寬均采用系統(tǒng)或 L&F 的默認(rèn)值,僅通過 setRowHeight、setPreferredWidth 等方法做靜態(tài)設(shè)置。要滿足上述多樣化需求,需要一套靈活、可配置且易擴(kuò)展的“自定義表格寬高”方案。本項(xiàng)目將全面覆蓋從需求分析、技術(shù)選型、架構(gòu)設(shè)計(jì),到核心實(shí)現(xiàn)、接口設(shè)計(jì)與性能優(yōu)化的全過程,幫助開發(fā)者在任意 Swing 應(yīng)用中快速集成并管理表格的寬度與高度。
二、項(xiàng)目需求詳細(xì)介紹
行高自定義
- 支持全局設(shè)置:為整張表一次性指定行高;
- 支持按行設(shè)置:根據(jù)模型數(shù)據(jù)或行索引,動(dòng)態(tài)調(diào)整某幾行的高度(如帶圖片、富文本的行更高);
列寬自定義
- 支持默認(rèn)寬度:根據(jù)列數(shù)據(jù)類型或列名,在初始化時(shí)為所有列分配合理寬度;
- 支持按列設(shè)置:動(dòng)態(tài)調(diào)整單列或多列寬度;
- 支持自適應(yīng)寬度:根據(jù)內(nèi)容(Header 與可見數(shù)據(jù))自動(dòng)計(jì)算最優(yōu)寬度;
響應(yīng)容器變化
- 當(dāng)表格所在滾動(dòng)面板或父容器大小變化時(shí),根據(jù)策略自動(dòng)調(diào)整“可伸縮”列寬;
- 支持總寬度固定或隨容器拉伸而改變兩種模式;
動(dòng)態(tài)接口
- 提供編程接口:
setGlobalRowHeight(int height); setRowHeight(int row, int height); setColumnWidth(int column, int width); fitColumnToContent(int column, int sampleRows); setFillViewportWidth(boolean fill);
- 支持批量設(shè)置與恢復(fù)默認(rèn);
持久化與用戶偏好
- 當(dāng)用戶手動(dòng)拖拽列寬或通過 API 調(diào)整后,能夠?qū)⒃O(shè)置保存(本地文件或數(shù)據(jù)庫),下次啟動(dòng)自動(dòng)恢復(fù);
- 支持多個(gè)表格場(chǎng)景的配置隔離;
性能與體驗(yàn)
- 在數(shù)據(jù)量大(萬行以上)或列數(shù)多(幾十列)時(shí),自動(dòng)計(jì)算與更新操作應(yīng)在后臺(tái) 完成,避免阻塞 EDT;
- 拖拽或接口調(diào)整時(shí),界面響應(yīng)流暢;
可擴(kuò)展與定制
- 可與表格排序、過濾、分組、編輯功能并行工作;
- 可針對(duì)富文本、圖表、按鈕等自定義渲染單元格的特殊行/列,動(dòng)態(tài)設(shè)置寬高;
- 提供鉤子接口,允許業(yè)務(wù)層對(duì)寬高變化做額外處理(如日志、動(dòng)畫效果);
三、相關(guān)技術(shù)詳細(xì)介紹
JTable 行高設(shè)置
table.setRowHeight(int rowHeight):一行行高統(tǒng)一設(shè)置;table.setRowHeight(int row, int rowHeight)(Java 1.7+):針對(duì)單行設(shè)置高度;- 自動(dòng)增長行高:通過
table.getRowSorter()在排序或過濾后重新計(jì)算行高。
TableColumn 與列寬控制
TableColumn對(duì)象提供setPreferredWidth、setMinWidth、setMaxWidth方法;table.getColumnModel().getColumn(int index)獲取目標(biāo)列;table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF/ALL_COLUMNS/LAST_COLUMN/…)控制拖拽與自動(dòng)填充行為;
自適應(yīng)寬度計(jì)算
- 通過渲染器測(cè)量:
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);
}- 只對(duì)可見行或抽樣行做測(cè)量,控制性能;
監(jiān)聽容器大小變化
- 通過
ComponentListener監(jiān)聽componentResized,在窗口、JSplitPane、JInternalFrame等大小變化后觸發(fā)列寬重分配;
后臺(tái)計(jì)算與 EDT 更新
- 使用
SwingWorker< Map<Integer,Integer>, Void>在后臺(tái)線程計(jì)算多列寬度映射; - 在
done()中調(diào)用SwingUtilities.invokeLater應(yīng)用設(shè)置;
持久化方案
- 簡易:Java
PreferencesAPI 或.properties; - 復(fù)雜:基于數(shù)據(jù)庫的配置表,支持多用戶多表持久化;
四、實(shí)現(xiàn)思路詳細(xì)介紹
模塊劃分
- ResizableTablePanel(視圖層):封裝
JTable與列寬、行高設(shè)置邏輯,暴露接口; - DimensionController(控制層):處理自動(dòng)計(jì)算、自適應(yīng)、持久化加載與保存;
- DimensionConfig(模型層):存儲(chǔ)用戶偏好配置,支持文件或數(shù)據(jù)庫讀寫。
初始化流程
- 構(gòu)造
ResizableTablePanel時(shí),載入DimensionConfig(讀取持久化配置); - 根據(jù)配置調(diào)用
setRowHeight、setColumnWidth等接口恢復(fù)上次設(shè)置; - 若無配置或需要自動(dòng)自適應(yīng),調(diào)用
autoAdjustAllColumns與setGlobalRowHeight;
自動(dòng)調(diào)整算法
- 選擇合適的抽樣行數(shù)(如前 50 行或所有可見行),并在后臺(tái)線程中測(cè)量所需寬度;
- 考慮列最小最大寬度約束,并合并 Header 與內(nèi)容寬度;
- 根據(jù)
AUTO_RESIZE_MODE決定是否在剩余空間平分或保持總寬度;
手動(dòng)拖拽與監(jiān)聽
- 利用
JTableHeader的拖拽行為,無需額外監(jiān)聽; - 在
TableColumnModelListener.columnMarginChanged中捕獲列寬變化,并延遲(防抖)調(diào)用DimensionController.saveConfig;
動(dòng)態(tài)接口調(diào)用
- 外部業(yè)務(wù)可通過
ResizableTablePanel的fitColumn(int column)、resetToDefaults()等方法在人為觸發(fā)自適應(yīng)或恢復(fù);
容器變化響應(yīng)
ResizableTablePanel注冊(cè)自身父級(jí)容器的ComponentListener,在大小變化后根據(jù)模式執(zhí)行整體列寬分配邏輯;
五、完整實(shí)現(xiàn)代碼
// ===== 文件:ColumnWidthConfig.java =====
package com.example.resizetable;
import java.util.Map;
import java.util.prefs.Preferences;
/**
* 持久化列寬配置:使用 Java Preferences API 存儲(chǔ)用戶列寬偏好
*/
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) {
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.*;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* 列寬控制器:自動(dòng)/手動(dòng)調(diào)整列寬,響應(yīng)容器變化,并持久化配置
*/
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();
}
/** 初始化防抖定時(shí)器,等待用戶停止拖拽后再保存 */
private void initSaveDebounce() {
saveTimer = new Timer(500, e -> saveConfig());
saveTimer.setRepeats(false);
}
/** 安裝列寬變化監(jiān)聽,觸發(fā)防抖保存 */
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) {}
});
}
/** 自動(dòng)調(diào)整所有列寬(后臺(tái)線程) */
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();
}
/** 測(cè)量單列所需寬度 */
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);
}
// 加入一點(diǎn)緩沖
return max + 10;
}
/** 恢復(fù)持久化配置的列寬 */
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);
}
}
/** 保存當(dāng)前列寬到配置 */
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);
}
/** 清除所有持久化并恢復(fù)默認(rèn) */
public void clearAndDefault() {
config.clear();
autoAdjustAll();
}
/** 編程方式設(shè)置單列寬度 */
public void setColumnWidth(int col, int width) {
table.getColumnModel().getColumn(col).setPreferredWidth(width);
saveConfig();
}
/** 獲取單列當(dāng)前寬度 */
public int getColumnWidth(int col) {
return table.getColumnModel().getColumn(col).getWidth();
}
}
// ===== 文件:ResizableTablePanel.java =====
package com.example.resizetable;
import javax.swing.*;
import java.awt.*;
/**
* 自適應(yīng)表格面板:封裝 JTable、滾動(dòng)條和寬度控制
*/
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);
// 恢復(fù)歷史配置,若無則自動(dòng)調(diào)整
controller.restoreConfig();
if (config.loadWidth(0) < 0) {
controller.autoAdjustAll();
}
add(new JScrollPane(table), BorderLayout.CENTER);
}
// 對(duì)外 API
public void fitAllColumns() { controller.autoAdjustAll(); }
public void resetWidths() { controller.clearAndDefault(); }
public void setColumnWidth(int col, int w) { controller.setColumnWidth(col, w); }
public int getColumnWidth(int col) { return controller.getColumnWidth(col); }
}六、代碼詳細(xì)解讀
ColumnWidthConfig.java
- 使用 Java Preferences API(userRoot 節(jié)點(diǎn))存儲(chǔ)以 tableKey.col.<index> 為鍵的列寬整數(shù);
- 提供單列保存/加載、批量保存及清除所有配置的方法,實(shí)現(xiàn)與平臺(tái)無關(guān)的輕量持久化。
DimensionController.java
- 構(gòu)造時(shí)接收 JTable、ColumnWidthConfig 及采樣行數(shù) sampleRows;
- 自動(dòng)調(diào)整 (autoAdjustAll):使用 SwingWorker 在后臺(tái)測(cè)量每列所需寬度,考慮表頭和前 sampleRows 行內(nèi)容,完成后在 EDT 中批量應(yīng)用并保存;
- 測(cè)量算法 (measureColumn):分別測(cè)量表頭和可見單元格的 Component.getPreferredSize().width,取最大值并加緩沖;
- 持久化保存:監(jiān)聽 columnMarginChanged 事件,使用防抖 Timer 延遲 500ms 后調(diào)用 saveConfig,避免拖拽過程中頻繁寫入;
- 恢復(fù)配置 (restoreConfig):在初始化時(shí)讀取并應(yīng)用上次保存的列寬;
- API 可編程調(diào)用:提供 setColumnWidth、getColumnWidth、clearAndDefault 等方法,滿足業(yè)務(wù)動(dòng)態(tài)調(diào)整需求。
ResizableTablePanel.java
- 將 JTable 與滾動(dòng)面板封裝在 JPanel 中,并創(chuàng)建 DimensionController;
- 初始化時(shí)先調(diào)用 restoreConfig 恢復(fù)上次配置,再判斷是否存在歷史配置,否則調(diào)用 autoAdjustAll 自動(dòng)自適應(yīng);
- 對(duì)外暴露 fitAllColumns、resetWidths、setColumnWidth、getColumnWidth 等簡潔 API,便于集成。
七、項(xiàng)目詳細(xì)總結(jié)
本項(xiàng)目提供了一套完整的 Java Swing JTable 列寬自動(dòng)/手動(dòng)調(diào)整與持久化方案:
- 利用渲染器測(cè)量與后臺(tái)線程異步計(jì)算,確保在大數(shù)據(jù)場(chǎng)景下快速、平滑地完成自適應(yīng);
- 通過 Preferences API 實(shí)現(xiàn)輕量且跨平臺(tái)的列寬持久化,用戶下次啟動(dòng)即可恢復(fù)上次自定義設(shè)置;
- 采用防抖 Timer 與 TableColumnModelListener,保障拖拽過程中不頻繁寫入,提升性能與響應(yīng);
- 封裝 ResizableTablePanel 與 DimensionController,對(duì)外提供簡潔、可編程的 API,便于在各種 Swing 應(yīng)用中復(fù)用。
八、項(xiàng)目常見問題及解答
Q:為何自動(dòng)調(diào)整后列寬仍被截?cái)啵?br />A:請(qǐng)檢查 sampleRows 是否足夠大,如果數(shù)據(jù)分布不均,可增大采樣行數(shù)或改為遍歷可見行。
Q:持久化配置找不到或未生效?
A:tableKey 應(yīng)唯一標(biāo)識(shí)不同表格,避免沖突;可使用類名或業(yè)務(wù)名稱作為 tableKey。
Q:拖拽調(diào)整列寬卡頓?
A:拖拽過程僅讀取內(nèi)存并更新 UI,不應(yīng)進(jìn)行 IO;若仍卡頓,請(qǐng)確認(rèn)沒有在監(jiān)聽器中執(zhí)行耗時(shí)操作。
Q:如何在窗口大小變化時(shí)按比例分配寬度?
A:可在外層容器 ComponentListener 中調(diào)用自定義邏輯,例如獲取增量并均勻分配給未鎖定列。
Q:如何支持行高自適應(yīng)?
A:可仿照列寬實(shí)現(xiàn),在 DimensionController 中增加 autoAdjustRowHeights(),測(cè)量行內(nèi)容高度并調(diào)用 table.setRowHeight(row, height)。
九、擴(kuò)展方向與性能優(yōu)化
行高自適應(yīng)
- 在 DimensionController 中添加行高測(cè)量與設(shè)置功能,定制多行/富文本行高。
配置持久化多選方案
- 支持 .json、.xml 等多種存儲(chǔ)格式,可導(dǎo)入/導(dǎo)出配置文件;
容器大小響應(yīng)策略
- 提供“保持總寬度”與“填滿可用寬度”兩種自動(dòng)模式,結(jié)合滑塊 UI 讓用戶可視化切換;
緩存與性能
- 對(duì)列寬測(cè)量結(jié)果做 LRU 緩存,避免在同一列上多次重復(fù)測(cè)量;
- 在測(cè)量時(shí)僅對(duì)前 N 列或活躍區(qū)域執(zhí)行,提高初始加載速度。
插件化與鉤子
- 在 DimensionController 中提供監(jiān)聽接口,如 addDimensionChangeListener,讓業(yè)務(wù)邏輯在寬高變化時(shí)執(zhí)行自定義操作(動(dòng)畫、日志等)。
以上就是Java實(shí)現(xiàn)自定義table寬高的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Java自定義table寬高的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在IDEA啟動(dòng)多個(gè)Spring Boot工程實(shí)例
這篇文章主要介紹了在IDEA啟動(dòng)多個(gè)Spring Boot工程實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
解決springboot 無法配置多個(gè)靜態(tài)路徑的問題
這篇文章主要介紹了解決springboot 無法配置多個(gè)靜態(tài)路徑的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring中ApplicationListener的使用解析
這篇文章主要介紹了Spring中ApplicationListener的使用解析,ApplicationContext事件機(jī)制是觀察者設(shè)計(jì)模式的實(shí)現(xiàn),通過ApplicationEvent類和ApplicationListener接口,需要的朋友可以參考下2023-12-12
Netty分布式從recycler對(duì)象回收站獲取對(duì)象過程剖析
這篇文章主要為大家介紹了Netty分布式從recycler獲取對(duì)象的過程源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03

