Javafx Column in Tableview Auto Fit Size

javafx column in tableview auto fit size

After 3 years I come back to this problem again, some suggestions are calculating the size of text of data in each cell (it's complicated depending on font size, font family, padding...)

But I realize that when I click on the divider on table header, it's resized fit to content as I want. So I dig into JavaFX source code I finally found resizeColumnToFitContent method in TableViewSkin, but it is protected method, we can resolve by reflection:

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();
}
}
}
});
}
}

Note that we call "tableView.getItems()" so we have to call this function after setItems()

(javafx) How automatically width of tableview column depending on the content

Finally I found the solution:

    TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
NestedTableColumnHeader rootHeader = headerRow.getRootHeader();
for (TableColumnHeader columnHeader : rootHeader.getColumnHeaders()) {
try {
TableColumn<?, ?> column = (TableColumn<?, ?>) columnHeader.getTableColumn();
if (column != null) {
Method method = skin.getClass().getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
method.setAccessible(true);
method.invoke(skin, column, 30);
}
} catch (Throwable e) {
e = e.getCause();
e.printStackTrace(System.err);
}
}

Javafx still crude. Many simple things need to do through deep ass...

how to fix javafx - tableview size to current window size

This can be done by binding the preferred height and width to the height and width of the primary stage. Here's an MCVE:

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class MCVE extends Application {

@Override
public void start(Stage stage) {

TableView<ObservableList<String>> table = new TableView<ObservableList<String>>();

// We bind the prefHeight- and prefWidthProperty to the height and width of the stage.
table.prefHeightProperty().bind(stage.heightProperty());
table.prefWidthProperty().bind(stage.widthProperty());

stage.setScene(new Scene(table, 400, 400));
stage.show();
}

public static void main(String[] args) {
launch();
}

}

JavaFX 2 Automatic Column Width

If your total number of columns are pre-known. You can distribute the column widths among the tableview's width:

nameCol.prefWidthProperty().bind(personTable.widthProperty().divide(4)); // w * 1/4
surnameCol.prefWidthProperty().bind(personTable.widthProperty().divide(2)); // w * 1/2
emailCol.prefWidthProperty().bind(personTable.widthProperty().divide(4)); // w * 1/4

In this code, the width proportions of columns are kept in sync when the tableview is resized, so you don't need to do it manually. Also the surnameCol takes the half space of the tableview's width.

JavaFX table column won't resize to prefWidth

It is because you included the CONSTRAINED_RESIZE_POLICY to the table view.

The java doc says::

Simple policy that ensures the width of all visible leaf columns in
this table sum up to equal the width of the table itself.

When the user resizes a column width with this policy, the table
automatically adjusts the width of the right hand side columns. When
the user increases a column width, the table decreases the width of
the rightmost column until it reaches its minimum width. Then it
decreases the width of the second rightmost column until it reaches
minimum width and so on. When all right hand side columns reach
minimum size, the user cannot increase the size of resized column any
more.

Having said that, if you included the CONSTRAINED_RESIZE_POLICY on purpose, then you may need to add a bit more custom logic to satisfy the custom widths along with the policy.

UPDATE:

If you want to acheive a feature something like "percentWidth" feature in GridPane, you can try the below approach.

The idea is :

  • Create a custom TableColumn that has a new property "percentWidth"
  • Create a custom TableView that has a listener to its widthProperty and adjust the columns prefWidth based on the percentWidth.
  • Import these controls in fxml and update the fxml with the new controls.

CustomTableColumn.java

public class CustomTableColumn<S, T> extends TableColumn<S, T> {
private DoubleProperty percentWidth = new SimpleDoubleProperty();

public CustomTableColumn(String columnName) {
super(columnName);
}

public DoubleProperty percentWidth() {
return percentWidth;
}

public double getPercentWidth() {
return percentWidth.get();
}

public void setPercentWidth(double percentWidth) {
this.percentWidth.set(percentWidth);
}
}

CustomTableView.java

public class CustomTableView<S> extends TableView<S> {
public CustomTableView() {
widthProperty().addListener((obs, old, tableWidth) -> {
// Deduct 2px from the total table width for borders. Otherwise you will see a horizontal scroll bar.
double width = tableWidth.doubleValue() - 2;
getColumns().stream().filter(col -> col instanceof CustomTableColumn)
.map(col -> (CustomTableColumn) col)
.forEach(col -> col.setPrefWidth(width * (col.getPercentWidth() / 100)));
});
}
}

Updated fxml code:

<CustomTableView fx:id="reportTableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<columns>
<CustomTableColumn fx:id="date" percentWidth="35" text="Date" />
<CustomTableColumn fx:id="company" percentWidth="40" text="Company" />
<CustomTableColumn fx:id="number" percentWidth="25" text="Number" />
...
</columns>
</CustomTableView>

Please note that sum of all columns percentWidth should be equal to 100 for better results :)

A full working demo of this implementation(non fxml) is below: (I updated the code to fix the horizontal scroll bar in the gif)

