JavaFx有条件地改变标签文本颜色

JavaFx changing Label text color conditionally

我正在尝试有条件地更改标签的颜色和文本。

文本更改有效,但颜色更改无效。

我收到一个错误:

The method setFill(Color) is undefined for the type When.StringConditionBuilder

代码片段:

assessmentLabel.textProperty().bind(
    new When(alcoholPercentageField.textProperty().greaterThanOrEqualTo("5"))
        .then("Be careful").setFill(Color.RED)
        .otherwise("Smart choice").setFill(Color.GREEN)
);

有什么办法吗?

使用更改侦听器,而不是绑定

您正在尝试使用单个绑定来更改多个目标属性。你不能那样做。

您可以创建多个绑定来更改多个属性,但我不建议这样做。

相反,在您希望监听的 属性 上放置一个更改监听器(在本例中为 alcoholPercentageField.textProperty()),并在监听器的代码块中检查新监听器的状态值并更新所有相关属性(在本例中为评估标签文本和填充)。

一边

  1. 数字值的文本比较不是一个好主意,即使在某些 single-digit 情况下可能有效。相反,您应该将文本转换为数字以将其与另一个数字进行比较。

  2. 使用 CSS 和样式 classes 而不是直接设置填充。

    • 对于某些应用程序来说,这可能更复杂且不必要,因此请在 case-by-case 的基础上对其进行评估。
  3. 使用绑定时,请始终检查是否可以使用绑定中的辅助函数 class,例如 Bindings.when().

    • 使用绑定 API 通常比使用 When.
    • 等构造直接创建绑定表达式更好、更流畅且经过充分测试

简单的解决方案

  • 演示如何使用更改侦听器来更改多个属性。
    • 标签的文本和填充。
  • 不使用文本格式化程序或样式 classes.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class SimpleChangeReaction extends Application {

    @Override
    public void start(Stage stage) {
        final TextField alcoholPercentageField = new TextField();
        final Label assessmentLabel = new Label();
        alcoholPercentageField.textProperty().addListener((observable, oldValue, newValue) -> {
            try {
                int intValue = Integer.parseInt(newValue);

                if (intValue > 5) {
                    assessmentLabel.setText("Be careful");
                    assessmentLabel.setTextFill(Color.RED);
                } else {
                    assessmentLabel.setText("Smart choice");
                    assessmentLabel.setTextFill(Color.GREEN);
                }
            } catch (NumberFormatException e) {
                assessmentLabel.setText("Invalid");
                assessmentLabel.setTextFill(Color.ORANGE);
            }
        });

        VBox layout = new VBox(10,
                alcoholPercentageField,
                assessmentLabel
        );
        layout.setPadding(new Insets(10));

        stage.setScene(new Scene(layout));
        stage.show();
    }

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

更复杂的解决方案

  • 演示如何使用更改侦听器来更改多个属性。
  • 使用文本格式化程序和样式 classes.
  • 这个更复杂的解决方案不需要演示更改侦听器,它只是用来显示切换逻辑的替代方法,UI 基于用户输入。
  • 复杂解的输出与简单解相同。
  • 复杂解决方案的行为不同于简单解决方案,因为它不允许出现无效输入。
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.IntegerStringConverter;

import java.util.Arrays;
import java.util.function.UnaryOperator;

public class ChangeReaction extends Application {

    @Override
    public void start(Stage stage) {
        final Style style = new Style();

        final TextField alcoholPercentageField = new TextField();
        final Label assessmentLabel = new Label();

        PositiveIntTextFormatterFactory formatterFactory = new PositiveIntTextFormatterFactory();
        TextFormatter<Integer> positiveIntFormatter = formatterFactory.createPositiveIntTextFormatter(0);
        alcoholPercentageField.setTextFormatter(positiveIntFormatter);

        positiveIntFormatter.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue > 5) {
                assessmentLabel.setText("Be careful");
                style.getStatusStyleClassChooser().chooseStyleClass(
                        assessmentLabel,
                        Style.StatusStyleClassChoices.warning
                );
            } else {
                assessmentLabel.setText("Smart choice");
                style.getStatusStyleClassChooser().chooseStyleClass(
                        assessmentLabel,
                        Style.StatusStyleClassChoices.ok
                );
            }
        });

        VBox layout = new VBox(10,
                alcoholPercentageField,
                assessmentLabel
        );
        layout.setPadding(new Insets(10));

        Scene scene = new Scene(layout);
        scene.getStylesheets().add(Style.CSS);

        stage.setScene(scene);
        stage.show();
    }

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

final class Style {
    public static final String CSS = """
            data:text/css,
            .label.ok {
                -fx-text-fill: green;
            }
            .label.warning {
                -fx-text-fill: red;
            }
            """;

    public enum StatusStyleClassChoices {
        ok, warning;
    };

    private final StyleClassChooser<StatusStyleClassChoices> statusStyleClassChooser = new StyleClassChooser<>(StatusStyleClassChoices.class);

    public StyleClassChooser<StatusStyleClassChoices> getStatusStyleClassChooser() {
        return statusStyleClassChooser;
    }
}

final class PositiveIntTextFormatterFactory {
    private static final String POSITIVE_INT_PATTERN = "([1-9][0-9]*)?";

    private final UnaryOperator<TextFormatter.Change> positiveIntFilter = change -> {
        String newText = change.getControlNewText();

        if (newText.matches(POSITIVE_INT_PATTERN) && newText.length() < 4) {
            return change;  // allow change
        }

        return null; // disallow change
    };

    private final StringConverter<Integer> positiveIntConverter = new IntegerStringConverter() {
        @Override
        public Integer fromString(String s) {
            if (s.isEmpty()) return 0 ;
            return super.fromString(s);
        }
    };

    public TextFormatter<Integer> createPositiveIntTextFormatter(int defaultValue) {
        return new TextFormatter<>(
                positiveIntConverter,
                defaultValue,
                positiveIntFilter
        );
    }
}

final class StyleClassChooser<T extends Enum<T>> {
    private final String[] choices;

    public StyleClassChooser(Class<T> styleclassEnum) {
        choices = Arrays.stream(styleclassEnum.getEnumConstants())
                .map(Enum::toString)
                .toArray(String[]::new);
    }

    public void chooseStyleClass(Node node, T styleClass) {
        node.getStyleClass().removeAll(choices);
        node.getStyleClass().add(styleClass.toString());
    }
}