Пытаюсь сделать программу для визуального анализа фрактальных множеств. Я выбираю Processing 3 в качестве библиотеки для рисования и JavaFX для пользовательского интерфейса. Вот несколько скриншотов текущего состояния:
Мой графический интерфейс:
есть код лаунчера:
import Graphics.Canvas2D;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import processing.core.PGraphics;
import java.io.IOException;
public class Launcher extends Application {
private static Stage primaryStage;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
Parent root = loadFXML("MainUI.fxml");
Scene scene = new Scene(root, 500, 400);
primaryStage.setTitle("Fractal Analyzer");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setMaximized(true);
Launcher.primaryStage = primaryStage;
}
@Override
public void init() {
}
@Override
public void stop() {
System.exit(0);
}
public static Stage getPrimaryStage() {
return primaryStage;
}
public void setCanvas(Canvas2D canvas){
}
private Parent loadFXML(String path) {
try {
return FXMLLoader.load(getClass().getResource(path));
} catch (IOException e) {
e.printStackTrace();
}
System.exit(1);
return null;
}
}
Тестирование фрактального PAplet:
Вот код этого PAplet:
package Fractal;
import processing.core.PApplet;
public class SirpenskiTriangle extends PApplet {
public static void main(String[] args) {
PApplet.main("Fractal.SirpenskiTriangle");
}
public void settings() {
size(640, 640);
smooth();
if (frame != null) {
frame.setResizable(true);
}
}
public void draw() {
drawTriangle(new Position(300, 20), new Position(620, 620), new Position(20, 620), 0);
noLoop();
scale(10f);
}
public void setup(){}
public void drawTriangle(Position top, Position right, Position left, int depth) {
if (depth > 10) return;
line(top.x, top.y, right.x, right.y);
line(right.x, right.y, left.x, left.y);
line(left.x, left.y, top.x, top.y);
drawTriangle(top, top.middleWith(right), top.middleWith(left), depth + 1);
drawTriangle(top.middleWith(left), left.middleWith(right), left, depth + 1);
drawTriangle(top.middleWith(right), right, left.middleWith(right), depth + 1);
}
class Position {
final float x;
final float y;
Position(float x, float y) {
this.x = x;
this.y = y;
}
Position middleWith(Position other) {
return new Position((x + other.x) / 2, (y + other.y) / 2);
}
}
}
Есть ли способ поместить обработку PAplet в сцену JavaFX, такую как холст или что-то подобное?
Я надеюсь, что это может работать так, но этот код недействителен:





