I'm trying to make it so that when a button is clicked, the user presses any key and this is written to tog1. But I get that ok = null. I don't understand why "ok" which is a string only works inside "nativeKeyReleased". Already tried to use tog1.setText inside "nativeKeyReleased" but it recognizes it. I am using JavaFx and jnativehook.
Controller:
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleButton;
import com.github.kwhat.jnativehook.GlobalScreen;
import com.github.kwhat.jnativehook.NativeHookException;
import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent;
import com.github.kwhat.jnativehook.keyboard.NativeKeyListener;
public class HelloController implements NativeKeyListener {
public String ok;
public void nativeKeyReleased(NativeKeyEvent e) {
System.out.println("Key Released: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
ok = (NativeKeyEvent.getKeyText(e.getKeyCode()));
}
@FXML
void Togg(ActionEvent e) {
if (tog1.isSelected()) {
tog1.setStyle("-fx-background-color: #333; -fx-border-color: green; -fx-border-radius: 5px; -fx-border-width: 2;");
tog1.setText(ok);
System.out.println(ok);
}
else {
tog1.setStyle("-fx-background-color: #333; -fx-border-color: white; -fx-border-radius: 5px; -fx-border-width: 2;");
tog1.setText("Key");
}
}
@FXML
public ToggleButton tog1;
@FXML
void initialize() {
try {
GlobalScreen.registerNativeHook();
}
catch (NativeHookException ex) {
System.err.println("There was a problem registering the native hook.");
System.err.println(ex.getMessage());
System.exit(1);
}
GlobalScreen.addNativeKeyListener(new HelloController());
}
}
HelloApplication:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.awt.*;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 695, 356);
stage.setTitle("Instapeek");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) throws AWTException {
launch(args);
}
}
hello-view.fxml:
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="657.0" style="-fx-background-color: #333; -fx-border-color: #686868; -fx-border-width: 4;" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demo.HelloController">
<children>
<ToggleButton fx:id="tog1" layoutX="56.0" layoutY="29.0" mnemonicParsing="false" onAction="#Togg" prefHeight="31.0" prefWidth="164.0" style="-fx-background-color: #333; -fx-border-color: white; -fx-border-radius: 5px; -fx-border-width: 2;" text="Key" textFill="WHITE">
<font>
<Font name="Panton Black Caps" size="11.0" />
</font>
</ToggleButton>
</children>
</AnchorPane>
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>demo</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.8.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>18-ea+6</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>18-ea+6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.9.0</version>
<configuration>
<source>18</source>
<target>18</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>com.example.demo/com.example.demo.HelloApplication</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Your code doesn't work, because you are passing a new instance of
HelloControllertoGlobalScreen.addNativeKeyListener(...);. This meansnativeKeyReleased()is being called on one instance ofHelloController(the one you create), buttogg()(I have changed the name of the method to conform to proper naming conventions) is being called on a different instance (the one created by theFXMLLoaderwhen you load the FXML). So they are both referring different copies of theokvariable.The following works for me (at least, to the extent that I can get
jnativehookworking on my system), but there are threading and design issues discussed further down to which you should pay attention:Note that this is not guaranteed to work. The
jnativehookcallbacks are executed on a different thread to the FX Application Thread (on which the event handler for the toggle button is called). So you are settingokin one thread and accessing it in another. In this circumstance, the second thread is not guaranteed to ever see the updates to the variable made in the first thread (though, as stated, it does work on some systems/JVMs).You should modify the code to either make the variable
volatile, or use an atomic wrapper, or (probably the best approach) only access it from a single thread, as shown here:It's also probably not a good idea to register the controller as a native listener. The reason is that it forces the controller to persist for the entire lifecycle of the application. The controller has references to UI elements, so you also force those to persist. If you were to stop displaying the UI, those elements would not be available for garbage collection, causing a memory leak. You should use a different class for the native listener and arrange for it and the controller to access shared data (i.e. through some kind of shared data model).
The preferred solution looks something like this:
The data model class; an instance of this will be shared between any objects that need to access the shared data (e.g. the key that was typed, in this example):
Here is the key listener implementation:
Here is the revised controller class. Notice how this class only takes responsibility for control of the UI defined in the FXML file, which is its job. Handling events outside of that part of the UI is delegated elsewhere:
Here's the FXML, for completeness (the only change was to modify the method name to conform to naming conventions):
And finally the application class. This class manages application lifecycle, so it is the appropriate place to manage registering and deregistering the native listeners (this allows the application to shut down correctly), creation of persistent objects (e.g. the data model), and dependency management (giving the controller and global key listener access to the data model).