Zephyr
Zephyr

Reputation: 10253

How to set one TableColumn to grow with TableView?

I have a TableView that will shrink/grow properly as the window is resized.

However, I want one of the columns to automatically grow as well. Similar to how the HGrow property works on other nodes.

A TableColumn does not seem to have any type of HGrow or similar properties to tell that column to use all available space.

I did try to set the PrefWidth property to Double.MAX_VALUE, but is causing the column to expand indefinitely.

My goal is to have the TableView fit within the window and if the window is resized, instead of expanding the last pseudo column (which isn't actually a column at all), it will expand the preferred column.

Here is my MCVE:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableColumnGrow extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        TableView<Person> tableView = new TableView<>();
        TableColumn<Person, String> colFirstName = new TableColumn<>("First Name");
        TableColumn<Person, String> colLastName = new TableColumn<>("Last Name");

        colFirstName.setCellValueFactory(tf -> tf.getValue().firstNameProperty());
        colLastName.setCellValueFactory(tf -> tf.getValue().lastNameProperty());

        tableView.getColumns().addAll(colFirstName, colLastName);

        tableView.getItems().addAll(
                new Person("Martin", "Brody"),
                new Person("Matt", "Hooper"),
                new Person("Sam", "Quint")
        );

        root.getChildren().add(tableView);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("TableColumnGrow Sample");
        primaryStage.show();

    }
}

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

    public Person(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);
    }

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

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

    public StringProperty firstNameProperty() {
        return firstName;
    }

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

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

    public StringProperty lastNameProperty() {
        return lastName;
    }
}

screenshot

So the goal is to remove the column in red and instead have the "First Name" column take up the extra space (without pushing "Last Name" out of bounds).

Is this possible without resorting to expensive calculations of widths?

Edit: While my example does use the first column in the TableView, I would want this to be applicable to any column I choose, and regardless of how many columns the TableView has.

Upvotes: 3

Views: 1657

Answers (3)

SedJ601
SedJ601

Reputation: 13859

I thought I would take a crack at it. I am basically listening to when a column width changes and when the table width changes.

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.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 *
 * @author blj0011
 */
public class JavaFXTestingGround extends Application{

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        TableView<Person> tableView = new TableView<>();
        TableColumn<Person, String> colFirstName = new TableColumn<>("First Name");
        TableColumn<Person, String> colMiddleName = new TableColumn<>("Last Name");
        TableColumn<Person, String> colLastName = new TableColumn<>("Last Name");

        colFirstName.setCellValueFactory(tf -> tf.getValue().firstNameProperty());
        colLastName.setCellValueFactory(tf -> tf.getValue().lastNameProperty());
        colLastName.setCellValueFactory(tf -> tf.getValue().lastNameProperty());

        tableView.getColumns().addAll(colFirstName, colMiddleName, colLastName);

        tableView.getItems().addAll(
                new Person("Martin", "One", "Brody"),
                new Person("Matt", "Two", "Hooper"),
                new Person("Sam", "Three", "Quint")
        );


        root.getChildren().add(tableView);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("TableColumnGrow Sample");
        primaryStage.show();

        DoubleProperty width = new SimpleDoubleProperty();
        width.bind(colMiddleName.widthProperty().add(colLastName.widthProperty()));
        width.addListener((ov, t, t1) -> {
            colFirstName.setPrefWidth(tableView.getWidth() - 5 - t1.doubleValue());
        });

        colFirstName.setPrefWidth(tableView.getWidth() - 5 - width.doubleValue());
        colFirstName.setResizable(false);

        tableView.widthProperty().addListener((ov, t, t1) -> {
            colFirstName.setPrefWidth(tableView.getWidth() - 5 - width.doubleValue());
        });
    }
}

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

    public Person(String firstName, String middleName, String lastName) {
        this.firstName.set(firstName);
        this.middleName.set(middleName);
        this.lastName.set(lastName);
    }

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

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

    public StringProperty firstNameProperty() {
        return firstName;
    }

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

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

    public StringProperty middleNameProperty() {
        return lastName;
    }

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

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

    public StringProperty lastNameProperty() {
        return lastName;
    }
}

Upvotes: 1

Sai Dandem
Sai Dandem

Reputation: 9989

This could be one of the solution if you say you dont want to alter the other columns width.

Include this after your tableView and column declarations.

UPDATE : Sorry didnt noticed the last line of the question.(regarding calculations). So if it not suits you, discard this solution.

TableColumn<?, ?> columnToResize = colFirstName;
tableView.widthProperty().addListener((obs, old, width) -> {
    double otherColsWidth = tableView.getColumns().stream().filter(tc -> tc != columnToResize).mapToDouble(tc -> tc.getWidth()).sum();
    double padding = tableView.getPadding().getLeft() + tableView.getPadding().getRight();
    columnToResize.setPrefWidth(width.doubleValue() - otherColsWidth - padding);
});

