JavaFX: Console Log in a TextArea + Multithreading and Tasks - java

I'm trying to achieve a feast which many attempted here on StackOverflow: showing the console output of a Java application in a TextArea built on JavaFX.
I've managed to show my output in said TextArea but, as many others, my UI freezes, 'cause this thread is heavily loading the one used to show the UI itself.
So I've started reading about Platform.runLater(), but it doesn't solve my issue, mostly because I'm outputting a lot of text and this slows down said function. Looking around, I've got into this question, where a nice solution based on Task is proposed. Neverthless, my UI keeps freezing as soon as I start to show my console log into the TextArea. I'll show you a snippet of my code, so that you may be able to tell me what I'm missing and/or doing wrong.
This is a snippet of my JavaFX controller:
public class MainViewController extends AbstractController implements Initializable {
#FXML private TextArea textAreaLog;
#Override
public void initialize(URL location, ResourceBundle resources) {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
boolean fxApplicationThread = Platform.isFxApplicationThread();
System.out.println("Is call on FXApplicationThread: " + fxApplicationThread);
Console console = new Console(textAreaLog);
PrintStream ps = new PrintStream(console, true);
System.setOut(ps);
System.setErr(ps);
return null;
}
#Override
protected void succeeded() {
boolean fxApplicationThread = Platform.isFxApplicationThread();
System.out.println("Is call on FXApplicationThread: " + fxApplicationThread);
super.succeeded();
textAreaLog.textProperty().unbind();
}
};
textAreaLog.textProperty().bind(task.messageProperty());
new Thread(task).start();
}
// Console Class
public static class Console extends OutputStream {
private TextArea output;
Console(TextArea ta) {
this.output = ta;
}
#Override
public void write(int i) throws IOException {
output.appendText(String.valueOf((char) i));
}
}
}
I've edited the code taken from answer to the question I've previously linked, leaving all the debug messages just to help me out.
That's all. My UI just freezes, even if I'm apparently running my heavy-load task in the background instead of doing that directly in my UI thread.

I think root of the problem is one of the below;
System.out.println("text") is being a synchronized method.
accesing ui component outside of Ui thread
When you call System.out.println("text") from ui thread, the synchronization on System.out will cause UI to freeze for duration of synchronization.
You can check if this is the cause like below;(You have to wrap all your System.out calls like below, for only to test if the above theory is correct)
This will cause println methods to synchronize in different thread.(common-pool threads)
CompletableFuture.runAsync(()->System.out.println("text"));
You should also update output component in ui thread.(Problem is solved with this in this case)
// or create new runnable if you are not using java8
Platform.runLater(()->output.appendText(String.valueOf((char) i)));

Related

can't get sequential behavior Java Swing [duplicate]

basically, I have this code which was initially working with console i/o now I have to connect it to UI. It may be completely wrong, I've tried multiple things although it still ends up with freezing the GUI.
I've tried to redirect console I/O to GUI scrollpane, but the GUI freezes anyway. Probably it has to do something with threads, but I have limited knowledge on it so I need the deeper explanation how to implement it in this current situation.
This is the button on GUI class containing the method that needs to change this GUI.
public class GUI {
...
btnNext.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
controller.startTest(index, idUser);
}
});
}
This is the method startTest from another class which contains instance of Question class.
public int startTest() {
for (int i = 0; i < this.numberofQuestions; i++) {
Question qt = this.q[i];
qt.askQuestion(); <--- This needs to change Label in GUI
if(!qt.userAnswer()) <--- This needs to get string from TextField
decreaseScore(1);
}
return actScore();
}
askQuestion method:
public void askQuestion() {
System.out.println(getQuestion());
/* I've tried to change staticaly declared frame in GUI from there */
}
userAnswer method:
public boolean userAnswer() {
#SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
if( Objects.equals(getAnswer(),userInput) ) {
System.out.println("Correct");
return true;
}
System.out.println("False");
return false;
}
Thanks for help.
You're correct in thinking that it related to threads.
When you try executing code that will take a long time to process (eg. downloading a large file) in the swing thread, the swing thread will pause to complete execution and cause the GUI to freeze. This is solved by executing the long running code in a separate thread.
As Sergiy Medvynskyy pointed out in his comment, you need to implement the long running code in the SwingWorker class.
A good way to implement it would be this:
public class TestWorker extends SwingWorker<Integer, String> {
#Override
protected Integer doInBackground() throws Exception {
//This is where you execute the long running
//code
controller.startTest(index, idUser);
publish("Finish");
}
#Override
protected void process(List<String> chunks) {
//Called when the task has finished executing.
//This is where you can update your GUI when
//the task is complete or when you want to
//notify the user of a change.
}
}
Use TestWorker.execute() to start the worker.
This website provides a good example on how to use
the SwingWorker class.
As other answers pointed out, doing heavy work on the GUI thread will freeze the GUI. You can use a SwingWorker for that, but in many cases a simple Thread does the job:
Thread t = new Thread(){
#Override
public void run(){
// do stuff
}
};
t.start();
Or if you use Java 8+:
Thread t = new Thread(() -> {
// do stuff
});
t.start();

