javagradlejavafxinitialization

JavaFX won't initialize views


I've encountered with issue that JavaFX won't initialize any view or container by fx:id.

And it throws NullPointerException with reference at object that I've already initialized.

For an example I've tried to initialize using scene.lookup("#updatelist") method and set listener on updateList button and got this exception:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at javafx.graphics@21/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
    at javafx.graphics@21/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1135)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics@21/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:893)
    at javafx.graphics@21/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.control.Button.setOnMouseClicked(javafx.event.EventHandler)" because "this.updateList" is null
    at com.lifedrained.injector.injector@1/com.lifedrained.injector.injector.HelloApplication.start(HelloApplication.java:49)
    at javafx.graphics@21/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:839)
    at javafx.graphics@21/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:483)
    at javafx.graphics@21/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:456)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at javafx.graphics@21/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:455)
    at javafx.graphics@21/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at javafx.graphics@21/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics@21/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:185)
    ... 1 more
Exception running application com.lifedrained.injector.injector.HelloApplication

Here's my main class with initialization:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import javafx.util.Callback;

import java.io.IOException;
import java.util.Objects;

public class HelloApplication extends Application {
     Button updateList, inject, chooseDll, copyEx;
    private ListView<ProcessData> listView;
    private Label dllPath, exLog;
     Scene scene;
    private ListAdapter adapter;
    private ProcessGainer processGainer;
    private ProcessData selectedProcess;

    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("view.fxml"));

         scene = new Scene(fxmlLoader.load(), 600, 400);
        listView  =(ListView<ProcessData>) scene.lookup("#processlist");
        updateList = (Button) scene.lookup("#updatelist");
        inject = (Button) scene.lookup("#injectbtn");
        chooseDll = (Button) scene.lookup("#choose_dll_btn");
        copyEx = (Button) scene.lookup("#copy_btn");
        dllPath = (Label) scene.lookup("#path_lbl");

        stage.setTitle("Injector by Drained");
        stage.setScene(scene);
        stage.getIcons().add(new Image(Objects.requireNonNull(HelloApplication.class.getResourceAsStream("assets/syringe_icon.svg.png"))));
        stage.setResizable(false);
        stage.show();
        updateList.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                listView.setCellFactory(new Callback<ListView<ProcessData>, ListCell<ProcessData>>() {
                    @Override
                    public ListCell<ProcessData> call(ListView<ProcessData> param) {
                        param.setItems(FXCollections.observableArrayList(processGainer.listProcesses()));
                        return new ListAdapter(new ClickCallback() {
                            @Override
                            public void onSelect(ProcessData data) {
                                selectedProcess = data;
                                dllPath.setText("dll path: "+selectedProcess.getPath());
                            }
                        });
                    }
                });
            }
        });
    }

    public static void main(String[] args) {
        launch();
    }


}

in fxml file i have same fx:id as in scene.lookup() method. Here's my fxml file to proof that i correctly enter fx:id:

