-3

SOLUTION FOUND

This code works with fxml.files and rest of the code I used was from Slaws's answer.

Main.java:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    final static ExecutorService threadPool = Executors.newFixedThreadPool(3, r -> {
        var t = new Thread(r);
        t.setDaemon(true);
        return t;
    });

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

    @Override
    public void start(Stage stage) throws Exception {

        Parent root = FXMLLoader.load(getClass().getResource("mainScreen.fxml"));
        String css = this.getClass().getResource("style.css").toExternalForm();
        Scene scene = new Scene(root);
        scene.getStylesheets().add(css);
        stage.setMinHeight(800);
        stage.setMinWidth(1200);
        stage.setScene(scene);
        stage.show();
        
    }

    @Override
    public void stop() {
        threadPool.shutdownNow();
    }
}

Controller.java

import java.io.IOException;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.text.Font;
import javafx.application.Platform;

public class Controller {
    
    @FXML
    private TextArea console;
    @FXML
    private TextField inputPane;
    
    public void initialize() throws IOException{

        wireInputAndOutput(inputPane, console);
        startConsoleTask();

    }

    static void wireInputAndOutput(TextField input, TextArea output) throws IOException {
        var inputConsumer = StreamWriter.redirectStandardIn(Main.threadPool);
        StreamReader.redirectStandardOut(new BufferedTextAreaAppender(output), Main.threadPool);

        input.setOnAction(e -> {
            e.consume();
            var text = input.textProperty().getValueSafe() + "\n";
            output.appendText(text);
            inputConsumer.accept(text);
            input.clear();
        });
    }
    private void startConsoleTask() {
        Main.threadPool.execute(new ConsoleTask(Platform::exit));
    }
}


Edit 2

the problem now is that the ConsoleTask doesn't run when I put it inside controller.java (this is the problem atm.). It worked when I copy/pasted Slaw's main.java but I wanted to use fxml file so I tried this and it doesn't work anymore.

test.fxml:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>


<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
   <children>
      <TextArea fx:id="console" editable="false" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
         <VBox.margin>
            <Insets bottom="20.0" />
         </VBox.margin>
      </TextArea>
      <TextField fx:id="inputPane" prefHeight="40.0" VBox.vgrow="ALWAYS" />
   </children>
   <padding>
      <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
   </padding>
</VBox>

ConsoleTask.java:

import java.util.NoSuchElementException;
import java.util.Scanner;

class ConsoleTask implements Runnable {

    private final static Scanner scanner = new Scanner(System.in);
    private final Runnable onExit;

    ConsoleTask(Runnable onExit) {
        this.onExit = onExit;
    }

    @Override
    public void run() {
        System.out.println("WELCOME TO CONSOLE APP!");

        boolean running = true;
        while (running && !Thread.interrupted()) {
            printOptions();

            int choice = readInt("Choose option: ", 1, 20);
            switch (choice) {
                case 1 -> doCheckIfLeapYear();
                case 2 -> doCheckIfPrime();
                case 3 -> doPrintStringBackwards();
                case 20 -> running = false;
                default -> System.out.println("Unknown option!");
            }
            
            System.out.println("\nPlease wait...");
            wait(6000);
        }
        
        onExit.run();
    }

    private void printOptions() {
        System.out.println();
        System.out.println("Options");
        System.out.println("-------");
        System.out.println("  1) Test Leap Year");
        System.out.println("  2) Test Prime");
        System.out.println("  3) Print String backwards");
        System.out.println("  20) Exit");
        System.out.println();
    }

    private int readInt(String prompt, int min, int max) {
        while (true) {
            System.out.print(prompt);

            try {
                int i = Integer.parseInt(scanner.nextLine());
                if (i >= min && i <= max) {
                    return i;
                }
            } catch (NumberFormatException | NoSuchElementException ignored) {
            }
            System.out.printf("Please enter an integer between [%,d, %,d]%n", min, max);
        }
    }

