首页 > 解决方案 > JavaFX ToolBar 调整为儿童动态宽度

问题描述

我不得不使用 ToggleButtons 而不是 Tabs 将 TabPane 切换为 Toolbar。这是因为与开箱即用的 Tab 相比,我需要更好地控制“选项卡”的内容和图形以及拖放功能。我可以让 Tab 表现出我想要的样子,但是因为我需要在 Tab 的图形中的标签上设置我的文本(在我的例子中是一个 HBox),这意味着溢出的 ContextMenu 没有任何可见的文本。我还希望我的选项卡中的其他布局属性可以在 ContextMent 项目中继承,而工具栏很好地为我提供了这个(背景颜色和位于“选项卡文本”旁边的图像。我遇到的问题是图像是现在显示在选项卡上的 ToggleButtons(我的代码中的 FakeTabs)是动态更新的,因此会更改 ToggleButton 的宽度。

//set minSize to prevent Label text being truncated.

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

这样图像旁边的标签上的文本就不会被截断,工具栏不会调整大小,并且用于打开 ContextMenu 的 >> 按钮最终会超过最后一个 ToggleButton 但它也错过了菜单中的一些 ToggleButtons 所以你无法访问它们。有没有办法设置工具栏,以便在更改时将其调整为孩子的宽度,或者我可以强制重新布局?当我说调整大小时,我希望它保持它的实际宽度,但重新计算哪些 ToggleButtons 需要位于 ContextMenu 中,并保持最右边的 >> 按钮不超过 ToggleButton。我在 ToolBar 上尝试了各种 .requestLayout() 方法,但到目前为止没有成功。

我的 reprex 添加了一些我的 FakeTabs(ToggleButtons),然后在应用程序运行后将逐渐添加一些 Circles(替换我的应用程序中添加的图像)。这会导致 ToggleButtons 变宽并且 ToolBar 无法调整大小以适应孩子们的新宽度的问题。移除

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

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

}

FakeTab 类:

import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;


public class FakeTab extends ToggleButton {

    private Label label;
    private HBox imageHBox;
    private MainApp.DataModel dataModel;


    public FakeTab(MainApp.DataModel dataModel) {
        super("");
        this.dataModel = dataModel;
        //set minSize to prevent Label text being truncated.
        this.setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);
        label = new Label();
        imageHBox=new HBox(1.0);
        HBox hBox = new HBox(imageHBox,label);
        hBox.setStyle("-fx-padding: 1.0em;");
        hBox.setAlignment(Pos.CENTER_LEFT);
        this.setGraphic(hBox);
        this.setUserData("" + dataModel.getStringId());
        label.setText("" + dataModel.getStringId());

        int[] rgb = dataModel.getRgbColor();
        Color color = getFXColor(rgb[0], rgb[1], rgb[2], 1.0);
        String style = "-fx-background-color:rgb(" + dataModel.getRgbString() + ");-fx-text-fill:" + (isColorDark(color) ? "white" : "black") + ";";
        String style2 = "-fx-background-color:rgb(" + dataModel.getRgbString() + ");-fx-text-fill:transparent;";
        this.setStyle(style2);
        label.setStyle(style);

    }

    //add a Circle to simulate the effect adding images has.
    public void addImage(Circle circle){
        imageHBox.getChildren().add(circle);
    }

    private  Color getFXColor(int iR, int iG, int iB, double opacity) {
        double r = ((double) iR) / 255;
        double g = ((double) iG) / 255;
        double b = ((double) iB) / 255;
        return new Color(r, g, b, opacity);
    }

    private  boolean isColorDark(Color color) {
        boolean isDark = false;
        double boundary = 0.4980392156862745;
        int colorChk = 0;
        if (color.getRed() > boundary) {
            colorChk += 1;
        }
        if (color.getGreen() > boundary) {
            colorChk += 2;
        }
        if (color.getBlue() > boundary) {
            colorChk += 4;
        }
        switch (colorChk) {
            case 0:
            case 1:
            case 4:
                isDark = true;
                break;
        }
        return isDark;
    }

}

标签: javajavafxjavafx-11

解决方案


我已经查看了 ToolBarSkin 的源代码,因为它以我需要的方式处理溢出(渲染 ToggleButtons 的背景颜色并包括图像等子项)。它有一个私有的布尔值 needsUpdate,它设置为在删除或添加项目时尝试,但在子项目的尺寸发生变化时不尝试。现在我使用的技巧是访问这个私有字段的一些反射,所以我可以在请求工具栏重新布局它的孩子之前将它设置为 true。它可以满足我的需要,但我不想使用反射。

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

谢谢你的评论。


推荐阅读