Update 2:

If you want a little more flexibility of resizing hgrow column when other columns are resized below is a quick demo. Just note that resizing the hgrow column itself has no specific code as it depends up to you how to handle that case.

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


public class TableColumnGrow extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        HGrowTableView<Person> tableView = new HGrowTableView<>();
        TableColumn<Person, String> colFirstName = new HGrowTableColumn<>("First Name");
        TableColumn<Person, String> colLastName = new HGrowTableColumn<>("Last Name");

        colFirstName.setCellValueFactory(tf -> tf.getValue().firstNameProperty());
        colLastName.setCellValueFactory(tf -> tf.getValue().lastNameProperty());

        tableView.setColumnToResize(colLastName);
        tableView.getColumns().addAll(colFirstName, colLastName);

        tableView.getItems().addAll(
                new Person("Martin", "Brody"),
                new Person("Matt", "Hooper"),
                new Person("Sam", "Quint")
        );
        root.getChildren().add(tableView);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("TableColumnGrow Sample");
        primaryStage.show();

    }

    /**
     * Custom Table View
     *
     * @param <S>
     */
    class HGrowTableView<S> extends TableView<S> {
        TableColumn<?, ?> columnToResize;
        ChangeListener<Number> tableWidthListener = (obs, old, width) -> {
            double otherColsWidth = getColumns().stream().filter(tc -> tc != columnToResize).mapToDouble(tc -> tc.getWidth()).sum();
            double padding = getPadding().getLeft() + getPadding().getRight();
            columnToResize.setPrefWidth(width.doubleValue() - otherColsWidth - padding);
        };

        public void setColumnToResize(TableColumn<?, ?> columnToResize) {
            widthProperty().removeListener(tableWidthListener); // Ensuring to remove any previous listener
            this.columnToResize = columnToResize;
            if (this.columnToResize != null) {
                widthProperty().addListener(tableWidthListener);
            }
        }

        public TableColumn<?, ?> getColumnToResize() {
            return columnToResize;
        }
    }

    /**
     * Custom TableColumn
     *
     * @param <S>
     * @param <T>
     */
    class HGrowTableColumn<S, T> extends TableColumn<S, T> {
        public HGrowTableColumn(String title) {
            super(title);
            init();
        }

        private void init() {
            widthProperty().addListener((obs, old, width) -> {
                if (getTableView() instanceof HGrowTableView) {
                    TableColumn<?, ?> columnToResize = ((HGrowTableView) getTableView()).getColumnToResize();
                    if (columnToResize != null && HGrowTableColumn.this != columnToResize) {
                        double diff = width.doubleValue() - old.doubleValue();
                        columnToResize.setPrefWidth(columnToResize.getWidth() - diff);
                    }
                }
            });
        }
    }

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

        public Person(String firstName, String lastName) {
            this.firstName.set(firstName);
            this.lastName.set(lastName);
        }

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

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

        public StringProperty firstNameProperty() {
            return firstName;
        }

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

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

        public StringProperty lastNameProperty() {
            return lastName;
        }
    }
}

Upvotes: 0

SciGuyMcQ
SciGuyMcQ

Reputation: 1043

I usually use a combination of setting the TableView#setColumnResizePolicy to TableView.CONSTRAINED_RESIZE_POLICY and binding a width property of the column (or columns) you want to control to the width property of the table itself. I believe this is similar to what brian is alluding to.

Below is an example.

package tmp_table;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class App extends Application {
    public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(Stage primaryStage) throws Exception {
      var people = FXCollections.observableArrayList(
          new Person("John Doe", "Male"),
          new Person("Jane Doe", "Female")
      );
      var table = new TableView<Person>(people);
      var nameCol = new TableColumn<Person, String>("Name");
      var genderCol = new TableColumn<Person, String>("Gender");

      nameCol.setCellValueFactory(
          cell -> cell.getValue().nameProperty());
      genderCol.setCellValueFactory(
          cell -> cell.getValue().genderProperty());

      table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
      // this gives name column 70% of table width, the 
      // CONSTRAINED_RESIZE_POLICY will make the other
      // remaining columns fill the table width equally
      nameCol.minWidthProperty().bind(
          table.widthProperty().multiply(0.7));

      table.getColumns().add(nameCol);
      table.getColumns().add(genderCol);

      var stackPane = new StackPane(table);
      StackPane.setAlignment(table, Pos.CENTER);
      primaryStage.setScene(new Scene(stackPane, 400, 200));
      primaryStage.show();
  }
}

class Person {
    StringProperty name = new SimpleStringProperty("");
    StringProperty gender = new SimpleStringProperty("");

    public Person(String name, String gender) {
        this.name.set(name);
        this.gender.set(gender);
    }

    public StringProperty nameProperty() {
        return name;
    }

    public StringProperty genderProperty() {
        return gender;
    }
}

enter image description here

Upvotes: 2

Related Questions