Reputation: 21
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
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:
Upvotes: 5