JavaFX KeyEvent when Stage inactive - events

when i minimize my Stage, JavaFX don't fire my KeyPressed Event.
How can i listen to the KeyEvent when the Stage is minimized?
Here i call my Stage:
public void start(Stage primaryStage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Gui.fxml"));
Parent root = fxmlLoader.load();
primaryStage.setTitle("KillSwitch");
primaryStage.setScene(new Scene(root));
primaryStage.getScene().getRoot().requestFocus();
primaryStage.show();
}
And this is the EventHandler:
public void handleKeyInput(javafx.scene.input.KeyEvent event) throws InterruptedException, SerialPortException, IOException {
if (event.getCode() == KeyCode.UP) {
handletriggerButton();
}
}

Most User Interface Toolkits, including JavaFX deliver key events to the currently focussed user interface element, propagating them up until they've reached the root node.
If your window is minimized none of its elements will have the keyboard focus, and thus nobody will receive a key event.
Therefore as #James_D commented you'll need a native hook into the operating system to intercept system wide keyboard event, like e.g. a keylogger does.
For integrating native could you could go the Java Native Interface way, or have some of the rougher edges taken off by a library such as Java Native Access.
On how to do it in your targeted operating system you should probably research that for the operating system itself.

Related

JavaFX image not changing when function is called by Platform.runLater

