in my application I am displaying a TreeTableView and like to increase the indentation. I found the respective CSS property -fx-indent
which works perfectly fine.
However I'm now struggling with the calculation of the preferred width.
I'd like the column to perfectly wrap its content and achived this using reflection as described in
Auto resize column to fit content
This works using the default indentation, but it does not seem to include my changes in the calculation. Digging through the source I found the following snippent in the TreeTableCellSkin
class:
if (isDeferToParentForPrefWidth) {
// RT-27167: we must take into account the disclosure node and the
// indentation (which is not taken into account by the LabeledSkinBase.
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
}
return columnWidthProperty().get();
EDIT: I answered my previous question in part and found that this comment is there by mistake as the code was moved to the leftLabelPadding()
method in the same class:
// RT-27167: we must take into account the disclosure node and the
// indentation (which is not taken into account by the LabeledSkinBase.
...
double indentPerLevel = 10;
if (treeTableRow.getSkin() instanceof TreeTableRowSkin) {
indentPerLevel = ((TreeTableRowSkin<?>)treeTableRow.getSkin()).getIndentationPerLevel();
}
leftPadding += nodeLevel * indentPerLevel;
...
The debugger shows, that the code snippet works and adds the increased indentation to the padding, however the prefWidth of the cell stays the same and my text is overrun...
Do you have any ideas on where or how to fix this. Any hints into any direction are appreciated.
Thank you very much in advance!
EDIT
It looks like the problem has been fixed with fx9, so the following solution is only necessary (and possible) with fx8
/EDIT
Alright I did some more digging and found an (hopefully right) explanation and a dirty solution for the problem. Who is interested in the explanation can read it further down this post, for all others here is my dirty quickfix.
Dirty but working Solution:
Method fitColumn = TreeTableViewSkin.class.getDeclaredMethod("resizeColumnToFitContent", TreeTableColumn.class, int.class);
fitColumn.setAccessible(true);
List<TreeTableColumn<S, ?>> tableColumns = aTreeTableView.getColumns();
for (TreeTableColumn<S, ?> treeTableColumn : aTreeTableView.getColumns()) {
fitColumn.invoke(aTreeTableView.getSkin(), treeTableColumn, -1);
/*
* FX does not include the altered indentation in the calculation for the prefWidth.
* Instead it uses a fixed constant of 10 (TreeTableCellSkin.leftLabelPadding()).
* To ensure the column can still display all its contents the remaining indentation must be added manually.
* To achieve this the maximum depth of the displayed tree is calculated and multiplied with the remaining indentation per level.
*/
TreeItem<S> rootItem = aTreeTableView.getRoot();
Stack<TreeItem<S>> items = new Stack<>();
if (aTreeTableView.isShowRoot() && rootItem != null) {
items.push(rootItem);
} else if (rootItem != null) {
rootItem.getChildren().forEach(items::push);
}
int maxLevel = -1;
while (!items.isEmpty()) {
TreeItem<S> curItem = items.pop();
int curLevel = aTreeTableView.getTreeItemLevel(curItem);
maxLevel = Math.max(curLevel, maxLevel);
if (curItem.isExpanded()) {
curItem.getChildren().forEach(items::push);
}
}
if (maxLevel >= 0) {
double indentation = 20; // the indentation used in the css stylesheet
double additionalIndentPerLevel = indentation - 10; // constant from TreeTableCellSkin
treeTableColumn.setPrefWidth(treeTableColumn.getWidth() + maxLevel * additionalIndentPerLevel);
}
Try of an explanation:
As mentioned I am using reflection to fit the column widths to their content:
Method fitColumn = TreeTableViewSkin.class.getDeclaredMethod("resizeColumnToFitContent", TreeTableColumn.class, int.class);
fitColumn.setAccessible(true);
List<TreeTableColumn<S, ?>> tableColumns = aTreeTableView.getColumns();
for (TreeTableColumn<S, ?> treeTableColumn : aTreeTableView.getColumns()) {
fitColumn.invoke(aTreeTableView.getSkin(), treeTableColumn, -1);
}
So I tried to understand how this process works. It seems, that the resizeColumnToFitContent
methond in the TreeTableViewSkin
FX iterates over the displayed values and for each value it constructs a new Cell and a new Row.
It then puts the cell into the row, adds the row to the current view and then read the computed prefWidth. Afterwards it removes the new row and cell.
TreeTableCell<S,?> cell = (TreeTableCell) cellFactory.call(col);
...
TreeTableRow<S> treeTableRow = new TreeTableRow<>();
treeTableRow.updateTreeTableView(treeTableView);
...
getChildren().add(cell);
cell.applyCss();
double w = cell.prefWidth(-1);
maxWidth = Math.max(maxWidth, w);
getChildren().remove(cell);
...
At the same time the TreeTableCellSkin
class uses the TableRowSkin
associated with the cell to determine the indentation:
// RT-27167: we must take into account the disclosure node and the
// indentation (which is not taken into account by the LabeledSkinBase.
...
double indentPerLevel = 10;
if (treeTableRow.getSkin() instanceof TreeTableRowSkin) {
indentPerLevel = ((TreeTableRowSkin<?>)treeTableRow.getSkin()).getIndentationPerLevel();
}
leftPadding += nodeLevel * indentPerLevel;
...
Up to this point both function work correctly and calculate the width correct.
However I assume that they do not work in combination. The TreeTableCellSkin
checks if the row associated with the cell is an instance of TreeTableRowSkin
which seems to be the case for all displayed rows. However the TreeTableViewSkin
sets the associated row to be of type TreeTableRow
.
As a result the instanceof
check fails and instead of the custom indentation the magic default constant 10
is used for width calculation.
From my point of view, I would say that this is a bug in JavaFX, but maybe my conclusions are just wrong. So any different opinion or perspective is welcome.
As a consequence the prefWidth for each column will always use a indentation of 10
per level. So if you changed the indentation you must add it manually after the FX calculation. To achieve this, I calculate the max depth of the displayed tree (my implementation is probably not the most efficient, but after all these hours I did not really care...). Moreover I calculate the missing indent for each level. Multiplying both values you can calculate the missing indentation and just have to add it to the column:
TreeItem<S> rootItem = aTreeTableView.getRoot();
Stack<TreeItem<S>> items = new Stack<>();
if (aTreeTableView.isShowRoot() && rootItem != null) {
items.push(rootItem);
} else if (rootItem != null) {
rootItem.getChildren().forEach(items::push);
}
int maxLevel = -1;
while (!items.isEmpty()) {
TreeItem<S> curItem = items.pop();
int curLevel = aTreeTableView.getTreeItemLevel(curItem);
maxLevel = Math.max(curLevel, maxLevel);
if (curItem.isExpanded()) {
curItem.getChildren().forEach(items::push);
}
}
if (maxLevel >= 0) {
double indentation = 20; // the indentation used in the css stylesheet
double additionalIndentPerLevel = indentation - 10; // constant from TreeTableCellSkin
treeTableColumn.setPrefWidth(treeTableColumn.getWidth() + maxLevel * additionalIndentPerLevel);
}