JavaFX controller class not working

2019-01-01 13:38发布

问题:

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);
    }
}

回答1:

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

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);
    }
}