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