JavaFX + AWT Canvas - javafx

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).

Related

Background thread directly accessing UI anyway

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.

lanching same JavaFx window after closing it

I am open A JavaFX application using a Jframe.And after using JavaFx application window i close the window .And want to again open this same Javafx window but an error occur-
java.lang.IllegalStateException: Application launch must not be called more than once
As stated in the documentation, calling Application.launch(...) more than once will result in an exception:
public static void launch(String... args)
Launch a standalone application. This method is typically called from
the main method(). It must not be called more than once or an
exception will be thrown.
If you need to mix Swing and JavaFX, you should embed the JavaFX pieces in a JFXPanel and place it in a JFrame. You can then show and hide the JFrame as often as you need, using setVisible(...). An application working this way will not have an Application subclass at all.
Mixing Swing and JavaFX is tricky, and not recommended for beginners. The problem is that each toolkit has its own UI thread, and all access of the UI must be executed on the correct UI thread (i.e. the AWT event dispatch thread for Swing/AWT components, and the JavaFX Application Thread for JavaFX components). Data that is shared between both must provide proper synchronization to ensure that it is safely accessible from multiple threads.
Here is a very simple example. Clicking the button will show the window with FX content. If you close that window, and then click the button again, it will be shown again.
import java.awt.BorderLayout;
import java.util.Random;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingFXExample {
private JFrame mainFrame ;
private JFrame fxFrame ;
private JFXPanel fxPanel ;
public SwingFXExample() {
// must be on Swing thread...
if (! SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Not on Event Dispatch Thread");
}
mainFrame = new JFrame();
JButton showFX = new JButton("Show FX Window");
JPanel content = new JPanel();
content.add(showFX);
mainFrame.add(content, BorderLayout.CENTER);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fxFrame = new JFrame();
fxPanel = new JFXPanel();
fxFrame.add(fxPanel);
fxFrame.setSize(640, 640);
fxFrame.setLocationRelativeTo(null);
Platform.runLater(() -> initFX());
showFX.addActionListener(event -> fxFrame.setVisible(true));
}
private void initFX() {
// must be on FX Application Thread...
if (! Platform.isFxApplicationThread()) {
throw new IllegalStateException("Not on FX Application Thread");
}
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis());
Series<Number, Number> series = new Series<>();
series.setName("Random data");
Random rng = new Random();
for (int i = 0; i <= 10; i++) {
series.getData().add( new Data<>(i, 100*rng.nextDouble()) );
}
chart.getData().add(series);
chart.setOnMouseClicked(evt -> {
if (evt.getClickCount() == 2) {
System.out.println("Double click!");
}
});
fxPanel.setScene(new Scene(chart, 400, 400));
}
public void showMainWindow() {
mainFrame.setSize(350, 120);
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SwingFXExample app = new SwingFXExample();
app.showMainWindow();
});
}
}

Javafx RotateTransition rendering smooth

I’m making some trials with JavaFX RotateTransition, applying a simple model found on a jfx itself documentation file:
rotateTransition = RotateTransitionBuilder.create()
.node(elements)
.duration(Duration.seconds(4))
.fromAngle(0)
.toAngle(720)
.cycleCount(3)
.autoReverse(true)
.build();
Above, elements is a Group of bare Arc primitives.
When this group has a limited number of nodes, say 20, the animation goes smooth but when I increase the number of nodes to 500 (nested actually, Group of Group) the animation still works but does not result any more fluid.
The question is: does this nodes limit can be considered too much for this task? How to speed up the rendering?
I have found the thread below that in a similar context asserts that could be a matter of using the right Animation class, but I’m not sure that the proposed AnimationTimer does apply well to a rotation.
http://mail.openjdk.java.net/pipermail/openjfx-dev/2013-June/008104.html
I have also tried to use setCache(true) to every node with no visible improvements.
Thank you!
Edit: Arc generation. No strange things but a binder and a EventHandler.
Arc arc = new Arc();
arc.centerXProperty().bind(plotRadiusBinding);
arc.centerYProperty().bind(plotRadiusBinding);
arc.radiusXProperty().bind(plotRadiusBinding);
arc.radiusYProperty().bind(plotRadiusBinding);
arc.setStartAngle(startAngle * 180 / PI);
arc.setLength(radiansLength * 180 / PI);
arc.setType(ArcType.ROUND);
arc.setStroke(defaultArcColor);
arc.setStrokeType(StrokeType.INSIDE);
arc.setFill(null);
arc.setOnMouseClicked(arcEventHandler);
I had similar issues when moving images around when I used KeyFrames (KeyFrames)
I could improve it by using
I had success with implementing the stuff by myself:
Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
}
#Override
protected void interpolate(double frac) {
// your rotation code here
}
}
If you could provide your arc generation source code it might be helpful.
for me this works flawlessly for 500 arcs:
package application;
import java.util.Random;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Arc;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
Group group = new Group();
Random rand = new Random();
for(int i=0; i<1000; i++){
Arc c = new Arc(200 + rand.nextInt(400), 200 + rand.nextInt(400), 10, 10, 0, 360);
group.getChildren().add(c);
}
root.getChildren().add(group);
RotateTransition rotateTransition = new RotateTransition(Duration.millis(5000), group);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(720);
rotateTransition.setCycleCount(3);
rotateTransition.setAutoReverse(true);
rotateTransition.play();
Scene scene = new Scene(root,800,800);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
if this is still to laggy and you only want to draw arcs you could directly invoke the graphics-context of a canvas.

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.
}

