Search code examples
javajavafxscenebuilder

How to set text to a label on keyboard key press in JavaFX application?


I'm making JavaFX application with Scene Builder. I have Main Class that launch Controller with main window.

I have a label in the Controller class

I need to assign keyboard key press in main window.

Example: if I press '1' on keyboard the text of a label should be set as "key 1 is pressed.

If I press "2" on keyboard the text should be set as "key 2 is pressed"

How Can I do that?

I've tried to use this code in initialize method, but nothing happens:

  category1.setOnKeyPressed(new EventHandler<KeyEvent>() {
  @Override
  public void handle(KeyEvent ke) {
    KeyCode kc = ke.getCode();
    if((kc.equals(KeyCode.D))) {
      category1.setText("Key D is pressed");
    }
  }
});

Main Class: package src.card;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.*;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class Main extends Application {


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


  @Override
  public void start(Stage primaryStage) throws Exception {

    try {

      Parent root = FXMLLoader.load(getClass().getResource("resources/fxml/card.fxml"));
      Scene scene = new Scene(root, 1600, 600);
      primaryStage.setScene(scene);
      scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
      primaryStage.initStyle(StageStyle.UNDECORATED);
      primaryStage.setMaximized(true);
      primaryStage.setResizable(true);

      primaryStage.getIcons().add(new Image("src/card/resources/logo-icon.png"));
      primaryStage.show();

      //adding resize and drag primary stage
      ResizeHelper.addResizeListener(primaryStage);


      //assign ALT+ENTER to maximize window
      final KeyCombination kb = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.CONTROL_DOWN);
      scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent event) {
          if (kb.match(event)) {
            primaryStage.setMaximized(!primaryStage.isMaximized());
            primaryStage.setResizable(true);
            Controller cont = Context.getInstance().getController();
            if (!primaryStage.isMaximized()) {
              cont.getBtnFont().setPrefWidth(20);
              cont.getBtnPalette().setPrefWidth(20);
              cont.getBtnQuestCards().setPrefWidth(20);
              cont.getBtnNonQuestCards().setPrefWidth(20);

            } else if (primaryStage.isMaximized()){
              cont.getBtnFont().setPrefWidth(50);
              cont.getBtnPalette().setPrefWidth(50);
              cont.getBtnQuestCards().setPrefWidth(50);
              cont.getBtnNonQuestCards().setPrefWidth(50
              );
            }

          }
        }
      });

    } catch (Exception e) {
      e.printStackTrace();
    }

  }

}

Controller:

@FXML  private Label category1

@FXML public void initialize(URL location, ResourceBundle resources) {

    category1.setOnKeyPressed(new EventHandler<KeyEvent>() {
      @Override
      public void handle(KeyEvent ke) {
        KeyCode kc = ke.getCode();
        if((kc.equals(KeyCode.D))) {
          category1.setText("Key D is pressed");
        }
      }
    });


    //register Controller in  Context Class
    Context.getInstance().setController(this);
  }

EDIT:

I've tried to use it in initialize method:

category1.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
      @Override
      public void handle(KeyEvent ke) {
        KeyCode kc = ke.getCode();
        if((kc.equals(KeyCode.D))) {
          category1.setText("Key D is pressed");
        }
      }
    });

and I got errors:

javafx.fxml.LoadException: 
/D:/IDEA%20Projects/CategoryCards/out/production/CategoryCards/src/card/resources/fxml/card.fxml

    at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2603)

If I remove getScene() from category1 - everything is going fine and application launch

It seems when I use getScene() it try to get fxml file from out folder, not my main folder, but how can I fix this?


Solution

  • You can do so by setting key press event(setOnKeyPressed) to parent of Label instead of Scene. Trying to add event to Scene will give error because initialize will be called before creation of the Scene.

    Let's say you have FXML file called sample.fxml and In that file you have GridPane having id root and a Label having id category1

    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.GridPane?>
    <GridPane fx:controller="Controller"
              xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10" fx:id="root">
        <Label text="Category" fx:id="category1"/>
    </GridPane>
    

    And you have Controller class called Controller. To change label of category1 when any key is pressed, first you have to set category1 focus traversable by calling setFocusTraversable method on category1 and passing true. And then you have to add key press event on root(parent of category1), As shown below

    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Label;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.GridPane;
    import java.net.URL;
    import java.util.ResourceBundle;
    
    public class Controller implements Initializable{
        
        @FXML private Label category1;
        @FXML private GridPane root;
        
        @Override
        public void initialize(URL location, ResourceBundle resources){
            category1.setFocusTraversable(true);
            root.setOnKeyPressed(this::handle);
        }
        
        private void handle(KeyEvent keyEvent){
            category1.setText("Key " + keyEvent.getCode().getName() + " is pressed");
        }
        
    }
    

    And here is simple version of Main class

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class Main extends Application{
        
        public static void main(String[] args){
            launch(args);
        }
        
        @Override
        public void start(Stage primaryStage) throws Exception{
            Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
            Scene scene = new Scene(root, 300, 275);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
        
    }