    private void doCheckIfLeapYear() {
        System.out.println();
        int year = readInt("Enter year: ", 0, 1_000_000);
        if (year % 4 == 0 || (year % 100 == 0 && year % 400 != 0)) {
            System.out.printf("The year %d is a leap year.%n", year);
        } else {
            System.out.printf("The year %d is NOT a leap year.%n", year);
        }
    }

    private void doCheckIfPrime() {
        System.out.println();
        int limit = readInt("Enter an Integer: ", 1, Integer.MAX_VALUE);

        while (limit <= 1){
            System.out.print("Wrong number! the number should be higher or equal to 2: ");
            limit = scanner.nextInt();
        }

        if(isPrime(limit)){
            System.out.println(limit+" is a prime number.");
        }else{
            System.out.println(limit+" is not a prime number.");
        }
    }
    private static boolean isPrime(int n){
        if(n <= 1){
            return false;
        }
        for(int i = 2; i <= Math.sqrt(n); i++){
            if(n % i == 0){
            return false;
            }
        }
        return true;   
    }

    private static void doPrintStringBackwards(){
        System.out.println();
        System.out.print("\nEnter a word or a sentence: ");
        String answer = scanner.nextLine();

        //StackOverflow version: (simple) 
        System.out.println("\n"+new StringBuilder(answer).reverse().toString());
        
        //Lecture version:
        /*for (int i = answer.length()-1;i>=0;i--){
            System.out.print(answer.charAt(i));
        }*/
    }
    private static void wait(int ms){
        try {
            Thread.sleep(ms);
        }
        catch(InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
    }
}

Edit 1

The result I want to achieve with this is:

to use the text field in the gui as an input to the java console program but I have no idea how to achieve that result.

Original

I'm new to java/javaFX and I'm trying to make a console gui app. The problem is that I can't find a way to redirect system.in to JavaFX text field.

I have tried these solutions but I didn't get them working:

this and this.

pictures of some of the code:

the methods

