Background thread directly accessing UI anyway - javafx

Here is my code, can someone explain why it works every time?
package dingding;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Dingding extends Application {
TextField tfAuto = new TextField("0");
AutoRunThread runner = new AutoRunThread();
boolean shouldStop = false;
private class AutoRunThread extends Thread {
#Override
public void run() {
while (true) {
int i = Integer.parseInt(tfAuto.getText());
++i;
tfAuto.setText(String.valueOf(i));
try {
Thread.sleep(1000);
} catch (Throwable t) {
}
if (shouldStop) {
runner = null;
shouldStop = false;
return;
}
}
}
}
#Override
public void start(Stage primaryStage) {
Button btnStart = new Button("Increment Automatically");
Button btnStop = new Button("Stop Autotask");
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (runner == null) {
runner = new AutoRunThread();
runner.setDaemon(true);
}
if (runner != null && !(runner.isAlive())) {
runner.start();
}
}
});
btnStop.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
shouldStop = true;
}
});
VBox rootBox = new VBox();
HBox autoBox = new HBox();
autoBox.getChildren().addAll(tfAuto, btnStart, btnStop);
rootBox.getChildren().addAll(autoBox);
Scene scene = new Scene(rootBox, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

As I said in my comment, improperly synchronized code doesn't guarantee errors per se. However, that doesn't mean said code, when used in a multi-threaded context, is actually working—you're merely getting lucky. Eventually you'll run into undefined behavior such as corrupted state, stale values, and unexpected exceptions. This is because, without synchronization, actions performed by one thread are not guaranteed to be visible to any other thread. You need a happens-before relationship, better described in the package documentation of java.util.concurrent and this SO question.
JavaFX, like most UI frameworks/toolkits, is single threaded. This means there's a special thread—in this case, the JavaFX Application Thread— that is responsible for all UI related actions1. It is this thread, and this thread only, that must be used to access and/or modify state related to a "live" scene graph (i.e. nodes that are in a scene that's in a window that's showing2). Using any other thread can lead to the undefined behavior described above.
Some UI related functions actually ensure they're being called on the JavaFX Application Thread, usually throwing an IllegalStateException if not. However, the remaining functions will silently let you call them from any thread—but that doesn't mean it's safe to do so. This is done this way, I believe, because checking the thread in every UI related function is a maintenance nightmare and would incur a not-insignificant performance cost.
1. It's slightly more complicated that this; JavaFX also has a "prism render thread" and a "media thread". See Understanding JavaFX Architecture for more information. But note that, from an application developer's point of view, the only thread that matters is the JavaFX Application Thread.
2. This is documented by Node. Note that some nodes, such as WebView, are more restrictive when it comes to threading; this will be documented in the appropriate places.

Related

Verify inner/top nodes inside Parent node

I have an AnchorPane which has a considerable amount of containers inside of it (most of them are AnchorPanes as well): MainAnchorPane is always set as the top anchorPane and it has multiple anchorPane as children(let's say these are AnchorPane2 and AnchorPane3).
Looking forward to finding a way to identify which anchorPane is inside of the main container, I've tried to do the following:
if (mainAnchorPane.getChildren().contains(anchorPane2))
{ System.out.println("anchorPane2 is opened here"); }
else if (mainAnchorPane.getChildren().contains(anchorPane3))
{System.out.println("anchorPane3 is opened here");}
else {System.out.println("no anchorPane found");}
Although, this method does not work.
I also tried to compare anchorPanes from another classes, but that gives me NullPointerException (even with that anchorPane being open is another part of the system -For instance, I have multiple MainAnchorPanes which can handle the same anchorPane2 and 3 to be open at the same time-):
if (mainAnchorPane.getChildren().contains(anotherClass.anchorPane2))
{System.out.println("anchorPane2 is opened here");}
else {System.out.println("no anchorPane found");}
That would be my preferred method, but as I mentioned, for some reason it gives me a null exception in the life of the if statement.
Finally, I have tried the last effort to identify which anchorPane is currently being shown in my MainAnchorPane by creating a list of nodes inside the mainAnchorPane and comparing each of them with the target node, which is anchorPane2 or anchorPane3:
for (Node node: getAllNodes(mainAnchorPane))
{
System.out.println(node);
// in the place of equals() I've tried contains() as well
if (node.equals(anchorPane2)) {
System.out.println("ap2 is here");
}
else {System.out.println("ap2 is not here");}
}
public static ArrayList<Node> nodes = new ArrayList<Node>();
public static ArrayList<Node> getAllNodes(Parent root) {
addAllDescendents(root, nodes);
return nodes;
}
private static void addAllDescendents(Parent parent, ArrayList<Node> nodes) {
for (Node node : parent.getChildrenUnmodifiable()) {
nodes.add(node);
if (node instanceof Parent)
addAllDescendents((Parent)node, nodes);
}
}
None of these methods worked. I even went further and checked whether my ifs catch buttons and labels nodes inside of AnchorPanes which are nodes of that pane, although the condition always returns as false, suggesting the node is not in there (which is not true, since if shows up in all in System.out.println(nodes, mainAnchorPane.getChildren(), and getAllNodes(mainAnchorPane)))
Any thoughts would be appreciated.
Edit: it should be as simple as
if (mainAnchorPane.getChildren().contains(node))
but that doesn't work as well
Naming convention were simplified aiming for better explanation.
On high level your code looks fine. Having said that there are some considerable changes/cleanup you need to do.
no need of static ArrayList
no need to check in a for loop.
...
I quickly tried your code with some changes and it is working fine. Try modifying accordingly. Even if you have issues.. then you might be doing something wrong elsewhere.
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class LayoutDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
AnchorPane anchorPane2 = new AnchorPane(new StackPane());
AnchorPane anchorPane3 = new AnchorPane(new StackPane());
// Change the internal node to anchorPane3 to get "not here" output
AnchorPane mainAnchorPane = new AnchorPane(new StackPane(new Pane(new VBox(anchorPane2, new Pane()), new Pane())), new Pane());
Pane root = new Pane(mainAnchorPane);
Scene scene = new Scene(root, 400, 400);
stage.setScene(scene);
stage.show();
if (getAllNodes(mainAnchorPane).contains(anchorPane2)) {
System.out.println("ap2 is here");
} else {
System.out.println("ap2 is not here");
}
}
public static List<Node> getAllNodes(Parent root) {
List<Node> nodes = new ArrayList<Node>();
addAllDescendents(root, nodes);
return nodes;
}
private static void addAllDescendents(Parent parent, List<Node> nodes) {
for (Node node : parent.getChildrenUnmodifiable()) {
nodes.add(node);
if (node instanceof Parent)
addAllDescendents((Parent) node, nodes);
}
}
}

