There is no particularly easy way to do this. The (current) implementation of TableViewSkin just hard-codes the preferred height to 400. Consequently, the approach of limiting the max height of both tables to the preferred height just prevents both tables from growing more than 400 pixels tall. The VBox, even with vGrow set to PRIORITY.NEVER will still try to size the second table to its preferred size, so it will be 400 pixels tall, no matter how many items it contains.
The API-supported way to do this would be to create a skin subclass for the table which computed its size based on the number of items. Here is a very basic implementation which is intended to serve only as a proof of concept: it is not intended to be production quality (or anything close to it).
The basic idea here is to keep the VirtualFlow's cellCount set to the required value (the number of items in the table; it's also probably a good idea to impose a maximum which I just hard-code as 10 here), then compute the preferred height of the table by adding up the padding, preferred height of the header, and the preferred height of the VirtualFlow. For some reason, the scrolling ends up being off by one, so I have included a workaround for that in the layoutChildren() method to fix the scroll position. I don't know why this is necessary.
public static class ResizingTableSkin<T> extends TableViewSkin<T> { private static final int MAX_MEASURED_ROWS = 10; public ResizingTableSkin(TableView<T> tableView) { super(tableView); tableView.itemsProperty().addListener((Observable _) -> getVirtualFlow().setCellCount(Math.min(tableView.getItems().size(), MAX_MEASURED_ROWS))); getVirtualFlow().setCellCount(Math.min(tableView.getItems().size(), MAX_MEASURED_ROWS)); } @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { VirtualFlow<TableRow<T>> virtualFlow = getVirtualFlow(); double padding = topInset + bottomInset; double availableWidth = width - leftInset - rightInset; TableHeaderRow header = getTableHeaderRow(); return padding + header.prefHeight(availableWidth) + virtualFlow.prefHeight(availableWidth); } @Override protected void layoutChildren(double x, double y, double w, double h) { if (getSkinnable().getItems().size() <= MAX_MEASURED_ROWS) { getVirtualFlow().scrollTo(0); } super.layoutChildren(x, y, w, h); } }
To use this, you should set the minimum height of the table to its preferred height. This will ensure all items (up to 10) are visible. Using a layout that doesn't allow it to grow beyond its preferred size (such as the VBox you have configured in the original post) will keep it at this preferred size.
The skin classes in JavaFX were originally not part of the public API. Since JavaFX 9 they were promoted to the public API, but the design of these classes is still very much as it was originally, and they are not designed for easy extension. To make this work correctly and robustly you would really need to read through a lot of the skin source code to understand how it works, and it would be a bit of a project. How much you want to invest in that depends on how important this layout is to you.
Here is a complete example, which works on my system. You can use this as a starting point for a more robust solution if you want to go that direction.
import javafx.application.Application; import javafx.beans.Observable; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.skin.TableHeaderRow; import javafx.scene.control.skin.TableViewSkin; import javafx.scene.control.skin.VirtualFlow; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class TestFx extends Application { @Override public void start(Stage primaryStage) { ObservableList<TestItem> data1 = FXCollections.observableArrayList(); ObservableList<TestItem> data2 = FXCollections.observableArrayList(); for (int i = 1; i <= 30; i++) { data1.add(new TestItem("Item " + i, "Value " + i)); } for (int i = 1; i <= 15; i++) { data2.add(new TestItem("Item " + i, "Value " + i)); } TableView<TestItem> table1 = createTable("Type Table (30 rows)", data1); TableView<TestItem> table2 = createTable("Base Table (5 rows)", data2); table2.setSkin(new ResizingTableSkin<>(table2)); VBox container = new VBox(); VBox.setVgrow(table1, Priority.ALWAYS); table1.setMaxHeight(Double.MAX_VALUE); VBox.setVgrow(table2, Priority.NEVER); table2.setMinHeight(Region.USE_PREF_SIZE); container.getChildren().addAll(table1, table2); BorderPane root = new BorderPane(); root.setCenter(container); Scene scene = new Scene(root, 600, 400); primaryStage.setTitle("TableView Test"); primaryStage.setScene(scene); primaryStage.show(); } private TableView<TestItem> createTable(String title, ObservableList<TestItem> data) { TableView<TestItem> table = new TableView<>(); table.setItems(data); TableColumn<TestItem, String> col1 = new TableColumn<>(title); col1.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); TableColumn<TestItem, String> col2 = new TableColumn<>("Value"); col2.setCellValueFactory(cellData -> cellData.getValue().valueProperty()); table.getColumns().addAll(col1, col2); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); return table; } public static class ResizingTableSkin<T> extends TableViewSkin<T> { private static final int MAX_MEASURED_ROWS = 10; public ResizingTableSkin(TableView<T> tableView) { super(tableView); tableView.itemsProperty().addListener((Observable _) -> getVirtualFlow().setCellCount(Math.min(tableView.getItems().size(), MAX_MEASURED_ROWS))); getVirtualFlow().setCellCount(Math.min(tableView.getItems().size(), MAX_MEASURED_ROWS)); } @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { VirtualFlow<TableRow<T>> virtualFlow = getVirtualFlow(); double padding = topInset + bottomInset; double availableWidth = width - leftInset - rightInset; TableHeaderRow header = getTableHeaderRow(); return padding + header.prefHeight(availableWidth) + virtualFlow.prefHeight(availableWidth); } @Override protected void layoutChildren(double x, double y, double w, double h) { if (getSkinnable().getItems().size() <= MAX_MEASURED_ROWS) { getVirtualFlow().scrollTo(0); } super.layoutChildren(x, y, w, h); } } public static class TestItem { private final SimpleStringProperty name; private final SimpleStringProperty value; public TestItem(String name, String value) { this.name = new SimpleStringProperty(name); this.value = new SimpleStringProperty(value); } public String getName() { return name.get(); } public void setName(String name) { this.name.set(name); } public SimpleStringProperty nameProperty() { return name; } public String getValue() { return value.get(); } public void setValue(String value) { this.value.set(value); } public SimpleStringProperty valueProperty() { return value; } } public static void main(String[] args) { launch(args); } }