You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 07:01:09