Asked  6 Months ago    Answers:  5   Viewed   56 times

I'm currently teaching myself JavaFX, and I've taken a simple example program that's hardcoded the view and am turning it into one that uses FXML (mostly so I can use SceneBuilder for building UIs). Rather than writing a separate controller class, I'm using the application class (so there's 1 Java file and 1 FXML file). I'm not using an initialize() method as it's a linear flow (display the UI, populate the fields, wait for input). The view pops up, but then the app errors out as none of the controls are mapped to the appropriate variables (so for @FXML TableView<...> table, table is null).

However, I put in an initialize() method for debugging, the controls are injected while in initialize(), and then return to null when initialize() exits.

So the question is, does JavaFX instantiate a new instance of the application class as a separate controller class? This would explain why the variable are going out of scope. Or is it something else (e.g. the controls are injected only when being called back from JavaFX actions)?

 Answers

85

The default behavior of the FXMLLoader is to create a new instance of the controller class and use that instance as the controller.

Specifically, the FXMLLoader does something like:

  • Read the root FXML element.
    • If the root FXML element has a fx:controller attribute, then
      • If a controller already exists, throw an exception, otherwise create an instance of the specified class1 and set that as the controller
  • Continue parsing the FXML file. If elements have a fx:id attribute, and a controller exists (by any mechanism), inject those fields into the controller. Similarly register event handlers as calls to methods in the controller instance.
  • Invoke initialize() on the controller, if a controller exists and it has such a method.

So, the question you asked:

Can application class be the controller class

Yes, but it's probably a terrible idea. If you simply specify the Application subclass as the controller class using fx:controller, then a second instance of the Application subclass is created, @FXML-annotated fields are injected on that second instance, and the initialize() method is invoked on that second instance. Obviously, the @FXML-fields are never initialized on the instance on which start(...) is invoked, and the initialize() method is never invoked on that instance.

The question you probably meant is:

Can the application class instance created at launch be used as the controller?

The answer to this is also yes, and, aside from very small demo programs you intend to immediately discard, it's also probably a very bad idea. You would do this by

public class MyApp extends Application {

    @FXML
    private Node someNode ;

    public void initialize() {
        // do something with someNode 
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml/file.fxml"));
        loader.setController(this);
        Parent root = loader.load();
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
}

Note that to use this code, your FXML file must not have a fx:controller attribute.

The problem with this is that you have no separation and no flexibility. (E.g. if you create a second instance of the view defined in your FXML file somewhere, you end up with a second Application subclass instance, which is at best counterintuitive (one application with two Application instances...).)

So I would advocate using a separate class for the controller in basically every case. The Application subclass should contain minimal code and should be used only for starting the application.

1 This step is actually a little more complex. If a class is specified in the fx:controller attribute, and no controller already exists, the FXMLLoader checks for a controllerFactory. If one exists, then the controller is set as the result of passing the specified Class to the controllerFactory's call() method, otherwise it is created by calling newInstance() on the specified class (effectively calling its no-argument constructor).

Tuesday, June 1, 2021
 
edorian
answered 6 Months ago
79

Your problem is in import in Controller class:

import java.awt.*;

Should be:

import javafx.scene.control.TextField;

The programs tries to convert java.awt.TextField to javafx.scene.control.TextField

I assume you'd like to use javafx, not AWT TextField

Note: don't trust the imports which IDE gives you :)

Wednesday, June 2, 2021
 
samrap
answered 6 Months ago
55

Update for the March 2018 Java Client Roadmap

I encourage readers interested in this question to review the following Oracle Whitepaper:

  • Java Client Roadmap Update

The above paper outlines the official Oracle position on related technologies (JavaFX/Swing/AWT/Applets/WebStart), the dates until which it intends to support those technologies and which of those technologies it intends to transition to open source development projects separate from the JDK and JRE.

Update for Java 9, Oct 2017

According to the Java 9 release notes:

Java Applet and WebStart functionality, including the Applet API, The Java plug-in, the Java Applet Viewer, JNLP and Java Web Start including the javaws tool are all deprecated in JDK 9 and will be removed in a future release.

So, for Java 9+, deployment of JavaFX in a browser using a Java plug-in will only be possible using deprecated technology. Oracle notes in another part of the Java 9 release notes:

Deprecates the Java Plug-in and associated applet technologies in Oracle's JDK 9 builds. While still available in JDK 9, these technologies will be considered for removal from the Oracle JDK and JRE in a future release. Applets and JavaFX applications embedded in a web page require the Java Plug-in to run. Consider rewriting these types of applications as Java Web Start or self-contained applications.

Note: Java Web Start isn't really an alternative as that is also deprecated (anyway, it's different from an application embedded and rendered within a browser page). Also note for those who wish to use Web Start, even though it has now been removed from Oracle JDK 11+, it is available as open source, info on this is at OpenWebStart.

As an alternative which offers similar functionality, consider something like jpro, which deploys JavaFX applications in a browser without a Java plugin (jpro is currently only in closed beta, so it is not a viable solution as of October 2017, but maybe someday in the future...).

For now, I would recommend that the best way to deploy JavaFX applications is as self-contained applications, running outside a browser.


Previous answer for Java 7 and 8

JavaFx - can it really be deployed in a browser?

Yes, JavaFX applications can be deployed so that they run inside a web browser hosted html web page.

The technology which allows this to occur is the Java Plugin. This plugin is currently a NPAPI based browser plugin solution. The Java Plugin is shipped with the Oracle Java 7 Runtime Standard Environment.

Not all browsers are supported, only those listed on the JavaFX Supported Configurations page.

How easy is it to deploy via web browser?

The easiest way to deploy a JavaFX project in a web browser is:

  1. Create a new JavaFX project in NetBeans.
  2. Develop a simple HelloWorld App.
  3. Follow the instructions for Deploying your first JavaFX Application.

This is not difficult (in my opinion).

Alternatively, follow instructions on the detailed reference for Deploying JavaFX Applications (or use 3rd party tools). For many projects, using a tool other than the NetBeans IDE to perform packaging is a better approach. However, it is generally easier to let the NetBeans IDE handle deployment packaging tasks for you.


What follows is not related to answering the original question, but provides some opinions and information requested in comments on this answer.

Some Advice

There are pitfalls to deploying Java in a browser. I encourage you to do your own Google research on the subject.

In my mind, based upon the current JavaFX 2.x deployment model, for most application types that require a browser as the primary runtime container, using JavaFX is not an appropriate solution.

Examine the deployment and runtime requirements for your application. Based on your requirements, and knowledge of the JavaFX browser based execution model, decide if a browser based deployment using JavaFX technology is the correct mechanism for your application.

A couple of potential difficulties for browser embedded JavaFX applications

Some important browsers (e.g. Internet Explorer 10, iOS Safari and soon Desktop Chrome) don't permit execution of plugins using the NPAPI currently used by the Java browser plugin => some of your target users may be unable to use your application.

With a browser embedded application, you don't have complete control over the runtime on which your application executes (browser + Java runtime + plugin interface) => an application which was working when first installed may stop functioning as expected after an update to these runtime components.

Deployment Alternatives

If a browser based deployment model is not appropriate for your application, there are other ways to deploy JavaFX applications (e.g. WebStart, stand-alone jars and self-contained applications).

Wednesday, June 9, 2021
 
aWebDeveloper
answered 6 Months ago
82

You can do something like this. As in your answer, you would either want to implement this as a singleton, or use a DI framework to inject a single instance wherever you need it:

public class ObservableResourceFactory {

    private ObjectProperty<ResourceBundle> resources = new SimpleObjectProperty<>();
    public ObjectProperty<ResourceBundle> resourcesProperty() {
        return resources ;
    }
    public final ResourceBundle getResources() {
        return resourcesProperty().get();
    }
    public final void setResources(ResourceBundle resources) {
        resourcesProperty().set(resources);
    }

    public StringBinding getStringBinding(String key) {
        return new StringBinding() {
            { bind(resourcesProperty()); }
            @Override
            public String computeValue() {
                return getResources().getString(key);
            }
        };
    }
}

Now you can do things like:

ObservableResourceFactory resourceFactory = .... ;

resourceBundle.setResources(...);

Label greetingLabel = new Label();
greetingLabel.textProperty().bind(resourceFactory.getStringBinding("greeting"));

And any time you update the resource with

resourceFactory.setResources(...);

will cause the label to update its text.

Here's an SSCCE (with apologies for the extremely ugly way of forcing a ResourceBundle into a single runnable class...)

import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.ResourceBundle;

import javafx.application.Application;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class ResourceBundleBindingExample extends Application {

    private static final String RESOURCE_NAME = Resources.class.getTypeName() ;

    private static final ObservableResourceFactory RESOURCE_FACTORY = new ObservableResourceFactory();

    static {
        RESOURCE_FACTORY.setResources(ResourceBundle.getBundle(RESOURCE_NAME));
    }

    @Override
    public void start(Stage primaryStage) {
        ComboBox<Locale> languageSelect = new ComboBox<>();
        languageSelect.getItems().addAll(Locale.ENGLISH, Locale.FRENCH);
        languageSelect.setValue(Locale.ENGLISH);
        languageSelect.setCellFactory(lv -> new LocaleCell());
        languageSelect.setButtonCell(new LocaleCell());

        languageSelect.valueProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue != null) {
                RESOURCE_FACTORY.setResources(ResourceBundle.getBundle(RESOURCE_NAME, newValue));
            }
        });

        Label label = new Label();
        label.textProperty().bind(RESOURCE_FACTORY.getStringBinding("greeting"));

        BorderPane root = new BorderPane(null, languageSelect, null, label, null);
        root.setPadding(new Insets(10));
        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static class LocaleCell extends ListCell<Locale> {
        @Override
        public void updateItem(Locale locale, boolean empty) {
            super.updateItem(locale, empty);
            if (empty) {
                setText(null);
            } else {
                setText(locale.getDisplayLanguage(locale));
            }
        }
    }

    public static class ObservableResourceFactory {

        private ObjectProperty<ResourceBundle> resources = new SimpleObjectProperty<>();
        public ObjectProperty<ResourceBundle> resourcesProperty() {
            return resources ;
        }
        public final ResourceBundle getResources() {
            return resourcesProperty().get();
        }
        public final void setResources(ResourceBundle resources) {
            resourcesProperty().set(resources);
        }

        public StringBinding getStringBinding(String key) {
            return new StringBinding() {
                { bind(resourcesProperty()); }
                @Override
                public String computeValue() {
                    return getResources().getString(key);
                }
            };
        }

    }

    public static class Resources extends ListResourceBundle {

        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                    {"greeting", "Hello"}
            };
        }

    }

    public static class Resources_fr extends ListResourceBundle {

        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                    {"greeting", "Bonjour"}
            };
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}
Wednesday, July 28, 2021
 
makadev
answered 4 Months ago
60

If you set the static methods setTopAnchor( child, value ), setBottomAnchor( ... ), setLeftAnchor( ... ), setRightAnchor( ... ) of class AnchorPane to 0.0, the child Node will get stretched to the full extend of the parent AnchorPane.

Documentation Link: AnchorPane

edit: in the documentation link you can also see how you can set these values in your java code.

FXML example:

<AnchorPane fx:id="mainContent" ...>
<StackPane fx:id="subPane" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" ></StackPane>
</AnchorPane>
Sunday, August 1, 2021
 
BaajiRao
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share