Waliur Rahman
Waliur Rahman

Reputation: 71

How to implement CAPS LOCK alert bubble on password field in JavaFX?

I’m trying to implement a caps lock alert on password field. If caps lock is ON then the bubble will appear below the password field. I’ve searched a lot but didn’t get any solution that how can I implement such bubble on input fields in JavaFX. I’ve found some source code to get the caps lock state.

    boolean isOn=Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
scene.setOnKeyReleased( event -> {
            if ( event.getCode() == KeyCode.CAPS ) {
                System.out.println("Capslock pressed");
                System.out.println("Capslock state: " + isOn);
            }
        });

But my problem is how to implement the bubble alert on text field. Here you can see what I have to do.

enter image description here

It would be helpful if you suggest me some possible ways as I’m new in JavaFX. Is there any JavaFX library to do such bubble alert on input fields?

Upvotes: 6

Views: 983

Answers (2)

VGR
VGR

Reputation: 44345

Update: As of JavaFX 17, you can use Platform.isKeyLocked:

BooleanProperty capsLockOn = new SimpleBooleanProperty();

Animation timer = new Timeline(new KeyFrame(Duration.millis(500),
    e -> capsLockOn.set(
        Platform.isKeyLocked(KeyCode.CAPS).orElse(false))));
timer.setCycleCount(Animation.INDEFINITE);
timer.play();

Region message = new BorderPane(new Label("Caps Lock is on"));
message.setStyle(
    "-fx-background-color: #f4f4f4;" +
    "-fx-border-color: black;" +
    "-fx-border-width: 1px;" +
    "-fx-padding: 1em 1em 0.75em 1em;" +
    "-fx-shape: 'M 0 10 h 20 l 10 -10 l 10 10 h 150 v 90 h -190 z';"
);

Popup capsLockWarning = new Popup();
capsLockWarning.getContent().add(message);

capsLockOn.addListener((o, wasOn, on) -> {
    if (on) {
        Point2D location =
            passwordField.localToScreen(-15, passwordField.getHeight());
        capsLockWarning.show(passwordField,
            location.getX(), location.getY());
    } else {
        capsLockWarning.hide();
    }
});

(The following also works, but is needlessly complicated.)

JavaFX doesn’t have any way to detect CapsLock. In theory, you could install a Scene-wide listener, but that wouldn’t catch when the state changes while other applications have focus.

Mixing AWT/Swing and JavaFX is perilous, because each has its own thread on which nearly all of its methods must be executed. Since CapsLock needs to be polled anyway, it makes sense to use javax.swing.Timer, which both executes an action regularly and ensures that action is run in the proper thread (the AWT event dispatch thread):

BooleanProperty capsLockOn = new SimpleBooleanProperty();

EventQueue.invokeLater(() -> {
    Timer timer = new Timer(500, e -> {
        boolean state = Toolkit.getDefaultToolkit().getLockingKeyState(
            KeyEvent.VK_CAPS_LOCK);
        Platform.runLater(() -> capsLockOn.set(state));
    });
    timer.start();
    Platform.runLater(() -> {
        Window window = passwordField.getScene().getWindow();
        window.setOnShown(e -> EventQueue.invokeLater(timer::restart));
        window.setOnHidden(e -> EventQueue.invokeLater(timer::stop));
    });
});

Region message = new BorderPane(new Label("Caps Lock is on"));
message.setStyle(
    "-fx-background-color: #f4f4f4;" +
    "-fx-border-color: black;" +
    "-fx-border-width: 1px;" +
    "-fx-padding: 1em 1em 0.75em 1em;" +
    "-fx-shape: 'M 0 10 h 20 l 10 -10 l 10 10 h 150 v 90 h -190 z';"
);

Popup capsLockWarning = new Popup();
capsLockWarning.getContent().add(message);

capsLockOn.addListener((o, wasOn, on) -> {
    if (on) {
        Point2D location =
            passwordField.localToScreen(-15, passwordField.getHeight());
        capsLockWarning.show(passwordField,
            location.getX(), location.getY());
    } else {
        capsLockWarning.hide();
    }
});

Upvotes: 1

Matt
Matt

Reputation: 3187

It sounds like you have figured out how to get the input state you could try something like this for the listener

public class Main extends Application {

    private Label capsLabel = new Label("Caps is ON");

    private boolean capsIsOn;

    @Override
    public void start(Stage stage) {
        System.out.println(Toolkit.getDefaultToolkit().getLockingKeyState(20));

        //Try adding this line to get state on startup
        capsLabel.setVisible(Toolkit.getDefaultToolkit().getLockingKeyState(20));

        TextField textField = new TextField();

        //Also try adding this line and to check again so when the field 
        //is selected it will check again
        textField.setOnMouseClicked(event -> capsLabel.setVisible(Toolkit.getDefaultToolkit().getLockingKeyState(20)));

        textField.setOnKeyReleased(keyEvent -> {
            if(keyEvent.getCode().toString().equals("CAPS")){
                capsIsOn = !capsIsOn;
                capsLabel.setVisible(capsIsOn);
            }
        });

        VBox vBox = new VBox();
        vBox.getChildren().addAll(textField, capsLabel);

        stage = new Stage();
        stage.setScene(new Scene(vBox));
        stage.show();
    }

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

Alternatively you could set this on a timer and have it constantly checking personally I don't like the idea of constant use of computer resources but its not my project.

Upvotes: 1

Related Questions