tsProgrammer
tsProgrammer

Reputation: 21

How To Make Button Background Change OnClick

I am trying to solve an issue with a calculator project I am working on with javafx 11 and java11 using Scene Builder. I am trying to figure out a way to make the buttons on the calculator change color when typing the corresponding value from the keyboard. Is there a method, or onKeyPressed etc type solution to this issue?

I was able to make the button change colors when the a user clicks on it with a mouse (changes to green). This was accomplished in my css style sheet. I tried to add a method to my onKeyReleased method in my controller class, and could change the background that way, but could not determine a way to have the color change back in a timely manner without causing a lag on the UI. I want the UI to change colors similar to most calculators such as the standard windows calculator. This is where a user holds the key down and it changes color, and when releasing the key it changes back.

//main.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception {
    Parent root = 
 FXMLLoader.load(getClass().getResource("calculator.fxml"));
    primaryStage.getIcons().add(new Image("CALC.png"));
    primaryStage.setTitle(" TS Calculator");
    primaryStage.setScene(new Scene(root, 250, 375));
    primaryStage.setResizable(true);
    primaryStage.setMinHeight(375);
    primaryStage.setMinWidth(250);
    primaryStage.show();
}
public static void main(String[] args) {
    launch(args);
}

 /CSS
Button{
-fx-background-color: black;
-fx-text-align: center;
-fx-text-fill: white;
-fx-border-color: green;
-fx-font-size: 1em;
-fx-border-radius: 10 10 10 10;
-fx-background-radius: 10 10 10 10;
}
Button:pressed{
-fx-background-color: green;
}
TextField{
-fx-font-size: 1.5em;
}

//FXML (Only showing 1 button)

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane maxHeight="1.7976931348623157E308" 
maxWidth="1.7976931348623157E308" minHeight="306.00" minWidth="204.0"
      prefHeight="288.0" prefWidth="208.0" style="-fx-background-color: 
DARKSLATEGREY; -fx-border-color: green;"
      stylesheets="@styles.css" xmlns="http://javafx.com/javafx/11.0.1" 
xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="com.trevorsmith.Controller"
      onKeyReleased="#acceptKeyboardInput">
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnSpan="4" 
GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS" 
onKeyPressed="#acceptKeyboardInput">
    <children>
            <TextField fx:id="textFieldDisplay"  editable="false" 
alignment="CENTER_RIGHT" maxHeight="1.7976931348623157E308" 
 maxWidth="1.7976931348623157E308" prefWidth="196.0" 
AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" 
AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
  </children>
</AnchorPane>
<AnchorPane maxHeight="1.7976931348623157E308" 
maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" 
GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
  <children>
      <Button maxHeight="1.7976931348623157E308" 
maxWidth="1.7976931348623157E308" text="0" AnchorPane.bottomAnchor="0.0" 
AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="0.0" 
AnchorPane.topAnchor="0.0" />
  </children>

I am unable to make the background color change on click, and when I release my key it change back.

Upvotes: 1

Views: 2110

Answers (1)

Zephyr
Zephyr

Reputation: 10253

You'll want to register a couple of listeners on your Scene to listen to the pressed keys.

Once you have the key, you can use JavaFX's PseudoClass selectors to update the pressed style of each Button.

There could be a more streamlined way to do this, but this is my implementation. Here is a complete sample you can try out to see it in action.

Note that I did not implement the actual functionality of the calculator.

Final result/screenshot at the end.


Main.java:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("MainLayout.fxml"));

            Scene scene = new Scene(loader.load());
            scene.getStylesheets().add("style.css");
            primaryStage.setScene(scene);
            primaryStage.setTitle("Calculator");
            primaryStage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