javafx textfield doesn't change

I have the following code:
#FXML
private void test(){
textField.setText("Pending...");
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
And what I tries to achieve is that while the doStuff() do his stuff in a textField in the GUI there should be written "Pending..." and as soon as it finish it should change to "OK" / "Error".
I want that the GUI is blocked while doStuff is running so the user has to wait and can't click something else.
But what happens is that as soon as I start test it does the doStuff() but only updates the textField with "OK"/"Error" but I never see "Pending...".
I have the feeling that I have somehow update the GUI, but I'm not sure how it should be done.
Update:
What I tried is to move the doStuff in another Thread:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
public void run(){
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
};
t.start();
t.join();
}
It would works if i would remove the t.join(); command, but then the UI wouldn't be blocked. So I'm at a loss right now.
Thanks
You must never run long running tasks on the JavaFX Application Thread. Doing so will prevent said thread from doing any GUI related things which results in a frozen UI. This makes your user(s) sad. However, your attempt at putting the long running task on a background task is flawed. You call Thread.join which will block the calling thread until the target thread dies; this is effectively the same thing as just running the task on the calling thread.
For a quick fix to your example, you could do the following:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
#Override public void run(){
boolean passed = doStuff();
Platform.runLater(() -> {
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
});
}
};
t.start();
}
That will create a thread, start it, and let it run in the background while letting the JavaFX Application Thread continue doing what it needs to. Inside the background thread you must update the TextField inside a Platform.runLater(Runnable) call. This is needed because you must never update a live scene graph from a thread other than the JavaFX Application Thread; doing so will lead to undefined behavior. Also, you should look into “implements Runnable” vs “extends Thread” in Java. It's better, or at least more idiomatic, to do:
Thread t = new Thread(() -> { /* background code */ });
You could also use a javafx.concurrent.Task which may make it easier to communicate back to the JavaFX Application Thread. One option would be:
#FXML
private void test(){
textField.setText("Pending...");
Task<Boolean> task = new Task<>() {
#Override protected Boolean call() throws Exception {
return doStuff();
}
};
task.setOnSucceeded(event -> textField.setText(task.getValue() ? "Ok" : "Error"));
new Thread(task).start();
}
You could also bind the TextField to the message property of the Task and call updateMessage("Pending...") inside the call method. You could even provide more detailed messages if and when possible.
That said, creating and starting Threads yourself is not ideal and you should look into thread pooling (using something like an ExecutorService). You might also want to look into javafx.concurrent.Service for "reusing" Tasks.
For more information about JavaFX concurrency see Concurrency in JavaFX and read the documentation of the classes in javafx.concurrent. For the basics of multi-threading in Java see Lesson: Concurrency from The Java™ Tutorials.

JavaFX Update Issue?

