afaik javafx 中的 TableView 有 2 列调整大小策略:CONSTRAINED_RESIZE_POLICY 和 UNCONSTRAINED_RESIZE_POLICY,但我希望列调整大小以适合其单元格的内容我认为这是其他平台中的一个简单问题(如 C# 中的 datagridview)但无法解决
15 回答
3年后我再次回到这个问题,一些建议是计算每个单元格中数据文本的大小(这很复杂,取决于字体大小、字体系列、填充......)
但是我意识到,当我单击表头上的分隔线时,它会根据我的需要调整大小以适应内容。于是我深入JavaFX源代码,终于在TableViewSkin中找到了resizeColumnToFitContent方法,但它是受保护的方法,我们可以通过反射来解决:
import com.sun.javafx.scene.control.skin.TableViewSkin;
import javafx.scene.control.Skin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class GUIUtils {
private static Method columnToFitMethod;
static {
try {
columnToFitMethod = TableViewSkin.class.getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
columnToFitMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public static void autoFitTable(TableView tableView) {
tableView.getItems().addListener(new ListChangeListener<Object>() {
@Override
public void onChanged(Change<?> c) {
for (Object column : tableView.getColumns()) {
try {
columnToFitMethod.invoke(tableView.getSkin(), column, -1);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
});
}
}
注意我们调用“ tableView.getItems() ”所以我们必须在setItems()之后调用这个函数
在测试了以前的解决方案后,我终于找到了一个适合我的解决方案。所以这是我的(将数据插入表后调用该方法):
public static void autoResizeColumns( TableView<?> table )
{
//Set the right policy
table.setColumnResizePolicy( TableView.UNCONSTRAINED_RESIZE_POLICY);
table.getColumns().stream().forEach( (column) ->
{
//Minimal width = columnheader
Text t = new Text( column.getText() );
double max = t.getLayoutBounds().getWidth();
for ( int i = 0; i < table.getItems().size(); i++ )
{
//cell must not be empty
if ( column.getCellData( i ) != null )
{
t = new Text( column.getCellData( i ).toString() );
double calcwidth = t.getLayoutBounds().getWidth();
//remember new max-width
if ( calcwidth > max )
{
max = calcwidth;
}
}
}
//set the new max-widht with some extra space
column.setPrefWidth( max + 10.0d );
} );
}
我认为仅通过覆盖返回 true 的回调函数就可以解决您的问题,它将禁用重新调整列的大小,并且所有列都将重新调整大小以适合其单元格的内容。
例子:
TableView<String[]> table = new TableView<>();
table.setColumnResizePolicy(new Callback<TableView.ResizeFeatures, Boolean>() {
@Override
public Boolean call(ResizeFeatures p) {
return true;
}
});
如果您希望只有一列填充表格的剩余宽度,我找到了一个非常简单的解决方案,它很短并且不需要上面描述的 hacky 反射解决方案:
DoubleBinding usedWidth = columnA.widthProperty().add(columnB.widthProperty()).add(columnC.widthProperty());
fillingColumn.prefWidthProperty().bind(tableView.widthProperty().subtract(usedWidth));
或者简而言之:
// automatically adjust width of columns depending on their content
configAttributeTreeTable.setColumnResizePolicy((param) -> true );
我在这个问题上使用了其他解决方案,效果很好。但是,这样做的缺点是当 TableView 的宽度大于 TableColumns 所需的宽度时。我创建了一个 hack 来解决这个问题,它工作正常:
orderOverview.setColumnResizePolicy((param) -> true );
Platform.runLater(() -> FXUtils.customResize(orderOverview));
其中 FXUtils.customResize() 创建如下:
public static void customResize(TableView<?> view) {
AtomicDouble width = new AtomicDouble();
view.getColumns().forEach(col -> {
width.addAndGet(col.getWidth());
});
double tableWidth = view.getWidth();
if (tableWidth > width.get()) {
TableColumn<?, ?> col = view.getColumns().get(view.getColumns().size()-1);
col.setPrefWidth(col.getWidth()+(tableWidth-width.get()));
}
}
我希望这对其他人也有帮助!
这是我发现的方式:
tableview.setColumnResizePolicy( TableView.CONSTRAINED_RESIZE_POLICY );
idCol.setMaxWidth( 1f * Integer.MAX_VALUE * 50 ); // 50% width
nameCol.setMaxWidth( 1f * Integer.MAX_VALUE * 30 ); // 30% width
ageCol.setMaxWidth( 1f * Integer.MAX_VALUE * 20 ); // 20% width
此代码自动调整与表格宽度成比例的所有列宽,
而当表格宽度小于 x 时,它可以将第一列宽度固定为给定值
// To generalize the columns width proportions in relation to the table width,
// you do not need to put pixel related values, you can use small float numbers if you wish,
// because it's the relative proportion of each columns width what matters here:
final float[] widths = { 1.2f, 2f, 0.8f };// define the relational width of each column
// whether the first column should be fixed
final boolean fixFirstColumm = true;
// fix the first column width when table width is lower than:
final float fixOnTableWidth = 360; //pixels
// calulates sum of widths
float sum = 0;
for (double i : widths) {
sum += i;
}
// calculates the fraction of the first column proportion in relation to the sum of all column proportions
float firstColumnProportion = widths[0] / sum;
// calculate the fitting fix width for the first column, you can change it by your needs, but it jumps to this width
final float firstColumnFixSize = fixOnTableWidth * firstColumnProportion;
// set the width to the columns
for (int i = 0; i < widths.length; i++) {
table.getColumns().get(i).prefWidthProperty().bind(table.widthProperty().multiply((widths[i] / sum)));
// ---------The exact width-------------^-------------^
if (fixFirstColumm)
if (i == 0) {
table.widthProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> arg0, Number oldTableWidth, Number newTableWidth) {
if (newTableWidth.intValue() <= fixOnTableWidth) {
// before you can set new value to column width property, need to unbind the autoresize binding
table.getColumns().get(0).prefWidthProperty().unbind();
table.getColumns().get(0).prefWidthProperty().setValue(firstColumnFixSize);
} else if (!table.getColumns().get(0).prefWidthProperty().isBound()) {
// than readd the autoresize binding if condition table.width > x
table.getColumns().get(0).prefWidthProperty()
.bind(table.widthProperty().multiply(firstColumnProportion));
}
}
});
}
}
建议将代码放在单独的 TableAutoresizeModel 类中,在那里您可以处理进一步的计算,例如在隐藏列时添加侦听器...
我实现了一个解决方案,它比我在这里找到的解决方案要复杂得多,但是它允许通过双击标题来调整特定列的大小,同时仍然让用户手动调整列的大小。
这是通过监听表头 ( TableHeaderRow
) 上的点击事件来实现的。当发生双击时,通过匹配鼠标事件 X 和 Y 来找到特定的列标题。
注意:要完成这项工作,每列都必须设置一个 ID。
// when skin is loaded (hence css), setup click listener on header to make column fit to content max width on double click
tableView.skinProperty().addListener((a, b, newSkin) -> {
TableHeaderRow headerRow = (TableHeaderRow) tableView.lookup("TableHeaderRow");
NestedTableColumnHeader headers = (NestedTableColumnHeader) (headerRow.getChildren().get(1));
headerRow.setOnMouseClicked(evt -> {
if (evt.getClickCount() != 2 || evt.getButton() != MouseButton.PRIMARY) return;
// find the header column that contains the click
for (TableColumnHeader header : headers.getColumnHeaders()) {
if (header.contains(header.parentToLocal(evt.getX(), evt.getY()))) {
fitColumnWidthToContent(header.getId());
}
}
evt.consume();
});
});
负责调整大小的方法如下:
private void fitColumnWidthToContent (String colId) {
// find column matching id
TableColumn column = null;
for (TableColumn tempCol : tableView.getColumns()) {
if (tempCol.getId().equals(colId)) {
column = tempCol;
break;
}
}
if (column == null) {
throw new IllegalStateException("Column ID doesn't match any actual column");
}
// set default width to column header width
Text text = new Text(column.getText());
double max = text.getLayoutBounds().getWidth();
for (int i = 0; i < tableView.getItems().size(); i++ ) {
if (column.getCellData(i) == null) continue;
text = new Text(column.getCellData(i).toString());
double textWidth = text.getLayoutBounds().getWidth();
if (textWidth > max) {
max = textWidth;
}
}
column.setPrefWidth(max + 12);
}
我希望这对任何人都有用。
为了也允许手动调整大小,有必要在表初始化上添加更多代码:
// listen to width changes in columns and set to pref width (otherwise if for example width changes because of
// user resizing the column, applying the old pref width won't work because it stayed the same)
for (TableColumn col : tableView.getColumns()) {
col.widthProperty().addListener((obs, oldVal, newVal) -> {
col.setPrefWidth(newVal.doubleValue());
});
}
我已经为 TreeTableView 实现了一个解决方案。它仍在进化中,但现在显示出可喜的结果。以下是解决方案的说明。
在控件外观类中,我向控件子控件添加了 TreeTableView 和一个不可见的 VBox。单元工厂向目标 TreeTableColumn 提供派生单元。派生单元格包裹一个Label节点,该节点根据空属性添加或删除到不可见的VBox中,其prefWidth根据单元格宽度设置。细胞利用:
getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE)
我覆盖单元格的 computePrefWidth() 方法如下:
@Override
protected double computePrefWidth(double height) {
return Double.max(_box.prefWidth(-1.0), super.computePrefWidth(height) + 24.0);
}
Vbox 宽度属性绑定到 TreeTableColumn 的 prefWidth。这也是调整列标题的大小所必需的。
值得注意的是,目前,为了简化解决方案的开发,这种方法在禁用内置排序、排序和调整大小功能的情况下效果很好。IE。
_nameColumn = new TreeTableColumn<>("Name");
_nameColumn.setResizable(false);
_nameColumn.setReorderable(false);
_nameColumn.setSortable(false);
快乐编码
@HarleyDavidson 在 kotlin 中的回答
val String.fxWidth: Double
get() = Text(this).layoutBounds.width
// call the method after inserting the data into table
fun <T> TableView<T>.autoResizeColumns() {
columnResizePolicy = TableView.UNCONSTRAINED_RESIZE_POLICY
columns.forEach { column ->
column.setPrefWidth(
(((0 until items.size).mapNotNull {
column.getCellData(it)
}.map {
it.toString().fxWidth
}.toMutableList() + listOf(
column.text.fxWidth
)).maxOrNull() ?: 0.0) + 10.0
)
}
}
这将根据字体和文本设置列的最小宽度,以便不会裁剪列名。
public static void setDataTableMinColumnWidth(TableView<?> dataTable)
{
for (Node columnHeader : dataTable.lookupAll(".column-header"))
{
var columnString = columnHeader.getId();
if (columnString != null)
{
for (Node columnHeaderLabel : columnHeader.lookupAll(".label"))
{
var tableColumn = dataTable.getColumns()
.stream()
.filter(x -> x.getId()
.equals(columnString))
.findFirst();
if (columnHeaderLabel instanceof Label && tableColumn.isPresent())
{
var label = (Label) columnHeaderLabel;
/* calc text width based on font */
var theText = new Text(label.getText());
theText.setFont(label.getFont());
var width = theText.getBoundsInLocal()
.getWidth();
/*
* add 10px because of paddings/margins for the button
*/
tableColumn.get()
.setMinWidth(width + 10);
}
}
}
}
}
如何使用:
dataTable.needsLayoutProperty()
.addListener((obs, o, n) -> setDataTableMinColumnWidth(dataTable));
对于列,需要首先设置 id 属性:
TableColumn<BundImportTask, String> columnTask = new TableColumn<>("My Column");
columnTask.setId("MyColumnId");
columnTask.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()
.fileName()));
经过长时间的研究。最好的解决方案是..
tblPlan.setColumnResizePolicy((param) -> true );
Platform.runLater(() -> customResize(tblPlan));
“自定义调整大小”
public void customResize(TableView<?> view) {
AtomicLong width = new AtomicLong();
view.getColumns().forEach(col -> {
width.addAndGet((long) col.getWidth());
});
double tableWidth = view.getWidth();
if (tableWidth > width.get()) {
view.getColumns().forEach(col -> {
col.setPrefWidth(col.getWidth()+((tableWidth-width.get())/view.getColumns().size()));
});
}
}
<TableView fx:id="datalist" layoutX="30.0" layoutY="65.0" prefHeight="400.0" AnchorPane.bottomAnchor="100.0" AnchorPane.leftAnchor="30.0" AnchorPane.rightAnchor="30.0" AnchorPane.topAnchor="100.0">
<columns>
<TableColumn fx:id="number" minWidth="-1.0" prefWidth="-1.0" style="width: auto;" text="number" />
<TableColumn fx:id="id" minWidth="-1.0" prefWidth="-1.0" text="id" />
<TableColumn fx:id="name" minWidth="-1.0" prefWidth="-1.0" text="name" />
<TableColumn fx:id="action" minWidth="-1.0" prefWidth="-1.0" text="todo" />
</columns>
**<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>**
</TableView>
public static void autoFillColumn(TableView<?> table, int col) {
double width = 0;
for (int i = 0; i < table.getColumns().size(); i++) {
if (i != col) {
width += table.getColumns().get(i).getWidth();
}
}
table.getColumns().get(col).setPrefWidth(table.getWidth() - width);
}