  • 3
    Perhaps I missed it, but where is your attempt to redirect standard input to a `TextField`? What went wrong with that attempt? Please provide a [mre]. – Slaw Oct 06 '22 at 17:15
  • @Slaw it what at "where the problem starts" but that was my latest attempt as in I tried to just send the input straight to the method instead of redirecting it and as you might guess it didn't work. I've also looked into inputstream but I got confused by how to use it. so now I'm hoping I get help with this problem since I have no clue. – UnseenSnick Oct 06 '22 at 17:27
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Oct 06 '22 at 17:45
  • But nowhere in the code between the "where the problem starts" and "where the problem ends" comments do you do anything resembling redirecting standard input to the text field. What you do is read the text from the text field and convert it to an int, and then pass it to a method call, which is all completely normal JavaFX work. – James_D Oct 06 '22 at 17:46
  • But what actually are you trying to do here? Normally, standard input is a stream of text that the user types at the keyboard into the console/terminal. It would be redundant to pipe standard input in this context to a text field; you may as well have the user type into the text field. If you are piping the output of one program into your Java program (the other context for standard input), you can just read the value from standard input and use it (there's no obvious need for the text field). What is the actual use case for what you say you are trying to do? – James_D Oct 06 '22 at 17:49
  • @James_D well the idea was to use the text field in the gui as an input to the java console program but as you can tell I have no idea how to achieve that result. – UnseenSnick Oct 06 '22 at 18:12
  • What goes wrong? What actually happens when you execute the code you posted? – James_D Oct 06 '22 at 18:35
  • @James_D when I run the program without input the program works and runs the function in task.java but without input from the user the task can never get past the first line. So that's why I need input field. btw the task.java file is just an example. – UnseenSnick Oct 06 '22 at 19:13
  • You should refactor `Task.java` (aside: please use proper naming conventions) so that getting the user input is separated from the functionality. I.e. you should have `public boolean isLeapYear(int year)`, `public String isLeapYearDialog(int year)` and `public void isLeapYearDialog()` (the latter using standard input for input and output and delegating to the second method). That way you can use the logic part from the UI without hacking standard input and output. – James_D Oct 06 '22 at 20:15
  • @James_D could you show an example of that? since I'm new to java it's hard to understand what I should do and btw did you check out the methods picture? since that's what actually should be in the task.java. – UnseenSnick Oct 06 '22 at 20:52

2 Answers2

2

Refactor Task.java to separate the logic from the user input/output. I'd also recommend not naming the class something that clashes with a standard class from the JavaFX API (though you can obviously get around this with imports and explicit use of fully-qualified class names.)

Something like:

import java.util.Scanner;

public class LeapYearTask {
    
    private static final Scanner input = new Scanner(System.in);

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

    public static void leapYearDialog(){
        System.out.print("\nWrite a year: ");
        int year = input.nextInt();
        System.out.println(leapYearDialog(year));
    }

    public static String leapYearDialog(int year) { 
        
        String standardMsg ="This is leapYearDialog method:";


        if (isLeapYear(year)){
            return "\n"+standardMsg+"\n"+year+" is a leap year.";
        }else{
            return "\n"+standardMsg+"\n"+year+" is not a leap year.";
        }
    }

    public static boolean isLeapYear(int year) {
        boolean divideByFour = year % 4 ==0;
        boolean divideByHundred = year % 100 ==0;
        boolean divideByFourHundred = year % 400 ==0;
        return divideByFour && (!divideByHundred || divideByFourHundred);
    }
}

(You may even want to refactor further, separating the logical isLeapYear() into a different class to the methods with the textual interaction.)

Then your JavaFX program only needs to interact with the logical part, as is natural. There is now no need to hack the standard input and standard output.

public class Controller {
    
    @FXML
    private TextArea console;
    @FXML
    private TextField inputField;
    

    
    //somehow get input to work with task.leapYearDialog()
    public void runCommand(ActionEvent event) {

        int year = Integer.parseInt(inputField.getText());
        // I'm assuming you were using a background thread because this is a
        // stand-in for a long-running process.
        Task<String> leapYearTask = new Task<>() {
            @Override
            protected String call() throws Exception {
                return LeapYearTask.leapYearDialog(year);
            }
        };
        leapYearTask.setOnSucceeded(e -> console.append(leapYearTask.getValue()));
        new Thread(leapYearTask).start();
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • okay, so how would you do "the methods" function in the picture? since that is the actual task I want to run in the gui. - I can provide a picture of what the functions do in that picture as well if you need it. – UnseenSnick Oct 06 '22 at 21:13
  • 1
    @UnseenSnick Don’t post pictures of text. (Seriously; why do you think that’s a good idea?) Post the code as text, properly formatted. – James_D Oct 06 '22 at 22:00
  • there I put the actual task in edit 3 – UnseenSnick Oct 06 '22 at 22:21
  • this part didn't output: **System.out.print("\nWrite a year: ");** to the text area, is there a reason for that?@James_D – UnseenSnick Oct 06 '22 at 22:46
  • @UnseenSnick Yes: there’s no code that displays that text in the text area. Why would you want it to be there anyway? – James_D Oct 06 '22 at 23:35
  • well because of something called user feedback, so the user knows what to do.@James_D – UnseenSnick Oct 07 '22 at 00:12
  • But why would you put that in the `TextArea`? Put that in a label next to the text field. – James_D Oct 07 '22 at 10:29
  • @UnseenSnick What I'm driving at here, is that if you want to write a GUI application, you should write a GUI application. Slaw's answer shows how to do what you explicitly ask, but really all that does is recreate an interface which is already available to the user via the command line or terminal. Ultimately, that isn't really writing a GUI program; you're using JavaFX simply to recreate your terminal program. Instead, I would recommend using the correct UI tools; e.g. to choose something, use a combo box. When the user makes a choice, display the appropriate controls for them to input data. – James_D Oct 07 '22 at 14:36
  • if I wasn't clear enough of what I was trying to do then let me spell it out for you **I wanted to recreate the terminal inside a gui** and with the answer from Slaw now I can do it without fxml file but I want to recreate the terminal using an **fxml file** so that part I haven't figured out yet, do you have an idea of how I could do that? @James_D – UnseenSnick Oct 07 '22 at 14:53
2

Preface: Given your "console application" doesn't involve forking any processes or performing any "actual" I/O, you may want to redesign your application to use the TextField and TextArea directly, or at least more directly than you're currently trying to do. Using System.out and System.in adds a layer of unnecessary indirection and makes everything more complex. Also, redirecting standard out and standard in affects the whole process, which may not be desirable.


General Idea

Reading characters from standard in and writing characters to standard out will involve decoding and encoding those characters. You'll want to use classes which already implement this for you, such as BufferedReader and BufferedWriter. And since you essentially want to communicate between threads via streams, you can have the underlying streams be instances of PipedInputStream and PipedOutputStream.

Also, since most console-based applications can print prompting text without a new line at the end, you have to "read from" standard out by reading each individual character rather than line-by-line. This could involve a lot of calls to Platform#runLater(Runnable), potentially overwhelming the FX thread, which means you should probably buffer this output.

The example below shows a proof-of-concept for this. It uses UTF-8 for the character encoding. There's a GIF of the example code running at the end of this answer.

Here's a brief overview of the classes:

Class Description
Main The JavaFX application class. Creates the UI and otherwise initializes and starts the application.
StreamWriter Responsible for writing the user's input so that it can later be read by System.in. Also provides the code for redirecting System.in so that ultimately the input can come from a TextField.
StreamReader Responsible for reading the output from System.out character-by-character. Also provides the code for redirecting System.out so that ultimately it can be appended to a TextArea.
BufferedTextAreaAppender Responsible for taking the stream of characters from StreamReader and appending them to the TextArea in batches.
ConsoleTask The "console application". Uses System.in and System.out to interact with the user.

Example

Note that I have the TextArea set up to automatically scroll to the end when the text changes. Unfortunately, this doesn't seem to work the first time text goes "off the screen". But after that first time it seems to work.

Main.java

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class Main extends Application {

    private final ExecutorService threadPool = Executors.newFixedThreadPool(3, r -> {
        var t = new Thread(r);
        t.setDaemon(true);
        return t;
    });

    @Override
    public void start(Stage primaryStage) throws Exception {
        var textField = new TextField();
        textField.setFont(Font.font("Monospaced", 13));

        var textArea = new TextArea();
        textArea.setFont(textField.getFont());
        textArea.setEditable(false);
        textArea.setFocusTraversable(false);
        textArea.textProperty().addListener(obs -> textArea.end());

        var root = new VBox(10, textArea, textField);
        root.setPadding(new Insets(10));
        VBox.setVgrow(textArea, Priority.ALWAYS);

        primaryStage.setScene(new Scene(root, 600, 400));
        textField.requestFocus();

        primaryStage.setTitle("Console App");
        primaryStage.show();

        wireInputAndOutput(textField, textArea);
        startConsoleTask();
    }

    private void wireInputAndOutput(TextField input, TextArea output) throws IOException {
        var inputConsumer = StreamWriter.redirectStandardIn(threadPool);
        StreamReader.redirectStandardOut(new BufferedTextAreaAppender(output), threadPool);

        input.setOnAction(e -> {
            e.consume();
            var text = input.textProperty().getValueSafe() + "\n";
            output.appendText(text);
            inputConsumer.accept(text);
            input.clear();
        });
    }

    private void startConsoleTask() {
        threadPool.execute(new ConsoleTask(Platform::exit));
    }

    @Override
    public void stop() {
        threadPool.shutdownNow();
    }
}

BufferedTextAreaAppender.java

import java.util.function.IntConsumer;
import javafx.application.Platform;
import javafx.scene.control.TextArea;

class BufferedTextAreaAppender implements IntConsumer {

    private final StringBuilder buffer = new StringBuilder();
    private final TextArea textArea;
    private boolean runLater = true;

    BufferedTextAreaAppender(TextArea textArea) {
        this.textArea = textArea;
    }

    @Override
    public void accept(int characterCodePoint) {
        synchronized (buffer) {
            buffer.append((char) characterCodePoint);
            if (runLater) {
                runLater = false;
                Platform.runLater(this::appendToTextArea);
            }
        }
    }

    private void appendToTextArea() {
        synchronized (buffer) {
            textArea.appendText(buffer.toString());
            buffer.delete(0, buffer.length());
            runLater = true;
        }
    }
}

StreamReader.java (could probably use a better name)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

class StreamReader implements Runnable {

    static void redirectStandardOut(IntConsumer onNextCharacter, Executor executor) throws IOException {
        var out = new PipedOutputStream();
        System.setOut(new PrintStream(out, true, StandardCharsets.UTF_8));

        var reader = new StreamReader(new PipedInputStream(out), onNextCharacter);
        executor.execute(reader);
    }

    private final BufferedReader reader;
    private final IntConsumer onNextCharacter;

    StreamReader(InputStream stream, IntConsumer onNextCharacter) {
        this.reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
        this.onNextCharacter = onNextCharacter;
    }

    @Override
    public void run() {
        try (reader) {
            int charAsInt;
            while (!Thread.interrupted() && (charAsInt = reader.read()) != -1) {
                onNextCharacter.accept(charAsInt);
            }
        } catch (InterruptedIOException ignore) {
        } catch (IOException ioe) {
            throw new UncheckedIOException(ioe);
        }
    }
}

StreamWriter.java (could probably use a better name)

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

class StreamWriter implements Runnable {

    static Consumer<CharSequence> redirectStandardIn(Executor executor) throws IOException {
        var in = new PipedInputStream();
        System.setIn(in);

        var queue = new ArrayBlockingQueue<CharSequence>(500);
        var writer = new StreamWriter(new PipedOutputStream(in), queue);
        executor.execute(writer);

        return queue::add;
    }

    private final BufferedWriter writer;
    private final BlockingQueue<? extends CharSequence> lineQueue;

    StreamWriter(OutputStream out, BlockingQueue<? extends CharSequence> lineQueue) {
        this.writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
        this.lineQueue = lineQueue;
    }

    @Override
    public void run() {
        try (writer) {
            while (!Thread.interrupted()) {
                writer.append(lineQueue.take());
                writer.flush();
            }
        } catch (InterruptedIOException | InterruptedException ignore) {
        } catch (IOException ioe) {
            throw new UncheckedIOException(ioe);
        }
    }
}

ConsoleTask.java

import java.nio.charset.StandardCharsets;
import java.util.NoSuchElementException;
import java.util.Scanner;

class ConsoleTask implements Runnable {

    private final Scanner scanner = new Scanner(System.in);
    private final Runnable onExit;

    ConsoleTask(Runnable onExit) {
        this.onExit = onExit;
    }

    @Override
    public void run() {
        System.out.println("WELCOME TO CONSOLE APP!");

        boolean running = true;
        while (running && !Thread.interrupted()) {
            printOptions();

            int choice = readInt("Choose option: ", 1, 3);
            switch (choice) {
                case 1 -> doCheckIfLeapYear();
                case 2 -> doCheckIfPrime();
                case 3 -> running = false;
                default -> System.out.println("Unknown option!");
            }
        }

        onExit.run();
    }

    private void printOptions() {
        System.out.println();
        System.out.println("Options");
        System.out.println("-------");
        System.out.println("  1) Test Leap Year");
        System.out.println("  2) Test Prime");
        System.out.println("  3) Exit");
        System.out.println();
    }

    private int readInt(String prompt, int min, int max) {
        while (true) {
            System.out.print(prompt);

            try {
                int i = Integer.parseInt(scanner.nextLine());
                if (i >= min && i <= max) {
                    return i;
                }
            } catch (NumberFormatException | NoSuchElementException ignored) {
            }
            System.out.printf("Please enter an integer between [%,d, %,d]%n", min, max);
        }
    }

    private void doCheckIfLeapYear() {
        System.out.println();
        int year = readInt("Enter year: ", 0, 1_000_000);
        if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
            System.out.printf("The year %d is a leap year.%n", year);
        } else {
            System.out.printf("The year %d is NOT a leap year.%n", year);
        }
    }

    private void doCheckIfPrime() {
        System.out.println();
        int number = readInt("Enter an integer: ", 1, Integer.MAX_VALUE);

        boolean isPrime = true;
        for (int i = 2; i <= (int) Math.sqrt(number); i++) {
            if (number % i == 0) {
                isPrime = false;
                break;
            }
        }

        if (isPrime) {
            System.out.printf("The number %,d is prime.%n", number);
        } else {
            System.out.printf("The number %,d is NOT prime.%n", number);
        }
    }
}

GIF of Example

GIF image of example code running

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • what should I change in order to use this with a controller.java and fxml file? @Slaw – UnseenSnick Oct 07 '22 at 11:04
  • or if there is a way to use it inside the main.java with fxml file could you show an example of it? (didn't get the chance to edit the other comment and sorry if this is a stupid question but I had to ask so I can learn about this)@Slaw – UnseenSnick Oct 07 '22 at 12:13
  • 1
    Off the top of my head, essentially only the `wireInputAndOutput` method would need to be moved to the controller class (assuming the `TextArea` and `TextField` are injected into the controller). Then call it in the `initialize` method. I suggest studying the example to try and truly understand it. If you understand what is happening, then adapting it to your needs will be relatively trivial. – Slaw Oct 07 '22 at 15:03
  • check edit 4, I tried to just put the **wireInputAndOutput** into the controller and it didn't run. Then I tried also putting **startConsoleTask** inside controller and it didn't run, so what am I doing wrong? @Slaw – UnseenSnick Oct 07 '22 at 16:10
  • I assume you're getting a `NullPointerException`? Your `inputPane` field is not annotated with `@FXML`, so it won't be injected. On top of that, your `TextField` element in the FXML file you provided has `fx:id="inputField"`. The name of the field and the value of the `fx:id` attribute must match exactly. – Slaw Oct 07 '22 at 16:16
  • check the fxml file in edit 4...had forgoten to add it -.-' @Slaw – UnseenSnick Oct 07 '22 at 16:24
  • Well, you still have the missing annotation problem. Also, it may be best to only have your _current_ code in the question—it's getting quite bloated. If someone wants to look at the old code, then they can look at the question's revision history. – Slaw Oct 07 '22 at 16:27
  • what do you mean **missing annotation problem?** I don't understand what you mean, could you show me what I have to do? @Slaw – UnseenSnick Oct 07 '22 at 16:35
  • As I said, your `inputPane` field does not have an `@FXML` annotation. The `console` field does, but not the `inputPane` field. – Slaw Oct 07 '22 at 17:19
  • I see you found a solution. Looking at the older code, I think the problem was that you were manually assigning the `console` and `inputPane` fields in the `initialize` method. This replaces the instances that were injected by the `FXMLLoader`, which means it replaced the instances that were actually being displayed in the scene. So, your code was working, it just was updating the wrong `TextArea` instance and using the wrong `TextField` instance. – Slaw Oct 07 '22 at 18:13