How to create a custom loading screen in JavaFX?

I'd like to create a custom loading screen for a JavaFX application. Don't want the user to see the Java coffee cup icon, I want to put my own graphic there!
I've found out how to provide a static image, or even an animated GIF, but I'm more interested in a Flash-like screen where I can specify what the state of the image looks like at certain percentages.
Any ideas?
For JavaFX2, you can set a custom preloader. You have complete control over then scene. I haven't used them personally, but this might be what you want.
http://docs.oracle.com/javafx/2/deployment/preloaders.htm
JavaFX preloader class
I have created a very simple preloader screen using native JavaFX APIs. Here it's explained how to do this: https://docs.oracle.com/javafx/2/deployment/preloaders.htm (old but workable examples) - this is newer and seems to be the same: https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/preloaders.html (Newer page and JavaFX version but I don't see the difference).
The older link is easier to read, because of page formatting.
Main class
package YOUR_PACKAGE_NAME;
import javafx.application.Application;
/**
* Minimal reproducible example (MRE) - Example of a simple JavaFX preloader.
* Java Main class for starting up the JavaFX application with a call to launch MainApplication.
* #author Remzi Cavdar - ict#remzi.info - #Remzi1993
*/
public class Main {
public static void main(String[] args) {
/*
* The following Java system property is important for JavaFX to recognize your custom preloader class.
* Which should extend javafx.application.Preloader.
*/
System.setProperty("javafx.preloader", Preloader.class.getName());
// Launch the main JavaFX application class.
Application.launch(MainApplication.class, args);
}
}
Preloader class
package YOUR_PACKAGE_NAME;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
/**
* Minimal reproducible example (MRE) - Example of a simple JavaFX preloader class.
* #author Remzi Cavdar - ict#remzi.info - #Remzi1993
*/
public class Preloader extends javafx.application.Preloader {
private ProgressBar progressBar;
private Stage stage;
private Scene createPreloaderScene() {
progressBar = new ProgressBar();
BorderPane borderPane = new BorderPane();
borderPane.setCenter(progressBar);
return new Scene(borderPane, 800, 600);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
// I also recommend to set app icon: stage.getIcons().add();
stage.setTitle("YOUR TILE HERE");
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
progressBar.setProgress(pn.getProgress());
}
#Override
public void handleStateChangeNotification(StateChangeNotification evt) {
if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
stage.hide();
}
}
}
Testing
Tested on: 01-11-2022
Tested OS: Windows 11 - Version 21H2 (OS Build 22000.1098)
Tested with: OpenJDK 19 - Eclipse Temurin JDK with Hotspot 19.0.1+10 (x64) (See: https://adoptium.net/en-GB/temurin/releases/?version=19)
Tested with JavaFX (OpenJFX) version: OpenJFX 19 (See: https://openjfx.io and repo: https://github.com/openjdk/jfx)
If you're setting things up as shown on This blog entry, it looks like the answer would be 'no' - the loading graphic is just part of the overall options that are passed to the applet. Because this applet could be any java code (not just javaFX), there's no way to tie your custom renderer in.
you should use java timer:
Timer tm= new Timer();
Stage ilk;
int count;
public void check() {
ilk=new Stage();
TimerTask mission;
gorev = new TimerTask() {
#Override
public void run() {
Group root = new Group();
Scene scene;
scene = new Scene(root, 960, 540);
scene.setFill(Color.BLACK);
ilk.setScene(scene);
ilk.setTitle("Splash Screen");
sayac++;
if(count==5){
tm.cancel();
ilk.show();
}
}
};
tm.schedule(mission, 0, 2000);
}
For changing the coffee cup icon:
stage.getIcons().add(new Image("images/myimage.png"));
and here is a reference for a very clear preloader screen out there and awesome css too:
http://docs.oracle.com/javafx/2/best_practices/jfxpub-best_practices.htm

Resources