I\'m using this table to display data in Table View:
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans
Changing tableview's height and removing "empty" rows are two different things. Be specific.
For removing rows see this tutorial.
For changing height, first set the fixedCellSizeProperty
of the table view then use it in binding:
table.setFixedCellSize(25);
table.prefHeightProperty().bind(Bindings.size(table.getItems()).multiply(table.getFixedCellSize()).add(30));
Adding 30px is for tableview's header.
Here's my solution, in order not to be deeply dependant on table.getFixedCellSize
(we yet depend on it during FX initialization, while CSS is not yet computed/applied).
Note that we also need to add some pixels (don't understand why).
public static <S> void ensureDisplayingRows(@NotNull TableView<S> table, @Null Integer rowCount) {
DoubleProperty headerRowHeightProperty = new SimpleDoubleProperty();
table.skinProperty().addListener((observable, oldValue, newValue) -> {
if (!Objects.equals(oldValue, newValue)) {
TableHeaderRow headerRow = headerRow(table);
// TableHeaderRow not defined until CSS is applied.
if (headerRow == null) {
assert table.getFixedCellSize() > 0.0 : "TableView '" + table.getId() + "' is not 'fixedCellSize'."; // TODO Find a better way to control.
headerRowHeightProperty.setValue(table.getFixedCellSize()); // Approximation. // TODO Find a better approximation.
} else {
headerRowHeightProperty.bind(headerRow.heightProperty());
}
}
});
IntegerBinding itemsCountBinding = Bindings.size(table.getItems()); // NB: table.getItems() may not (yet) contains all/"new" items, may contain the "old" items.
IntegerBinding maxRowsCountBinding = (rowCount == null) ? itemsCountBinding :
(IntegerBinding) Bindings.min(
rowCount,
itemsCountBinding
);
IntegerBinding rowCountBinding = (IntegerBinding) Bindings.max(
1, // Ensure to display at least 1 row, for JavaFX "No contents" message when table.items.isEmpty.
maxRowsCountBinding
);
DoubleBinding tableHeightBinding = headerRowHeightProperty
.add(rowCountBinding.multiply(table.getFixedCellSize()))
.add(10); // TODO Understand why we need to add a dozen of pixels.
table.minHeightProperty().bind(tableHeightBinding);
table.prefHeightProperty().bind(tableHeightBinding);
table.maxHeightProperty().bind(tableHeightBinding);
}
@Null
public static TableHeaderRow headerRow(@NotNull TableView<?> table) {
TableHeaderRow tableHeaderRow = (TableHeaderRow) table.lookup("TableHeaderRow");
return tableHeaderRow;
}
Unfortunately, configuration of the visibleRowCount isn't supported in TableView (you might consider filing a feature request in fx' jira - no need, already done years ago). And it's not entirely straightforward to let the view return a prefHeight based on a such a preference: we'll need to measure the size requirements of the "real" cell and that's somehow buried inside the bowels.
Just for fun, experimented with extending the whole stack of collaborators:
The code:
/**
* TableView with visibleRowCountProperty.
*
* @author Jeanette Winzenburg, Berlin
*/
public class TableViewWithVisibleRowCount<T> extends TableView<T> {
private IntegerProperty visibleRowCount = new SimpleIntegerProperty(this, "visibleRowCount", 10);
public IntegerProperty visibleRowCountProperty() {
return visibleRowCount;
}
@Override
protected Skin<?> createDefaultSkin() {
return new TableViewSkinX<T>(this);
}
/**
* Skin that respects table's visibleRowCount property.
*/
public static class TableViewSkinX<T> extends TableViewSkin<T> {
public TableViewSkinX(TableViewWithVisibleRowCount<T> tableView) {
super(tableView);
registerChangeListener(tableView.visibleRowCountProperty(), "VISIBLE_ROW_COUNT");
handleControlPropertyChanged("VISIBLE_ROW_COUNT");
}
@Override
protected void handleControlPropertyChanged(String p) {
super.handleControlPropertyChanged(p);
if ("VISIBLE_ROW_COUNT".equals(p)) {
needCellsReconfigured = true;
getSkinnable().requestFocus();
}
}
/**
* Returns the visibleRowCount value of the table.
*/
private int getVisibleRowCount() {
return ((TableViewWithVisibleRowCount<T>) getSkinnable()).visibleRowCountProperty().get();
}
/**
* Calculates and returns the pref height of the
* for the given number of rows.
*
* If flow is of type MyFlow, queries the flow directly
* otherwise invokes the method.
*/
protected double getFlowPrefHeight(int rows) {
double height = 0;
if (flow instanceof MyFlow) {
height = ((MyFlow) flow).getPrefLength(rows);
}
else {
for (int i = 0; i < rows && i < getItemCount(); i++) {
height += invokeFlowCellLength(i);
}
}
return height + snappedTopInset() + snappedBottomInset();
}
/**
* Overridden to compute the sum of the flow height and header prefHeight.
*/
@Override
protected double computePrefHeight(double width, double topInset,
double rightInset, double bottomInset, double leftInset) {
// super hard-codes to 400 .. doooh
double prefHeight = getFlowPrefHeight(getVisibleRowCount());
return prefHeight + getTableHeaderRow().prefHeight(width);
}
/**
* Reflectively invokes protected getCellLength(i) of flow.
* @param index the index of the cell.
* @return the cell height of the cell at index.
*/
protected double invokeFlowCellLength(int index) {
double height = 1.0;
Class<?> clazz = VirtualFlow.class;
try {
Method method = clazz.getDeclaredMethod("getCellLength", Integer.TYPE);
method.setAccessible(true);
return ((double) method.invoke(flow, index));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
return height;
}
/**
* Overridden to return custom flow.
*/
@Override
protected VirtualFlow createVirtualFlow() {
return new MyFlow();
}
/**
* Extended to expose length calculation per a given # of rows.
*/
public static class MyFlow extends VirtualFlow {
protected double getPrefLength(int rowsPerPage) {
double sum = 0.0;
int rows = rowsPerPage; //Math.min(rowsPerPage, getCellCount());
for (int i = 0; i < rows; i++) {
sum += getCellLength(i);
}
return sum;
}
}
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(TableViewWithVisibleRowCount.class
.getName());
}
Note that you might get away with a plain override of table's prefHeight when having a fixed-cell-size, didn't try that - no risk no fun :-)
Update: the custom skin in javafx15 - basically the same, just some access details changed (in both directions ;)
/**
* Skin that respects table's visibleRowCount property.
*/
public class TableViewSkinX<T> extends TableViewSkin<T> {
public TableViewSkinX(TableViewWithVisibleRowCount<T> tableView) {
super(tableView);
registerChangeListener(tableView.visibleRowCountProperty(), e -> visibleRowCountChanged());
}
private void visibleRowCountChanged() {
getSkinnable().requestLayout();
}
/**
* Returns the visibleRowCount value of the table.
*/
private int getVisibleRowCount() {
return ((TableViewWithVisibleRowCount<T>) getSkinnable()).visibleRowCountProperty().get();
}
/**
* Calculates and returns the pref height of the for the given number of
* rows.
*/
protected double getFlowPrefHeight(int rows) {
double height = 0;
for (int i = 0; i < rows && i < getItemCount(); i++) {
height += invokeFlowCellLength(i);
}
return height + snappedTopInset() + snappedBottomInset();
}
/**
* Overridden to compute the sum of the flow height and header prefHeight.
*/
@Override
protected double computePrefHeight(double width, double topInset,
double rightInset, double bottomInset, double leftInset) {
// super hard-codes to 400 .. doooh
double prefHeight = getFlowPrefHeight(getVisibleRowCount());
return prefHeight + getTableHeaderRow().prefHeight(width);
}
/**
* Reflectively invokes protected getCellLength(i) of flow.
* @param index the index of the cell.
* @return the cell height of the cell at index.
*/
protected double invokeFlowCellLength(int index) {
// note: use your own utility method to reflectively access internal fields/methods
return (double) FXUtils.invokeGetMethodValue(VirtualFlow.class, getVirtualFlow(),
"getCellLength", Integer.TYPE, index);
}
}
If you're not wedded to bindings, a simple way to do this is to calculate the desired height based on the fixed cell size (cf. Fred Danna's answer) and update it with a listener on the table data.
static void setTableHeightByRowCount(TableView table, ObservableList data) {
int rowCount = data.size();
TableHeaderRow headerRow = (TableHeaderRow) table.lookup("TableHeaderRow");
double tableHeight = (rowCount * table.getFixedCellSize())
// add the insets or we'll be short by a few pixels
+ table.getInsets().getTop() + table.getInsets().getBottom()
// header row has its own (different) height
+ (headerRow == null ? 0 : headerRow.getHeight())
;
table.setMinHeight(tableHeight);
table.setMaxHeight(tableHeight);
table.setPrefHeight(tableHeight);
}
In start(Stage)
, we create the table and add a ListChangeListener
:
TableView<String> table = new TableView<>();
table.setFixedCellSize(24);
table.getItems().addListener((ListChangeListener<String>) c ->
setTableHeightByRowCount(table, c.getList()));
// init scene etc...
stage.show();
table.getItems().addAll("Stacey", "Kristy", "Mary Anne", "Claudia");
Note that the table header row doesn't exist till after stage.show()
, so the simplest thing to do is to wait to set the table data till then.
Alternatively, we could set the data at table construction time, and then call setTableHeightByRowCount()
explicitly:
TableView<String> table = new TableView<>(
FXCollections.observableArrayList("Stacey", "Kristy", "Mary Anne", "Claudia")
);
// add listener, init scene etc...
stage.show();
setTableHeightByRowCount(table, table.getItems());
Just change background color of empty rows using css
.table-row-cell:empty {
-fx-background-color: white;
-fx-border-color: white;
}
and modify number of rows on the basis of combobox.
Is there a way to do it... yes, what you need to do is when you create the table (since you re-create it every time you select a new number) you need to compute what the height of the table is with the current number of entries, and then use the setPrefHeight()
property of TableView to make the table smaller to account for only those rows.
I toyed with it a little bit, and I didn't find any quick solutions to this to calculate the size of the table properly, so I don't have any code for you, but that is what you need to do. You could also 'style' the table to not have the alternating color scheme, which would make the rows below the ones that have data look 'empty' even though there would be some white space.
Good luck!