2

I'm really struggling to understand JavaFX controllers, my aim is to write to a TextArea to act as a log.

My code is below, but I want to be able to change values ETC from another class that I can call when needed. I have tried to create a controller class that extents Initializable but i cant get it to work. Could some one steer me in the correct direction?

I want to move the @FXML code at the bottom to another class and it update the Scene.

package application;
import javafx.event.ActionEvent;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("Root.fxml"));
            Scene scene = new Scene(root,504,325);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

    public Thread thread = new Thread(new webimporter());

    @FXML
    public Label runningLabel;

    @FXML 
    public TextArea txtArea;

    @FXML
    void runClick(ActionEvent event) throws IOException{
        changeLabelValue("Importer running...");
        thread.start();

    }   

    @FXML
    protected void stopClick(ActionEvent event){
        changeLabelValue("Importer stopped...");
        thread.interrupt();
    }           

    @FXML
    void changeLabelValue(String newText){
        runningLabel.setText(newText);
    }

    void changeTextAreaValue(String newText1){
        txtArea.setText(newText1);
    }
}
Lewis Morris
  • 1,916
  • 2
  • 28
  • 39

1 Answers1

6

Don't make the Application class a controller. It's a sin. There are other questions and answers which address this, but my search skills cannot find them at this time.

The reason it is a sin is:

  1. You are only supposed to have one Application instance, and, by default, the loader will make a new instance, so you end up with two application objects.
  2. Referencing the member objects is confusing, because the original launched application doesn't have the @FXML injected fields, but the loader created application instance does have @FXML inject fields.

Also, unrelated advice: Don't start trying to write multi-threaded code until you have the application at least working to the extent where it displays your UI.

A multi-threaded logger for JavaFX is in the answer to Most efficient way to log messages to JavaFX TextArea via threads with simple custom logging frameworks, though unfortunately it is not straight-forward in its implementation and comes with little documentation.


sample output

textlogger/Root.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="400.0" spacing="10.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="textlogger.ImportController">
   <children>
      <HBox alignment="BASELINE_LEFT" minHeight="-Infinity" minWidth="-Infinity" spacing="10.0">
         <children>
            <Button mnemonicParsing="false" onAction="#run" text="Run" />
            <Button mnemonicParsing="false" onAction="#stop" text="Stop" />
            <Label fx:id="runningLabel" />
         </children>
         <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </padding>
      </HBox>
      <TextArea fx:id="textArea" editable="false" prefHeight="200.0" prefWidth="200.0" />
   </children>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</VBox>

textlogger.ImportController.java

package textlogger;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;

import java.io.IOException;

public class ImportController {
    @FXML
    private Label runningLabel;

    @FXML
    private TextArea textArea;

    private WebImporter importer;

    @FXML
    void run(ActionEvent event) throws IOException {
        changeLabelValue("Importer running...");

        if (importer == null) {
            importer = new WebImporter(textArea);
            Thread thread = new Thread(
                    importer
            );
            thread.setDaemon(true);
            thread.start();
        }
    }

    @FXML
    void stop(ActionEvent event){
        changeLabelValue("Importer stopped...");
        if (importer != null) {
            importer.cancel();
            importer = null;
        }
    }           

    private void changeLabelValue(String newText){
        runningLabel.setText(newText);
    }

}

textlogger.WebImporter.java

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.TextArea;

import java.time.LocalTime;

public class WebImporter extends Task<Void> {

    private final TextArea textArea;

    public WebImporter(TextArea textArea) {
        this.textArea = textArea;
    }

    @Override
    protected Void call() throws Exception {
        try {
            while (!isCancelled()) {
                Thread.sleep(500);

                Platform.runLater(
                        () -> textArea.setText(
                                textArea.getText() + LocalTime.now() + "\n"
                        )
                );
            }
        } catch (InterruptedException e) {
            Thread.interrupted();
        }

        return null;
    }
}

textlogger.TextLoggingSample.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class TextLoggingSample extends Application {
    @Override
    public void start(Stage stage) {
        try {
            FXMLLoader loader = new FXMLLoader();
            Parent root = loader.load(
                getClass().getResourceAsStream(
                        "Root.fxml"
                )
            );

            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Community
  • 1
  • 1
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • I can get it to display and work fine but i cant work out how to write in the TextArea from the class i have created in the thread. – Lewis Morris Aug 18 '15 at 20:45
  • When you don't provide an [mcve](http://stackoverflow.com/help/mcve), you make me do more work to help you solve your issues. – jewelsea Aug 18 '15 at 21:02
  • When you cannot grasp a concept it is hard to fully explain it. – Lewis Morris Aug 18 '15 at 21:28
  • 1
    Yes, I know, asking good questions is harder than writing answers. Hopefully the info here might assist you. – jewelsea Aug 19 '15 at 07:07
  • Thank you jewelsea you have solved my problem. Learning Java with no one to mentor you is not an easy job. The book I bought "Beginning Programming with Java for DUMMIES" is only so good. It taught me to use the Application class as a controller. I will not do this in the future. I managed to work out how to specify a new controller in the scene builder. You have helped me learn how to parse the TextArea into the class which has helped me no end. I don't understand the "extends Task {" but I guess that polymorphism or one of the ways you can copy a class into your own. – Lewis Morris Aug 19 '15 at 18:59
  • If you could recommend any resources/ websites to help me on my journey I would appreciate it. Just so you know this is multi threaded as I have another thread that uses selenium to fill out a webform from a csv and prints a label out of our thermal printer and cleans up the used csv. The log thread is for the user to see which have been printed. – Lewis Morris Aug 19 '15 at 19:00