2
votes

I am trying to make a program that allows a user to select RGB colors through radio buttons, and changing the value from 0 - 255 using a slider. The color changes should apply to a text. When R, G or B is selected, only the selected color should be present in the text (i.e if green is selected, red and blue values are 0).

At the moment the program works to some extent. For example, if the slider is located at value 150, and I selected a new color and then move the slider, the text color is set to 150, or in any case the value the slider is set on before trying to move it to a new value. I have to select a new color before moving the slider if I want it to update. It only updates once for each selected color. I want it to update the selected color seamlessly. Code example below:

public class Oblig5 extends Application {

    static int colorValue = 0;
    static int red = 0;
    static int green = 0;
    static int blue = 0;

    public static void main(String[] args) {

        launch(args);

    }

    public void start(Stage primaryStage) {
        // Create panes
        BorderPane bPane = new BorderPane();
        VBox vBox = new VBox();
        bPane.setLeft(vBox);

        // Create text and place it in the pane
        Text text = new Text("Oblig 5");
        text.setFont(Font.font("Times New Roman", FontWeight.NORMAL, FontPosture.REGULAR, 40));
        bPane.setCenter(text);

        // Create radio buttons and place them in the VBox
        RadioButton rbRed = new RadioButton("Red");
        RadioButton rbGreen = new RadioButton("Green");
        RadioButton rbBlue = new RadioButton("Blue");


        ToggleGroup group = new ToggleGroup();
        rbRed.setToggleGroup(group);
        rbGreen.setToggleGroup(group);
        rbBlue.setToggleGroup(group);

        // Create handlers for radiobuttons 
        rbRed.setOnAction(e -> {
            if (rbRed.isSelected()) {
                red = colorValue;
                green = 0;
                blue = 0;
            }
        });

        rbGreen.setOnAction(e -> {
            if (rbGreen.isSelected()) {
                red = 0;
                green = colorValue;
                blue = 0;
            }
        });

        rbBlue.setOnAction(e -> {
            if (rbBlue.isSelected()) {
                red = 0;
                green = 0;
                blue = colorValue;
            }
        });

        vBox.getChildren().addAll(rbRed, rbGreen, rbBlue);

        // Create a slider and place it in the BorderPane
        Slider slider = new Slider(0, 255, 135);
        slider.setShowTickLabels(true);
        slider.setShowTickMarks(true);
        bPane.setBottom(slider);
        bPane.setAlignment(slider, Pos.CENTER);

        // Create a handler for the slider
        slider.valueProperty().addListener(ov -> {
            colorValue = (int) slider.getValue();
            text.setFill(Color.rgb(red, green, blue));
        });

        // Create a scene and place it in the stage
        Scene scene = new Scene(bPane, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Oblig 5");
        primaryStage.show();

    }

}

Any inputs are highly appreciated!

2

2 Answers

6
votes

[Note: @fabian posted an answer while I was writing this one. Either answer will work, I thought I would post this too since it shows slightly different techniques. As usual in JavaFX there are multiple good ways of achieving the same thing.]

When either the selected button changes, or the slider value changes, you need to set the text fill with the appropriate values. Right now, when the selected button changes, you only update the variables red, green, and blue (but don't change the text fill) and when the slider value changes, you call setFill(...) with red, green, and blue, but don't change the values of those variables.

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Slider;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Oblig5 extends Application {

    public static void main(String[] args) {

        launch(args);

    }

    public void start(Stage primaryStage) {
        // Create panes
        BorderPane bPane = new BorderPane();
        VBox vBox = new VBox();
        bPane.setLeft(vBox);

        Text text = new Text("Oblig 5");
        text.setFont(Font.font("Times New Roman", FontWeight.NORMAL, FontPosture.REGULAR, 40));
        bPane.setCenter(text);

        RadioButton rbRed = new RadioButton("Red");
        RadioButton rbGreen = new RadioButton("Green");
        RadioButton rbBlue = new RadioButton("Blue");


        ToggleGroup group = new ToggleGroup();
        rbRed.setToggleGroup(group);
        rbGreen.setToggleGroup(group);
        rbBlue.setToggleGroup(group);

        vBox.getChildren().addAll(rbRed, rbGreen, rbBlue);

        Slider slider = new Slider(0, 255, 135);
        slider.setShowTickLabels(true);
        slider.setShowTickMarks(true);
        bPane.setBottom(slider);
        BorderPane.setAlignment(slider, Pos.CENTER);

        // Create a handler for the updating text fill:
        ChangeListener<Object> updateListener = (obs, oldValue, newValue) -> {
            int colorValue = (int) slider.getValue() ;
            int red   =   rbRed.isSelected() ? colorValue : 0 ;
            int green = rbGreen.isSelected() ? colorValue : 0 ;
            int blue  =  rbBlue.isSelected() ? colorValue : 0 ;
            text.setFill(Color.rgb(red, green, blue));
        };
        slider.valueProperty().addListener(updateListener);
        group.selectedToggleProperty().addListener(updateListener);

        // Create a scene and place it in the stage
        Scene scene = new Scene(bPane, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Oblig 5");
        primaryStage.show();

    }


}
4
votes

You could set the Color to use as userData to the toggles. This allows you to create a binding for the fill property of the text based on the slider value and the selected toggle:

// create radio buttons and add color at full brightness
RadioButton rbRed = new RadioButton("Red");
rbRed.setUserData(Color.RED);
RadioButton rbGreen = new RadioButton("Green");
rbGreen.setUserData(Color.LIME);
RadioButton rbBlue = new RadioButton("Blue");
rbBlue.setUserData(Color.BLUE);

ToggleGroup group = new ToggleGroup();
rbRed.setToggleGroup(group);
rbGreen.setToggleGroup(group);
rbBlue.setToggleGroup(group);

group.selectToggle(rbRed);

...

// create binding based on toggle user data and slider value
text.fillProperty().bind(Bindings.createObjectBinding(() -> {
    Color color = (Color) group.getSelectedToggle().getUserData();

    // use color determined by toggle with brightness adjusted based on slider value
    return color.deriveColor(0, 1, slider.getValue() / slider.getMax(), 1);
}, slider.valueProperty(), group.selectedToggleProperty()));

Note that using these code snippets there is no need to keep the listeners. Especially the listener updating the text fill should be removes, since the attempt to assign a bound property results in an exception.