Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaFX extension #9313

Closed
maxandersen opened this issue May 14, 2020 · 49 comments · Fixed by quarkiverse/quarkiverse-devops#213
Closed

JavaFX extension #9313

maxandersen opened this issue May 14, 2020 · 49 comments · Fixed by quarkiverse/quarkiverse-devops#213
Labels
kind/extension-proposal Discuss and Propose new extensions

Comments

@maxandersen
Copy link
Member

Describe the extension
As discussed on Quarkus Insights with command mode we now can work with swing/javafx but we should have an extension to enable CDI injection in the UI eventqueues.

This one is for JavaFX and in this world Gluon Ignite seem to be the popular option.

Interested in this extension, please +1 via the emoji/reaction feature of GitHub (top right).

@maxandersen maxandersen added the kind/extension-proposal Discuss and Propose new extensions label May 14, 2020
@maxandersen maxandersen added the good first issue Good for newcomers label May 15, 2020
@geoand
Copy link
Contributor

geoand commented May 19, 2020

Seems that JavaFX does work in native mode: https://gluonhq.com/native-windows-applications-using-gluon-substrate-javafx-and-graalvm/

@maxandersen
Copy link
Member Author

Yes. This issue is about having cdi injection between javafx models / controllers.

@geoand
Copy link
Contributor

geoand commented May 19, 2020

👍

I would love to look into it, but I dont know when I'll be able to...

@geoand
Copy link
Contributor

geoand commented May 19, 2020

Although it might be really easy, won't really know until / unless I look into it

@geoand geoand removed the good first issue Good for newcomers label May 26, 2020
@geoand
Copy link
Contributor

geoand commented May 26, 2020

I removed the good first issue because of the following:

  1. We need to be able to have JavaFX controllers be CDI beans, otherwise the experience would be bad
  2. We should provide an easy way for a user to create a JavaFX application, perhaps an annotation like @QuarkusJavaFXApplication or a QuarkusJavaFXApplication interface. This would allow Quarkus to hide all the boilerplate code needed to make JavaFX and Quarkus play nicely together

@starksm64
Copy link
Contributor

Another issue is the use of FXML and how the FXMLLoader uses reflection on the generated CDI controller proxies if the controller is not using Dependent scoping. Ideally a CDI aware FXMLLoader would be produced that does not use reflection, but it did not look possible to do with a simple subclass of FXMLLoader given the current architecture.

@johanvos
Copy link

FXML strongly depends on reflection, but I am very open to discuss how we can avoid this (without breaking the top-level API). I agree CDI would be interesting here.

@jmartisk
Copy link
Contributor

I've been investigating this a little bit..
IIUC we'd also need to somehow integrate Gluon Substrate into our Maven plugin that builds native images, without it I suppose we won't be able to build native images.
And one more problem will be with dev mode, where the application potentially gets booted multiple times in the same JVM, which is something that JavaFX protects itself against

@gastaldi gastaldi added the area/quarkiverse This issue/PR is part of the Quarkiverse organization label Oct 23, 2020
@Christopher-Chianelli
Copy link
Contributor

For FXML, I think we could generate a custom implementation of FXMLLoader. We would need to generate custom classes for all fxml files, and the FXMLLoader simple construct instances of the generated class for the corresponding FXML files. There is the issue that FXMLLoader can load via an InputStream, so we would need to keep the entire fxml file in memory so we can map the input stream to an instance.

@pedrolopix
Copy link

I created a simple demo with minimal implementation, see https://github.com/pedrolopix/quarkus-javafx-example

@maxandersen
Copy link
Member Author

Nice @pedrolopix !

@maxandersen
Copy link
Member Author

Haven't been at laptop where I can run it but from reading code I assume this is only jvm mode and injection is not happening in the jfx application side - correct ?

@pedrolopix
Copy link

Yes it's correct, it's only jvm mode and no injection in jfx side. It's only a simple example, better than nothing :)

@maxandersen
Copy link
Member Author

Definitely! Very appreciated.

@gastaldi gastaldi removed the area/quarkiverse This issue/PR is part of the Quarkiverse organization label Feb 3, 2021
@nham-xuan-thanh
Copy link

Nice @pedrolopix ! But I can't debug, when click button it throw null pointer!
Any solution for debug, thanks!

@pedrolopix
Copy link

Just open the pom.xml in intellij as a project, then start CDIApplication.main as a debug, it should work.

@nham-xuan-thanh
Copy link

Perfect @pedrolopix ! Many thanks !!!

@ennishol
Copy link
Contributor

@pedrolopix based on your example (thanks!) I got it working for my application with latest quarkus on java 16 and jfx 17.0.0.1. But it works with jar only. (java -jar --illegal-access=permit target/quarkus-app/*.jar)
IDE and mvn quarkus:dev fail

2021-09-17 13:35:10,901 ERROR [com.acme.app.JavaFxApp] (JavaFX Application Thread) Do exit due to error: java.lang.InternalError: bouncer cannot be found
	at com.sun.javafx.reflect.MethodUtil.getTrampoline(MethodUtil.java:312)
	at com.sun.javafx.reflect.MethodUtil.<clinit>(MethodUtil.java:87)
	at com.sun.javafx.fxml.BeanAdapter.getStaticSetterMethod(BeanAdapter.java:924)
	at com.sun.javafx.fxml.BeanAdapter.put(BeanAdapter.java:583)
	at javafx.fxml.FXMLLoader$PropertyElement.set(FXMLLoader.java:1429)
	at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:801)
	at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2924)
	at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2639)
	at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2532)
	at com.acme.app.JavaFxApp.start(JavaFxApp.java:40)
	at com.acme.app.app.JavaFxApp_Observer_start_3258073454a8dd2db56a11c729e58aa8381e7513.notify(JavaFxApp_Observer_start_3258073454a8dd2db56a11c729e58aa8381e7513.zig:153)
	at io.quarkus.arc.impl.EventImpl$Notifier.notifyObservers(EventImpl.java:307)
	at io.quarkus.arc.impl.EventImpl$Notifier.notify(EventImpl.java:289)
	at io.quarkus.arc.impl.EventImpl.fire(EventImpl.java:70)
	at io.quarkus.arc.impl.BeanManagerImpl.fireEvent(BeanManagerImpl.java:122)
	at com.acme.app.app.FxApplication.start(FxApplication.java:16)
	at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
	at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
	at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
	at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
	at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
	at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:290)
	at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Class.getDeclaredMethod(String, java.lang.Class[])" because "<local1>" is null
	at com.sun.javafx.reflect.MethodUtil$1.run(MethodUtil.java:306)
	at com.sun.javafx.reflect.MethodUtil$1.run(MethodUtil.java:300)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:554)
	at com.sun.javafx.reflect.MethodUtil.getTrampoline(MethodUtil.java:299)
	... 24 more

Any ideas why does it work in jar mode but not with maven?

@pedrolopix
Copy link

Just bumped the demo project to latest versions and work fine

@ennishol
Copy link
Contributor

@pedrolopix I pulled your changes and get the same error when pressing 'Click' button with mvn quarkus:dev:

Exception in thread "JavaFX Application Thread" 
                                               java.lang.InternalError: bouncer cannot be found
...
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Class.getDeclaredMethod(String, java.lang.Class[])" because "<local1>" is null
	at com.sun.javafx.reflect.MethodUtil$1.run(MethodUtil.java:306)
	at com.sun.javafx.reflect.MethodUtil$1.run(MethodUtil.java:300)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:569)
	at com.sun.javafx.reflect.MethodUtil.getTrampoline(MethodUtil.java:299)
	... 49 more

@jdbranham
Copy link

I used a similar approach but required a vm option for the maven javafx plugin.
I don't know if it's the same root cause as @ennishol issue, but it might help.

pedrolopix/quarkus-javafx-example#3

@moamenhredeen
Copy link

moamenhredeen commented Oct 23, 2023

any update here ?
i tried to do something similar using micronaut.
the problem is (as you all already said) the FXMLLoader.
the FXMLLoader relay heavly on reflection.
i would love to help with writing quarkus extension for javafx.

SORRY for my bad english.

@maxandersen
Copy link
Member Author

@moamenhredeen I haven't had time to look deeper. If you want to help go try and make JavaFX based example and see how far you get. As mentioned above running JavaFX in quarkus app is all doable as it is "just java" - what needs solving is how we get Arc dependency injection happening in a JavaFX app.

@MarceloRuiz
Copy link

I would be great to have Quarkus able to run JavaFX applications.
I don't know if this could be relevant, but the following project got it working based on CDI version 2. It might help to give an idea of how the solution could be implemented:

https://github.com/cgiesche/javafx-cdi-bootstrap

@CodeSimcoe
Copy link

CodeSimcoe commented Nov 27, 2023

Hello,
based on pedrolopix demo, i've written a minimalistic (yet working) quarkus extension that allows quarkus to run javafx app and is up to date (java 21 / quarkus 3.5.3)

https://github.com/CodeSimcoe/quarkus-fx-extension

Read the readme file to see how to use it in your application.
Live reload is still problematic as it runs the launcher again which is forbidden, i'll be studying this in the future.

@maxandersen
Copy link
Member Author

Very cool. Will give it a try!

@CodeSimcoe
Copy link

Very cool. Will give it a try!

Have you managed to give it a try ?
Any feedback ?

@maxandersen
Copy link
Member Author

completely fell through the cracks - taking a look now!

@maxandersen
Copy link
Member Author

I took a look:

  1. the project did not build unless I fixed parent version, submitted PR at fix parent ref. add gitignore and remove target content CodeSimcoe/quarkus-fx-extension#1

  2. my javafx skills are rusty so didn't manage to make app work with it. do you have example app that is setup and working (with working pom.xml) ?

  3. does it actually work with injection in larger javafx constructs?

  4. the license is currently GPL 3.0 - that isn't a license that we can accept for quarkus/quarkiverse extensions. if you want to contribute it there I encourage you to update/change it to ASL as everything else.

@CodeSimcoe
Copy link

CodeSimcoe commented Jan 25, 2024

Hello !

  1. Thanks for this, PR merged

  2. You can check https://github.com/CodeSimcoe/quarkus-using-fx-extension : start the app with mvn quarkus:dev
    image

  3. Do you have any specific use cases in mind ? I don't why it could not... but let's figure it out.

  4. Updated to Apache 2.0

@hallvard
Copy link

hallvard commented Jan 26, 2024

I tried the extension and it works well. I'm working on a chatbot using langChain4j and coded a simple gui for browsing the internal data. I can use DI as expected to get hold of all chatbot-related objects. There's two general things I miss:

  • a custom scope that corresponds to a container/pane, e.g. imagine a tabbed application where each tab is a separate scope
  • an annotation to simplify rigging the application, now you need both a quarkus application class and the class that observes the PrimaryStage event. I would be nice if I only needed the latter, with an annotation that provides the former.

@hallvard
Copy link

hallvard commented Jan 26, 2024

BTW, I also tried it with jbang, here's a self-contained jbang class that worked for me:

//usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS io.quarkus.platform:quarkus-bom:3.6.6@pom
//DEPS org.openjfx:javafx-controls:21.0.1
//DEPS org.openjfx:javafx-fxml:21.0.1
//DEPS com.codesimcoe:quarkus-fx-extension:1.0.0-SNAPSHOT

//JAVAC_OPTIONS -parameters
//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager

import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.Pane;
import javafx.scene.Scene;
import javafx.stage.Stage;

import com.codesimcoe.quarkus.fx.extension.runtime.FxApplication;
import com.codesimcoe.quarkus.fx.extension.runtime.PrimaryStage;

public class fxgui {

    // run with jbang

    public static void main(String[] args) throws Exception {
        Quarkus.run(runner.class, args);
    }

    public static class runner implements QuarkusApplication {

        @Override
        public int run(String... args) throws Exception {
            Application.launch(FxApplication.class, args);
            return 0;
        }
    }

    public static class FxApplicationDelegate {

        private static String fxml = """
        <?xml version="1.0" encoding="UTF-8"?>

        <?import javafx.scene.control.ComboBox?>
        <?import javafx.scene.control.ListView?>
        <?import javafx.scene.layout.VBox?>
        <?import javafx.scene.layout.HBox?>

        <VBox prefHeight="800" prefWidth="1000" xmlns:fx="http://javafx.com/fxml" fx:controller="%s">
            <ComboBox fx:id="keySelector" onAction="#handleKeySelected"/>
            <ListView fx:id="valuesList"/>
        </VBox>
        """.formatted(FxController.class.getName());

        @Inject
        FXMLLoader fxmlLoader;
    
        public void start(@Observes @PrimaryStage Stage stage) throws java.io.IOException {
            Pane fxmlParent = this.fxmlLoader.load(new ByteArrayInputStream(fxml.getBytes()));

            Scene scene = new Scene(fxmlParent);
            stage.setScene(scene);
            stage.show();
        }
    }

    @Dependent
    public static class FxController {

        @FXML
        ComboBox<String> keySelector;

        @FXML
        ListView<String> valuesList;

        Map<String, Collection<String>> map = Map.of("Norway", List.of("NTNU", "UiO"), "Sweden", List.of("KTH", "Uppsala"));

        @FXML
        void initialize() {
            keySelector.getItems().addAll(map.keySet());
            Platform.runLater(() -> keySelector.setValue("Norway"));
        }

        @FXML
        void handleKeySelected() {
            valuesList.getItems().clear();
            valuesList.getItems().addAll(map.get(keySelector.getSelectionModel().getSelectedItem()));
        }
    }
}

@hallvard
Copy link

P.S. It would be super cool to have an FXML string template processor that simplified loading embedded FXML snippets like the one above.

@CodeSimcoe
Copy link

CodeSimcoe commented Jan 26, 2024

I tried the extension and it works well. I'm working on a chatbot using langChain4j and coded a simple gui for browsing the internal data. I can use DI as expected to get hold of all chatbot-related objects. There's two general things I miss:

* a custom scope that corresponds to a container/pane, e.g. imagine a tabbed application where each tab is a separate scope

* an annotation to simplify rigging the application, now you need both a quarkus application class and the class that observes the PrimaryStage event. I would be nice if I only needed the latter, with an annotation that provides the former.

I'll think/work on that when I get the opportunity to.
I'll keep you updated.

Thanks !

Clement

@CodeSimcoe
Copy link

In branch https://github.com/CodeSimcoe/quarkus-fx-extension/tree/fx_startup, the application is automatically started (no more need to redefine the Quarkus main method and call the JavaFx Application::launch.

@maxandersen
Copy link
Member Author

If You are interested @CodeSimcoe - I Think it would be worth we create a quarkiverse repo to iterate on it.

Is Quarkus-fx the best name ?

/cc @gastaldi

@CodeSimcoe
Copy link

CodeSimcoe commented Jan 26, 2024

Let's do this @maxandersen

@gastaldi
Copy link
Contributor

gastaldi commented Jan 26, 2024

Is Quarkus-fx the best name ?

I'd prefer quarkus-openjfx or quarkus-javafx, as it enables using the org.openjfx:javafx-fxml dependency in Quarkus. But I am not opposed to being quarkus-fx if we all agree.

Thoughts?

@CodeSimcoe
Copy link

All sound good to me

@maxandersen
Copy link
Member Author

taking a look at https://github.com/mhrimaz/AwesomeJavaFX I'll say its majority FX so io.quarkiverse.quarkus-fx works - and yeah it will use openjfx:javafx-* dependencies

@gastaldi
Copy link
Contributor

gastaldi commented Jan 27, 2024

Repository https://github.com/quarkiverse/quarkus-fx created, welcome aboard @CodeSimcoe!

Here are the next steps: https://hub.quarkiverse.io/checklist/#after-the-repository-is-created

@CodeSimcoe
Copy link

As a start, I've pushed code from my repo, with package update.

@CodeSimcoe
Copy link

@hallvard

* an annotation to simplify rigging the application, now you need both a quarkus application class and the class that observes the PrimaryStage event. I would be nice if I only needed the latter, with an annotation that provides the former.

I've added a "auto-launch" build time property, leveraging the need for a CDIApplication class.

Please check https://github.com/quarkiverse/quarkus-fx
Any feedback is appreciated.

Regards,
Clement

@maxandersen
Copy link
Member Author

Is a property really needed ? Wouldn't a @quarkusmain be sufficient similar how done for command mode/picocli?

@CodeSimcoe
Copy link

Is a property really needed ? Wouldn't a @quarkusmain be sufficient similar how done for command mode/picocli?

Can you give me some more details on what you could expect ?
I'll be glad to improve

@CodeSimcoe
Copy link

CodeSimcoe commented Jan 30, 2024

Is a property really needed ? Wouldn't a @quarkusmain be sufficient similar how done for command mode/picocli?

@maxandersen
Just had a look into quarkus-picocli.
I'll make a code proposition soon.

Thanks for the tip.

@maxandersen
Copy link
Member Author

Excellent :)

@CodeSimcoe
Copy link

Excellent :)

@maxandersen
quarkiverse/quarkus-fx#5

@maxandersen
Copy link
Member Author

@CodeSimcoe good - lets open issues on the quarkus-fx repo for future updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/extension-proposal Discuss and Propose new extensions
Projects
None yet
Development

Successfully merging a pull request may close this issue.