Why JavaFX PrintJob cannot print node's text properly?

I try to print a javafx scene with texts on it. I have created this very simple test code. As you can see it's a standard hello world application with a button that says "Say Hello World.
The application supposes to print out this scene with the button that shows "Say 'Hello World'". The program prints out the form fine. However, the texts on the button are shown as unrecognizable characters instead. I use Mac.
I am trying to implement a javafx report, and it requires to print out a javafx scene.
Any help will be greatly appreciated. Please let me know if you can successfully print the characters. Therefore, at least, I know it's my printer's problem.
To make the test easy, you can save the result as a pdf file instead of printing it out.
package javafxprinttest;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class JavaFXPrintTest extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
StackPane root = new StackPane();
root.getChildren().add(btn);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
print(root);
}
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void print(Node node)
{
// Define the Job Status Message
System.out.println("Creating a printer job...");
// Create a printer job for the default printer
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(node.getScene().getWindow()))
{
// Print the node
boolean printed = job.printPage(node);
if (printed)
{
// End the printer job
job.endJob();
}
else
{
// Write Error Message
System.out.println("Printing failed.");
}
}
else
{
// Write Error Message
System.out.println("Could not create a printer job.");
}
}
}
Apparently, the font that displays on a screen may not be the same font that an OS recognizes it. Therefore, when you try to print or save the scene as pdf, random characters may be saved as result.
btn.setFont(new Font("Arial", 30));
The above code fixes the problem by providing a font that is known by the OS.
Notice that not everyone will be able to produce the same result. I have a friend of mine to run the same code on his box, and the result shows up fine.