<VBox prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <MenuBar fx:id="menu_bar" visible="false" VBox.vgrow="NEVER">
            <menus>
                <Menu mnemonicParsing="false" text="File">
                    <items>
                        <MenuItem mnemonicParsing="false" text="New" />
                        <MenuItem mnemonicParsing="false" text="Open…" />
                        <Menu mnemonicParsing="false" text="Open Recent" />
                        <SeparatorMenuItem mnemonicParsing="false" />
                        <MenuItem mnemonicParsing="false" text="Close" />
                        <MenuItem mnemonicParsing="false" text="Save" />
                        <MenuItem mnemonicParsing="false" text="Save As…" />
                        <MenuItem mnemonicParsing="false" text="Revert" />
                        <SeparatorMenuItem mnemonicParsing="false" />
                        <MenuItem mnemonicParsing="false" text="Preferences…" />
                        <SeparatorMenuItem mnemonicParsing="false" />
                        <MenuItem mnemonicParsing="false" text="Quit" />
                    </items>
                </Menu>
                <Menu mnemonicParsing="false" text="Edit">
                    <items>
                        <MenuItem mnemonicParsing="false" text="Undo" />
                        <MenuItem mnemonicParsing="false" text="Redo" />
                        <SeparatorMenuItem mnemonicParsing="false" />
                        <MenuItem mnemonicParsing="false" text="Cut" />
                        <MenuItem mnemonicParsing="false" text="Copy" />
                        <MenuItem mnemonicParsing="false" text="Paste" />
                        <MenuItem mnemonicParsing="false" text="Delete" />
                        <SeparatorMenuItem mnemonicParsing="false" />
                        <MenuItem mnemonicParsing="false" text="Select All" />
                        <MenuItem mnemonicParsing="false" text="Unselect All" />
                    </items>
                </Menu>
                <Menu mnemonicParsing="false" text="Help">
                    <items>
                        <MenuItem mnemonicParsing="false" text="About MyHelloApp" />
                    </items>
                </Menu>
            </menus>
        </MenuBar>
        <SplitPane dividerPositions="0.33121657754010686, 0.6871657754010694" focusTraversable="true" VBox.vgrow="ALWAYS">
            <items>
                <ScrollPane prefHeight="350.0" prefWidth="198.0">
                    <content>
                        <AnchorPane id="Content" minHeight="-1.0" minWidth="-Infinity" prefHeight="350.0" prefWidth="194.0" style="-fx-background-color: E9E9E9;">
                            <children>
                                <ListView fx:id="processlist" layoutX="15.0" layoutY="28.0" prefHeight="200.0" prefWidth="150.0" />
                                <Button fx:id="updatelist" layoutX="54.0" layoutY="228.0" maxWidth="1.7976931348623157E308" minHeight="-Infinity" mnemonicParsing="false" text="Update list" />
                                <Label contentDisplay="CENTER" layoutX="11.0" layoutY="264.0" prefHeight="70.0" prefWidth="160.0" style="-fx-background-color: white; -fx-alignment: center;" text="Before reinjection: Make sure you've updated process list by pressing &quot;update list&quot; button !" textAlignment="CENTER" textOverrun="WORD_ELLIPSIS" wrapText="true" />
                                <Label contentDisplay="CENTER" layoutX="16.0" layoutY="4.0" prefHeight="20.0" prefWidth="150.0" style="-fx-background-color: white; -fx-alignment: center;" text="Process list" textAlignment="CENTER" textOverrun="WORD_ELLIPSIS" wrapText="true" />
                            </children>
                        </AnchorPane>
                    </content>
                </ScrollPane>
                <AnchorPane prefHeight="550.0" prefWidth="20.0" style="-fx-background-color: E9E9E9;">
                    <children>
                        <Button fx:id="choose_dll_btn" layoutX="47.0" layoutY="160.0" mnemonicParsing="false" prefHeight="30.0" prefWidth="120.0" text="Choose dll path" AnchorPane.topAnchor="240.0" />
                        <Button fx:id="injectbtn" layoutX="69.0" layoutY="254.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="70.0" text="Inject" AnchorPane.topAnchor="205.0" />
                        <ScrollPane layoutX="7.0" prefHeight="200.0" prefWidth="200.0">
                            <content>
                                <Label fx:id="path_lbl" alignment="TOP_LEFT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="&#10;" text="dll path: " textOverrun="CLIP" wrapText="true">
                                    <textFill>
                                        <Color red="0.624" green="0.624" blue="0.624" fx:id="x2" />
                                    </textFill>
                                </Label>
                            </content>
                        </ScrollPane>
                    </children>
                </AnchorPane>
                <AnchorPane style="-fx-background-color: E9E9E9;">
                    <children>
                        <ScrollPane fx:id="exception_pane" layoutX="14.0" layoutY="14.0" visible="false">
                            <content>
                                <Label fx:id="exception_lbl" alignment="TOP_LEFT" disable="true" prefHeight="271.0" prefWidth="158.0" style="&#10;" textAlignment="CENTER" visible="false" wrapText="false">
                                    <font>
                                        <Font size="11.0" fx:id="x7" />
                                    </font>
                                </Label>
                            </content>
                        </ScrollPane>
                        <Button fx:id="copy_btn" disable="true" layoutX="43.0" layoutY="301.0" mnemonicParsing="false" text="copy exception" visible="false" />
                    </children>
                </AnchorPane>
            </items>
        </SplitPane>
        <HBox id="HBox" alignment="CENTER_LEFT" spacing="5.0" VBox.vgrow="NEVER">
            <children>
                <Label maxHeight="1.7976931348623157E308" maxWidth="-1.0" text="Special created for Techno - " HBox.hgrow="ALWAYS">
                    <font>
                        <Font size="11.0" fx:id="x3" />
                    </font>
                    <textFill>
                        <Color red="0.625" green="0.625" blue="0.625" fx:id="x4" />
                    </textFill>
                </Label>
                <Pane prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="ALWAYS">
                    <children>
                        <Hyperlink layoutY="-4.0" text="https://www.youtube.com/@TechnoKVofficial/videos" />
                    </children>
                </Pane>
            </children>
            <padding>
                <Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
            </padding>
        </HBox>
    </children>
</VBox>

I've tried to use @FXML annotation on views in controller, but button and other views still have null value. By the way i've other JavaFX project with same view initialization way as in main class, but it's working perfectly fine. My JavaFX version is 21. And JDK that i used is 21. I'm using gradle build system with groovy DSL. Here's my build.gradle file:

plugins {
    id 'java'
    id 'application'
    id 'org.javamodularity.moduleplugin' version '1.8.12'
    id 'org.openjfx.javafxplugin' version '0.0.13'
    id 'org.beryx.jlink' version '2.25.0'
}

group 'com.lifedrained.injector.injector'
version '1'

repositories {
    mavenCentral()
}

ext {
    junitVersion = '5.10.0'
}

sourceCompatibility = '21'
targetCompatibility = '21'

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

application {
    mainModule = 'com.lifedrained.injector.injector'
    mainClass = 'com.lifedrained.injector.injector.HelloApplication'
}

javafx {
    version = '21'
    modules = ['javafx.controls', 'javafx.fxml', 'javafx.web', 'javafx.swing']
}

dependencies {
    implementation('com.dlsc.formsfx:formsfx-core:11.6.0') {
        exclude(group: 'org.openjfx')
    }
    implementation('net.synedra:validatorfx:0.4.0') {
        exclude(group: 'org.openjfx')
    }
    // https://mvnrepository.com/artifact/net.java.dev.jna/jna
    implementation 'net.java.dev.jna:jna:5.14.0'
    implementation 'net.java.dev.jna:jna-platform:5.14.0'
    implementation 'org.apache.logging.log4j:log4j-core:2.23.1'
    implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
    implementation('org.kordamp.ikonli:ikonli-javafx:12.3.1')
    implementation('org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0')
    implementation('eu.hansolo:tilesfx:11.48') {
        exclude(group: 'org.openjfx')
    }

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}

test {
    useJUnitPlatform()
}

jlink {
    imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'app'
    }
}

jlinkZip {
    group = 'distribution'
}

Solution

  • Well. The way that i'm currnetly using is bad. Credits to @James_D and @Jewelsea those people gave me clues about my way to access views.

    This just isn’t the way to access elements defined in the FXML file. Define a separate controller class in the usual way. -James_D

    Using dynamic lookups rather than FXML injection of reference is usually a bad idea. IDEs and the FXMLLoader can validate that FXML-injected references are OK and type-safe, but there is no such validation for the node lookup function. At the time you are performing your lookups, the scene has not been rendered, and CSS and layout passes have not been executed, which means the scene graph has not been fully initialized -Jewelsea

    I should use controller class with @FXML annotation to access views instead of using scene.lookup() as i pointed in question.

     public class Controller {
        @FXML
        private VBox vBox;
        @FXML
        private Pane pane;
        @FXML
        private Button updatelist;
        @FXML
        private void initialize() {
    
             //any logic here with views...
            //example: setting listener
            updatelist.setOnMouseClicked(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {
    
                }
            });
        }
    }