I'm working on a game in JavaFX, and right now I'm trying to create a loading screen, since loading the assets takes some time. I've created a LoadingPane class that displays several progress bars, and I know for sure that it works. However, in the below code, the loading pane will not be visible until after the loadAssets function, even though I'm adding it beforehand. When I run the below code, I get a blank stage for the time it takes for the assets to load, and then a screen with the completed progress bars.
I haven't been able to find anyone with similar issues, or any sort of refresh or update function to force the scene to display the loading pane before continuing with the program.
Note: I've deleted some irrelevant code setting up keyboard input handling.
public class Main extends Application{
Pane root = new Pane();
Scene mainScene = new Scene(root, Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT, Color.BLACK);
static LoadingPane loadingPane = new LoadingPane(3);
private static int loadingIndex = 0;
public static void main(String[] args) {
if(Constants.DEBUG_MODE)
System.out.println("WARNING: Game has launched in debug mode!");
launch(args);
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
public static void loadAssets(){
RoomLoader.createRooms();
updateProgress(1.0);
loadingIndex++;
ProjectileLoader.load("imgs/projectiles/");
ProjectileLoader.load(Constants.BATTLE_IMAGES_FILEPATH);
updateProgress(1.0);
loadingIndex++;
BattleLoader.createBattles();
updateProgress(1.0);
loadingIndex++;
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
#Override
public void start(final Stage primaryStage) {
//root.getChildren().add(new javafx.scene.image.ImageView(new Image("imgs/loading.png")));
root.setLayoutX(0);
primaryStage.setScene(mainScene);
primaryStage.show();
primaryStage.toFront();
primaryStage.setTitle("Branch");
primaryStage.setResizable(false);
//primaryStage.getIcons().add(new Image("core/imgs/soul/red.png"));
//This allows the closing of the primaryStage to end the program
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>(){
#Override
public void handle(WindowEvent t) {
System.exit(0);
}
});
root.resize(Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT);
primaryStage.getIcons().add(new Image("imgs/icon.png"));
//End GUI setup
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
}
}
EDIT: Working Code
For anyone who arrives here with a similar issue, below is the code I used to get this to work:
I replaced this:
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
With this:
root.getChildren().add (loadingPane);
Task<Integer> loadingTask = new Task<Integer>() {
#Override protected Integer call() throws Exception {
loadAssets();
return 1;
}
};
loadingTask.setOnSucceeded(new EventHandler<WorkerStateEvent>(){
#Override
public void handle(WorkerStateEvent t){
loadingPane.setVisible(false);
load(); //note: this function sets up the actual game
//updating the GUI, adding game elements, etc
}
});
new Thread(loadingTask).start();
I can't say that this is the best way to go about this, but I can say that it works. Good luck!
You need a separate thread for the update method.
Code runs in a linear fashion, one bit of code runs then the next. With a separate thread, the two “lines” of code can run side by side. The process runs and the GUI updates at the same time.
JavaFX application runs on specific thread called Application Thread, in other to make GUI responsive while doing some expensive operation, like in your case loading assets, you will need to load assets on another Thread that you create yourself or you can use Task which is one of JavaFX classes meant to be used in such use cases.
I suggest you to read about Task in official javadocs

Building Multithreading ProgressBar in JavaFx