How to add a shortcut event in javafx with combination of Ctrl + P +X

table.setOnKeyPressed(new EventHandler<KeyEvent>() {
// final KeyCombination kb = new KeyCodeCombination(KeyCode.P, KeyCombination.CONTROL_DOWN);
// final KeyCombination k = new KeyCodeCombina
public void handle(KeyEvent key) {
if (key.getCode() == KeyCode.P && key.isControlDown()) {
//My Code
}
}
});
I want to invoke the event with the shortcut keycombination of Ctrl+P+X
It is actually a little hard to understand what Ctrl+P+X means. I am going to assume it means that you press ctrl, then you press p, then you press x (potentially releasing the p before you press the x). I'll also assume that the order matters, e.g. press ctrl, then press x then press p would not count. Anyway a bit of speculation on my part, perhaps not exactly what you want, but hopefully you will get the gist of the provided solution and be able to adapt it to your situation.
The solution monitors both key presses and releases so that it can keep track of the state of key presses to determine if the key combination triggers.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.time.LocalTime;
public class KeyCombo extends Application {
KeyCombination ctrlP = KeyCodeCombination.keyCombination("Ctrl+P");
KeyCombination ctrlX = KeyCodeCombination.keyCombination("Ctrl+X");
#Override
public void start(Stage stage) throws Exception {
Label lastPressedLabel = new Label();
TextField textField = new TextField();
BooleanProperty pDown = new SimpleBooleanProperty(false);
textField.setOnKeyPressed(event -> {
if (ctrlP.match(event)) {
pDown.set(true);
}
if (pDown.get() && ctrlX.match(event)) {
pDown.set(false);
lastPressedLabel.setText(
LocalTime.now().toString()
);
}
});
textField.setOnKeyReleased(event -> {
if (!event.isControlDown()) {
pDown.set(false);
}
});
VBox layout = new VBox(10,
new Label("Press Ctrl+P+X"),
textField,
lastPressedLabel
);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you can, I'd advise trying to use a simpler control scheme, e.g. just Ctrl+P or Ctrl+X (which is directly supported by the key code combination event matching), rather than using a composite control scheme of Ctrl+P+X.

Make JavaFX wait and continue with code

Basically I am trying to make a short effect using JavaFX. I have the shape of a heart (added together from two circles and a polygon) that I can vary in size using the double value p. "Standart Size" would be p = 1.0;.
I am trying to add a pumping effect to the heart. I have the method pumpOnce():
public void pumpOnce(){
p = p + 1;
initHeart();
//Here goes what ever it takes to make stuff working!!
p = p - 1;
initHeart();
}
initHeart() draws the heart based on p.
I have found out that Thread.sleep(); or similar methods will not work due to the thread philosophy in JavaFX.
But what can I use instead?
The JavaFX animations are probably the way to go, but the "thread philosophy" in JavaFX isn't hard to work with if you want to roll your own, or do other, more complicated things in background threads.
The following code will pause and change the value in a label (full disclosure, I'm reusing code I wrote for another question):
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.xml.datatype.Duration;
public class DelayWithTask extends Application {
private static Label label;
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
label = new Label();
label.setText("Waiting...");
StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
delay(5000, () -> label.setText("Hello World"));
}
public static void delay(long millis, Runnable continuation) {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try { Thread.sleep(millis); }
catch (InterruptedException e) { }
return null;
}
};
sleeper.setOnSucceeded(event -> continuation.run());
new Thread(sleeper).start();
}
}
The basic JavaFX background tool is the Task, any JavaFX application that actually does anything will probably be littered with these all over. Learn how to use them.
Dave's solution is great for general purpose off thread based work in JavaFX.
If you wish to use the animation facilities of JavaFX, the solutions below demonstrate this using a Timeline or a ScaleTransition. The timeline implements a discrete scale of the UI element, so every quarter of a second the UI element is scaled larger or back to it's original size. The scale transition implements a smooth scale of the UI element, so the UI element gradually gets larger then smaller using an interpolated scale factor with the default easing interpolator.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BeatingHeart extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) {
ImageView heart = new ImageView(HEART_IMAGE_LOC);
animateUsingTimeline(heart);
// animateUsingScaleTransition(heart);
StackPane layout = new StackPane(heart);
layout.setPrefWidth(heart.getImage().getWidth() * 2);
layout.setPrefHeight(heart.getImage().getHeight() * 2);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void animateUsingTimeline(ImageView heart) {
DoubleProperty scale = new SimpleDoubleProperty(1);
heart.scaleXProperty().bind(scale);
heart.scaleYProperty().bind(scale);
Timeline beat = new Timeline(
new KeyFrame(Duration.ZERO, event -> scale.setValue(1)),
new KeyFrame(Duration.seconds(0.5), event -> scale.setValue(1.1))
);
beat.setAutoReverse(true);
beat.setCycleCount(Timeline.INDEFINITE);
beat.play();
}
private void animateUsingScaleTransition(ImageView heart) {
ScaleTransition scaleTransition = new ScaleTransition(
Duration.seconds(1), heart
);
scaleTransition.setFromX(1);
scaleTransition.setFromY(1);
scaleTransition.setFromZ(1);
scaleTransition.setToX(1.1);
scaleTransition.setToY(1.1);
scaleTransition.setToZ(1.1);
scaleTransition.setAutoReverse(true);
scaleTransition.setCycleCount(Animation.INDEFINITE);
scaleTransition.play();
}
private static final String HEART_IMAGE_LOC =
"http://icons.iconarchive.com/icons/mirella-gabriele/valentine/128/Heart-red-icon.png";
// icon obtained from: http://www.iconarchive.com/show/valentine-icons-by-mirella-gabriele/Heart-red-icon.html
// icon license: Free for non-commercial use, commercial use not allowed.
}

