JavaFX工具栏调整到子对象的动态宽度

JavaFX工具栏调整到子对象的动态宽度,java,javafx,javafx-11,Java,Javafx,Javafx 11,我不得不用ToggleButtons而不是Tabs来切换TabPane作为工具栏。这是因为我需要对“Tab”的内容和图形以及拖放功能进行更好的控制,而不是像Tab那样开箱即用。我可以让选项卡按我想要的方式运行和显示,但因为我需要在选项卡图形的标签上设置文本(在我的例子中是HBox),这意味着溢出上下文菜单没有任何可见文本。我还希望我的标签页的其他布局属性可以在上下文项中继承,工具栏为我提供了很好的布局属性(背景颜色和图像布置在标签文本旁边)。 我遇到的问题是,选项卡上显示的图像(现在是Toggl

我不得不用ToggleButtons而不是Tabs来切换TabPane作为工具栏。这是因为我需要对“Tab”的内容和图形以及拖放功能进行更好的控制,而不是像Tab那样开箱即用。我可以让选项卡按我想要的方式运行和显示,但因为我需要在选项卡图形的标签上设置文本(在我的例子中是HBox),这意味着溢出上下文菜单没有任何可见文本。我还希望我的标签页的其他布局属性可以在上下文项中继承,工具栏为我提供了很好的布局属性(背景颜色和图像布置在标签文本旁边)。 我遇到的问题是,选项卡上显示的图像(现在是ToggleButtons(代码中的FakeTabs))会动态更新,因此会更改ToggleButton的宽度。如果我在FakeTab中设置以下内容:

//设置minSize以防止标签文本被截断。

`this.setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);`
为了使图像旁边标签上的文本不会被截断,工具栏不会调整大小,打开ContextMenu的>>按钮结束于最后一个ToggleButton,但它也会错过菜单中的一些ToggleButton,因此您无法访问它们。 有没有一种方法可以设置工具栏,使其在更改时可以调整到其子对象的宽度,或者我可以强制重新显示?当我说“调整大小”时,我希望它保持其实际宽度,但重新计算哪些切换按钮需要位于ContextMenu中,并将>>按钮保持在最右侧,而不是切换按钮上方。 我尝试了各种方法来打开工具栏上的.requestLayout(),但到目前为止没有成功

我的reprex添加了一些faketab(ToggleButtons),然后在应用程序运行后,会逐渐添加一些圆圈(替换我应用程序中添加的图像)。这会导致ToggleButtons变宽,工具栏无法调整大小以适应孩子们的新宽度。 移除

this.setMinSize(Button.USE_PREF_SIZE,Button.USE_PREF_SIZE);

From FakeTab导致FakeTab的文本在添加圆圈时被截断,这对我来说是不可行的

谢谢你的帮助

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;

import java.util.Arrays;
import java.util.List;


public class MainApp extends Application {

    private CustomTabPane customTabPane;

    @Override
    public void start(Stage stage) {

        customTabPane = new CustomTabPane();
        VBox.setVgrow(customTabPane, Priority.ALWAYS);

        VBox vbox = new VBox(2.0);
        vbox.getChildren().setAll(customTabPane);

        BorderPane root = new BorderPane();
        root.setMaxWidth(800);
        root.setMaxHeight(400);
        root.setCenter(vbox);

        //add some fake tabs
        List<DataModel> dataModels = Arrays.asList(

                new DataModel("Tab1 name ", new int[]{0, 0, 255}),
                new DataModel("Tab2 name 22", new int[]{0, 0, 140}),
                new DataModel("Tab3 name 333", new int[]{0, 0, 100}),
                new DataModel("Tab4 name 4444", new int[]{255, 0, 0}),
                new DataModel("Tab5 name 55555", new int[]{100, 0, 0}),
                new DataModel("Tab6 name 666666", new int[]{0, 255, 0}),
                new DataModel("Tab7 name 7777777", new int[]{0, 140, 0}),
                new DataModel("Tab8 name 88888888", new int[]{0, 100, 0}),
                new DataModel("Tab9 name 999999999", new int[]{50, 0, 50}),
                new DataModel("Tab10 name xxxxxxxxxx", new int[]{110, 0, 50}));
        for (DataModel data : dataModels) {
            addFakeTab(data);
        }
        //simulated Images being updated to Tabs after the CustomTabPane has been populated with FakeTabs.
        Task task = new Task<Void>() {
            @Override
            public Void call() throws InterruptedException {
                Thread.sleep(3000);
                for (int i = 0; i < 3; i++) {
                    Platform.runLater(() -> {
                        updateTabIcons();
                    });
                    Thread.sleep(3000);
                }
                return null;
            }
        };
        new Thread(task).start();

        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setTitle("CustomTabPane");
        stage.show();

    }

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

    /**
     * Fake updating FakeTabs with 'images'.
     */
    private void updateTabIcons() {
        for (int i = 0; i < customTabPane.getToolBarNodes().size(); i++) {
            Node node = customTabPane.getToolBarNodes().get(i);
            if (node instanceof FakeTab) {
                ((FakeTab) node).addImage(createCircle());
            }
        }
    }

    /**
     * Add a circle where the image would go
     **/
    private Circle createCircle() {
        Circle circle = new Circle(10);
        circle.setFill(Color.BLACK);
        circle.setStrokeType(StrokeType.INSIDE);
        circle.setStrokeWidth(2.0);
        circle.setStrokeLineCap(StrokeLineCap.BUTT);
        circle.setStroke(Color.WHITE);

        return circle;
    }

    private void addFakeTab(DataModel data) {
        FakeTab fakeTab = new FakeTab(data);
        customTabPane.addTab(fakeTab);
    }

    class CustomTabPane extends VBox {
        private StackPane stackPane;
        private ToolBar toolBar;

        public CustomTabPane() {
            toolBar = new ToolBar();
            stackPane = new StackPane();

            this.setAlignment(Pos.TOP_CENTER);
            this.getChildren().setAll(toolBar, stackPane);
        }

        public ObservableList<Node> getToolBarNodes() {
            return toolBar.getItems();
        }

        public void addTab(ToggleButton toggleButton) {
            toolBar.getItems().add(toggleButton);
        }

    }

    class DataModel {
        private String id;
        private int[] rgbColor;

        public DataModel(String id, int[] rgbColor) {
            this.id = id;
            this.rgbColor = rgbColor;
        }

        public int[] getRgbColor() {
            return rgbColor;
        }

        public String getRgbString() {
            return "" + rgbColor[0] + "," + rgbColor[1] + "," + rgbColor[2];
        }

        public String getStringId() {
            return id;
        }
    }

}

我已经浏览了ToolBarSkin的源代码,因为它以我需要的方式处理溢出(渲染切换按钮的背景色,包括子项,如图像)。它有一个私有布尔值needsUpdate,设置为在删除或添加项目时尝试,但不会在儿童项目的维度发生变化时尝试。目前,我使用的hack是访问此私有字段的反射,因此我可以在请求工具栏重新显示其子项之前将其设置为true。它满足我的需要,但我不希望有e使用反射

 //use reflection to access private member field needsUpdate. This is the boolean
                //used by the skin to trigger a relayout of the ToolBar when items are added/removed.
                //we need to use this to force a relayout when children change sizes too.
                Field f1 = ToolBarSkin.class.getDeclaredField("needsUpdate");
                f1.setAccessible(true);
                f1.setBoolean(skin, true);
                this.requestLayout();

感谢您的评论。

看看TabPaneSkin的源代码,当弹出窗口显示时,它应该会处理这个问题-您可以从以下位置获得复制的想法:)
 //use reflection to access private member field needsUpdate. This is the boolean
                //used by the skin to trigger a relayout of the ToolBar when items are added/removed.
                //we need to use this to force a relayout when children change sizes too.
                Field f1 = ToolBarSkin.class.getDeclaredField("needsUpdate");
                f1.setAccessible(true);
                f1.setBoolean(skin, true);
                this.requestLayout();