I am working on a college course project, a JavaFX application that is simulating a garage with vehicles moving around. Following on this answer, I created a mechanism for constantnly refreshing GUI without flooding the JavaFX thread.
The entire code can be found here.
Observer
is a daemon thread whose task is computing a String
value for output and sending it it to a TextArea
, which is constantly being updated.
Observer run method:
@Override
public void run() {
while (true) {
synchronized (Garage.getLock()) {
try {
// buffer = new StringBuffer("");
buffer.append('\n');
garage.print(buffer);
buffer.append('\n');
garage.outputText.set(buffer.toString());
System.out.println(buffer.toString());
Garage.getLock().wait(3000);
} catch (InterruptedException exception) {
exception.printStackTrace();
} finally {
Garage.getLock().notifyAll();
}
}
}
}
Observer
object has a reference to the model class - garage
. print()
method of the Garage
class basically appends stuff to the buffer.
The complete output is printed to the console, which works fine. The output is also used to set outputText
, a SimpleStringProperty
, which has a listener attached in the MainControllerClass
.
outputText member:
public class Garage implements Externalizable {
...
public SimpleStringProperty outputText = new SimpleStringProperty();
...
}
Refreshing the GUI is initiated with a button click.
MainController class:
package garage.controller;
import java.util.concurrent.atomic.AtomicInteger;
import garage.model.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
public class MainController {
@FXML
private TextArea output;
@FXML
private Button startButton;
private Garage model;
public MainController() {
}
private AtomicInteger control = new AtomicInteger(-1);
@FXML
public void initialize() {
}
@FXML
private void handleStartButton() {
model.outputText.addListener(new ChangeListener<String>() {
@Override
public void changed(final ObservableValue<? extends String> observable, final String oldValue,
final String newValue) {
if (control.getAndSet(1) == -1) {
javafx.application.Platform.runLater(new Runnable() {
@Override
public void run() {
control.set(-1);
output.setText(newValue);
}
});
}
}
});
}
public void setModel(Garage model) {
this.model = model;
}
}
When I run the application and click the button, everything is going well. TextArea
is updated in real time. Since the output is constantly being appended to the StringBuffer
, I wanted to refresh it in every cycle and get something like a simulation.
This is where the problems start.
When I uncomment the line in the Observer run
method
// buffer = new StringBuffer("");
nothing is being printed to the TextArea
. The console output is working well.
I also tried with other StringBuffer
methods like delete
and setLenght
but nothing seems to work. However I try to clean the buffer, TextArea
is no longer updated.
I cannot seem to reset the StringBuffer
.
What am I missing here?
EDIT: print()
methods
Garage print method
public void print(StringBuffer buffer) {
synchronized (Garage.lock) {
synchronized (lock) {
for (int i = 0; i < platformCount; i++)
platforms.get(i).print(buffer);
}
}
}
GarageItem print method
package garage.model;
import java.io.*;
public interface GarageItem extends Serializable {
public void print(StringBuffer buffer);
}
Lane print method
package garage.model;
public class Lane implements GarageItem {
private static final long serialVersionUID = 5L;
@Override
public void print(StringBuffer buffer) {
synchronized (Garage.getLock()) {
buffer.append('.');
}
}
}
ParkingSpot print method
package garage.model;
public class ParkingSpot implements GarageItem {
private static final long serialVersionUID = 4L;
@Override
public void print(StringBuffer buffer) {
synchronized (Garage.getLock()) {
buffer.append('*');
}
}
}
Vehicle print method
@Override
public void print(StringBuffer buffer) {
synchronized (Garage.getLock()) {
buffer.append(symbol);
}
}
where symbol
is 'V'.
Found the answer today. My text example was trivial and by the time I clicked the handleStartButton
, all of the cars have finished moving and parked in the garage.
Because I reset the buffer in every loop iteration, it meant that the state of the garage didn't change anymore and it's String
representation was the same. Because of this, change listener was not triggered and nothing was printed out to the TextArea.