So I got my Processing code working in java. But now I want to embed it in JavaFX for my GUI. How can I do so?
I tried using the following code but it does not seem to work.
package testprocessing;
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.swing.JApplet;
import javax.swing.SwingUtilities;
import java.awt.Dimension;
import java.util.concurrent.*;
import processing.core.*;
public class JavaFxApplet extends Application {
private PApplet applet = new MyProcessingSketch();
private Dimension appletSize;
#Override public void init() throws ExecutionException, InterruptedException {
applet.init();
FutureTask<Dimension> sizingTask = new FutureTask<>(() ->
applet.getRootPane().getPreferredSize()
);
SwingUtilities.invokeLater(sizingTask);
appletSize = sizingTask.get();
}
#Override public void start(Stage stage) {
final SwingNode swingNode = new SwingNode();
SwingUtilities.invokeLater(() ->
swingNode.setContent(applet.getRootPane())
);
stage.setScene(
new Scene(
new Group(swingNode),
appletSize.getWidth(), appletSize.getHeight(),
Color.BLACK
)
);
stage.show();
}
#Override public void stop() {
applet.stop();
applet.destroy();
}
public static void main(String[] args) {
launch(args);
}
}
I get error at getRootPane(). Can you suggest an alternative for it?
Background
Introduced in Processing 3 was a JavaFX rendering mode that makes it possible to include JavaFX in our sketches. Rather than creating our own JavaFX window from scratch and then embedding our sketch within it, we can modify the window that is constructed by the PApplet class when it is initialised in JavaFX mode, adding new JavaFX elements therein.
During initialisation in JavaFX mode, the PApplet class creates a javafx.scene.canvas.Canvas object and adds this as a child to a javafx.scene.layout.StackPane object. Then, a javafx.scene.Scene object is constructed with the stackPane object as a parameter. Finally, the PApplet class creates a javafx.stage.Stage object and sets its scene to the scene object, to give us our PApplet instance - the sketch.
So in terms of JavaFX elements, the PApplet window is initialised with four elements in the following hierarchy: Stage > Scene > StackPane > Canvas, where the canvas is the graphical canvas of the sketch (ie. the object that Processing draws to).
To create our own GUI, we can add any javafx.scene.Node object (this is the superclass of JavaFX graphical elements) to the stackPane object. Alternatively you could construct a new Scene, add the Processing's canvas to it, and replace the existing Scene of the Stage.
What doesn't seem to work
Without specifying the rendering mode, Processing defaults to JAVA2D mode. In this mode, the PApplet class creates a PApplet instance with java.awt versions of the canvas and window (a java.awt.Canvas and java.awt.Frame respectively). In theory, it is possible to cast the java.awt.Frame to a javax.swing.JFrame, embed this in a javafx.embed.swing.SwingNode object and finally add this to a JavaFX stage. However, I haven't been able to get this to work.
There are also the P2D & P3D modes. In these modes, the canvas is a com.jogamp.newt.opengl.GLWindow object. Again, I have tried to embed this in a Swing Node, with the help of a com.jogamp.opengl.awt.GLJPanel, but it has not proven successful.
Implementation
Initialise your sketch in Processing's FX2D rendering mode in the call to size():
size([width], [height], FX2D);
We can then expose the four JavaFX elements that were created during initialisation by repeated casting:
final PSurfaceFX FXSurface = (PSurfaceFX) surface;
final Canvas canvas = (Canvas) FXSurface.getNative();
final StackPane stackPane = (StackPane) canvas.getParent();
final Scene scene = canvas.getScene();
final Stage stage = (Stage) canvas.getScene().getWindow();
We now have an option as to how we add our JavaFX elements:
1) Add to the existing stackPane
We can add JavaFX elements (javafx.scene.Node objects) to the stackPane that was created during initialisation with the following method:
stackPane.getChildren().add(Node node);
2) Create a new scene (recommended)
Alternatively (recommended, unless you want a stackPane as a top-level aligner), we can create a new scene object (rather than using the scene and stackPane objects that were created during initialisation) and add JavaFX elements to this.
Scene newscene = new Scene(new Group(canvas)); // simple group containing only the Processing canvas
stage.setScene(Scene scene);
During initialisation, the canvas' dimensions are binded to those of the stackPane. If we wish to change the size of the Processing canvas within the window during runtime, we must include the following:
canvas.widthProperty().unbind();
canvas.heightProperty().unbind();
Now we can freely call canvas.setHeight() and canvas.setWidth() to resize to Processing canvas within the JavaFX window (the stage).
Example
Let's add a javafx.scene.control.MenuBar to the window. Note that I am initialising our JavaFX elements within the initSurface() method rather than doing so within the setup() method, as it's safer.
In this example, the stackPane is replaced with a javafx.scene.layout.VBox, first, so that the menubar sits atop of the canvas and second, to ensure the stage is the correct height (the sum of the menuBar height and canvas height) at launch.
#Override
public void settings() {
size(500, 500, FX2D);
}
#Override
protected PSurface initSurface() {
PSurface surface = super.initSurface();
final PSurfaceFX FXSurface = (PSurfaceFX) surface;
final Canvas canvas = (Canvas) FXSurface.getNative(); // canvas is the processing drawing
final Stage stage = (Stage) canvas.getScene().getWindow(); // stage is the window
stage.setTitle("Processing/JavaFX Example");
canvas.widthProperty().unbind();
canvas.heightProperty().unbind();
final MenuItem menuItem1 = new MenuItem("Fill green");
menuItem1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
noLoop();
background(0, 255, 0); // Fills the canvas green on click
}
});
final MenuItem menuItem2 = new MenuItem("Exit");
menuItem2.setOnAction(actionEvent -> exit()); // Exit PApplet on click
final Menu menu = new Menu("Menu");
menu.getItems().add(menuItem1);
menu.getItems().add(menuItem2);
final MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(menu);
final VBox vBox = new VBox(menuBar, canvas); // Menubar will sit on top of canvas
final Scene newscene = new Scene(vBox); // Create a scene from the elements
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.setScene(newscene); // Replace the stage's scene with our new one.
}
});
return surface;
}
#Override
public void draw() {
background(50);
fill(0, 255, 0);
strokeWeight(5);
stroke(255, 5, 5);
line(0, 0, width, 0); // shows us that window is the correct dimensions
line(0, height, width, height); // shows us that window is the correct dimensions
noStroke();
ellipse(100, 100, 200, 200);
fill(255, 0, 0);
ellipse(100, 200, 200, 200);
fill(0, 0, 255);
ellipse(100, 300, 200, 200);
}
Result
Why are you trying to get the applet's root pane? Just add the applet to a JPanel, then add the JPanel to your SwingNode:
JPanel panel = new JPanel();
panel.add(applet);
swingNode.setContent(panel)
Related
Can a set of lines be drawn on a text area component which would look like this .
I then need to be able to type in text over them . These lines also need to be erased and redrawn
Consider drawing lines on a Pane like so:
public class StageTest extends Application{
private static final double WIDTH = 100, HEIGHT = 60;
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Test Stage");
Label label = new Label("Some text ");
label.setStyle("-fx-background-color:TRANSPARENT");
label.setAlignment(Pos.CENTER);
label.setPrefSize(WIDTH, HEIGHT);
Pane linesPane = getPane(label);
StackPane root = new StackPane(linesPane, label);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private Pane getPane(Label label) {
Pane pane = new Pane();
pane.setStyle("-fx-background-color:WHITE");
Line blueLine = new Line();
blueLine.setStroke(Color.BLUE);
blueLine.startXProperty().bind(label.layoutXProperty());
blueLine.startYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.333)));
blueLine.endXProperty().bind(label.layoutXProperty().add(label.widthProperty()));
blueLine.endYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.333)));
Line redLine = new Line();
redLine.setStroke(Color.RED);
redLine.startXProperty().bind(label.layoutXProperty());
redLine.startYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.666)));
redLine.endXProperty().bind(label.layoutXProperty().add(label.widthProperty()));
redLine.endYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.666)));
pane.getChildren().addAll(blueLine, redLine);
return pane;
}
public static void main(String[] args) {
launch(args);
}
}
You may want to look at using the background property of TextArea.
new TextArea().setBackground(new Background(new BackgroundImage(myImage,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,BackgroundSize.DEFAULT)));
This code here is assuming you can get those lines as an image.
You can find more info for backgrounds here: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Background.html
If you want the background image to change and be dynamic for your current needs, you have two options.
Just use a Canvas for the whole project. Paint the lines onto the canvas first, and then paint the letters on top of this. This could be better as it would allow you to customize your project to however you would like it, but it would take a bit more code and thinking to do.
Use the TextArea, and for the BackgroundImage, use a Snapshot of another Canvas. You can use a Canvas to draw the lines however you would like, and then convert it to an image using Snapshot.
WritableImage i = canvas.snapshot(new SnapshotParameters(), null);
Then, using this image, you can use that as the background of the TextArea by using BackgroundImage.
I was assigned in school to build a small JAVAFX program with buttons that travels through different Scenes (layouts).
My program is a log in window, and details window. But I have to create the details window in a different class than Main class. How do I make the button call the 'details window'from Main when it's a class?
(See createAccountBtn.setOnAction)
Main Class
Detail window Class
So Here's the deal: I created an interface where I put this method:
void displayLayout(VBox layout);
And then It triggers this method in Main:
public void displayLayout(VBox layout){
Scene scene = new Scene(layout, 200, 200);
window.setScene(scene);
I also have in Main this:
Button createAccountBtn = new Button("Create new account");
createAccountBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
displayLayout(new DetailScreen(listener));
}
});
layout = new VBox();
layout.getChildren().addAll(accountNumberTF, loginBtn, createAccountBtn);
layout.setAlignment(Pos.CENTER);
displayLayout(layout);
window.show();
Which is the layout options and the button. the setOnAction button points to a new Class that inherits VBox and acts as a layout:
private ChangeScreenListener listener;
public DetailScreen(ChangeScreenListener listener) {
this.listener = listener;
Button exitBtn = new Button("EXIT");
TextField input = new TextField();
VBox layout = new VBox();
layout.getChildren().addAll(input, exitBtn);
However after I click the button, the window becomes empty and does not change layout.
Ideas?
public static variables can pass variables between classes. This is the easiest way i could think off to pull off the results you desire.
In the main class define the following variables:
public static Stage stage;
public static Scene sceneMain;
Assuming you have already set a scene and using a Stage in your main constructor write the following code after setting your Stage and Scene.
stage = primaryStage;
sceneMain = scene;
When you have done this you can easily call on to the stage in another class by and going back to your main scene like this: (this is what you can put under the event of your buttonclick())
Main.stage.setScene(sceneMain);
Or you can set another scene by simply putting the scene you want in the .setscene();
I have a Java project that plays a .flv media file through JavaFX Media Player, and it's working fine. Recently, I've been wanting to experiment by adding GUI components to this Project (JPanel, JLabel). However, I've failed in all my attempts and after doing some research turns out it's not as simple as i first thought.. I've tried borderPane.setTop(JLabel) but I get a "Cannot convert Jlabel to Node" error.. I feel that I'm missing something
If anyone has any idea why this isnt working for me, I would greatly appreciate any form of explanation or examples.. :)
Here is the code if it might be of use to you!
#Override
public void start(Stage stage){
String path = "Data/Video/Clip.flv";
Media media = new Media(new File(path).toURI().toString());
MediaPlayer mediaPlayer = new MediaPlayer(media);
MediaView mediaView = new MediaView(mediaPlayer);
BorderPane borderPane = new BorderPane();
borderPane.setCenter(mediaView);
//borderPane.add(logoPanel); <<<<<<< Error
Scene scene = new Scene(borderPane, 1024, 800);
scene.setFill(javafx.scene.paint.Color.BLACK);
stage.setTitle("Media Player");
stage.setScene(scene);
stage.show();
mediaPlayer.setAutoPlay(true);
BorderPane is a JavaFX Node whereas JPanel is a Java Swing Component.
You cannot add a JPanel to a BorderPane, what you are looking for instead is the JavaFX equivalent of the JPanel which is the Pane class.
If you are developing using JavaFX it is easier to just use JavaFX components. If you must use Swing components then you can use the SwingNode class.
What you basically want to achieve: add some Swing components to an JavaFX application.
You have to use SwingNode class to "wrap" a JComponent.
SwingNode Class
JavaFX 8 introduces the SwingNode class, which is located in the
javafx.embed.swing package. This class enables you to embed Swing
content in a JavaFX application. To specify the content of the
SwingNode object, call the setContent method, which accepts an
instance of the javax.swing.JComponent class.
You can check this tutorial how embed Swing component into JavaFX application.
Small example to put a Swing Button into the center of a BorderPane:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
SwingNode swingNode = new SwingNode();
JButton jButton = new JButton("I am a Swing button");
jButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Message from Swing");
}
});
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
swingNode.setContent(jButton);
}
});
root.setCenter(swingNode);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
By the way, BorderPane has no method "add" (as in your commented line):
You can use setCenter, setTop, setBottom, setLeft and setRight to add Nodes into it (as you used to fill the center).
If your goal was not to embed Swing into JavaFX, just to use JavaFX controls, you can check this article what controls JavaFX has by default.
I have a main stage that must open another stage without losing focus of the first one. I know i can call mainWindow.requestFocus() after calling secondWindow.show() but i want to make it to work without the first window even losing focus.
I wanto to do this because the second stage is a notification window with StageStyle.TRANSPARENT, that stays always on top and closes itself after some seconds.
Is there a way to make the second window "unfocusable"?
Do you really need to create a new Stage for showing your notification window? You could also use javafx.stage.Popup which creates transparent windows by default (so you would not need to set StageStyle.TRANSPARENT). Another advantage of using Popup instead of Stage is that it doesn't "steal" the focus from your main stage, wich should be pretty much what you need.
Here is some more information about the popup class: https://docs.oracle.com/javase/8/javafx/api/javafx/stage/Popup.html
And here is a simple example of how to use a popup in your application: https://gist.github.com/jewelsea/1926196
Well, I managed to do this embedding my java fx stage inside a swing JFrame. On JFrame i can do what i want. here is how:
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setBackground(new java.awt.Color(0, 0, 0, 0));
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setSize(422, 116);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
fxPanel.setScene(scene);
frame.setResizable(false);
frame.setAlwaysOnTop(true);
frame.setFocusableWindowState(false); // <- Here is the secret
frame.setVisible(true);
If anyone knows a less dirty way of doing this, i would appreciate.
You need some parameters for this. There is a method called stage.setOnShown() that will be called immediatly after opening the new stage.
But be aware of the code down below, it will open the second stage without any possibility to close it, so you need to kill the app. This could be made better with a timer where the windows automatically will close.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class TwoStage extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Open second stage");
btn.setOnAction((e) -> {
Label l = new Label("I'm a second window");
Scene s = new Scene(l, 100, 100);
Stage s1 = new Stage(StageStyle.TRANSPARENT);
s1.centerOnScreen();
s1.setScene(s);
s1.initModality(Modality.NONE);
s1.setAlwaysOnTop(true);
s1.setOnShown((e1) -> {
primaryStage.requestFocus();
});
s1.show();
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Two Windows");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
so i am using SceneBuilder 2.0 to create a stage with a Canvas node and then use the ContextGraphics of the Canvas to modify it from the controller file, is this possible? i read that the Canvas can be modified before it is attached to a Stage from any thread, but if its already been attached to a stage it has to be modified by the JavaFX application thread or else you will find problems like the Canvas not updating (which is exactly what is happening to me).
I currently have this code which is not working:
in the JavaFX Application:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Window.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
And in the controller file:
#FXML
private Canvas canvas;
public void render() {
GraphicsContext gc = canvas.getGraphicsContext2D();
Image image1 = new Image("/img/Test1.png");
gc.drawImage(image1, 300, 300);
}
and then i call the method render() but the Canvas wont update, any ideas?
You are not telling us where and from which thread you are calling the render method. Anyway - I'd bet that your image has just not been loaded correctly. Check the error property of the image before you draw it.