JavaFX TableView自定义单元格列自适应宽度失效问题求助
问题描述
在JavaFX 14中,resizeColumnToFitContent方法可实现列宽自适应表头与数据内容,该方法对普通列有效,但当使用CheckBoxTableCell.forTableColumn创建列或自定义TableCell(如带过滤的下拉框列)时,列始终保持默认80宽度,表头文本被截断(示例中"Enabled Test"表头显示为"Enabled...")。
复现代码
import scalafx.application.JFXApp3 import scalafx.application.JFXApp3.PrimaryStage import scalafx.scene.Scene import scalafx.scene.control.TableView import scalafx.scene.control.TableColumn import scalafx.scene.control.cell.CheckBoxTableCell import scalafx.beans.property.BooleanProperty import scalafx.beans.value.ObservableValue import scalafx.collections.ObservableBuffer import javafx.scene.control.TableColumnBase import javafx.scene.control.skin.{NestedTableColumnHeader,TableColumnHeader,TableHeaderRow,TableViewSkin} import scalafx.application.Platform object TableColumnWidth extends JFXApp3 { override def start(): Unit = { stage = new PrimaryStage { title = "Test Table Column Width Problem" scene = new Scene { val ob = ObservableBuffer[TableColumnData]() val table = new TableView[TableColumnData](ob) { def createDefaultSkin = new TableViewSkin(this) { override protected def createTableHeaderRow:TableHeaderRow = { new TableHeaderRow(this) { override protected def createRootHeader:NestedTableColumnHeader = { new NestedTableColumnHeader(null) { override protected def createTableColumnHeader(col:TableColumnBase[_,_]) = { if(col == null || col.getColumns.isEmpty || col == getTableColumn) new FormFxTableColumnHeader(col) else new NestedTableColumnHeader(col) } } } } } } columns ++= Seq{new TableColumn[TableColumnData,java.lang.Boolean](){ text = "Enabled Test" cellValueFactory = { c => (new BooleanProperty(this,"Enabled",c.value.enabled) { onChange{ (_,_,newValue) => c.value.enabled = newValue } }).asInstanceOf[ObservableValue[java.lang.Boolean,java.lang.Boolean]] } cellFactory = CheckBoxTableCell.forTableColumn(this) editable = true resizable = false } } editable = true } root = table ob ++= Seq(TableColumnData("First",false), TableColumnData("Second",false)) Platform.runLater(() => { val w = table.columns.map { col => col.getStyleableNode() match { case mtch:FormFxTableColumnHeader => mtch.resizeCol() case u => col.getWidth() } }.sum + 25 table.prefWidth = w table.minWidth = w table.maxWidth = w }) sizeToScene() } } } } class FormFxTableColumnHeader(tc:TableColumnBase[_,_]) extends TableColumnHeader(tc) { def resizeCol():Double = { resizeColumnToFitContent(-1) tc.getWidth() } } class TableColumnData(var name:String,var enabled:Boolean)
解决方案
问题根源在于resizeColumnToFitContent默认仅计算单元格内容宽度,但对于CheckBoxTableCell这类特殊单元格,其默认prefWidth较小,且未将表头文本的宽度纳入最终列宽计算;同时列的resizable=false设置会限制列宽调整的生效。以下是针对性的配置修改:
1. 完善表头与单元格宽度的双重计算
在自定义FormFxTableColumnHeader的resizeCol方法中,同时计算表头文本的宽度,取表头宽度与单元格内容宽度的最大值作为最终列宽:
class FormFxTableColumnHeader(tc:TableColumnBase[_,_]) extends TableColumnHeader(tc) { def resizeCol():Double = { // 临时开启列可调整,确保resize方法生效 tc.setResizable(true) // 调用默认自适应方法计算单元格内容宽度 resizeColumnToFitContent(-1) // 计算表头文本的实际宽度(包含内边距) val headerLabel = getLabel() val headerTextWidth = headerLabel.prefWidth(-1) + getInsets().getLeft() + getInsets().getRight() // 取单元格内容宽度与表头文本宽度的最大值 val finalWidth = math.max(tc.getWidth(), headerTextWidth) // 设置列的固定宽度 tc.setPrefWidth(finalWidth) // 恢复列不可调整的设置 tc.setResizable(false) finalWidth } }
2. 确保UI渲染完成后再执行宽度调整
原代码中Platform.runLater已保证在UI线程执行,但如果数据量较大,可监听TableView的items变化事件,确保数据完全加载后再计算宽度:
// 替换原Platform.runLater代码 ob.onChange { (_, _, _) => Platform.runLater(() => { val w = table.columns.map { col => col.getStyleableNode() match { case mtch:FormFxTableColumnHeader => mtch.resizeCol() case u => col.getWidth() } }.sum + 25 table.prefWidth = w table.minWidth = w table.maxWidth = w }) } // 初始加载数据 ob ++= Seq(TableColumnData("First",false), TableColumnData("Second",false))
3. 自定义TableCell的宽度适配(针对自定义单元格场景)
如果是自己实现的自定义TableCell,需要重写computePrefWidth方法,确保返回内容的实际宽度:
class CustomTableCell[S,T] extends TableCell[S,T] { override protected def updateItem(item: T, empty: Boolean): Unit = { super.updateItem(item, empty) // 设置单元格内容逻辑... } override def computePrefWidth(height: Double): Double = { if (isEmpty) 0.0 else { // 计算内容的实际宽度,比如文本的宽度或控件的prefWidth val contentWidth = getChildren().get(0).prefWidth(height) contentWidth + getInsets().getLeft() + getInsets().getRight() } } }
内容的提问来源于stack exchange,提问作者David Goodenough
相关产品推荐
相关产品推荐

