4

I'm trying to make colored toggle buttons to remember their color before they were selected, for example, a black button is selected will be gray, and after it's deselected it will be back to black.

When the buttons are created they are given these properties:

    cell.setStyle("-fx-border-color: black; -fx-background-color: gray; -fx-base: gray; -fx-border-width: 1");
    cell.setOnAction(event -> setPerformAction(cell));

That's the event:

public void setPerformAction(ToggleButton cell) {
    if(cell.isSelected()) {
        cell.setStyle("-fx-border-color: red");
    }
    else{
        cell.setStyle("-fx-border-color: black");
    }
}

The black and white were applied like this:

cell.setStyle("-fx-base: white; -fx-background-color: white; -fx-border-width: 1");

But as you see in the gif below, when the buttons are deselected, all of them return to a different color. How can they remember their previous color?

BTW those buttons are generated dynamically on runtime so I can't see them in scene builder and they don't have css code.

enter image description here

shinzou
  • 5,850
  • 10
  • 60
  • 124

2 Answers2

3

This is easiest to achieve using a CSS stylesheet. You need to come up with a way to identify the ToggleButtons using a CSS selector.

The selected pseudoclass can be used to identify the selected toggles. By defining a css variable, you can easily define the color, even using inline styles:

E.g.

.toggle-container>ToggleButton {
    -fx-pref-width: 100;
    -fx-pref-height: 100;
    -fx-background-radius: 0;
    -fx-background-color: -fx-original-background;
}

.toggle-container>ToggleButton:selected {
    -fx-background-color: gray;
}

would work with the following fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1" stylesheets="@style.css" styleClass="toggle-container">
    <children>
        <ToggleButton style="-fx-original-background: black;" />
        <ToggleButton style="-fx-original-background: red;" />
        <ToggleButton style="-fx-original-background: yellow;" />
    </children>
</VBox>

but you could of course also add the ToggleButtons from java code and set the unselected color e.g. using

cell.setStyle("-fx-original-background: black;");

or

cell.setStyle("-fx-original-background: white;");

Java equivalent to fxml

@Override
public void start(Stage primaryStage) {
    ToggleButton b1 = new ToggleButton(),
            b2 = new ToggleButton(),
            b3 = new ToggleButton();

    b1.setStyle("-fx-original-background: black;");
    b2.setStyle("-fx-original-background: red;");
    b3.setStyle("-fx-original-background: yellow;");

    VBox root = new VBox(b1, b2, b3);
    root.getStyleClass().add("toggle-container");
    root.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
    Scene scene = new Scene(root);

    // alternative place to add the stylesheet
    // scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

Note for this to work the style.css file needs to be in the classpath in the same directory as the .class file of the application class (or equivalent location inside a .jar file)

fabian
  • 80,457
  • 12
  • 86
  • 114
  • How do you use the first code snippet in your answer? Do you place this in a fxml file? and the `>` links it to a toggle button? Also do you place it in a separate fxml file? – shinzou Aug 28 '16 at 14:21
  • 1
    @kuhaku The first code snippet is it's own file. If used with the fxml file, it should be named `style.css` and placed in the same directory as the fxml file (`stylesheets="@style.css"`). However it can be added to the `Scene` too (https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Scene.html#getStylesheets--) or to a parent (https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Parent.html#getStylesheets--), see also https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#introstylesheets – fabian Aug 28 '16 at 14:43
  • It doesn't work, I create buttons dynamically like so http://pastebin.com/Yq3gG6P0 I also gave the pane the css file and it's not a problem with the path... – shinzou Sep 01 '16 at 17:51
  • @kuhaku `.../style.css` seems like a weird url. I recommend adding the CSS file as resource and using `Class.getResource()` to get the resource URL, e.g. `getClass().getResource("style.css").toExternalForm()` (may lead to a NullPointerException, if the path is incorrect http://stackoverflow.com/a/6608848/2991525). Note that adding the stylesheet directly to the `ToggleButton` won't work, unless you adjust the selectors in the css file. – fabian Sep 01 '16 at 18:22
  • the `...` is instead of the actual url because it's not relevant. It's not a problem with the css path for sure, I added a different class selector in the css file and in the creation of the button `button.getStyleClass().add("boardToggleButton");` and it does change the style according to the values in `.boardToggleButton` in the css file. This was just a test to see if the problem was the path of course. – shinzou Sep 01 '16 at 18:25
  • I added the stylesheet to the pane the buttons are on as well. – shinzou Sep 01 '16 at 18:34
  • @kuhaku I added a java sample. Any other parent should also work, as long as the style class is added. – fabian Sep 01 '16 at 18:40
1

I jsut written this really fast , you can use method setUserData(Object obj) for convinience

 private class StyleData {

        private String selected = "";
        private String deselected = "";

        public StyleData(String selected, String deselected) {
            this.selected = selected;
            this.deselected = deselected;
        }

        /**
         * @return the selected
         */
        public String getSelected() {
            return selected;
        }

        /**
         * @param selected the selected to set
         */
        public void setSelected(String selected) {
            this.selected = selected;
        }

        /**
         * @return the deselected
         */
        public String getDeselected() {
            return deselected;
        }

        /**
         * @param deselected the deselected to set
         */
        public void setDeselected(String deselected) {
            this.deselected = deselected;
        }

    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO

        String style1 = "-fx-border-color: black; -fx-background-color: black; -fx-base: gray; -fx-border-width: 1";
        String style2 = "-fx-border-color: black; -fx-background-color: gray; -fx-base: gray; -fx-border-width: 1";
        btn.setUserData(new StyleData(style1, style2));
        btn.setStyle(((StyleData) btn.getUserData()).getDeselected());
        btn.selectedProperty().addListener(new ChangeListener<Boolean>() {

            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (newValue) {
                    btn.setStyle(((StyleData) btn.getUserData()).getSelected());
                } else {
                    btn.setStyle(((StyleData) btn.getUserData()).getDeselected());
                }
            }
        });
    }

Its really simple to read is this what you were looking for?

Initialize() its just a controller method i written this by creating fxml project with controller , bud you can just create ToggleButton in code and put same code that i have written

Tomas Bisciak
  • 2,801
  • 5
  • 33
  • 57
  • But if each button can have several original colors, how can they know what was the original color after it was pressed? – shinzou Sep 01 '16 at 18:31
  • @kuhaku Just put listener onto desired event and when you click it remember the color originally before you selected it, put that into Style data and you got your original color before selection and its dynamic by that it doesnt matter what color was original toggleButton at. – Tomas Bisciak Sep 03 '16 at 01:17