I realize there is an nearly identical question in title. The question does not seem to be relevant to my particular issues.
I'm using the JavaFX scene builder to create my UI, (which includes the TextArea in question). All I want to do is take the message I get from the server and post it into the text area. Through various println statements I have narrowed the problem to this. I have tried various solutions (for hours); coming here was a last resort.
The only other possible cause I can think of would be something going wrong with the multithreading, but can even begin to think of what.
public class IncomingReader implements Runnable
{
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
FXMLDocumentController.controller.chatBox
.appendText(message + "\n"); /////////PROBLEM HERE//////
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
FXML controller class (relevant line only):
#FXML protected TextArea chatBox;
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader());
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The line of FXML that defines the chatBox:
<TextArea id="ChatBox" fx:id="chatBox" focusTraversable="false" mouseTransparent="true" prefHeight="400.0" prefWidth="200.0" promptText="Chat:" wrapText="true" BorderPane.alignment="CENTER">
The resulting exception:
Exception in thread "Thread-4" java.lang.NullPointerException
at javachat.IncomingReader.run(IncomingReader.java:28)
at java.lang.Thread.run(Thread.java:745)
Note: this is really covered fully in Passing Parameters JavaFX FXML. However, the code you posted is so far from being structured properly to use the approaches there, that you probably need to see it specifically for your example. I would strongly recommend you read through that post and understand it, though.
Why you are seeing a NullPointerException:
FXMLDocumentController.controller refers to an instance of FXMLDocumentController which is not the same instance that was created for you by the FXMLLoader. Consequently, the chatBox field in FXMLDocumentController.controller was not initialized by the FXMLLoader.
What FXMLLoader does:
When you call one of the FXMLLoader.load(...) methods, it basically does the following:
Parses the FXML file (or stream)
If the root element of the FXML contains an fx:controller attribute, and no controller has been set by other means, it creates an instance of the class specified in that attribute
Creates the object hierarchy described by the FXML. If any of the objects defined in FXML have fx:id attributes, and a controller is associated with the FXMLLoader, it initializes #FXML annotated fields of the controller with the corresponding objects
Associates event handlers with the nodes, where defined
Returns the root object of the FXML object hierarchy
How to access the controller after loading the FXML
To access the controller, you must create an FXMLLoader instance instead of relying on the (evil) static FXMLLoader.load(URL) method. You can either pass the URL of the FXML resource into the FXMLLoader constructor, or call setLocation(...) on the FXMLLoader. Then, to load the FXML file, just call the no-argument load() method on the FXMLLoader. Once that is complete, you can access the controller by calling getController() on the FXMLLoader instance.
Other issues in your code
You cannot update the UI from a background thread: you must update it from the JavaFX Application Thread. As it stands (if you fix your NullPointerException issue), your code would throw an IllegalArgumentException in Java 8. In JavaFX 2.2 and earlier you would have to live with the possibility of bugs showing up at arbitrary times in the future. You can schedule code to be executed on the FX Application Thread by wrapping that code in a call to Platform.runLater().
Not quite so bad, but imo a bad design, is that you are exposing UI elements (the TextArea) outside of your FXML-controller pair. This becomes a real issue when your boss comes into your office and tells you he doesn't want the messages displayed in a TextArea any more, he wants them in a ListView. Since the TextArea is exposed, you have to trawl through all your code looking for any references to it. A better approach is to define a appendMessage(String) method in your controller, and access it. You might even want to factor all the data out into a separate model class, and pass references an instance of that class to both the controller and the reader class, but that is beyond the scope of this question.
I will refrain from complaining about your overuse of static stuff.. ;).
Here's the skeleton of one way to fix this code:
public class IncomingReader implements Runnable
{
private final FXMLDocumentController controller ;
public IncomingReader(FXMLDocumentController controller) {
this.controller = controller ;
}
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
final String msg = message ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.appendMessage(msg + "\n");
}
});
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
Controller:
public class FXMLDocumentController {
#FXML
private TextArea chatBox ;
public void appendMessage(String message) {
chatBox.appendText(message);
}
// other stuff as before...
}
Application:
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
FMXLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = (FXMLDocumentController) loader.getController();
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader(controller));
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I have two window controllers, both of which load an FXML file and show a screen. The first, GUIController is the main window, which spawns a second window, PackageBuilder, inside which the user inputs some data and it's stored.
The second window is spawned via a Button in the main window like so (FXML linked):
#FXML private void onNewPackage(ActionEvent e){
PackageBuilder pb = new PackageBuilder(owner); // Scene owner
}
I figured the second class cannot call the JavaFX Application launch method and the original Scene needs to be passed along. So I wrote this inside PackageBuilder:
public PackageBuilder(Scene owner) {
/* Removed redundant setup info */
fstart(owner);
}
private void fstart(owner) {
Stage window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader;
Scene scene;
try {
loader = new FXMLLoader(getClass().getResource("PackageBuilder.fxml"));
scene = new Scene(loader.load());
window.initOwner(owner);
window.setScene(scene);
window.showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
This all would be all good, if the initialize-method could be called. But it can't. So is there a way to for example initialize the elements of a ChoiceBox anywhere else?
I'm assuming there's a stupid mistake somewhere. There always is.
The second class with a ChoiceBox to be initialized can implement an interface:
public class PackageBuilder implements Initializable{
#Override
public void initialize(URL url, ResourceBundle rb){
/* Initialize things needed */
}
}
The method is called before the constructor of the class.
Alright, so hello everyone hope you're having a nice day.
So I'm pretty new to Java, however I know most of the basics, so not a superb noob at least; and I'm developing a JavaFX sample application, just for testing.
So I ran into a problem, I have a Main class in which I've created the interface, pretty simple, just a scene with 1 button.
Code for Main:
public class Main extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hi I'm a title");
//Initialize the Button Object
Button button = new Button();
button.setText("Bite me");
//Call the Handlers Class, with the name handlers
Handlers handlers = new Handlers();
/*
Here would go the code to pass the 'button' object to Handlers class
So then I can do whatever I need in there
*/
//The Button event, managed by the 'handle' method in Handlers class, that's why the 'handlers' in the
//parentheses
button.setOnAction(handlers);
//Just the scenery, not relevant
StackPane pane = new StackPane();
pane.getChildren().add(button);
Scene scene = new Scene(pane, 300,250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Code for Handlers:
public class Handlers implements EventHandler<ActionEvent> {
//Very sad try of a constructor, so as to pass the 'button' object from Main class
//In here, however I have no idea as to how to pass an Object type and how to receive it
public void Handlers(){
}
//The handle method to manage the 'button.setOnAction' event
//This is where I need the 'button' object to compare the source of the event
//to that specific button, so as to prevent that every single button does the same thing
//'button' object from Main class should go ".equals(button)"
#Override
public void handle(ActionEvent event) {
Main main = new Main();
if (event.getSource().equals()){
}
}
}
I'm adding an event to that button, so I've created a Handlers class to handle (dah) all the events on the Main class, the problem rises it's ugly head when I try to pass the 'button' object from Main to Handlers so then I can get it's source, so that every button doesn't do the same thing.
My question is: How can I pass the 'button' object from Main to Handlers? I know that I can use the Constructor, the only problem with that is that I still quite don't grasp the functionality of the Constructor, nor the correct use of the arguments.
I've read these forums for about 1 hour looking for the solution, and I'm pretty sure I've already encountered it, but due to my ignorance, can't understand it.
The clearest example of this would be:
Similar problem
Sorry for the utterly long post, but... I need help :(
Have a nice day :D
There are a couple of things wrong here that I recommend you learn a little more about before trying to fully tackle your application idea. But I can answer your question and give you some links to get your started.
Intro
here is a post about main(), typically you only have one for an application.
Part I
Look into class inheritance. In a JavaFX application, your main class will typically extend Application and your main() method typically calls Application.launch(), made available to your main class because of access control. Under the hood, launch() is calling Application.start(). Because you're inheriting all of the things from Application, you are required to implement (what is probably, but don't quote me on this) the abstract method start() to do all your UI work, see overriding methods and the related overloading. So, in start(), which is pretty much called in every JavaFX application, you then create a button Handler that can be assigned to multiple Buttons on your Stage.
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hi I'm a title");
EventHandler eventHandler = new BiteHandler("You've been bitten");
Button biteMeButton = new Button();
biteMeButton.setText("Bite me");
Button biteMeAgainButton = new Button();
biteMeAgainButton.setText("Bite me again");
biteMeButton.setOnAction(eventHandler)
biteMeAgainButton.setOnAction(eventHandler);
StackPane pane = new StackPane();
pane.getChildren().add(biteMeButton);
pane.getChildren().add(biteMeAgainButton);
Scene scene = new Scene(pane, 300,250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Part II
Here is the BiteHandler class. With this construct, check out polymorphism. You could have a BiteHandler, ChewHandler, and LickHandler that can all be stored in a List<EventHandler> due to polymorphism.
With the way EventHandler is setup, essentially the handle() method is what is called when the EventHandler is invoked, a la a button's on click action. So we need to give this thing something to do when the handler is invoked. Since I have no idea how to animate a mouth, I'm just gonna have this thing print to the passed message to the console. Here, I've left a comment about what a constructor is.
public class BiteHandler implements EventHandler<ActionEvent> {
private String message_;
/**
* Think of a constructor as any other method, however, it is special
* in that it is named after the class. Because it is named
* after the class, the compiler knows that the first thing
* that needs to be done when instantiating this class. A
* constructor is "automatic work" that "constructs" your object.
* In this class, when you instantiate it, a message is required.
* this message is stored in a "private class member" to be accessed
* later on when the bite() method is called
*/
public BiteHandler(String message) {
message_ = message;
}
#Override
public void handle(ActionEvent event) {
bite();
}
public void bite() {
System.out.println(message_);
}
}
Finale
On your application, you will have 2 buttons. When you click either button, the phrase "You've been bitten" will print to the console. This is because each button has the same BiteHandler. Pretty fk'ing lack luster for all that work but you've got plenty of homework to do to fully digest this answer
I have a JavaFX application with a TableView that I need to fill up with data once the application starts. I am starting the application in the following manner:
private LayoutController theController;
#Override
public void start(Stage primaryStage)
{
try {
FXMLLoader fxmlload = new FXMLLoader(getClass().getResource("Sample.fxml"));
BorderPane root = (BorderPane )fxmlload.load();
Scene scene = new Scene(root,640,480);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
theController = (LayoutController )fxmlload.getController();
primaryStage.setTitle("Title Application");
primaryStage.addEventHandler(WindowEvent.WINDOW_SHOWN,theController.windowStarted);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
In my controller, called SampleController, I have the TableView object so that it (initially) creates some columns once it is up:
#FXML Parent myRoot;
#FXML TableView datTable<DataClass>;
private Stage theStage;
public EventHandler<WindowEvent> windowStarted = event -> {
theStage = (Stage )myRoot.getScene().getWindow();
getData();
};
protected void getData()
{
dataTable.setEditable(false);
.
// Call a SOAP service to get the data
.
}
I had assumed that once the Stage's WINDOW_SHOWN event occurs, the controls are created and I can do things with them. That apparently isn't the case. Apparently, controls specified using FXML are actually created sometime after the main application window is created!
What happens is that when the windowStarted lambda is executed, the getData() method gets called, but apparently the dataTable was not created before the WINDOW_SHOWN event occurred. As a result, I get NullPointerException failures when I try to call any of dataTable's methods!
I need to catch when the dataTable actually gets created so that I can use its methods. Is there some way to do this?
Someone please advise...
Put your code for data download in method initialize which is called on controller after its root element has been completely processed or in other words after all FXML field are assigned.
#FXML
public void initialize() {
//Here!
}
In a JavaFX application, javafx.application.Application must be subclassed, and the inherited launch() method, although it's public, must be called from within this derived class, otherwise an exception is thrown. The launch() method then uses reflection to instantiate the derived class, making it difficult to set values for the class members without losing them when launching. All that appears totally unusual to me, and I was wondering why starting a JavaFX application is so complicated, if that kind of software design (design pattern?) has a name, or if it's just bad design?
EDIT:
To be more specific, I want to use the observer pattern, so my java application gets notified when a document was loaded, like this:
public class MyDocumentLoader extends Application
{
private ChangeListener<Worker.State> changeListener;
public void setChangeListener(ChangeListener<Worker.State> changeListener)
{
this.changeListener = changeListener;
}
...
public void loadDocument(String url)
{
webEngine.getLoadWorker().stateProperty().addListener(changeListener);
webEngine.load(url);
}
...
}
I need the callback member in several methods, and ideally I can have more than one instances of the class that loads documents, so I can set different ChangeListeners for different URLs.
My guess is that this design was motivated by the (vast) number of Swing applications that were incorrectly written, with the "primary" JFrames being instantiated and shown on the wrong thread (i.e. not on the AWT event dispatch thread). My guess is that so many Swing applications were incorrectly written that they had to defensively code the framework against the incorrect usage, and that they wanted to avoid this scenario with JavaFX.
Forcing (well, almost forcing, there are hack-arounds) an FX Application to start this way makes it much harder to write an application incorrectly in a similar way. The launch method (and the equivalent Oracle JVM startup process if you have an Application subclass without a main method and a call to launch) does quite a bit of boilerplate work: it starts the FX toolkit, instantiates the Application subclass and calls its init() method, then on the FX Application Thread it instantiates the primary Stage and passes it to the Application subclass's start(...) method. This then ensures everything is running on the correct thread.
You should basically consider the start(...) method in a JavaFX application as the replacement for the main(...) method in a "traditional" Java application, with the understanding it is invoked on the FX Application Thread.
My recommendation is that the Application subclass should be as minimal as possible; it should just delegate to something else to actually create the UI, and then should just place it in the primary stage and show it. Include a main method that does nothing other than call launch(...) as a fallback for non-JavaFX-aware JVMs. You should only have one instance of one Application subclass present in any JVM. This way your Application subclass has no class members to set, and so the issues you describe simply don't arise.
If you use FXML, this is actually fairly natural: the start(...) method essentially just delegates to the FXML-controller pair to do the real work. If you don't use FXML, create a separate class to do the actual layout, etc, and delegate to it. See this related question which gets at the same kind of idea.
Note also that your statement
the inherited launch() method, although it's public, must be called
from within this derived class
is not entirely accurate, as there is an overloaded form of the launch(...) method in which you can specify the application subclass. So, if you really need, you can just create a stub for starting the FX toolkit:
public class FXStarter extends Application {
#Override
public void start(Stage primaryStage) {
// no-op
}
}
Now you can do:
public class MyRegularApplication {
public static void main(String[] args) {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
// other stuff here...
}
}
Note that launch does not return until the FX toolkit shuts down, so it is imperative to put this call in another thread. This potentially creates race conditions, where you may try to do something needing the FX toolkit before launch(...) has actually initialized it, so you should probably guard against that:
public class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void init() {
latch.countDown();
}
#Override
public void start(Stage primaryStage) {
// no-op
}
}
and then
public class MyRegularApplication {
public static void main(String[] args) throws InterruptedException {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
// other stuff here...
}
}
SSCCE (I just used inner classes for everything so this is convenient to run, but in real life these would be standalone classes):
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BackgroundProcessDrivenApp {
public static void main(String[] args) throws InterruptedException {
Platform.setImplicitExit(false);
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
new MockProcessor().doStuff() ;
}
public static class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
#Override
public void init() {
latch.countDown();
}
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void start(Stage primaryStage) { }
}
public static class MockProcessor {
private final int numEvents = 10 ;
public void doStuff() {
Random rng = new Random();
try {
for (int event = 1 ; event <= numEvents; event++) {
// just sleep to mimic waiting for background service...
Thread.sleep(rng.nextInt(5000) + 5000);
String message = "Event " + event + " occurred" ;
Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
}
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} finally {
Platform.setImplicitExit(true);
}
}
}
public static class Messager {
private final String message ;
public Messager(String message) {
this.message = message ;
}
public void showMessageInNewWindow() {
Stage stage = new Stage();
Label label = new Label(message);
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
VBox root = new VBox(10, label, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 120);
stage.setScene(scene);
stage.setAlwaysOnTop(true);
stage.show();
}
}
}
JavaFX supports a great number of deployment and packaging strategies, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html, and having a standardized lifecycle entry- and exit-point simplifies supporting all these strategies.
If you are struggling to initialize your main application class, due to it being instanciated by the JavaFX launcher, your best option is to use the Application.init() and Application.stop() methods, as James_D points out.
I'm trying to create an Application, using JavaFX. But the JavaFX Application is not the Main entrance of the Application.
I'm using a Main-Class, a Controller-Class (which controlls everything), and other classes like the JavaFX Application
Main -(calls)-> Controller -(creates)-> JavaFX Application
After the JavaFX Application Object is created, the Controller calls a method, so the JavaFX Application Object has an instance of Controller
But this object is always null, as soon as I'm outside of the method-call.
Main
public class Main{
public static void main(String[] args){
Controller c = new Controller();
}
}
Controller
public class Controller{
private MyApplication app;
public Controller(){
app = new MyApplication(); //create Application
app.setController(this); //set Controller Object
app.startApplication(); //launch the application
}
}
MyApplication
public class MyApplication extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private Controller controller;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
initRootLayout();
}
public void setController(Controller con){
this.controller = con;
}
public void startApplication(String... args){
launch(args);
}
public void initRootLayout(){
System.out.println(controller==null); //returns true. But why?
try{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MyApplication.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
}
catch(IOException e){
e.printStackTrace();
}
}
}
How come, the Controller Instance within MyApplication is always null. The only time it's not null is within the call setController
Since JavaFX doesn't have system tray access, you basically need an AWT application to run in the system tray. I would then consider embedding the JavaFX aspects in Swing using a JFXPanel, and basically make it a Swing/AWT application with some JavaFX embedded.
Alternatively, you could launch everything from an Application subclass, and just bootstrap the AWT part in the start method, setting up the triggers to do the JavaFX stuff when needed. That feels uglier though.
Finally, your approach only really fails because you need to pass an object to the JavaFX application. If it makes sense to make that object a singleton, then you could just let the JavaFX application retrieve it, rather than passing the object to it.
I think your problem is that you are referencing different instances of your Controller class. After the call to launch(args) JavaFX creates its own private instance of your Application class. When you call setController you are setting the controller for a different instance of your class than what JavaFX is using. One way to fix this would be to make the controller variable and its setter static.