问题
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?
回答1:
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
回答2:
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)
来源:https://stackoverflow.com/questions/28266274/how-to-embed-a-papplet-in-javafx