JavaFX + AWT Canvas

I read that running AWT With JavaFX is a bad idea.
But we have an old application that runs on Swing and uses the AWT canvas(Cannot change due to an external library that uses the canvas)
Is it really such a horrible idea?
Is there a workaround for this?
Update
Although the code in this answer used to work on Windows with an earlier version of JavaFX, I retested the same same code on OS X 10.9.5 + JavaFX 8u72 and the code no longer works.
The line swingNode.setContent(awtInitializerTask.get()); which instructs the JavaFX thread to wait on the awt thread to initialize the awt canvas never returns, blocking execution and startup of the app.
Just put your AWT canvas in a SwingNode and watch your thread management and you'll be fine.
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class AwtCanvasWrapper extends Application {
private static final int W = 200;
private static final int H = 100;
#Override public void start(final Stage stage) throws Exception {
final AwtInitializerTask awtInitializerTask = new AwtInitializerTask(() -> {
JPanel jPanel = new JPanel();
jPanel.add(new CustomAwtCanvas(W, H));
return jPanel;
});
SwingUtilities.invokeLater(awtInitializerTask);
SwingNode swingNode = new SwingNode();
swingNode.setContent(awtInitializerTask.get());
stage.setScene(new Scene(new Group(swingNode), W, H));
stage.setResizable(false);
stage.show();
}
private class AwtInitializerTask extends FutureTask<JPanel> {
public AwtInitializerTask(Callable<JPanel> callable) {
super(callable);
}
}
private class CustomAwtCanvas extends Canvas {
public CustomAwtCanvas(int width, int height) {
setSize(width, height);
}
public void paint(Graphics g) {
Graphics2D g2;
g2 = (Graphics2D) g;
g2.setColor(Color.GRAY);
g2.fillRect(
0, 0,
(int) getSize().getWidth(), (int) getSize().getHeight()
);
g2.setColor(Color.BLACK);
g2.drawString("It is a custom canvas area", 25, 50);
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
Here is the output:
Related Question
Interoperability between Graphics2D and GraphicsContext
Answering some additional questions
But that one is for swing components.
Yes, but awt components can be wrapped in Swing components.
furthermore It says in the docs that it should not be used of heavyweight components
Regardless, it seems to work for me, your mileage may vary.
performance is crucial for my app
Then try the approach with your app and check:
The painting is reliable.
The performance is acceptable.
If either of the above checks fail then you may need to use a different approach (though I do not know what that approach would be, maybe just spawn Frame as a new window in which to include the the AWT canvas content rather than embedding the canvas inside the JavaFX scene).

Resources