簡體   English   中英

為什么自定義控件不隨其子級一起調整大小?

[英]Why custom control doesn't resize with its children?

考慮以下最小的可重現示例:

public class Launcher extends Application {

    public static final double SCENE_SIZE = 500;

    @Override
    public void start(Stage primaryStage) {
        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setGridLinesVisible(true);
        grid.getColumnConstraints().setAll(
                new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, HPos.LEFT, false),
                new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.LEFT, false)
        );
        grid.getRowConstraints().setAll(
                new RowConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, VPos.TOP, false),
                new RowConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, VPos.TOP, false),
                new RowConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, VPos.TOP, false)
        );

        CustomControl customControl = new CustomControl("Lorem Ipsum ".repeat(10));
        customControl.prefWidthProperty().bind(grid.widthProperty());

        TextFlow textFlow = new TextFlow(new Text("Lorem Ipsum ".repeat(10)));
        textFlow.prefWidthProperty().bind(grid.widthProperty());

        grid.add(new Label("0"), 0, 0);
        grid.add(customControl, 1, 0);
        grid.add(new Label("1"), 0, 1);
        grid.add(textFlow, 1, 1);
        grid.add(new Label("2"), 0, 2);
        grid.add(new Label("..."), 1, 2);

        Scene scene = new Scene(grid, SCENE_SIZE, SCENE_SIZE);

        primaryStage.setTitle("Demo");
        primaryStage.setScene(scene);
        primaryStage.setOnCloseRequest(t -> Platform.exit());
        primaryStage.show();
    }

    static class CustomControl extends Control {

        private final StringProperty text = new SimpleStringProperty();

        public CustomControl(String text) { setText(text); }

        public String getText() { return text.get(); }

        public StringProperty textProperty() { return text; }

        public void setText(String text) { this.text.set(text); }

        @Override
        protected Skin<?> createDefaultSkin() { return new CustomControlSkin(this); }
    }

    static class CustomControlSkin extends SkinBase<CustomControl> {

        TextFlow textFlow;

        public CustomControlSkin(CustomControl control) {
            super(control);

            textFlow = new TextFlow();
            textFlow.getChildren().setAll(new Text(control.getText()));
            control.textProperty().addListener(
                    (obs, old, value) -> textFlow.getChildren().setAll(new Text(control.getText()))
            );

            control.heightProperty().addListener((observable, oldValue, newValue) -> System.out.println("control = " + newValue));
            textFlow.heightProperty().addListener((observable, oldValue, newValue) -> { System.out.println("textFlow = " + newValue); });

            getChildren().setAll(textFlow);
        }
    }
}

Window 調整大小會觸發 TextFlow 自動換行,因此 TextFlow 會更改其高度。 問題是它不會導致 CustomControl 高度變化,它保持不變(注意控制台輸出)。

同時,下一個網格行上的“純”TextFlow 的行為正是 CustomControl 所期望的。 當 window 調整大小時,它會來回更改其高度(以及 GridPane 行高)。

我認為任何控件都會根據孩子的高度自動改變自己的高度(SkinBase 包含很多方法),但似乎我錯了。 簡單地在 TextFlow 監聽器中設置控制高度是沒有幫助的,因為這樣只能增加它。

更新:

好的,我找到了問題所在。 發生這種情況是因為 TextFlow 需要實際寬度來計算其首選高度,但 SkinBase 使用負寬度值 解決方案:

control.widthProperty().addListener((observable, oldValue, newValue) -> {
    control.setPrefHeight(textFlow.prefHeight(newValue.doubleValue()));
});

如果有更好的方法歡迎分享:)

更新 2:

根據@Slaw 的建議,這個也可以。 奇怪的是width arg 值總是-1 ,所以我們必須從 skinnable 中獲取寬度值。

static class CustomControlSkin extends SkinBase<CustomControl> {

    // ...

    @Override
    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
                                       double leftInset) {
        return textFlow.prefHeight(getSkinnable().getWidth() != 0 ? getSkinnable().getWidth() : -1);
    }

    @Override
    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset,
                                      double leftInset) {
        return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
    }
}

根據@Slaw 的建議,這是一種可能的解決方案。 奇怪的是width arg 值總是-1 ,所以我們必須從 skinnable 中獲取寬度值。

static class CustomControlSkin extends SkinBase<CustomControl> {

    // ...

    @Override
    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
                                       double leftInset) {
        return textFlow.prefHeight(getSkinnable().getWidth() != 0 ? getSkinnable().getWidth() : -1);
    }

    @Override
    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset,
                                      double leftInset) {
        return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
    }
}

每次 Control 寬度更改時,它都會調用computeMaxHeight() ,它只是將計算委托給SkinBase 然后我們只是強制 Control 始終使用 TextFlow 首選項高度。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM