JavaFX Unable to add modules to Processing IDE

The following JavaFX code will not run in Processing 4 due to absence of JavaFX modules (runs ok in version 3.5.4 minus the first line). I have tried many times adding the modules to the libraries folder or drag 'n dropping them on the sketch, but neither have worked. Text and graphics work fine with the FX2D renderer, but the controls won’t work. It is possible to create graphic buttons and trap mouse clicks or use SwingNode and add Swing components, but it just seems like we should be able to use official JavaFX controls. It is unclear to me why the requisite modules can not be added to the current Processing IDE.

Non-functional demo (see console error message)

import processing.javafx.*;  // Add this for Processing 4:
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

void setup() {  
  size(1, 1, FX2D); 
  Stage stage = new Stage();
  stage.setTitle("Button demo");
  Button btn = new Button("button");
  StackPane pane = new StackPane();
  pane.getChildren().add(btn);
  Scene scene = new Scene(pane, 200, 200);
  stage.setScene(scene);
  stage.show();  
}

void draw() {
}

A related GitHub issue:

:)

Thanks. I’ve seen the github messages, but am looking for ways to solve the problem, which shouldn’t be that difficult in my opinion. Just a few observations:

  1. With Processing 4 the code without a control will run just fine without the first line (import processing.javafx.*) indicating to me that Processing 4 is able to get requisite files from someplace else instead of the libraries folder in Documents/Processing.

  2. When the first line of code is included in Processing 3.5.4 the first thing that happens is an error dialog which says that more than one library is competing for the sketch and one of them has to be removed. The two locations are the libraries javafx folder and the other is in the app bundle. Removing the first line of code in 3.5.4 then allows the sketch to run, so at least for Processing 3.4.5 it appears to be using the libraries in the app bundle.

Therefore, it would seem reasonable to me to try adding the control modules to the app bundle of Processing 4 since my efforts of adding them to the libraries folder (Documents/Processing/libraries/javafx) have failed. I’m doing this on a Mac and from previous experience I have seen that if you alter the bundle on a code-signed app the gatekeeper will automatically disable the app so that it will no longer run. Nonetheless, I’m willing to try knowing that I’ll probably trash Processing 4 in the process, but can just download a new copy if necessary. The experiment would best be done by the Processing team; they could add the libraries to the app bundle and then code-sign the app prior to distribution which should not present a problem. Unfortunately I don’t know how to compile and code-sign the open source code for the Processing IDE. I think it would have to be an Xcode project in order for me to do it without trashing the editor.

Followup:

I just copy/pasted a javafx folder from another app (BlueJ) into the Processing 4 bundle and it accepted it without a problem; the app still runs as expected. This folder contains all the jar files and libraries necessary to add javafx controls to a Processing app; folder contents shown below. Now if we could just recode the Processing IDE to use the folder we should be able to use javafx controls.

Smoking gun?

https://github.com/processing/processing4/blob/3185ebae15f1ee894a9a2a056cc253b4677012e3/java/src/processing/mode/java/JavaBuild.java

line 1079:

 static public String[] getArgsJavaFX(String modulePath) {

    return new String[] {

      "--module-path", modulePath,




      // Full list of modules, let's not commit to all of these unless

      // a compelling argument is made or a reason presents itself.

      //"javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web"

      "--add-modules", "javafx.base,javafx.graphics,javafx.swing",




      // TODO Presumably, this is only because com.sun.* classes are being used?

      // https://github.com/processing/processing4/issues/208

      "--add-exports", "javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED",

      "--add-exports", "javafx.graphics/com.sun.glass.ui=ALL-UNNAMED"

    };

  }

Note that javafx.controls didn’t make the list.

Modules folder:

As it turns out all the requisite jar files and libraries for javafx live here on my system; no need to import them:

But they do have to be hooked up as shown above.

Hello @svan,

If you have access to a W10 PC consider doing a build with the changes you have in mind.

A link to building is here:

If you provide clear instructions on what to add\modify there may be interest in trying this for W10.

If time permits I may try it over the weekend…

:)

I greatly appreciate your interest and help with this (and other problems in the forum).

What to modify:

Start with the link that you provided above.

  1. Open the “java” folder.
  2. Open the “src/processing/mode/java” folder.
  3. Open the file “JavaBuild.java”
  4. Go to line 1086

change:
“–add-modules”, “javafx.base,javafx.graphics,javafx.swing”,

to:
“–add-modules”, “javafx.base,javafx.graphics,javafx.swing,javafx.controls”,

This should allow us to use javafx.controls in our Processing javafx projects. The way it is now we are unable to do this in Processing 4 as you are aware. Interesingly we can use SwingNode to add Swing controls (because they did add the javafx.swing module) but these controls are really ugly with a rectangular halo when initially used. This can be worked around by using a system call to set a transparent background and then jostling the frame to cause a refresh, but this is simply making it more difficult than it has to be and would be solved by using javafx controls to start with.

Why I think it will work:

If you try to compile/run pure javafx files from the command line you have to use “javac” and “java” with the “–add-modules” option added to both. If the correct modules are not added the code fails. For example:

To compile:
javac --module-path javafx-sdk-18.0.2/lib/ --add-modules javafx.controls,javafx.graphics,javafx.media,javafx.fxml ClickMe.java

and to run:
java --module-path javafx-sdk-18.0.2/lib/ --add-modules javafx.controls,javafx.graphics,javafx.media ClickMe

I believe that line 1086 determines what is inserted for the --add-modules option when our code is compiled and ran in Processing 4.

It does work!!

I followed the build instructions referenced in the link above (hardest part was getting apache-ant installed on Mac); after making the modifications to line 1086 of JavaBuild.java file outlined above the following code ran without error. Beautiful javafx button output is shown below that. I hope that the same modification will be made to Processing4 so that others can use these controls. Since Processing doesn’t have its own widgets I think it would be a nice addition for those that use controls in their projects.

Source code:

import processing.javafx.*;  // Add this for Processing 4:
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

void setup() {  
  size(1, 1, FX2D); 
  Stage stage = new Stage();
  stage.setTitle("Button demo");
  Button btn = new Button("button");
  btn.setLayoutX(80);
  btn.setLayoutY(80);
  Pane pane = new Pane();
  pane.getChildren().add(btn);
  Scene scene = new Scene(pane, 250, 200);
  stage.setScene(scene);
  stage.show();  
}

void draw() {
}

Output:
javafx_btn

Special thanks to @glv for valuable assistance.

2 Likes

The revised version of Processing4 is in the processing4/build/macos/work folder on my system. The processing4 folder was created by a github clone of the source code for the Processing4 editor. In order to continue to use the revised version it needs to be placed in the Applications folder on a Mac, thereby replacing the original distributed version.

And the adventure continues…

:)

Hi I would like to use processing 4.3 with JavaFX but I can’t use JavaFX for the problems you have already seen, you say to insert the following lines in JavaBuild.java in line 1086 “–add-modules”, “javafx.base,javafx.graphics,javafx.swing,javafx.controls”, and then compile again, but I can’t compile it, do you already have a compiled version? of processing 4.3 for mac? Greetings

I do not have an edited version of the current Processing editor (version 4.3). I have recently started using an M2 Mac and am no longer able to use JavaFX much to my dismay. I have not yet modified the current editor and do not know if that will fix the problems with the new mac. I do have the older version of Processing with the white icon (the version that was modified in this thread) which will run JavaFX on an Intel mac. I don’t know which operating system you are using, but I can post the old modified editor on Drop Box if you think you’ll be able to use it.

Thanks for your reply, I have a mac ventura intel, do you still use processing? I see that processing is not on its controls, I have only tried the G4P ones, they are limited., Greetings

I still use Processing on a daily basis. You should be able to use the modified version of the editor on your system if the gatekeeper doesn’t interfere. I’ll try to post the compiled app to DropBox and e-mail you the link. I did try to revise the current editor on the Processing website listed above, but they no longer allow editing of their code without committing the changes and sending a pull request, which I did. It would be nice if they made the changes to the code instead of us having to do it.

Ok, thanks and nice to use JavaFx on processing too, where you can also make projects with javafx controls, which ones do you use? greetings

I mainly use Swing components for my apps, but JavaFX controls are nice also and I would use them more if Processing provided better support.

I’ve never used Swing controls, is there a builder to create shapes?

You can build shapes with Processing:createShape() / Reference / Processing.org
Not sure about doing that with Swing.

Here’s a sample of a few Swing components if you want to see what they look like:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.io.*;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

int radius = 20;
Color dotColor = Color.RED;

GPanel panel;
JLabel label;
JTextArea txtArea;

final int _wndW = 700;
final int _wndH = 600;

void menuBar() {
  JMenuBar menuBar = new JMenuBar();
  JMenu menu = new JMenu("File");
  JMenuItem open = new JMenuItem("Open...");
  menu.add(open);
  menuBar.add(menu);
  menuBar.setToolTipText("menu bar");
  frame.setJMenuBar(menuBar);
  // **** Action **** //
  open.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent event) {
      if (event.getSource() == open) {
        JFileChooser fileChooser = new JFileChooser();
        int i = fileChooser.showOpenDialog(fileChooser);
        if (i == JFileChooser.APPROVE_OPTION) {
          File file = fileChooser.getSelectedFile();
          String filepath = file.getPath();
          try {
            BufferedReader buffer = new BufferedReader(new FileReader(filepath));
            String s1 = "", s2 = "";
            while ((s1 = buffer.readLine())!= null) {
              s2 += s1 + "\n";
            }
            txtArea.setText(s2);
            buffer.close();
          }
          catch (Exception ex) {
            ex.printStackTrace();
          }
        }
      }
    }
  }
  );
  println("EDT menuBar = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void textField() {
  JTextField txtFld = new JTextField("");
  txtFld.setBounds(50, 40, 280, 30);
  frame.add(txtFld);
  txtFld.setToolTipText("text field");
  // **** Action **** //
  txtFld.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      label.setText(txtFld.getText());
    }
  }
  );
  txtFld.repaint(); // To make it visible after export application.
  println("EDT txtFld = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void textArea() {
  txtArea = new JTextArea();
  JScrollPane scrlPane = new JScrollPane(txtArea);
  scrlPane.setBounds(30, 240, 380, 280);
  frame.add(scrlPane);
  txtArea.setEditable(true);
  txtArea.setToolTipText("text area");
  // txtArea.setFont(new Font("Menlo", Font.BOLD, 16));
  txtArea.setLineWrap(false);
  txtArea.setWrapStyleWord(true);
  txtArea.repaint();
  println("EDT txtArea = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void button() {
  JButton btn = new JButton("Press me.");
  btn.setBounds(50, 90, 100, 30);
  btn.setToolTipText("button");
  frame.add(btn);
  // **** Action **** //
  btn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      label.setText("Don't forget to check out the menubar.");
    }
  }
  );
  btn.repaint();
  println("EDT btn = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void sliderWithTicks() {
  JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 100, 25);
  slider.setBounds(30, 130, 250, 50);
  frame.add(slider);
  slider.setMinorTickSpacing(5);
  slider.setMajorTickSpacing(10);
  slider.setPaintTicks(true);
  slider.setPaintLabels(true);
  slider.setToolTipText("slider with ticks");
  println("EDT sliderWithTicks = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void slider_plain() {
  JSlider slider = new JSlider(JSlider.HORIZONTAL, 5, 70, 20);
  slider.setBounds(360, 10, 120, 30);
  slider.setToolTipText("slider no ticks");
  frame.add(slider);
  // **** Action **** //
  slider.addChangeListener(new ChangeListener() {
    void stateChanged(ChangeEvent changeEvent) {
      radius = slider.getValue();
      panel.repaint();
    }
  }
  );
  println("EDT slider = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void colorBtn() {
  JButton colorBtn = new JButton("Color");
  colorBtn.setBounds(480, 10, 80, 30);
  colorBtn.setToolTipText("button");
  frame.add(colorBtn);
  // **** Action **** //
  colorBtn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      dotColor = JColorChooser.showDialog(null, "Choose color", Color.RED);
      panel.repaint();
    }
  }
  );
  colorBtn.repaint();
  println("EDT colorBtn = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void checkBox() {
  JCheckBox chkBox = new JCheckBox("OK");
  chkBox.setBounds(150, 92, 60, 24);
  frame.add(chkBox);
  chkBox.setToolTipText("check box");
  chkBox.addItemListener(new ItemListener() {
    public void itemStateChanged(ItemEvent evt) {
      if (evt.getStateChange()==1) {
        label.setText("check box checked");
      } else {
        label.setText("check box unchecked");
      }
    }
  }
  );
  chkBox.repaint();
  println("EDT checkBox = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void label() {
  label = new JLabel();
  label.setHorizontalAlignment(JLabel.CENTER);
  label.setBounds(30, 10, 350, 30);
  label.setText("Label for output display.");
  label.setToolTipText("label");
  frame.add(label);
  label.repaint();
  println("EDT label = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void radioGrp() {
  ButtonGroup radioGrp = new ButtonGroup();
  JRadioButton radio1 = new JRadioButton("A");
  radio1.setSelected(true);
  JRadioButton radio2 = new JRadioButton("B");
  JRadioButton radio3 = new JRadioButton("C");
  radio1.setBounds(200, 90, 45, 30);
  radio2.setBounds(250, 90, 45, 30);
  radio3.setBounds(300, 90, 60, 30);
  radioGrp.add(radio1);
  radioGrp.add(radio2);
  radioGrp.add(radio3);
  radio1.setToolTipText("radio button group");
  frame.add(radio1);
  frame.add(radio2);
  frame.add(radio3);
  radio1.addItemListener( new ItemListener() {
    void itemStateChanged(ItemEvent evnt) {
      if (evnt.getStateChange()==1) {
        label.setText("Radio1 selected.");
      }
    }
  }
  );
  radio2.addItemListener( new ItemListener() {
    void itemStateChanged(ItemEvent evnt) {
      if (evnt.getStateChange()==1) {
        label.setText("Radio2 selected.");
      }
    }
  }
  );
  radio3.addItemListener( new ItemListener() {
    void itemStateChanged(ItemEvent evnt) {
      if (evnt.getStateChange()==1) {
        label.setText("Radio3 selected.");
      }
    }
  }
  );
  radio1.repaint(); // So they will show up when exported to app.
  radio2.repaint();
  radio3.repaint();
  println("EDT radioGrp = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void progressBar() {
  JProgressBar pbar = new JProgressBar();
  pbar.setBounds(50, 180, 250, 30);
  pbar.setValue(75);
  pbar.setToolTipText("progress bar");
  frame.add(pbar);
  pbar.repaint();
  println("EDT progressBar = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void comboBox() {
  String country[]={"France", "Australia", "U.S.A", "England", "New Zealand"};
  JComboBox comboBox = new JComboBox(country);
  comboBox.setBounds(550, 60, 130, 24);
  comboBox.setToolTipText("comboBox");
  frame.add(comboBox);
  println("EDT comboBox = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void listControl() {
  DefaultListModel<String> listArray = new DefaultListModel<>();
  listArray.addElement("Item 1");
  listArray.addElement("Item 2");
  listArray.addElement("Item 3");
  listArray.addElement("Item 4");
  JList<String> list = new JList<>(listArray);
  list.setBounds(560, 120, 100, 75);
  list.setToolTipText("list");
  frame.add(list);
  // **** Listener **** //
  list.addListSelectionListener( new ListSelectionListener() {
    void valueChanged(ListSelectionEvent evnt) {
      label.setText(list.getSelectedValue() + " was selected.");
    }
  }
  );
  list.repaint();
  println("EDT list = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void spinner() {
  //syntax: (init, min, max, step)
  SpinnerModel value = new SpinnerNumberModel(5, 0, 100, 1);
  JSpinner spinner = new JSpinner(value);
  spinner.setBounds(300, 130, 50, 30);
  spinner.setToolTipText("spinner");
  ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setEditable(false);
  frame.add(spinner);
  println("EDT spinner = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void tabbedPane() {
  JPanel panel1 = new JPanel();
  panel1.setBackground(Color.BLUE);
  JPanel panel2 = new JPanel();
  panel2.setBackground(Color.GREEN);
  JPanel panel3 = new JPanel();
  panel3.setBackground(Color.RED);
  JTabbedPane tabbedPane = new JTabbedPane();
  tabbedPane.setBounds(420, 230, 250, 150);
  tabbedPane.add("Tab 1", panel1);
  tabbedPane.add("Tab 2", panel2);
  tabbedPane.add("Tab 3", panel3);
  tabbedPane.setToolTipText("tabbed pane");
  frame.add(tabbedPane);
  // **** Action **** //
  tabbedPane.addChangeListener(new ChangeListener() {
    void stateChanged(ChangeEvent changeEvent) {
      int index = tabbedPane.getSelectedIndex();
      label.setText(tabbedPane.getTitleAt(index) + " was selected.");
    }
  }
  );
  tabbedPane.repaint();
  println("EDT tabbedPane = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void table() {
  String data[][] = {
    {"Boat", "Yellow", "Yes"},
    {"Car", "Magenta", "Yes"},
    {"House", "White", "No"}
  };
  String header[] = {"ITEM", "COLOR", "FOR SALE"};
  JTable table = new JTable(data, header);
  table.setSelectionForeground(Color.WHITE);
  table.setToolTipText("table");
  JScrollPane scrlPane = new JScrollPane(table);
  scrlPane.setBounds(430, 400, 220, 80);
  frame.add(scrlPane);
  // **** Listener **** //
  ListSelectionModel cellSelectionModel = table.getSelectionModel();
  cellSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  cellSelectionModel.addListSelectionListener(new ListSelectionListener() {
    void valueChanged(ListSelectionEvent evnt) {
      String selectedData = null;
      int[] selectedRow = table.getSelectedRows();
      int[] selectedColumns = table.getSelectedColumns();
      for (int i = 0; i < selectedRow.length; i++) {
        for (int j = 0; j < selectedColumns.length; j++) {
          selectedData = (String) table.getValueAt(selectedRow[i], selectedColumns[j]);
          label.setText(selectedData + " was selected.");
        }
      }
    }
  }
  );
  println("EDT table = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

class GPanel extends JPanel {
  GPanel() {
    setBounds(380, 60, 150, 150);
    setBorder(BorderFactory.createLineBorder(Color.BLACK));
  }

    void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor (dotColor);
    g.fillOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
  }
}

void panel() {
  panel = new GPanel();
  panel.setToolTipText("panel");
  frame.add(panel);
  panel.repaint();
  println("EDT panel = " + javax.swing.SwingUtilities.isEventDispatchThread());
}

void buildWnd() {
  panel();
  textArea();
  checkBox();
  radioGrp();
  label();
  progressBar();
  colorBtn();
  comboBox();
  listControl();
  spinner();
  tabbedPane();
  table();
  menuBar();
  textField();
  button();
  sliderWithTicks();
  slider_plain();
}

void setup() {
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(500, 300, _wndW, _wndH);
  frame.remove(canvas);
  javax.swing.SwingUtilities.invokeLater(() -> {
      buildWnd(); // Builds components on EventDispatchThread
  });
}

Thank you for your availability

Thank you for your availability

Glad to help. I may also be able to walk you through re-compiling the current editor after I try it myself. Thanks for your interest and participation.

if you modify the latest version of processing it’s welcome, if once compiled and everything works you could also put it in line with the whole forum, I apologize for my English. Greetings

Here’s the link for a revised Processing version 4.3 which will allow use of all the JavaFX modules:
https://www.dropbox.com/scl/fi/6w2736y8ydqb3wia099ww/Processing_4_3_jfx.zip?rlkey=skiejyo87ajpi85u4z5opjhdq&st=d9qjqagu&dl=0

This revision will also allow use of JavaFX on a M2 mac.

ok I’ll try it in the next few days and let you know. Best regards