1

I have a variety of buttons, each with their own background color (-fx-background-color: rgb (xxx,xxx,xxx). The button's colors are defined in the .fxml files.

Now I would like to define in a .css file for each button's background color to brighten on mouse-over.

E.g.: Button1's regular color is -fx-background-color: rgb(176,30,0) On mouse-over it should change to -fx-background-color: rgba(176,30,0,0.7)

My first problem: The defined -fx-background-color in the fxml file overwrites the .button:hover{-fx-background-color: rgba(176,30,0,0.7);} defined in the .css file.

Second problem: Is there even a way to specify via css that a button's color should on mouse-over retain its rgb values and just add the a value of 0.7 in addition?

Thanks in advance!

Varest
  • 57
  • 1
  • 11
  • Do you want to _brighten_ the color or do you want to change the _opacity_? Because for the former you can make use of looked up colors and `derive`—see the [color section](https://openjfx.io/javadoc/14/javafx.graphics/javafx/scene/doc-files/cssref.html#typecolor) of the _JavaFX CSS Reference Guide_. Unfortunately, I don't believe there's a function to change the opacity only while keeping the color. Though you can use anko's solution. – Slaw May 27 '20 at 15:31

1 Answers1

4

My first problem: [...]

It is simply the defined hierachy that e. g. a background color styling command for one control made in the FXML file will always overwrite the styling command for the same control made in the CSS file. It is the same behavior as if you have pure CSS with a set #id and a set .class for a control. The e. g. background color defined in the id statement will overwrite the background color defined for the class. So it is the standard behavior and you can not change it.

Second problem: [...]

There isn‘ t such a CSS command like „-fx-background-transparency: 0.7;“. You can do it like this with CSS (and without overwriting in FXML):

CSS File:

.my-btn-class {
    -fx-background-color: rgb(176, 30, 0);
}

.my-btn-class:hover {
    -fx-background-color: rgba(176, 30, 0, 0.7);
}

FXML File:

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

    <?import javafx.scene.control.Button?>


    <Button styleClass="my-btn-class" stylesheets="@styling.css" text="Button" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller" />

Or you could do it like this:

Controller Class:

package sample;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;

import java.net.URL;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Controller implements Initializable {

    @FXML
    private Button button;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        button.hoverProperty().addListener(((observable, oldValue, newValue) -> makeButtonTransparent(button, newValue)));
    }

    private void makeButtonTransparent(Button button, boolean transparent) {

        // Get the current style statements:
        String currentStyle = button.getStyle();

        // Check if there is a styling statement for background color with rgb or rgba:
        Pattern pattern = Pattern.compile("-fx-background-color: rgb(a?)\\(([\\d,\\s.])*\\);");
        Matcher matcher = pattern.matcher(currentStyle);

        String currentBackgroundColorStyle;
        if (matcher.find()) {
            // Extract the existing background color statement:
            currentBackgroundColorStyle = currentStyle.substring(matcher.start(), matcher.end());
        } else
            // No statement for background color in rgb(a) found:
            return;

        // Get the rgb values from the string:
        int[] rgb = new int[3];
        matcher = Pattern.compile("\\d{1,3}").matcher(currentBackgroundColorStyle);

        for (int i = 0; i < 3; i++) {
            if (matcher.find())
                rgb[i] = Integer.parseInt(currentBackgroundColorStyle.substring(matcher.start(), matcher.end()));
        }

        if (transparent)
            // Replace the background color statement with transparency value:
            button.setStyle(currentStyle.replace(currentBackgroundColorStyle, String.format("-fx-background-color: rgba(%d, %d, %d, 0.7);", rgb[0], rgb[1], rgb[2])));
        else
            // Replace the background color statement without transparency value:
            button.setStyle(currentStyle.replace(currentBackgroundColorStyle, String.format("-fx-background-color: rgb(%d, %d, %d);", rgb[0], rgb[1], rgb[2])));
    }
}

FXML File:

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

<?import javafx.scene.control.Button?>


<Button fx:id="button" style="-fx-background-color: rgb(176, 30, 0); -fx-border-color: blue;" text="Button" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller" />
anko
  • 1,628
  • 1
  • 6
  • 14
  • Thank you very much! Is there a simply way to make your second variant work with any button, not having to assign a unique -fx:id to each? – Varest May 28 '20 at 13:35
  • 1
    Yes, you can use the two methods from Hans Brendes answer: https://stackoverflow.com/questions/24986776/how-do-i-get-all-nodes-in-a-parent-in-javafx and then you can add to your initialize method: getAllNodes(root).stream() .filter(node -> node instanceof Button) .map(node -> (Button) node) .filter(button -> !button.getStyle().isEmpty()) .forEach(button -> button.hoverProperty().addListener(((observable, oldValue, newValue) -> makeButtonTransparent(button, newValue)))); – anko May 28 '20 at 14:22
  • I hope I don't take up too much of your time with follow-up questions but following your advice results in one or several null pointer exceptions. I assume because root is not defined in the controller class, only in a seperate Main class.How would I initialize root in the controller? – Varest May 29 '20 at 07:51
  • 1
    It's okay, just ask. With „root“ I meant the parent node (the base layout, e. g. a GridPane) which contrains the buttons. If you use an FXML file it would be – anko May 29 '20 at 11:23
  • Could it be that `makeButtonTransparent() ` is not only changing a button's color but that it overrides everything inside the style attribute? Now some buttons are losing their borders and others their width and height on hover. – Varest May 29 '20 at 14:26
  • Yes, this happens. If you have a lot of style commands, one thing you could do is String styling = button.getStyle() and then do some RegEx to change just the rgba values and put it back with button.setStyling(styling). – anko May 29 '20 at 14:43
  • Surprisingly enough, this worked as well: `button.setStyle(button.getStyle() + ";" + String.format("-fx-background-color: rgba(%d, %d, %d, 0.7)", rgb[0], rgb[1], rgb[2]));` – Varest May 29 '20 at 14:57
  • Hehe, yes but if you add System.out.println(button.getStyle()); to the makeButtonTransparent method, you will see how the style string will change after you hover multiple times over it. – anko May 29 '20 at 15:06
  • Me again, I tried to use the same approach to change the background color of BorderPanes, but so far without success. What I don't understand in your code is ```if(transparent)``` in ```makeButtonTransparent()```. I seem to be missing the part where it's defined that hovering over a button equates to ```transparent = true```. – Varest Jun 02 '20 at 10:29
  • 1
    It comes from the listener: button.hoverProperty().addListener(((observable, oldValue, newValue) -> [...] When the user hovers the mouse over the (in this case) button, *newValue* will be true and if the mouse cursor leaves the button *newValue* will be false. – anko Jun 02 '20 at 11:41