I have a function to change an image and its opacity in a JavaFX GUI:
private static Image image = null;
private static ImageView imageView = new ImageView();
// some code to add image in GUI
public static void changeImage() {
imageView.setOpacity(0.5);
imageView.setImage(null);
}
When I call this function within the JavaFX instance, the image disappears or is changing if I use an image instead of null for setImage(). I tried calling the function by pressing a button.
In this case all works as I expected.
When I call this function from another class, the actual image will change its opacity, but the image itself is never changing. I call the function the following way:
public static void changeImg() {
Platform.runLater(() -> FX_Gui.changeImage());
}
Changing labels, progess bars... all works, but I did not manage to change an image.
There's a lot of aspects to this question that don't make sense.
Generally speaking, the GUI in JavaFX is intended to be self-contained and non-linear in it's execution. Programming an outside method to assume some state of the GUI, and then to directly manipulate the GUI based on that assumption is not the correct approach. So any attempt to know the state of the GUI by kludging in a Thread.sleep() call is inherently incorrect.
The new JFXPanel() call is not needed, as Application.launch() will initialize JavaFX. Presumably, this was added before the sleep(500) was put in, since calling changeImg() would fail if run immediately after the Thread.start() command, since the launch() wouldn't have time to even start yet.
As has been noted, having some kind of startup image that's replaced once the screen completes initialization should be done from within the FX_Min.start(Stage) method, although it's highly unlikely that you'd even see the first image.
The question seems to be aimed at designing a kind of application where the GUI is just some small part of it and the main application is going to go on to do lengthy processing and then trigger the GUI to something in response to the results of that processing. Or perhaps the main application is monitoring an external API and feeding updates to GUI periodically. In most cases, however, the GUI is usually initialized so that it can take control of the operation, launching background threads to do the lengthy processing and using JavaFX tools to handle the triggering of GUI updates and intake of results.
In the instance that the design really needs to have something other than the GUI be the central control, then use of Application does not seem appropriate. It is, after all, designed to control the Application, and monitors the status of the GUI once it's been launched to shut everything down when the GUI is closed. This is why the OP had to put the Application.launch() call in a separate thread - launch() doesn't return until the GUI shuts down.
If the application outside of the GUI is going to control everything then it's best to manually start JavaFX with Platform.startup(), and handle all the monitoring manually. The following code doesn't do any monitoring, but it does start up the GUI and change the image without any issues:
public class Control_Min {
public static void main(String[] args) {
Platform.startup(() -> new Fx_Min().start(new Stage()));
Platform.runLater(() -> Fx_Min.changeImage());
}
}
Note that no changes are required to the OP's code in Fx_Min. However, there's no reason for Fx_Min to extend Application any more, and the code from its start() method can be placed anywhere.
It should be further noted that, although this works, it's really way outside the norm for JavaFX applications. It's possible that the OP's situation really does require this kind of architecture, but that would place it into a very small minority of applications. Designing the application around Application.launch() and initiating lengthy processing in background threads through the JavaFX tools provided is almost always a better approach.
OK, so given new information from the OP it's clear that this should be based on Application and that the GUI should launch some kind of socket listener that would presumably block waiting for input.
Anything that blocks can't run on the FXAT, and there needs to be a way to allow the socket listener to communicate back to the GUI when it receives data. Ideally, the socket listener should be JavaFX unaware, and just plain Java.
IMO, the best way to do this is to provide a Consumer to accept information from the socket listener, and to pass it to the socket listener in it's constructor. That way, the GUI knows nothing about the nature of the socket listener except that it has a dependency on requiring a message consumer. Similarly, the socket listener has no knowledge about what invoked it, just that it has given it a message consumer.
This limits your coupling, and you are free to write your GUI without worrying about any of the inner workings of the socket listener, and visa versa.
So here's the GUI, cleaned up and simplified a bit so that the socket listener stuff is easier to follow. Basically, the GUI is just going to throw the message from the socket listener into a Text already on the screen. The message consumer handles the Platform.runLater() so that the socket listener isn't even aware of it:
public class Fx_Min extends Application {
#Override
public void start(Stage primaryStage) {
ImageView imageView = new ImageView(new Image("/images/ArrowUp.png"));
Text text = new Text("");
primaryStage.setScene(new Scene(new VBox(10, imageView, text), 800, 600));
primaryStage.setResizable(true);
primaryStage.show();
imageView.setImage(new Image("/images/Flag.png"));
new SocketListener(socketMessage -> Platform.runLater(() -> text.setText(socketMessage))).startListening();
}
public static void main(String[] args) {
launch(args);
}
}
Here's the socket listener. Clearly, this isn't going to listen on a socket, but it loops around a sleep() to simulate action happening on the Pi. The message format here is String, just to keep everything simple, but obviously this is the worse possible choice for an actual implementation of this. Build a special message class:
public class SocketListener {
private Consumer<String> messageConsumer;
public SocketListener(Consumer<String> messageConsumer) {
this.messageConsumer = messageConsumer;
}
public void startListening() {
Thread listenerThread = new Thread(() -> listenForIRCommand());
listenerThread.setDaemon(true);
listenerThread.start();
}
private void listenForIRCommand() {
for (int x = 0; x < 100; x++) {
try {
Thread.sleep(5000);
messageConsumer.accept("Station " + x);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
It should be really clear that since the call to listenForIRCommand() is executed from inside a background thread, that it's completely freed from any JavaFX contstraints. Anything that generally possible in Java can be done from there without worrying about it's impact on the GUI.
In the meantime I found out that the reason for not changing the image is that I run changeImage() before the initialization of the GUI is completed. If I wait about 500 mS before I sent the changeImage() command all works fine.
Below is the minimal code that demonstrates the issue I had:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
public class Control_Min {
public static void changeImg() {
Platform.runLater(() -> Fx_Min.changeImage());
}
public static void main(String[] args) {
new Thread() {
public void run() {
Application.launch(Fx_Min.class);
}
}.start();
// JFXPanel will initialize the JavaFX toolkit.
new JFXPanel();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
changeImg();
}
}
And the Gui itself:
public class Fx_Min extends Application {
private static Stage stage;
private static GridPane rootPane;
private static Scene scene;
private static Image image = null;
private static ImageView imageView = new ImageView();
#Override
public void start(Stage primaryStage) {
stage = primaryStage;
rootPane = new GridPane();
scene = new Scene(rootPane,800,600);
try {
image = new Image(new FileInputStream("C:\\Users\\Peter\\eclipse-workspace\\FX_Test\\src\\application\\Image1.jpg"));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
imageView.setImage(image);
rootPane.add(imageView, 1, 0);
stage.setScene(scene);
stage.setResizable(true);
stage.show();
System.out.println("Gui is ready");
}
public static void changeImage() {
try {
image = new Image(new FileInputStream("C:\\Users\\Peter\\eclipse-workspace\\FX_Test\\src\\application\\Image2.jpg"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
imageView.setImage(image);
System.out.println("Image Changed");
}
public static void main(String[] args) {
launch(args);
}
}
This code works fine.
In the console I get:
Gui is ready
Image Changed
When I remove the Thread.sleep(500) the image will not change.
In the console I get:
Image Change
Gui is ready
My conclusion is that I have send the runlater method before the FX runtime has been initialized.
(Have not fixed the static issue yet as this was not the issue. I will do in my original program later.)
My task is the following:
I program a GUI for my internet radio player on my PC.
The GUI controls the radio and polls what is playing.
I want to control the radio by an IR remote control too.
I have already a Raspberry Pi that communicates with the remote.
Therefore, my plan is to run a server socket on the PC, that receives the commands from the Raspberry Pi.
The server will run in its own thread. I want to use the runLater command to update the GUI.
Is there a better way to update the GUI from the server?
Goal is that the GUI will update immediately when I press a button on the remote.
With my latest learnings about JavaFX I will start the application now directly in the FX class and get the server thread started from the FX class

JavaFX: How to remove Pane?

I wasn't really sure how to title this. I have a JavaFX application in which I have two pages (fxml's) which are different sizes. The first is 400x600; the second is maximized. I have a return button which sends the user back to the first fxml. I successfully set it so it goes back to the original size. However, when I go 1->2->1->2 the screen does not maximize. What I imagine is the issue is that it does not rerun the initialize() method the second time it creates this page. Another possibility is that it is caused by the new Runnable() I made, which was necessary to get the stage object.
The second controller:
#Override
public void initialize(URL location, ResourceBundle resources) {
Platform.runLater(new Runnable() {
public void run() {
// Display
stage = (Stage) menuPane.getScene().getWindow();
stage.setResizable(true);
stage.setMaximized(true);
stage.setResizable(false);
}
});
}
Thanks.

JavaFX FadeTransition.onSetOnFinished with CountdownLatch does not work as expected

I'm staring at my code and I'm pretty stuck with one issue:
I want to make a fade out transition, and i want to block current thread, while the fade transition is running.
So, my attempt was to create a CountDownLatch, which blocks the thread, until the transition.setOnFinished() is called, where I make a latch.countdown(). In short: i want to make sure, that the transition is always visible in full length.
Seemed pretty straight forward to me, but...
The setOnFinished() doesn't get called, BECAUSE the current thread mentioned above is blocked by the countdown latch.
How can i solve this issue? Thx in advance.
private void initView() {
Rectangle rect = new Rectangle();
rect.widthProperty().bind(widthProperty());
rect.heightProperty().bind(heightProperty());
rect.setFill(Color.BLACK);
rect.setOpacity(0.8f);
getChildren().add(rect);
MyUiAnimation animator = new MyUiAnimation();
fadeInTransition = animator.getShortFadeInFor(this);
fadeOutTransition = animator.getShortFadeOutFor(this);
fadeOutTransition.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
Platform.runLater(new Runnable() {
#Override
public void run() {
latch.countDown();
setVisible(false);
}
});
}
});
}
public void hide() {
fadeInTransition.stop();
if (isVisible()) {
latch = new CountDownLatch(1);
fadeOutTransition.playFromStart();
try {
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
You can only modify and query the active scene graph on the JavaFX application thread. All of your code works with the scene graph, so it all must be run on the JavaFX application thread. If everything is already running on the JavaFX application thread, there is no reason for concurrency related constructs in your code.
If you use blocking calls like latch.await(), you will block the JavaFX application thread, which will prevent any rendering, layout or animation steps to run. CountdownLatch should not be used in this context and should be removed from the code.
Calling Platform.runLater is unnecessary as it's purpose is to run code on the JavaFX application thread, and you are on the JavaFX application thread already. Platform.runLater should not be used in this context and should be removed from the code.

Closing a Stage from within its controller

I need a way to close a Stage from within itself by clicking a Button.
I have a main class from which I create the main stage with a scene. I use FXML for that.
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Builder.fxml"));
stage.setTitle("Ring of Power - Builder");
stage.setScene(new Scene(root));
stage.setMinHeight(600.0);
stage.setMinWidth(800.0);
stage.setHeight(600);
stage.setWidth(800);
stage.centerOnScreen();
stage.show();
}
Now in the main window that appears I have all the control items and menus and stuff, made through FXML and appropriate control class. That's the part where I decided to include the About info in the Help menu. So I have an event going on when the menu Help - About is activated, like this:
#FXML
private void menuHelpAbout(ActionEvent event) throws IOException{
Parent root2 = FXMLLoader.load(getClass().getResource("AboutBox.fxml"));
Stage aboutBox=new Stage();
aboutBox.setScene(new Scene(root2));
aboutBox.centerOnScreen();
aboutBox.setTitle("About Box");
aboutBox.setResizable(false);
aboutBox.initModality(Modality.APPLICATION_MODAL);
aboutBox.show();
}
As seen the About Box window is created via FXML with a controller. I want to add a Button to close the new stage from within the controller.
The only way I found myself to be able to do this, was to define a
public static Stage aboutBox;
inside the Builder.java class and reference to that one from within the AboutBox.java in method that handles the action event on the closing button. But somehow it doesn't feel exactly clean and right. Is there any better way?
You can derive the stage to be closed from the event passed to the event handler.
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// take some action
...
// close the dialog.
Node source = (Node) actionEvent.getSource();
Stage stage = (Stage) source.getScene().getWindow();
stage.close();
}
}
In JavaFX 2.1, you have few choices. The way like in jewelsea's answer or the way what you have done already or modified version of it like
public class AboutBox extends Stage {
public AboutBox() throws Exception {
initModality(Modality.APPLICATION_MODAL);
Button btn = new Button("Close");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
close();
}
});
// Load content via
// EITHER
Parent root = FXMLLoader.load(getClass().getResource("AboutPage.fxml"));
setScene(new Scene(VBoxBuilder.create().children(root, btn).build()));
// OR
Scene aboutScene = new Scene(VBoxBuilder.create().children(new Text("About me"), btn).alignment(Pos.CENTER).padding(new Insets(10)).build());
setScene(aboutScene);
// If your about page is not so complex. no need FXML so its Controller class too.
}
}
with usage like
new AboutBox().show();
in menu item action event handler.

Shared Toast does not show in Android 3.0.1 on Motorola Xoom

I use a shared Toast across different Activities in order to only show the latest message, immediately discarding any previous ones. I put the code in the custom Application object:
public class GameApp extends Application {
private Toast mToast;
#Override
public void onCreate() {
super.onCreate();
mToast = Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT);
}
public void displayToast(int textId) {
displayToast(getText(textId));
}
public void displayToast(CharSequence text) {
mToast.cancel();
mToast.setText(text);
mToast.show();
}
}
The Toast showed up on my 1.6, 2.2, and 3.0 emulators. But when I downloaded the released app from the Market, it only shows on my G1 (CyanMod 6.1) but not Xoom (3.0.1). I tried connecting the Xoom with USB debugging, but nothing relevant showed up in LogCat.
Prior to this, I used to do Toasts the conventional way (i.e. via Toast.makeText()) and that worked on everything as expected.
Could there be any potential problem with my above code, or could this be a bug in the Xoom? Here is the link to my app, in case you want to test it. The Toast should show up when you click Today, Progress in the Main screen. I appreciate any help. Thank you very much :)
i'm not sure but the sdk that motorola uses may be different.. and mToast.cancel() could be doin something terrible.. so have you tried this..
public void displayToast(CharSequence text) {
mToast.setText(text);
mToast.show();
}
This is because that mToast.cancel(); may close the toast if it's showing, or don't show it if it isn't showing yet.
Please create new Toast object when users click buttons. And keep the previous Toast object reference. Next time when user click buttons, cancel the previous Toast object and create new Toast again.
public class GameApp extends Application {
private Toast mToast;
private Context mContext;
#Override
public void onCreate() {
super.onCreate();
mToast = Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT);
}
public void displayToast(int textId,Context mContext) {
this.mContext = mContext;
displayToast(getText(textId));
}
public void displayToast(CharSequence text) {
mToast.cancel();
mToast = new Toast(mContext);
mToast.setText(text);
mToast.setDuration(Toast.LENGTH_SHORT);
mToast.show();
}
}

Resources