OpenJFX 16 自定义 TreeTableCell 缩进不正确

OpenJFX 16 custom TreeTableCell not indenting properly

更新 - 在末尾添加了最小的可重现示例

我正在努力将 JavaFX 8 应用程序迁移到 OpenJFX / JDK 16.

除了应用程序有一个带有自定义单元格工厂的 TreeTableView 控件外,一切都运行良好。单元格全部布局为与 TreeTableView 边界左对齐。我的期望是每个都将根据其在树中的级别进行适当的缩进。

这里是细胞工厂调用的自定义 TreeTableCell 的代码。

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

如何让 OpenJFX 16 正确对齐我的自定义 TreeTableCell?我尝试了各种对齐方式并尝试查看 Javadocs,但到目前为止没有任何效果。我在想也许我可以用 CSS 风格做点什么?

JDK 是 Amazon Corretto 16,如果重要的话。 (注意:由于外部原因,我们被迫使用版本 16)。

最小可重现示例:

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

结果:

如果我注释掉这一行:

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

...问题没有出现。似乎仅限于使用自定义 TreeTableCell class.

我不确定 Java 8 是如何工作的,但是 API 对于 updateItem() seems to require invoking setText() for non-empty cells, even if the text is not visible. As @VGR ,使用零宽度 space:

setText("\u2060");

或者,像往常一样显示 description 并将 name 设置为图形,如下所示。

作为@kleopatra , this is a bug, fixed in fx18. See also JDK-8278904—Cell: misleading doc of updateItem

经测试:

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.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();

        @Override
        protected void updateItem(MyTreeItem item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
                setGraphic(null);
            } else {
                nameLabel.setText(item.name);
                nameLabel.setTextFill(Color.DARKGOLDENROD);
                setGraphicTextGap(8);
                setGraphic(nameLabel);
                setText(item.description);
                setTextFill(Color.BLACK);
            }
        }
    }

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