I am attempting to build a progress bar that is being updated as my application is retrieving and populating data to the GUI. I figured that the progress bar will be reused a lot so I decided to create its own class. However, I don't believe I understand either the Worker/Task or Multi-Threading in general to create a re-usable situation. What would be the recommended approach to creating a progress bar that can listen to the application thread and update the bar accordingly. Here is my attempt:
// Simple Progress Bar View as Pop Up
public class ProgressIndicatorUtil{
#FXML
private ProgressBar progressBar;
#FXML
private Label statusLabel;
#FXML
private Button closeButton;
#FXML
private Label valueLabel;
private Worker worker;
private Stage stage;
public void setPopUpStage(Stage stage) {
this.stage = stage;
}
public void setWorker(Worker worker) {
this.worker = worker;
}
public void setLinkToMainPage(Object controller) {
((Task<String>) worker).setOnSucceeded(event -> stage.close());
((Task<String>) worker).setOnCancelled(event -> {
closeButton.setVisible(true);
stage.requestFocus();
statusLabel.setTextFill(Color.RED);}
);
valueLabel.textProperty().bind(Bindings.format("%5.1f%%", worker.progressProperty().multiply(100)));
progressBar.progressProperty().bind(worker.progressProperty());
statusLabel.textProperty().bind(worker.messageProperty());
}
#FXML
private void handleClose(ActionEvent e){
stage.close();
}
}
The Controller that calls the View Pop-Up and runs the Progress Bar Thread.
public class MyController{
//Controller calling the view and disabling the main GUI
private void loadProgressBar(Worker worker){
try{
FXMLLoader loader = new FXMLLoader(getClass()
.getClassLoader().getResource("main/resources/fxml/ProgressBar.fxml"));
AnchorPane pane = (AnchorPane)loader.load();
Stage popUpStage = new Stage();
popUpStage.initModality(Modality.WINDOW_MODAL);
Scene scene = new Scene(pane);
popUpStage.setScene(scene);
ProgressIndicatorUtil controller = loader.getController();
controller.setPopUpStage(popUpStage);
controller.setWorker(worker);
controller.setLinkToMainPage(this);
mainPane.setDisable(true);
popUpStage.showingProperty().addListener((obs, hidden, showing) -> {
if(hidden) mainPane.setDisable(false);});
popUpStage.show();
}catch(IOException e){
e.printStackTrace();
}
}
private void runProgressBar(Worker worker) {
new Thread((Runnable) worker).start();
}
//A user action that runs the progress bar and GUI
#FXML
private void aBigProcessingEvent(ActionEvent event) {
Worker worker = new Task<String>(){
#Override
protected String call() throws Exception {
updateProgress(0, 3);
updateMessage("Clearing Data");
processingEvent01();
updateProgress(1, 3);
updateMessage("Retriving Data And Adding To List");
processingEvent02();
updateProgress(2, 3);
updateMessage("Populating Data");
processingEvent03();
updateProgress(3, 3);
return "Finished!";
}
};
loadProgressBar(worker);
runProgressBar(worker);
}
}
The program works fine, visually, but it throws an Exception like this (Not On FX Application Thread) and running Platform.runLater() on my "processingEvent" methods will cause my progress bar to be 100% immediately, but it won't throw anymore Exceptions. Any suggestion to how to split the application modification methods and the worker methods apart while keeping the progression connected to the processingEvent methods? Much thanks.
There is nothing wrong with the (incomplete) code you have posted, so there errors are in other parts of your code. Since the code is incomplete, I have to make some educated guesses as to what is happening. (Note: it is actually much better if you can create complete examples when you post questions, so that you ensure the cause of the issue you are asking about is included.)
Since you are getting an IllegalStateException "Not on the FX Application Thread", you must be updating the UI from a background thread. Since the only code you've posted that runs in a background thread is in the Task you create in aBigProcessingEvent(), the UI updates must be happening in the one or more of the processingEventXX() methods you haven't shown.
If you wrap the calls to processingEventXX() in Platform.runLater(), then you won't see any progressive updates to the progress bar. Platform.runLater() schedules the runnable you provide to be executed on the FX Application Thread and exits immediately. There is no other code in the Task that takes time to run, so the entire task is completed in very little time, and by the time the FX Application Thread renders the next frame, the task is complete and the progress property is at 1.
So presumably your processingEventXX() methods take time to execute, and also update the UI. You must wrap the calls that update the UI in those methods in Platform.runLater(...). The code wrapped in Platform.runLater(...) must not include code that takes a long time to run. I.e. they should look like
private void processingEvent01() {
// some long-running process here...
Platform.runLater(() -> {
// update UI here....
});
// some other long-running process here (perhaps)
}

Program displays filenames in a JTextArea as it walks the directory tree but I don't know how to stop it via a keypress

