0

I'm working on an app that uses JavaFX as GUI to control a PApplet that runs in a different window.

I've managed to make the two things appear and work, but when I try to load files in the PApplet class, I get a warning that says "The sketch path is not set" and an error like this: "java.lang.RuntimeException: Files must be loaded inside setup() or after it has been called."

I'm guessing that I might not have initialized the PApplet properly.

Here is my javafx.Application class

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("application.fxml"));
            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setTitle("Clusters");
            primaryStage.setScene(scene);
            primaryStage.show();

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        PApplet.main("application.Controller");
        launch(args);
    }
}

Here is my PApplet class

public class Controller extends PApplet {

    private ArrayList<File> files;
    private ArrayList<PImage> images;

    public Controller() {
    }

    public void settings() {
        size(640, 360);
    }

    public void setup() {
        background(0);     
        images = new ArrayList<PImage>();
        files = new ArrayList<File>();
    }

    public void draw() {
    }

    public void importImages() {
        // Open File Chooser
        FileChooser dialog = new FileChooser();
        dialog.setTitle("Import Images");
        FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("Image files (*.jpg, *.png, *.tiff)", "*.jpg", "*.png", "*.tiff");
        dialog.getExtensionFilters().add(extFilter);
        List<File> imported_files = dialog.showOpenMultipleDialog(new Stage());
        System.out.println(imported_files); 

        for (File f: imported_files) {
            images.add(loadImage(f.getAbsolutePath()));
            System.out.println(f.getName() + " loaded");
        }
    }
}

This is how I link the importImages() method to the FXXML file

      <Menu mnemonicParsing="false" text="File">
        <items>
          <MenuItem fx:id="file_open" mnemonicParsing="false" text="Open" />
              <MenuItem fx:id="file_save" mnemonicParsing="false" text="Save" />
              <MenuItem fx:id="file_save_as" mnemonicParsing="false" text="Save As" />
              <MenuItem fx:id="file_import" mnemonicParsing="false" onAction="#importImages" text="Import" />
        </items>
      </Menu>
Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
  • How are you calling the `importImages()` function? – Kevin Workman Dec 25 '18 at 17:23
  • clicking a javafx menuItem which is linked to the method in the fxxml doc. __ – Lorenzo Rivosecchi Dec 25 '18 at 17:54
  • Just a guess, but this might be a threading issue? What happens if you call the `importImages()` function from a Processing function like `mousePressed()`? – Kevin Workman Dec 25 '18 at 18:26
  • java.lang.IllegalStateException: Not on FX application thread; currentThread = Animation Thread – Lorenzo Rivosecchi Dec 25 '18 at 18:42
  • So i replaced the fx.FileChooser with a awt.FileDialog and when the method is called by the animation thread it works, but it doesn't when is called by the fx thread. How would I call a certain method to be executed by a specific existing thread ? (or how would i solve my problem with a workaround ?) – Lorenzo Rivosecchi Dec 25 '18 at 18:51

1 Answers1

0

My guess is that Processing functions like loadImage() need to be called from Processing's main thread, and JavaFX functions like dialog.showOpenMultipleDialog() need to be called from the JavaFX Application Thread.

Putting work on the JavaFX Application Thread is pretty straightforward. Read Concurrency in JavaFX and JavaFX working with threads and GUI (or just google something like "JavaFX thread" for a ton of results). But basically you can call the Platform.runLater() function to put work on the JavaFX Application Thread.

Going the other way and putting work on the Processing thread is a bit more complicated. You can read more about concurrency here.

To test this theory, I'd do something simple like this:

public class Controller extends PApplet {
  boolean loadImages = false;
  // other variables you need to communicate between threads

  public void draw() {
    if(loadImages){
      // call loadImages()
      loadImages = false;
    }
  }

  // call this from JavaFX
  public void importImages() {
    // set other variables you need here
    loadImages = true;
  }
}

If this works, you can do something fancier like maintaining a queue of events.

Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
  • I tried the queue solution, but it's not working cause there's an inconsistency between my PApplet window (created through PApplet.main("")) and the PApplet known by the Controller. – Lorenzo Rivosecchi Dec 26 '18 at 15:53
  • making the queue static works, but i'm worried that it could raise other problems in future development, what do you think ? – Lorenzo Rivosecchi Dec 26 '18 at 16:00
  • @LorenzoRivosecchi Without seeing the code it's hard to say. The pattern you're looking for is called [producer-consumer](https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem). I'd recommend asking a follow-up question in a new post if you get stuck. – Kevin Workman Dec 26 '18 at 17:44