Я разработал два подхода: в первом мы обходим создание этапа JavaFX в Processing и точечную Processing для втягивания в этап JavaFX, загруженный из файла FXML; во втором мы заменяем сцену JavaFX по умолчанию для Processing на сцену, загруженную из файла FXML во время выполнения.
При первом подходе мы запускаем приложение, как приложение JavaFX (с использованием Application.launch(Launcher.class);), полностью обходя код создания этапа JavaFX в Processing.
Вам нужно будет загрузить слегка измененный core.jar, чтобы этот подход работал, где я изменил видимость некоторых членов классов PSurfaceFX и PGraphicsFX2D с Protected на Public. Изменения позволяют нам запускать JavaFX из нашего собственного класса ... extends Application, сохраняя при этом доступ к членам, которые Processing должен установить во время запуска для работы.
Обработка 3 вылетает в режиме FX2D, когда используемый JDK выше Java 8, поэтому я также сделал рабочую версию для 8+, поскольку для работы файлов FXML обычно требуется как минимум Java 9.
Это файл FXML, с которым я работаю в этом примере:
С измененным core.jar, добавленным в путь к классам вашего проекта, переопределите initSurface() в вашем классе PApplet с помощью следующего фрагмента. С помощью этого кода мы обходим вызов PApplet для initFrame() - здесь обработка создает свой собственный этап JavaFX, чего мы не хотим делать.
@Override
protected PSurface initSurface() {
g = createPrimaryGraphics();
PSurface genericSurface = g.createSurface();
PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;
fxSurface.sketch = this;
Launcher.surface = fxSurface;
new Thread(new Runnable() {
public void run() {
Application.launch(Launcher.class);
}
}).start();
while (fxSurface.stage == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
this.surface = fxSurface;
return fxSurface;
}
Установите режим рендеринга PApplet на FX2D следующим образом:
@Override
public void settings() {
size(0, 0, FX2D);
}
Поместите следующее или подобное в свой класс Launcher. В этом примере я вручную нашел узел, в который хочу добавить объект холста. Есть лучшие, более программные способы сделать это (например, .Погляди() с использованием fx: id желаемого узла - это можно определить в файле FXML). Я также привязал размеры холста к размерам его родительского элемента, поэтому при перетаскивании делителя, разделяющего панели Мастер и Вид, размер холста обработки изменяется соответствующим образом.
public class Launcher extends Application {
public static PSurfaceFX surface;
@Override
public void start(Stage primaryStage) throws Exception {
Canvas canvas = (Canvas) surface.getNative(); // boilerplate
GraphicsContext graphicsContext = canvas.getGraphicsContext2D(); // boilerplate
surface.fx.context = graphicsContext; // boilerplate
primaryStage.setTitle("FXML/Processing");
VBox root = FXMLLoader.load(new File("c:/Users/Mike/desktop/test.fxml").toURI().toURL());
SplitPane pane = (SplitPane) root.getChildren().get(1); // Manually get the item I want to add canvas to
AnchorPane pane2 = (AnchorPane) pane.getItems().get(0); // Manually get the item I want to add canvas to
pane2.getChildren().add(canvas); // Manually get the item I want to add canvas to
canvas.widthProperty().bind(pane2.widthProperty());
canvas.heightProperty().bind(pane2.heightProperty());
Scene scene = new Scene(root, 800, 800);
primaryStage.setScene(scene);
primaryStage.show();
surface.stage = primaryStage; // boilerplate
}
}
Вот результат:
Также см. Проект это Github - базовый проект, показывающий, как скетч обработки и этап FXML JavaFX могут быть интегрированы с использованием этого первого подхода, но включает JavaFX Controller для заполнения аннотированных полей @FXML (обеспечивая простой способ сначала получить, а затем ссылаться, Объекты JavaFX в коде).
Этот подход работает с ванильной обработкой. Здесь мы запускаем Обработку как обычно, а затем заменяем сцену по умолчанию новой сценой, загруженной из файла FXML во время выполнения. Это более простой подход (и не требует использования модифицированного .банка!), Но затруднит взаимодействие JavaFX / Processing, поскольку мы не можем использовать JavaFX Controller для получения полей с помощью инъекции FXML.
Пример кода PDE:
import java.util.Map;
import java.nio.file.Paths;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import processing.javafx.PSurfaceFX;
public void setup() {
size(800, 800, FX2D);
strokeWeight(3);
}
protected PSurface initSurface() {
surface = (PSurfaceFX) super.initSurface();
final Canvas canvas = (Canvas) surface.getNative();
final Scene oldScene = canvas.getScene();
final Stage stage = (Stage) oldScene.getWindow();
try {
FXMLLoader loader = new FXMLLoader(Paths.get("C:\\path--to--fxml\\stage.fxml").toUri().toURL()); // abs path to fxml file
final Parent sceneFromFXML = loader.load();
final Map<String, Object> namespace = loader.getNamespace();
final Scene newScene = new Scene(sceneFromFXML, stage.getWidth(), stage.getHeight(), false,
SceneAntialiasing.BALANCED);
final AnchorPane pane = (AnchorPane) namespace.get("anchorPane"); // get element by fx:id
pane.getChildren().add(canvas); // processing to stackPane
canvas.widthProperty().bind(pane.widthProperty()); // bind canvas dimensions to pane
canvas.heightProperty().bind(pane.heightProperty()); // bind canvas dimensions to pane
Platform.runLater(new Runnable() {
@Override
public void run() {
stage.setScene(newScene);
}
}
);
}
catch (IOException e) {
e.printStackTrace();
}
return surface;
}
public void draw() {
background(125, 125, 98);
ellipse(200, 200, 200, 200);
line(0, 0, width, height);
line(width, 0, 0, height);
}
Результат:
… Используя этот файл FXML:
Ваш код прерывается в этой строке `Canvas canvas = (Canvas) surface.getNative (); // шаблонный GraphicsContext graphicsContext = canvas.getGraphicsContext2D (); // шаблон поверхности. fx.context = graphicsContext; // шаблон `
@ JanČerný FXML файл. Как код ломается? Объект поверхности класса Launcher устанавливается в методе переопределения initsurface () PApplet, если он равен нулю.
треугольник не отображается, и я получаю это ПРЕДУПРЕЖДЕНИЕ: загрузка документа FXML с API JavaFX версии 9.0.1 с помощью среды выполнения JavaFX версии 8.0.211 java.lang.IllegalStateException: Эта операция разрешена только в потоке событий; currentThread = main в com.sun.glass.ui.Application.chat processing.core.PApplet.main (PApplet.java:10544) в processingfxnew.SirpenskiTriangle.main (SirpenskiTriangle.jav a: 13), но я использую java 8
@gtx Используйте Java 9 или выше.
Оказывается, это было просто предупреждение, причиной ошибки является java.lang.IllegalStateException: эта операция разрешена только в потоке событий; currentThread = main в com.sun.glass.ui.Application.checkEventThread (Application.ja va: 443) .... в processing.core.PApplet.main (PApplet.java:10544) в processingfxnew.SirpenskiTriangle.main ( SirpenskiTriangle.jav a: 13) эта строка является PApplet.main ("processingfxnew.SirpenskiTriangle"); не показывает причину этого, я не уверен, как это исправить
@gtx Я не могу сказать, почему отсюда ... У меня есть пример на моем Github загрузки этапа JavaFX из файла FXML в обработке. Вы можете использовать этот проект в качестве шаблона.
хорошо, спасибо, я не могу найти github с вашим именем, вы можете дать ссылку?
Я загрузил файлы вашего проекта, и здесь тоже появляется окно, но часть обработки все еще не отображается, и я все еще получаю ту же ошибку java.lang.IllegalStateException: эта операция разрешена только в потоке событий; currentThread = main в com.sun.glass.ui.Application.checkEventThread (Application.ja va: 443) А также ..FXMLLoader..56 mo Вызвано: java.lang.NullPointerException в javafx.Controller.greenPen (Controller. java: 60) ... еще 66
Используя java8 и ваше измененное ядро 1.8.jar и изменив ваш fxml на javafx 8 и обработав другие jar-файлы, такие как gluegen-rt
@gtx Я не уверен, что он будет работать с 1.8 - просто попробуйте Java 10!
Чтобы он заработал, вам нужно запустить скетч Processing, а не приложение JavaFX.
Просто делай
PApplet.main(Launcher.class.getName());
Также большое спасибо за вашу помощь! Я не имел ни малейшего представления о том, как использовать JavaFX, поставляемый с обработкой!
Пожалуйста, опишите подробнее свое решение, потому что я не знаю, что вы имеете в виду.
Вы все еще не решили это? Я могу отправить вам пример кода сегодня же
Это было бы очень хорошо :) Я больше занимаюсь математикой, чем практическим программированием, поэтому что-то вроде проблем с библиотекой для меня фатально.
Хорошо, это мой код, он работает! Я все скопировал и поменял названия. !!! Я не тестировал этот модифицированный код, поэтому не копируйте и вставляйте все !!! Однако необработанный принцип определенно должен работать.
Если у вас все еще есть проблемы или вопросы, просто прокомментируйте.
Главный
public class Main {
public static void main(String[] args) {
// Your code starts here, and runs Processing.
// This is also, how you normally start Processing sketches.
PApplet.main(Sketch.class.getName());
}
}
Эскиз
public class Sketch extends PApplet{
@Override
public void settings() {
size(200, 200, FX2D); // Size doesn't really matter
}
@Override
public void setup() {
}
@Override
public void draw() {
}
// Processing uses this function to determine,
// how to display everything, how to open the canvas...
// We override the code, that would normally open a window with the normal Processing stuff,
// to open start new JavaFX application in a new Thread.
// micycle's code
@Override
protected PSurface initSurface() {
g = createPrimaryGraphics();
PSurface genericSurface = g.createSurface();
PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;
fxSurface.sketch = this;
// Because the JavaFX App is being launched by reflection,
// we can't pass variables to it via constructor, so
// we have to access it in static context.
// Here, we give JavaFX the surface.
ExampleApp.surface = fxSurface;
// New thread started, so JavaFX and Processing don't interrupt each other.
new Thread(new Runnable() {
public void run() {
// JavaFX way of launching a new Application
Application.launch(ExampleApp.class);
}
}).start();
while (fxSurface.stage == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
this.surface = fxSurface;
return fxSurface;
}
}
ExampleApp
public class ExampleApp extends Application {
public Canvas canvas; // The Canvas you will be drawing to
public static PSurfaceFX surface; // The Processing surface
// JavaFX started, this method is being run to set everything up.
@Override
public void start(Stage primaryStage) {
// This sets up the canvas, and the drawing region.
canvas = (Canvas) surface.getNative();
surface.fx.context = canvas.getGraphicsContext2D();
surface.stage = primaryStage;
// I'm just loading my FXML file. You can do all JavaFX stuff via code, if you want
try {
// !!My root Container is a BorderPane!!
BorderPane root = FXMLLoader.load(getClass().getClassLoader().getResource("application.fxml"));
} catch(IOException e) {
e.printStackTrace();
}
// Getting the Anchor pane, that is in the center of my BorderPane
AnchorPane pane = (AnchorPane) root.getCenter();
// The Anchor pane is being used, so the canvas can fill the parent (Center)
// Canvases don't have a property to fill it's parent, like most Containers do (Because it isn't a container)
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
// Adding the canvas to your App
root.getChildren().add(canvas);
// Launching the Stage
primaryStage.setTitle("Example App");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Хорошо, с прошлого раза я изменил некоторые элементы. Родителем холста теперь является просто панель, а не AnchorPane.
FXML вам не очень поможет ... Это просто BorderPane с другой Pane в нем, но ладно ...
<center>
<VBox prefHeight = "200.0" prefWidth = "100.0" BorderPane.alignment = "CENTER">
<children>
<Pane maxHeight = "1.7976931348623157E308" VBox.vgrow = "ALWAYS" />
</children>
</VBox>
Итак, что я делаю, это беру элемент Canvas, который создает Processing, и просто добавляю его в Pane.
пожалуйста, не публикуйте несколько ответов на один и тот же вопрос - вместо этого отредактируйте и уточните тот, который решает проблему полностью (или как можно ближе к вам :)
Я новичок в Stackoverflow, извините за это. Имейте это в виду!
Не могли бы вы добавить свой файл FXML?