Home > front end >  OpenJFX 16 custom TreeTableCell not indenting properly
OpenJFX 16 custom TreeTableCell not indenting properly

Time:12-16

UPDATE - Added minimal reproduceable example at end

I am working to migrate a JavaFX 8 application to OpenJFX / JDK 16.

Everything works pretty well, except the application has a TreeTableView control with custom cell factory. The cells are all being laid out left-aligned to the TreeTableView bounds. My expectation is that each would be laid out with the proper indentation for its level in the tree.

enter image description here

Here is the code for the custom TreeTableCell that the cell factory calls.

package com.mycorp.myapp.features.saleshierarchy;

import com.mycorp.myapp.domain.SalesEntity;

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TreeTableCell;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.TextAlignment;

public class SalesTreeCell extends TreeTableCell<SalesEntity, SalesEntity> {
    private Label codeLabel = new Label();
    private Label descriptionLabel = new Label();
    private HBox group = new HBox(codeLabel, descriptionLabel);
    
    public SalesTreeCell() {
    }
    
    @Override
    protected void updateItem(SalesEntity item, boolean empty) {
        super.updateItem(item, empty);
        if ( empty || item == null ) {
            setGraphic(null);
        } else {
            final Color textColor;
            final Color backgroundColor;
            textColor = Color.DARKGOLDENROD; 
            backgroundColor = Color.TRANSPARENT;
            
            codeLabel.setText(item.getDisplayName());
            codeLabel.setFont(Font.font("Arial", 12));
            codeLabel.setTextFill(textColor); 
            codeLabel.setBackground(new Background(new BackgroundFill(backgroundColor, CornerRadii.EMPTY, Insets.EMPTY)));
            descriptionLabel.setText(item.getDisplayDescription());
            descriptionLabel.setFont(Font.font("Arial", FontPosture.ITALIC, 10));
            descriptionLabel.setTextFill(canControlNode ? Color.DARKSLATEGRAY : Color.GRAY); 
            group.setSpacing(10);
            
            setGraphic(group);
        }
    }
}

How can I make OpenJFX 16 align my custom TreeTableCell correctly? I tried various alignments and tried reviewing the Javadocs but nothing has worked so far. I am thinking maybe there is something I could do with a CSS style?

JDK is Amazon Corretto 16, if it matters. (Note: version 16 is forced on us for external reasons).

Minimal reproduceable example:

package com.subaru.SAM.sandbox;

import java.util.ArrayList;
import java.util.List;

import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.stage.Stage;

public class TestApp extends javafx.application.Application  {
    
    // Class for holding tree table data
    public static class MyTreeItem {
        public String name;
        public String description;
        public final List<TestApp.MyTreeItem> children = new ArrayList<>();
        
        public MyTreeItem( final String name, final String description, final MyTreeItem parent ) {
            this.name = name;
            this.description = description;
            if ( parent != null ) {
                parent.children.add(this);  // Bad form to allow this to escape constructor, but this is quick and dirty and we're single threaded anyway.
            }
        }
        
        @Override
        public String toString() {
            return this.name   " ... "   this.description;
        }
    }
    
    // Class for cell factory (to create TreeTableCell objects for TreeTableView)
    public static class MyTreeCell extends TreeTableCell<TestApp.MyTreeItem, TestApp.MyTreeItem> {
        private Label nameLabel = new Label();
        private Label descriptionLabel = new Label();
        private HBox group = new HBox(nameLabel, descriptionLabel);
        
        public MyTreeCell() {
        }
        
        @Override
        protected void updateItem(MyTreeItem item, boolean empty) {
            super.updateItem(item, empty);
            if ( empty || item == null ) {
                setGraphic(null);
            } else {
                final Color textColor;
                final Color backgroundColor;
                textColor = Color.DARKGOLDENROD;  
                backgroundColor = Color.TRANSPARENT;
                
                nameLabel.setText(item.name);
                nameLabel.setFont(Font.font("Arial", 12));   // FIXME Make this a preference
                nameLabel.setTextFill(textColor); 
                nameLabel.setBackground(new Background(new BackgroundFill(backgroundColor, CornerRadii.EMPTY, Insets.EMPTY)));
                descriptionLabel.setText(item.description);
                descriptionLabel.setFont(Font.font("Arial", FontPosture.ITALIC, 10));
                group.setSpacing(10);
                setGraphic(group);
            }
        }
    }


