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