Sample Image

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class PercentageTableColumnDemo extends Application {

@Override
public void start(Stage stage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.add(new Person("Harry", "John", "LS"));
persons.add(new Person("Mary", "King", "MS"));

CustomTableColumn<Person, String> fnCol = new CustomTableColumn<>("First Name");
fnCol.setPercentWidth(30);
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());

CustomTableColumn<Person, String> lnCol = new CustomTableColumn<>("Last Name");
lnCol.setPercentWidth(25);
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());

CustomTableColumn<Person, String> cityCol = new CustomTableColumn<>("City");
cityCol.setPercentWidth(45);
cityCol.setCellValueFactory(param -> param.getValue().cityProperty());

CustomTableView<Person> tableView = new CustomTableView<>();
tableView.getColumns().addAll(fnCol, lnCol, cityCol);
tableView.setItems(persons);

VBox root = new VBox();
root.getChildren().addAll(tableView);
VBox.setVgrow(tableView, Priority.ALWAYS);

Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.setTitle("Table demo");
stage.show();
}

class CustomTableColumn<S, T> extends TableColumn<S, T> {
private DoubleProperty percentWidth = new SimpleDoubleProperty();

public CustomTableColumn(String columnName) {
super(columnName);
}

public DoubleProperty percentWidth() {
return percentWidth;
}

public double getPercentWidth() {
return percentWidth.get();
}

public void setPercentWidth(double percentWidth) {
this.percentWidth.set(percentWidth);
}
}

class CustomTableView<S> extends TableView<S> {
public CustomTableView() {
widthProperty().addListener((obs, old, tableWidth) -> {
// Deduct 2px from the total table width for borders. Otherwise you will see a horizontal scroll bar.
double width = tableWidth.doubleValue() - 2;
getColumns().stream().filter(col -> col instanceof CustomTableColumn)
.map(col -> (CustomTableColumn) col)
.forEach(col -> col.setPrefWidth(width * (col.getPercentWidth() / 100)));
});
}
}

class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty city = new SimpleStringProperty();

public Person(String fn, String ln, String cty) {
setFirstName(fn);
setLastName(ln);
setCity(cty);
}

public String getFirstName() {
return firstName.get();
}

public StringProperty firstNameProperty() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName.set(firstName);
}

public String getLastName() {
return lastName.get();
}

public StringProperty lastNameProperty() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName.set(lastName);
}

public String getCity() {
return city.get();
}

public StringProperty cityProperty() {
return city;
}

public void setCity(String city) {
this.city.set(city);
}
}
}

automatically resize TableColumn of TableColumn in TableView with JavaFX -8

For those who are interested, my solution works but some of my column are not visible and that's why I have my exception.
Just put if(col.isVisible()) and that's fine !

table column won't take full size of the table view in javaFX

Update

New features in the JavaFX 2.2+ releases have rendered this answer obsolete when applied with an FXML document.

An FXML based solution which uses new FXML features to allow the column size to be set via FXML is provided in: FXML set TableView column resize policy.

<TableView fx:id="tableView" layoutX="110.0" layoutY="78.0" prefHeight="200.0" prefWidth="396.0">
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>

If you are not using FXML the TableView's resize policy can still be set by code as defined in this answer:

tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

In your FXML, you have defined the preferred width of the table to 837, and the preferred width of each column to 75. By default, the JavaFX column resizing policy will keep the columns at their preferred widths. You aren't going to get the table columns filling the width of your entire table unless you either set a new column resizing policy or set the sum of the preferred widths of all of your columns to add up to your table width.

JavaFX does provide a mechanism to modify the default column resizing policy. By setting the column resize policy to a CONSTRAINED_RESIZE_POLICY, the sum of the widths of every column will be constrained to fill the width of the tableview - even if the tableview is resized or individual columns are resized. This is likely the behaviour which you desire.

While there may be a way to set a CONSTRAINED_RESIZE_POLICY on a tableView from FXML, I am not aware of one (though my skills in fxml are rudimentary - so perhaps somebody more informed can accomplish the configuration using pure fxml).

What you can do is define a controller for your fxml file and then configure the resize policy in the controller. The following code demonstrates a complete example (written against the JavaFX 2.2b17 preview).

FXML file:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="tableview.TableViewController">
<children>
<TableView fx:id="tableView" layoutX="110.0" layoutY="78.0" prefHeight="200.0" prefWidth="396.0">
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>
</children>
</AnchorPane>

Controller class:

package tableview;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableView;

/* controller class for the tableview fxml definition */
public class TableViewController implements Initializable {
@FXML // fx:id="tableView"
private TableView<?> tableView; // Value injected by FXMLLoader

@Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
assert tableView != null : "fx:id=\"tableView\" was not injected: check your FXML file 'tableview.fxml'.";

// initialize your logic here: all @FXML variables will have been injected
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
}
}

Application class:

package tableview;

import java.io.IOException;
import java.net.URL;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

/** Main application class for tableview fxml demo application */
public class TableViewApplication extends Application {
public static void main(String[] args) { launch(args); }
@Override public void start(Stage stage) throws IOException {
AnchorPane layout = FXMLLoader.load(
new URL(TableViewApplication.class.getResource("tableview.fxml").toExternalForm())
);
stage.setScene(new Scene(layout));
stage.show();
}
}


Related Topics



Leave a reply



Submit