There are two windows: a GUI for user input and Output window for list of filenames found. Execution must be user-stoppable via a keypress and must leave both windows open because the program processes subdirectories, so it can run a long time, possibly stepping thru 100_000 files, either producing tons of output or none at all, depending on how user's filename pattern matches files encountered in the selected starting node.
Here's my question:
How do I look for a keypress (e.g., ESC or CTRL-C) to allow user to terminate? (Clicking red X isn't an option since that closes windows; user needs to see what's been found before termination. Doing so does not close either window anyway since all buttons are disabled once tree walk begins.)
I've tried putting keyListeners in several places, but once the "Start" button is clicked, all the swing components are disabled.
This seems like such a common situation that I'm surprised I can't find any textbook, thread, or Google info that directly answers the question. So I'm afraid it's not gonna be at all easy. That would be no surprise. I may have found a clue here but I can't get it to compile and the link contained there doesn't lead to that code snippet.
The search begins when the Search button is clicked:
private void jbSearchActionPerformed(ActionEvent evt) {
SearchyGUI.doIt();
}
The doIt() method walks the directory tree by an extension of SimplefileVisitor:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> {
...
}
public static void doIt(){
try {
visitor = new OverriddenFileVisitor();
info.setVisible(true);
Files.walkFileTree(SearchyGUI.p , visitor);
}
catch (Exception e) { }
}
}
Output is written to jTextArea1 via the report() method:
public static void report(String s){
Output.jTextArea1.append(s + "\n");
}
This is done primarily in the visitFile() method of SimpleFileVisitor:
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
report(foundkt + "--" + f.getFileName().toString());
return FileVisitResult.CONTINUE;
}
Here's the main class:
public class SearchyGUI {
static Output info;
static Path p ;
static FileVisitor visitor ;
static GUI gui
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
gui = new GUI();
gui.setVisible(true);
}
});
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
info = new Output();
}
});
}
The problem is you are hogging the GUI thread, so the GUI thread can't process any events originating from the user.
You need to create a new Thread and do the work in there. Then, to display output from that thread, you can use SwingUtilities.invokeLater or something like that.
The Key Bindings API is probably the best choice for monitoring key strokes.
I would also add a [Cancel] button to the UI, which shared the same action...
public class CancelAction extends AbstractAction {
public CancelAction() {
putValue(NAME, "Cancel");
}
public void actionPerformed(ActionEvent evt) {
// Perform the cancel operation...
}
}
Then some where else in your code...
CancelAction cancelAction = new CancelAction();
JButton cancelButton = new JButton(cancelAction);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
am.put("Cancel", am);
Now the other problem you're going to have is the fact that you look like you are running a long running task within the context of the Event Dispatching Thread. This is going to prevent your program from being able to update the UI or allow the user to interact with the UI.
If you need to make changes to the UI (ie, show the output of the file processing), you should try a SwingWorker.
The main reason being is that it allows you to execute the long running task in another thread, but provides the mechanism for re-syncing updates back to the EDT, where it is safe to make changes to the UI.
Take a look at Concurrency in Swing for more details.
Regardless of which direction you take, you're going to need to supply a reference to the object that is carrying out the task and provide some kind of "cancel" flag, which the "task" object will need to monitor
The way I had left this program last night was unsatisfactory since Exit resulted in user not being able to see the output so far displayed (it could be useful). So I established window listeners and used the close event to set a boolean aborted to true to prevent further output to the window, but the thread kept running, which led to intermittent problems if another search was started before the thread ended.
Here's how I fixed it.
The FileVisitor interface has 4 methods to implement to walk the tree--two for each file visited, two for each directory. Each returns a FileVisitResult which is normally FileVisitResult.CONTINUE. By changing the return value to FileVisitResult.TERMINATE in the file visitor thread, it terminates appropriately! That is, I set a flag that the thread could check and take appropriate action, which is exactly what #MadProgrammer suggested.
public static FileVisitResult disposition = FileVisitResult.CONTINUE;
...
private static void report(String s){
if (! aborted)
try{
Output.jTextArea1.append(s + "\n");
}
catch (Exception e){
aborted = true ;
disposition = FileVisitResult.TERMINATE;
}
}
...
#Override
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
f1 = new File(f.getParent().toString() + "\\" + f.getFileName().toString());
long filesize = f1.length();
report(f.getFileName().toString() + "\t found in " + f.getParent().toString());
return disposition;
}
I am one happy camper! Thank you BOTH for your ideas and input.
Well, I made it stop. I guess if you wander the woods long enough you'll find a gnome. I read Robin's hint last week and sort of gave up. Then I read some more and more. And then more. But Robin assured me that gnomes DO exist in these here woods!
The code I used was a modification of some I found for a MatLab/Java app. (Why'd I even look at it?? Best apparent Google hint.)
I made the "file visitor" (directory tree walker component) startable as a thread as Robin advised:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> implements Runnable{
// ................................................................^^^^^^^^^^^^^^^^^^^
In doIt() I made a couple of changes, moving the lines that process the directory to the now-runnable class and started the file visitor as its own thread in doIt():
public static void doIt(){
try {
new OverriddenFileVisitor().startTh();
//^^^^^^^^^^
//(moved) Files.walkFileTree(SearchyGUI.p , visitor);
...
I added the new method in the previous line to OverriddenFileVisitor class: (This is the main part of the MatLab/Java code that made sense to me so I used and modified it.)
public void startTh() {
Thread t = new Thread(this);
t.start();
}
And I inserted the overridden run() method for the class:
public void run() {
try {
Files.walkFileTree(SearchyGUI.p , this); // Used to be in doIt().
}
catch (IOException ex) { }
}
It ran and gave correct results and stopped when I hit Exit button, which "became" enabled after revising the file visitor to run in its own thread, which is what #Robin Green was saying. I almost feel like I know what I've done.
P.S. Note that I already was able to get my output via invokeLater()--last several lines of original question.
It's not finished but it's much more satisfactory.

Categories