satansdisciple14
satansdisciple14

Reputation: 21

JavaFx changing Label text color conditionally

I'm trying to change the color and text of a Label conditionally.

The text change works, but the color change does not.

I get an error:

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

Code snippet:

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

Any way to do this?

Upvotes: 1

Views: 1470

Answers (1)

jewelsea
jewelsea

Reputation: 159281

Use a change listener, not a binding

You are trying to use a single bind to change multiple target properties. You can’t do that.

You could create multiple bindings to change multiple properties, but I wouldn’t advise that.

Instead, place a change listener on the property you wish to listen to (in this case alcoholPercentageField.textProperty()) and in the code block of the listener, check the status of the new value and update all related properties (in this case the assessment label text and fill).

Aside

  1. Text comparison of numeric values is not a good idea, even if might work in some single-digit cases. Instead, you should convert the text to a number to compare it with another number.

  2. Use CSS and style classes rather than setting fills directly.

    • this can be more complex and unnecessary for some applications, so evaluate this on a case-by-case basis.
  3. When using bindings, always check to see if you can make use of the helper functions in the Bindings class, such as Bindings.when().

    • It is often better and more fluent and well tested to use the Bindings API rather than directly creating binding expressions using constructs such as When.

Simple Solution

okcareful

  • Demonstrates use of a change listener to change multiple properties.
    • text and fill of a label.
  • Does not use a text formatter or style 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();
    }
}

More Complex Solution

  • Demonstrates use of a change listener to change multiple properties.
  • Uses a text formatter and style classes.
  • This more complex solution is not necessary to demonstrate change listeners, it is just provided to show an alternate way of switching logic and UI based on user input.
  • output of the complex solution is the same as the simple solution.
  • the behavior of the complex solution differs from the simple solution in that it disallows invalid input from occurring.
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());
    }
}

Upvotes: 2

Related Questions