Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Friday, February 19, 2016

Workaround for JavaFX TableView Constrained Resize Policy

CONSTRAINED_RESIZE_POLICY tells JavaFX to have the TableColumns in a TableView take up remaining horizontal space.  But if you want only the last column to assume the extra space while allowing other TableColumns to be resized manually, you'll have to do something else.

By default, a JavaFX TableView has a column policy of UNCONSTRAINED_RESIZE_POLICY.  This means that the TableView will rely on the explicit specifications of the TableColumns.  This screenshot shows 3 columns each with different preferredWidth properties: 50, 100, and 100, respectively.  Notice the extra space after the Data TableColumn to the right.

Unconstrained TableView Resize Policy
The extra space can be captured by using a CONSTRAINED_RESIZE_POLICY.  This screenshot shows this setting.  Be aware that all of the TableColumns must be resizable for this to take effect.

Constrained TableView Resize Policy
In the source, the TableColumns have different preferrredWidth settings, but the constrained setting forces them all to be the same width.

This program uses JavaFX binding to link the Preferred Width property of the last column to the available space as calculated by TABLE WIDTH - SUM(OTHER COLUMNS). The binding expression is.

tcData.prefWidthProperty().bind(
                    tbl.widthProperty()
                    .subtract(tcIcon.widthProperty())
                    .subtract(tcText.widthProperty())
                    .subtract(2)  // a border stroke?
                 );

tcIcon, tcText, and tcData are 3 columns. tcIcon is fixed.  tcText and tcData are resizable.  tcData is expected to acquire any available space when the screen is displayed or if the screen is resized.

The result is a TableView that honors the preferredWidth settings for all but the last TableColumn.

TableView with Last TableColumn Width Based on an Expression

This class is followed with a definition of RowType which must be public so that the PropertyValueFactories will work.

public class TableResizeApp extends Application {

 private final ObservableList<RowType> rows = 
           FXCollections.observableArrayList();
 
 @Override
 public void start(Stage primaryStage) throws Exception {

  rows.add( new RowType("a", "b", "c"));
  rows.add( new RowType("1", "2", "3") );
  rows.add( new RowType("X", "Y", "Z"));
  
  // not using constrained policy
  TableView<RowType> tbl = new TableView<>);
  
  TableColumn<RowType, String> tcIcon = new TableColumn<>("Icon");
  TableColumn<RowType, String> tcText = new TableColumn<>("Text");
  TableColumn<RowType, String> tcData = new TableColumn<>("Data");
  
  tcIcon.setResizable(false);
  
  tcIcon.setPrefWidth( 50.0d );  
  tcText.setPrefWidth(100.0d);
  tcData.setPrefWidth(100.0d);
  
  tcIcon.setCellValueFactory(
      new PropertyValueFactory<RowType, String>("icon")
    );
  tcText.setCellValueFactory(
      new PropertyValueFactory<RowType, String>("text")
    );
  tcData.setCellValueFactory(
      new PropertyValueFactory<RowType, String>("data")
    );
  
  tcData.prefWidthProperty().bind(
                    tbl.widthProperty()
                    .subtract(tcIcon.widthProperty())
                    .subtract(tcText.widthProperty())
                    .subtract(2)  // a border stroke?
                 );
  
  tbl.getColumns().add( tcIcon );
  tbl.getColumns().add( tcText );
  tbl.getColumns().add( tcData );

  tbl.itemsProperty().bind( new SimpleObjectProperty<gt&(rows) );
  
  Scene scene = new Scene(tbl);
  
  primaryStage.setScene( scene );
  primaryStage.setWidth( 667 );
  primaryStage.setHeight( 375 );
  
  primaryStage.show();
 }

And this is the JavaBean for the TableView.
public  class RowType {
 private String icon;
 private String text;
 private String data;
 public RowType() {}
 public RowType(String icon, String text, String data) {
  this.icon = icon;
  this.text = text;
  this.data = data;
 }
 public String getIcon() {
  return icon;
 }
 public void setIcon(String icon) {
  this.icon = icon;
 }
 public String getText() {
  return text;
 }
 public void setText(String text) {
  this.text = text;
 }
 public String getData() {
  return data;
 }
 public void setData(String data) {
  this.data = data;
 }  
}

8 comments:

  1. Hi,

    This didn't work for me. I put the code in a stage.onShown() and tried a few resize policies. Note that the binding syntax also accounts for width changes to the overall window.

    You can probably also remove the Platform.runLater() since the iteration is likely already on the FX Thread.

    ReplyDelete
  2. Hello! It worked for me! Thanks a lot! Greetings from Brazil.

    ReplyDelete
  3. Hi
    is there a way for the first and the last column be constraint and the middle column have a fix size?

    ReplyDelete
    Replies
    1. Sure. Replace the tcData expression with the following which will apply binding to the first and last columns.

      tcIcon.prefWidthProperty().bind(
      tbl.widthProperty()
      .subtract(tcText.widthProperty())
      .divide(2)
      .subtract(1) // a border stroke?
      );

      tcData.prefWidthProperty().bind(
      tbl.widthProperty()
      .subtract(tcText.widthProperty())
      .divide(2)
      .subtract(1) // a border stroke?
      );

      Delete
  4. It's a good solution, BUT if width of your first or second column would be too big (if you scroll it manuell), "tcData" goes abroad TableView:(

    ReplyDelete
    Replies
    1. Just set a min width so that tcData won't disappear

      tcData.setMinWidth(100.0d);

      Delete
  5. Found an easy solution that works with CONSTRAINED_RESIZE_POLICY : just set minWidth and maxWidth to the same value on components you do not want to expand.

    ReplyDelete
  6. Jules that helped a lot. Thanks.

    ReplyDelete