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

