首页 > 解决方案 > 在扩展皮肤中调整 TextArea 的大小

问题描述

在 JavaFX 中,我正在尝试对 TextArea 进行扩展,这样如果在 TextArea 的底部有一个额外指定的 String 参数,则该文本将显示为提示。

这很容易在单独的窗格上实现,当他向属性添加一个侦听器并在下面添加一个标签时。

我尝试以更常规的方式进行:我创建了扩展 TextArea 和 TextAreaSkin 的类,并将 StringProperty“提示”添加到 TextArea 类。在 TextAreaSkin 中为此属性添加了一个侦听器:

public TextValueSkin(final TextValue control) {
    super(control);
    this.textValue = control;
    registerChangeListener(textValue.hintProperty(), e -> {
        updateHint();
        Platform.runLater(() -> {
            control.requestLayout();
            if (control.getParent() != null) {
                control.getParent().requestLayout();
            }
        });
    });
}

并覆盖方法 layoutChildren (double x, double y, double w, double h):

protected void layoutChildren(double x, double y, double w, double h) {
    final double bottomHeight = lblHint == null ? 0.0 : snapSizeY(lblHint.prefHeight(w));
    double textHeight = h - snapSizeY(bottomHeight);
    super.layoutChildren(x, y, w, textHeight);
    if (lblHint != null) {
        lblHint.resizeRelocate(x, textHeight, w, bottomHeight);
    }
}

因此,我得到了以下类型的带有提示的 TextArea:[在此处输入图像描述]1

也就是说,ScrollPane 和文本内容采用了新尺寸(减去 Label 高度),浅色边框(与 ContentView 相关)采用了新尺寸,但白色背景本身和浅色边框保持相同尺寸。

请帮我调整以降低此背景和边框的高度。

下面的完整演示。当 TextValue 聚焦时,我的问题是可见的(请参阅随附的屏幕截图)。提示位于明亮的边框内,并在可编辑文本的白色背景下,尽管它不以任何方式应用于它。

主类:

public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        TextValue textValue = new TextValue();
        textValue.setText("Text");
        Scene scene = new Scene(textValue);
        stage.setScene(scene);
        stage.show();
        Platform.runLater(() -> textValue.setHint("HINT"));
    }
}

文本值类:

public class TextValue extends TextArea {

    private StringProperty hint = new SimpleStringProperty(this, "hint");

    public ReadOnlyStringProperty hintProperty() {
        return hint;
    }

    public void setHint(String hint) {
        this.hint.set(hint);
    }

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

TextValueSkin.class:

public class TextValueSkin extends TextAreaSkin {

    final private TextValue textValue;
    private Label lblHint;

    public TextValueSkin(TextValue control) {
        super(control);
        this.textValue = control;
        registerChangeListener(textValue.hintProperty(), e -> {
            updateHint();
            Platform.runLater(() -> {
                control.requestLayout();
                if (control.getParent() != null) {
                    control.getParent().requestLayout();
                }
            });
        });
    }

    private void updateHint() {
        getChildren().remove(lblHint);
        String hint = textValue.hintProperty().get();
        if (hint != null && !hint.isEmpty()) {
            lblHint = new Label(hint);
            lblHint.getStyleClass().add("hint");
            lblHint.setManaged(false);
            getChildren().add(lblHint);
        } else {
            lblHint = null;
        }
    }

    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        final double bottomHeight = lblHint == null ? 0.0 : snapSizeY(lblHint.prefHeight(w));
        double textHeight = h - snapSizeY(bottomHeight);
        super.layoutChildren(x, y, w, textHeight);
        if (lblHint != null) {
            lblHint.resizeRelocate(x, textHeight, w, bottomHeight);
        }
    }
}

标签: javajavafx

解决方案


这是从 TextArea 的默认样式扩展而来的行为。突出显示滚动条周围的边框是必需的。我重写了默认样式,以便突出显示的边框指的是 ScrollPane 而不是 TextArea。

CSS:

.vbox-demo {
    -fx-padding: 1em 1em 1em 1em;
    -fx-spacing: 1em;
}

