How to embed a PApplet in javafx?

﹥>﹥吖頭↗ 提交于 2019-12-13 03:04:32

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!