MainLayout.fxml:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="TOP_CENTER" spacing="10.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="UI.BaseApps.Calculator.MainController">
    <padding>
        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
    </padding>
    <children>
        <TextField fx:id="txtDisplay" disable="true" alignment="CENTER_RIGHT" editable="false" minHeight="-Infinity"
                   prefHeight="50.0" text="0"/>
        <GridPane hgap="10.0" vgap="10.0" VBox.vgrow="ALWAYS">
            <columnConstraints>
                <ColumnConstraints hgrow="SOMETIMES" minWidth="-Infinity"/>
                <ColumnConstraints hgrow="SOMETIMES" minWidth="-Infinity"/>
                <ColumnConstraints hgrow="SOMETIMES" minWidth="-Infinity"/>
                <ColumnConstraints hgrow="NEVER" minWidth="-Infinity"/>
                <ColumnConstraints hgrow="SOMETIMES" minWidth="-Infinity"/>
                <ColumnConstraints hgrow="SOMETIMES" minWidth="-Infinity"/>
            </columnConstraints>
            <rowConstraints>
                <RowConstraints minHeight="-Infinity" vgrow="SOMETIMES"/>
                <RowConstraints minHeight="-Infinity" vgrow="SOMETIMES"/>
                <RowConstraints minHeight="-Infinity" vgrow="SOMETIMES"/>
                <RowConstraints minHeight="-Infinity" vgrow="SOMETIMES"/>
            </rowConstraints>
            <children>
                <Button fx:id="btn7" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="7"/>
                <Button fx:id="btn8" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="8" GridPane.columnIndex="1"/>
                <Button fx:id="btn9" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="9" GridPane.columnIndex="2"/>
                <Separator orientation="VERTICAL" prefHeight="200.0" GridPane.columnIndex="3" GridPane.rowSpan="4"/>
                <Button fx:id="btnDivide" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="÷" GridPane.columnIndex="4"/>
                <Button fx:id="btnClear" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="C" GridPane.columnIndex="5"/>
                <Button fx:id="btn4" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="4" GridPane.rowIndex="1"/>
                <Button fx:id="btn5" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="5" GridPane.columnIndex="1"
                        GridPane.rowIndex="1"/>
                <Button fx:id="btn6" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="6" GridPane.columnIndex="2"
                        GridPane.rowIndex="1"/>
                <Button fx:id="btnMultiply" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="x" GridPane.columnIndex="4"
                        GridPane.rowIndex="1"/>
                <Button fx:id="btn1" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="1" GridPane.rowIndex="2"/>
                <Button fx:id="btn2" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="2" GridPane.columnIndex="1"
                        GridPane.rowIndex="2"/>
                <Button fx:id="btn3" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="3" GridPane.columnIndex="2"
                        GridPane.rowIndex="2"/>
                <Button fx:id="btn0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" text="0" GridPane.columnSpan="2"
                        GridPane.rowIndex="3"/>
                <Button fx:id="btnDecimal" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="." GridPane.columnIndex="2"
                        GridPane.rowIndex="3"/>
                <Button fx:id="btnSubtract" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="-" GridPane.columnIndex="4"
                        GridPane.rowIndex="2"/>
                <Button fx:id="btnAdd" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="+" GridPane.columnIndex="4"
                        GridPane.rowIndex="3"/>
                <Button fx:id="btnEquals" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="="
                        GridPane.columnIndex="5" GridPane.rowIndex="2" GridPane.rowSpan="2"/>
                <Button fx:id="btnBackspace" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
                        mnemonicParsing="false" prefHeight="50.0" prefWidth="50.0" text="←" GridPane.columnIndex="5"
                        GridPane.rowIndex="1"/>
            </children>
        </GridPane>
    </children>
</VBox>

MainController.java:

import javafx.application.Platform;
import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;

public class MainController {

    // Here we'll define our PseudoClass needed to set the style for each pressed Button
    private static final PseudoClass PRESSED = PseudoClass.getPseudoClass("pressed");

    // Define FXML controls
    @FXML
    private TextField txtDisplay;
    @FXML
    private Button btn7, btn8, btn9;
    @FXML
    private Button btn4, btn5, btn6;
    @FXML
    private Button btn1, btn2, btn3;
    @FXML
    private Button btn0, btnDecimal;
    @FXML
    private Button btnMultiply, btnSubtract, btnAdd, btnDivide;
    @FXML
    private Button btnClear, btnEquals, btnBackspace;

    @FXML
    private void initialize() {

        // We need access to the Scene to register our key listeners, so we need to wrap the code in a Platform.runLater(). If we try to do this without Platform.runLater(), we'll get a NullPointerException because txtDisplay hasn't been rendered yet.
        Platform.runLater(() -> {

            Scene scene = txtDisplay.getScene();

            // Add a listener to capture any key that is pressed. We add this to the entire scene and we can then change the style of the corresponding button accordingly.
            scene.setOnKeyPressed(event -> {

                // We need to know which Button we're working with
                Button button = getButton(event.getCode());

                // Add our "pressed" style to the Button
                if (button != null) button.pseudoClassStateChanged(PRESSED, true);
            });

            // Once the user releases the key, remove our custom style and trigger whatever onAction() code has been applied to the corresponding Button.
            scene.setOnKeyReleased(event -> {

                Button button = getButton(event.getCode());
                if (button != null) {
                    button.pseudoClassStateChanged(PRESSED, false);

                    // Fire the button's onAction()
                    button.fire();
                }

            });
        });


    }

    // Helper method to get the Button that corresponds to the pressed key. The Scene.setOnKeyPressed() listener provides the KeyCode for the pressed key. We can use that to determine which of our Buttons to trigger.
    private Button getButton(KeyCode keyCode) {
        switch (keyCode) {
            case NUMPAD0: return btn0;
            case NUMPAD1: return btn1;
            case NUMPAD2: return btn2;
            case NUMPAD3: return btn3;
            case NUMPAD4: return btn4;
            case NUMPAD5: return btn5;
            case NUMPAD6: return btn6;
            case NUMPAD7: return btn7;
            case NUMPAD8: return btn8;
            case NUMPAD9: return btn9;
            case DECIMAL: return btnDecimal;
            case DIVIDE: return btnDivide;
            case ADD: return btnAdd;
            case MULTIPLY: return btnMultiply;
            case SUBTRACT: return btnSubtract;
            case ENTER: return btnEquals;
            case BACK_SPACE: return btnBackspace;
            case ESCAPE: return btnClear;
        }
        return null;
    }
}

style.css:

.text-field {
    -fx-opacity: 1.0;
    -fx-font-family: Consolas;
    -fx-font-size: 200%;
}

.button {
    -fx-font-family: Consolas;
    -fx-font-size: 150%;
    -fx-background-radius: 25px;
    -fx-border-radius: 25px;

    /* Remove focus highlighting */
    -fx-focus-traversable: false;
}

.button:pressed {
    -fx-background-color: lightgreen;
    -fx-border-color: green;
}

And here is the result:

animated screenshot

Upvotes: 5

Related Questions