    @Override
    public void start(Stage stage) {
        
        // Make a tree table view
        final TreeTableView<MyTreeItem> treeTableView = new TreeTableView<>();
        
        // Set data for tree table
        final MyTreeItem root = new MyTreeItem("Root Node","This is the root node", null);
        final TreeItem<MyTreeItem> rootTreeItem = new TreeItem<>(root);

        for ( int i = 0; i < 5; i  ) {
            final MyTreeItem nodeI = new MyTreeItem("Node "   i,"This is node "   i, root);
            final TreeItem<MyTreeItem> treeItemI = new TreeItem<>(nodeI); 
            rootTreeItem.getChildren().add(treeItemI);
            for ( int j = 0; j < 3; j   ) {
                final MyTreeItem nodeJ = new MyTreeItem("Node "   i   "."   j,"This is node "   i   "."   j, nodeI);
                final TreeItem<MyTreeItem> treeItemJ = new TreeItem<>(nodeJ);
                treeItemI.getChildren().add(treeItemJ);
                for ( int k = 0; k < 10; k   ) {
                    final MyTreeItem nodeK = new MyTreeItem("Node "   i   "."   j   "."   k,"This is node "   i   "."   j   "."   k, nodeJ);
                    final TreeItem<MyTreeItem> treeItemK = new TreeItem<>(nodeK); 
                    treeItemJ.getChildren().add(treeItemK);
                }
            }
        }
        
        rootTreeItem.setExpanded(true);
        
        // Create a column
        final TreeTableColumn<MyTreeItem, MyTreeItem> ttc = new TreeTableColumn<>();
//      ttc.setMinWidth(600);
        ttc.setCellValueFactory((CellDataFeatures<MyTreeItem, MyTreeItem> p) -> {
            final TreeItem<MyTreeItem> treeItem = p.getValue();
            return new ReadOnlyObjectWrapper<MyTreeItem>(treeItem == null ? null : treeItem.getValue());
        });
        ttc.setCellFactory(param -> new MyTreeCell());

        
        treeTableView.getColumns().add(ttc);
        treeTableView.setRoot(rootTreeItem);
        
        
        StackPane mainPane = new StackPane(treeTableView);
        
        Scene scene = new Scene(new StackPane(mainPane), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

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

Results:

enter image description here

If I comment out this line:

ttc.setCellFactory(param -> new MyTreeCell());

... the problem does not occur. It seems limited to using a custom TreeTableCell class.

CodePudding user response:

I'm not sure how this worked with Java 8, but the API for image

As tested:

import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class TreeTableApp extends javafx.application.Application {

    // Class for holding tree table data
    public static class MyTreeItem {

        public String name;
        public String description;
        public final List<MyTreeItem> children = new ArrayList<>();

        public MyTreeItem(final String name, final String description, final MyTreeItem parent) {
            this.name = name;
            this.description = description;
            if (parent != null) {
                parent.children.add(this);
            }
        }

        @Override
        public String toString() {
            return this.name   "—"   this.description;
        }
    }

    // Class for cell factory (to create TreeTableCell objects for TreeTableView)
    private static class MyTreeCell extends TreeTableCell<MyTreeItem, MyTreeItem> {

        private final Label nameLabel = new Label();
        private final Label descriptionLabel = new Label();
        private final HBox group = new HBox(nameLabel, descriptionLabel);

        @Override
        protected void updateItem(MyTreeItem item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
                setGraphic(null);
            } else {
                setText(Character.getName(0));
                nameLabel.setText(item.name);
                nameLabel.setTextFill(Color.DARKGOLDENROD);
                descriptionLabel.setText(item.description);
                group.setSpacing(8);
                setGraphic(group);
            }
        }
    }

    @Override
    public void start(Stage stage) {
        // Make a tree table view
        final TreeTableView<MyTreeItem> treeTableView = new TreeTableView<>();
        // Set data for tree table
        final MyTreeItem root = new MyTreeItem("Root Node", "This is the root node", null);
        final TreeItem<MyTreeItem> rootTreeItem = new TreeItem<>(root);
        for (int i = 0; i < 5; i  ) {
            final MyTreeItem nodeI = new MyTreeItem("Node "   i, "This is node "   i, root);
            final TreeItem<MyTreeItem> treeItemI = new TreeItem<>(nodeI);
            rootTreeItem.getChildren().add(treeItemI);
            for (int j = 0; j < 3; j  ) {
                final MyTreeItem nodeJ = new MyTreeItem("Node "   i   "."   j, "This is node "   i   "."   j, nodeI);
                final TreeItem<MyTreeItem> treeItemJ = new TreeItem<>(nodeJ);
                treeItemI.getChildren().add(treeItemJ);
                for (int k = 0; k < 10; k  ) {
                    final MyTreeItem nodeK = new MyTreeItem("Node "   i   "."   j   "."   k, "This is node "   i   "."   j   "."   k, nodeJ);
                    final TreeItem<MyTreeItem> treeItemK = new TreeItem<>(nodeK);
                    treeItemJ.getChildren().add(treeItemK);
                }
            }
        }
        rootTreeItem.setExpanded(true);
        // Create a column
        final TreeTableColumn<MyTreeItem, MyTreeItem> ttc = new TreeTableColumn<>();
        ttc.setCellValueFactory((CellDataFeatures<MyTreeItem, MyTreeItem> p) -> {
            final TreeItem<MyTreeItem> treeItem = p.getValue();
            return new ReadOnlyObjectWrapper<>(treeItem == null ? null : treeItem.getValue());
        });
        ttc.setCellFactory(param -> new MyTreeCell());
        ttc.setPrefWidth(320);
        treeTableView.getColumns().add(ttc);
        treeTableView.setRoot(rootTreeItem);
        StackPane mainPane = new StackPane(treeTableView);
        Scene scene = new Scene(new StackPane(mainPane), 320, 240);
        stage.setScene(scene);
        stage.show();
    }

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