.text-value {
    -fx-background-color: transparent;
    -fx-background-insets: 0;
    -fx-background-radius: 0;
    -fx-cursor: default;
    -fx-padding: 0;
}
.text-value:focused {
    -fx-background-color: transparent;
    -fx-background-insets: 0;
    -fx-background-radius: 0;
}
.text-value .scroll-pane:disabled {
    -fx-background-color: transparent;
}
.text-value .scroll-pane {
    -fx-background-insets: 0, 1;
    -fx-background-radius: 3, 2;
    -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
        linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
}
.text-value:focused .scroll-pane {
    -fx-background-color:
        -fx-focus-color,
        -fx-control-inner-background,
        -fx-faint-focus-color,
        linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
    -fx-background-insets: -0.2, 1, -1.4, 3;
    -fx-background-radius: 3, 2, 4, 0;
}
.text-value > .scroll-pane > .scroll-bar:horizontal {
    -fx-background-insets: 0 1 0 1;
    -fx-background-radius: 0 0 2 2;
}
.text-value > .scroll-pane > .scroll-bar:vertical {
    -fx-background-insets: 1 0 1 0;
    -fx-background-radius: 0 2 2 0;
}
.text-value > .scroll-pane > .corner {
    -fx-background-insets: 0;
    -fx-background-radius: 0 0 2 0;
}
.text-value .content {
    -fx-padding: 0.25em 0.583em 0.25em 0.583em; /* 3 7 3 7 */
    -fx-cursor: text;
    -fx-background-color:
        linear-gradient(from 0px 0px to 0px 4px, derive(-fx-control-inner-background, -8%), -fx-control-inner-background);
    -fx-background-radius: 2;
}
.text-value:focused .content {
    -fx-background-color:
        -fx-control-inner-background,
        -fx-faint-focus-color,
        linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
    -fx-background-insets: 0, 0, 2;
    -fx-background-radius: 2, 1, 0;
}
.text-value > .hint {
    -fx-padding: 0.25em 0 0 0;
}

最终 TextValue.class:

public class TextValue extends TextArea {

    public TextValue() {
        getStyleClass().add("text-value");
    }

    private StringProperty hint = new SimpleStringProperty(this, "hint");

    public ReadOnlyStringProperty hintProperty() {
        return hint;
    }

    public void setHint(String hint) {
        this.hint.set(hint);
    }

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

最终 TextValueSkin.class:

public class TextValueSkin extends TextAreaSkin {

    final private TextValue textValue;
    private Label lblHint;

    public TextValueSkin(final TextValue control) {
        super(control);
        this.textValue = control;
        registerChangeListener(textValue.hintProperty(), e -> updateHint());
    }

    private void updateHint() {
        getChildren().remove(lblHint);
        String hint = textValue.hintProperty().get();
        if (hint != null && !hint.isEmpty()) {
            lblHint = new Label(hint);
            lblHint.getStyleClass().add("hint");
            lblHint.setManaged(false);
            getChildren().add(lblHint);
        } else {
            lblHint = null;
        }
    }

    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        final double bottomHeight = lblHint == null ? 0.0 : snapSizeY(lblHint.prefHeight(w));
        final double textHeight = h - snapSizeY(bottomHeight);
        super.layoutChildren(x, y, w, textHeight);
        if (lblHint != null) {
            lblHint.resizeRelocate(x, textHeight, w, bottomHeight);
        }
    }

    @Override
    protected double computePrefHeight(double w, double topInset, double rightInset, double bottomInset, double leftInset) {
        final double prefHeight = super.computePrefHeight(w, topInset, rightInset, bottomInset, leftInset);
        final double addHeight = lblHint == null ? 0.0 : snapSizeY(lblHint.prefHeight(w));
        return prefHeight + addHeight;
}

    @Override
    protected double computeMinHeight(double w, double topInset, double rightInset, double bottomInset, double leftInset) {
        final double minHeight = super.computeMinHeight(w, topInset, rightInset, bottomInset, leftInset);
        final double addHeight = lblHint == null ? 0.0 : snapSizeY(lblHint.minHeight(w));
        return minHeight + addHeight;
    }

    @Override
    protected double computeMaxHeight(double w, double topInset, double rightInset, double bottomInset, double leftInset) {
        final double maxHeight = super.computeMaxHeight(w, topInset, rightInset, bottomInset, leftInset);
        final double addHeight = lblHint == null ? 0.0 : snapSizeY(lblHint.maxHeight(w));
        return maxHeight + addHeight;
    }
}

最终演示 Main.class:

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        TextValue textValue = new TextValue();
        textValue.setText("Text");
        TextField tf = new TextField("");
        VBox vBox = new VBox(textValue, tf);
        vBox.getStyleClass().add("vbox-demo");
        Scene scene = new Scene(vBox);
        scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm());
        stage.setScene(scene);
        stage.show();
        textValue.setHint("HINT");
    }
}

推荐阅读