简体   繁体   中英

JavaFx TreeTableCell width calculation

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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM