I'm calculating three different values at the same time which is depending on the level state of my application, so I'm using Tasks for a faster calculation. However if I want to change the level during calculation it's not possible, because all the buttons are disabled during the calculation.
I've found out that I cannot cancel the tasks because my GUI freezes until the tasks are finished. Therefore I cannot cancel tasks, because they are already finished
My GUI has this buttonclick event
buttonIncreaseLevel.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Platform.runLater(new Runnable() {
#Override
public void run() {
increaseLevelButtonActionPerformed(event);
}
});
}
});
It increases a level and starts this method
createTask(taskLeftEdge,EdgeType.LEFT);
createTask(taskBottomEdge,EdgeType.BOTTOM);
createTask(taskRightEdge,EdgeType.RIGHT);
pool.submit(taskRightEdge);
pool.submit(taskBottomEdge);
pool.submit(taskLeftEdge);
new Thread(new Runnable() {
#Override
public void run() {
try {
System.out.println("[Future] Started to wait for task (future)");
// A task is also its own future
ArrayList taskR = (ArrayList) taskRightEdge.get();
ArrayList taskB = (ArrayList) taskBottomEdge.get();
ArrayList taskL = (ArrayList) taskLeftEdge.get();
System.out.println("[Future] " + taskR);
System.out.println("[Future] " + taskB);
System.out.println("[Future] " + taskL);
} catch (InterruptedException | ExecutionException ex) {
Logger.getLogger(FUN3KochFractalFX.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
).start();
The method for creating a task
private void createTask(Task task, EdgeType type){
if (task != null) {
application.cancelProgress(type);
task.cancel();
}
// There's a new task that performs some work
switch(type){
case LEFT:
taskLeftEdge = new EdgeGeneratorTask("Task: " + taskNumber, type, this, application);
task = taskLeftEdge;
edges.addAll(koch.generateLeftEdge());
break;
case BOTTOM:
taskBottomEdge = new EdgeGeneratorTask("Task: " + taskNumber, type, this, application);
task = taskBottomEdge;
edges.addAll(koch.generateBottomEdge());
break;
case RIGHT:
taskRightEdge = new EdgeGeneratorTask("Task: " + taskNumber, type, this, application);
task = taskRightEdge;
edges.addAll(koch.generateRightEdge());
break;
}
taskNumber++;
application.setProgress( task, type);
}
And the Task itself. The class Kochfractal is where the calculations are made
private static final Logger LOG = Logger.getLogger(EdgeGeneratorTask.class.getName());
private final String id;
private EdgeType type;
private KochFractal calc;
private FUN3KochFractalFX application;
public EdgeGeneratorTask(String id, EdgeType type, KochManager manager, FUN3KochFractalFX main) {
this.id = id;
this.type = type;
calc = new KochFractal(manager);
application = main;
}
#Override
protected ArrayList<Edge> call() throws Exception {
ArrayList<Edge> edges = new ArrayList<>();
switch(type){
case LEFT:
edges = calc.generateLeftEdge();
break;
case BOTTOM:
edges = calc.generateBottomEdge();
break;
case RIGHT:
edges = calc.generateRightEdge();
break;
}
int MAX = edges.size();
for (int i = 1; i <= MAX; i++) {
if (isCancelled()) {
break;
}
updateProgress(i, MAX);
updateMessage(id + " " + i);
}
return edges;
}
I expect these threads to be cancelled, with an action on my JavaFX GUI.
The threads should run on a background Thread instead of the GUI thread itself
Related
So I have implemented Observer Pattern. In the update function of my Observer class, I want to update the progressbar. The problem is that it instantly updates to the maximum value. I don't get an loading animation. The application calculates some edges. After the calculation of the edges, they will be drawn on the screen.
My observers are the following :
GenerateEdgeBottom
GenerateEdgeLeft
GenerateEdgeRight
I also have a KochManager and an javaFX class.
I make my progressbars in the javaFX class. In the javaFX class is a function that binds all the progressbars.
Part of my javaFX class
public void binding(GenerateEdgeLeft leftEdges, GenerateEdgeRight rightEdges, GenerateEdgeBottom bottomEdges) {
progressBarLeft.progressProperty().unbind();
progressBarLeft.setProgress(0);
progressBarRight.progressProperty().unbind();
progressBarRight.setProgress(0);
progressBarBottom.progressProperty().unbind();
progressBarBottom.setProgress(0);
progressBarLeft.progressProperty().bind(leftEdges.progressProperty());
progressBarRight.progressProperty().bind(rightEdges.progressProperty());
progressBarBottom.progressProperty().bind(bottomEdges.progressProperty());
lblLeft.textProperty().bind(leftEdges.messageProperty());
lblRight.textProperty().bind(rightEdges.messageProperty());
lblBottom.textProperty().bind(bottomEdges.messageProperty());
}
My kochmanager
public void changeLevel(int nxt) {
edges.clear();
koch.setLevel(nxt);
this.leftEdgeTask = new GenerateEdgeLeft(koch);
this.bottomEdgeTask = new GenerateEdgeBottom(koch);
this.rightEdgeTask = new GenerateEdgeRight(koch);
tsCalc.init();
tsCalc.setBegin("Begin calculating");
//System.out.println("test 3");
Platform.runLater(() -> {
//System.out.println("test 4");
application.binding(leftEdgeTask,rightEdgeTask,bottomEdgeTask);
futures.clear();
Future<?> f = pool2.submit(bottomEdgeTask);
Future<?> f2 =pool2.submit(leftEdgeTask);
Future<?> f3 = pool2.submit(rightEdgeTask);
futures.add(f);
futures.add(f2);
futures.add(f3);
try {
edges.addAll(leftEdgeTask.get());
edges.addAll(rightEdgeTask.get());
edges.addAll(bottomEdgeTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//System.out.println("test 5");
koch.getEdges().clear();
tsCalc.setEnd("End calculating");
application.setTextNrEdges("" + koch.getNrOfEdges());
application.setTextCalc(tsCalc.toString());
application.requestDrawEdges();
drawEdges();
});
}
My GenerateEdgeLeft
public class GenerateEdgeLeft extends Task<ArrayList<Edge>> implements Observer {
private KochFractal koch;
//private KochManager manager;
private int numberOfEdges;
private int counter;
private ArrayList<Edge> edges = new ArrayList<>();
public GenerateEdgeLeft(KochFractal koch){
this.koch = koch;
numberOfEdges = koch.getNrOfEdges() / 3;
this.koch.addObserver(this);
}
public GenerateEdgeLeft() {
//edges.size
}
public ArrayList<Edge> getResult() {
return edges;
}
#Override
public ArrayList<Edge> call() throws Exception {
counter = 0;
System.out.println("Thread Started Left");
koch.generateLeftEdge();
edges = koch.getEdges();
return edges;
}
#Override
public void update(Observable o, Object arg) {
edges.add((Edge) arg);
counter++;
updateProgress(counter, numberOfEdges);
updateMessage( "Left : " + counter);
}
}
I have this task run in a thread. The problem is that it freezes the UI every time it is executed. The freeze is longer when the internet is slow. How can I prevent the UI from freezing even if it is still gathering data from the url?
Task<Void> task = new Task<Void>(){
#Override
public Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
#Override
public void run(){
String json = null;
try {
psname = null;
PumpSites n = table.getSelectionModel().getSelectedItem();
psname = n.getPs();
if(psname == "Cubacub")
json = readUrl(""); //read json from thingspeak.com webpage
else if(psname == "Canduman")
json = readUrl("");
} catch (InterruptedIOException iioe)
{
btn1.setTextFill(Color.RED);
btn2.setTextFill(Color.RED);
btn3.setTextFill(Color.RED);
btn4.setTextFill(Color.RED);
btn5.setTextFill(Color.RED);
btn1.setText("NULL");
btn2.setText("NULL");
btn3.setText("NULL");
btn4.setText("NULL");
btn5.setText("NULL");
}
catch (IOException ioe)
{
btn1.setTextFill(Color.RED);
btn2.setTextFill(Color.RED);
btn3.setTextFill(Color.RED);
btn4.setTextFill(Color.RED);
btn5.setTextFill(Color.RED);
btn1.setText("NULL");
btn2.setText("NULL");
btn3.setText("NULL");
btn4.setText("NULL");
btn5.setText("NULL");
}
catch (Exception e1) {
btn1.setTextFill(Color.RED);
btn2.setTextFill(Color.RED);
btn3.setTextFill(Color.RED);
btn4.setTextFill(Color.RED);
btn5.setTextFill(Color.RED);
btn1.setText("NULL");
btn2.setText("NULL");
btn3.setText("NULL");
btn4.setText("NULL");
btn5.setText("NULL");
}
Gson gson = new Gson();
Page page = gson.fromJson(json, Page.class);
for (Item item : page.feeds)
{
det2 = 1;
btn1.setText(item.field1);
btn2.setText(item.field2);
btn3.setText(item.field3);
btn4.setText(item.field4);
btn5.setText(item.field5);
f2 = Float.parseFloat(item.field2);
f3 = Float.parseFloat(item.field3);
//float f5 = Float.parseFloat(item.field5);
if (f2 <= 10.0)
{
btn1.setTextFill(Color.RED);
btn2.setTextFill(Color.RED);
}
else
{
btn1.setTextFill(Color.BLUE);
btn2.setTextFill(Color.BLUE);
}
if (f3 < 0.9 || f3 > 1.2)
{
btn3.setTextFill(Color.RED);
}
else
{
btn3.setTextFill(Color.BLUE);
}
/*if (f5 > 5.0)
{
btn5.setTextFill(Color.RED);
}
else
{
btn5.setTextFill(Color.BLUE);
}*/
btn4.setTextFill(Color.BLUE);
}
if(det2 == 0)
{
btn1.setTextFill(Color.RED);
btn2.setTextFill(Color.RED);
btn3.setTextFill(Color.RED);
btn4.setTextFill(Color.RED);
btn5.setTextFill(Color.RED);
btn1.setText("NULL");
btn2.setText("NULL");
btn3.setText("NULL");
btn4.setText("NULL");
btn5.setText("NULL");
}
det2 = 0;
}
});
Thread.sleep(10000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
The problem is that it freezes the UI every time it is executed. The freeze is longer when the internet is slow. How can I prevent the UI from freezing even if it is still gathering data from the url?
The UI thread freezes because you are still doing the all the logic on the JavaFX application Thread(Platform.runLater ).
You should do something like this instead:
public Void call() throws Exception
{
while (true)
{
try
{
//get json
} catch(Exception e)
{
Platform.runLater(new Runnable()
{
#Override
public void run()
{
//set buttons color and text here
}
}
}
//Rest of your logic here
}
}
The idea is that everything that is going to modify a UI component from a separate Thread should be handled in the Platform.runLater
If you use a background thread invoke Platform.runLater with a long-running Runnable as parameter, you've effectively achieved nothing. The Runnable is still run on the JavaFX application thread freezing your app.
Instead you should collect all the data on the background thread and process it to the point where you simply need to adjust some properties of the scene. Then you use Platform.runLater to do those updates.
But the good news is that there is a class designed for this scenario that could simplify your code a bit: ScheduledService.
Just make sure that you don't access the GUI in any way from the background thread (neither for reading nor for setting properties).
The following example simplified example should demonstrate the general approach. It calculates some multiples of the value chosen via Spinner on a background thread delaying 10 sec between each calculation:
#Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner(1, 100, 1);
// ensure the value is available in a way that allows synchronisation
final AtomicReference<Integer> input = new AtomicReference<>(spinner.getValue());
spinner.valueProperty().addListener((o, oldValue, newValue) -> input.set(newValue));
final int outputCount = 10;
GridPane root = new GridPane();
root.add(spinner, 0, 0, 2, 1);
// create output grid
Text[] output = new Text[outputCount];
for (int i = 1; i <= output.length; i++) {
Text text = new Text(Integer.toString(spinner.getValue() * i));
output[i - 1] = text;
root.addRow(i, new Text("Value multiplied by " + i + " = "), text);
}
root.setPrefWidth(300);
ScheduledService<int[]> service = new ScheduledService<int[]>() {
#Override
protected Task<int[]> createTask() {
return new Task<int[]>() {
#Override
protected int[] call() throws Exception {
// retrieve value and set it to null to denote a change
// that was already handled to avoid doing unnecessary
// work
Integer value = input.getAndSet(null);
int[] result = null;
if (value != null) {
int valueAsInt = value;
result = new int[outputCount];
for (int i = 0; i < outputCount; i++) {
result[i] = (i + 1) * valueAsInt;
}
}
// simpulate delay
Thread.sleep(2000);
return result;
}
};
}
};
service.valueProperty().addListener((o, oldValue, newValue) -> {
// update GUI
if (newValue != null) {
for (int i = 0; i < outputCount; i++) {
output[i].setText(Integer.toString(newValue[i]));
}
}
});
service.setPeriod(Duration.seconds(10));
// make sure service uses a daemon thread
service.setExecutor(Executors.newSingleThreadScheduledExecutor((Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}));
service.start();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I recommend looking through the javadoc of ScheduledService to get familiar with it's capabilities. It also allows for things like reacting to exceptions in the task and specifying a backoff strategy.
i am playing some animation from my apps by using infinite loop, working well. i need to make wait my thread when user want and again start when user want. For that i used wait and notify thread by clicking my root layout, first click make my thread wait and second click make my thread run. That also work as i want.
My problem is when i make click fast, it means when i make wait and also make notify instantly my Apps get hang.
So how I can fixed that problem???
below is my Code:
public class AboutC implements Initializable {
public VBox mainLayout;
#FXML
private
Label nameLvl = new Label();
#FXML
private
Label rollLvl = new Label();
#FXML
private
Label batchLvl = new Label();
#FXML
private
Label depLvl = new Label();
#FXML
private
Label uniLvl = new Label();
#FXML
private Circle circle = new Circle();
private int count = 0;
private boolean run = true;
private Thread thread;
private Task task;
private FadeTransition fd;
private RotateTransition rt;
private Timeline tm;
#Override
public void initialize(URL location, ResourceBundle resources) {
ArrayList<AboutDevelopers> list = new ArrayList<>();
list.add(....)
fd = new FadeTransition(Duration.seconds(4), mainLayout);
fd.setFromValue(0.2);
fd.setToValue(1.0);
fd.setCycleCount(2);
rt = new RotateTransition(Duration.seconds(4), circle);
rt.setByAngle(360);
rt.setAutoReverse(true);
rt.setCycleCount(2);
KeyFrame keyFrame = new KeyFrame(Duration.seconds(4), new KeyValue(circle.radiusProperty(), 0));
tm = new Timeline(keyFrame);
tm.setCycleCount(2);
tm.setAutoReverse(true);
task = new Task<Void>() {
#Override
synchronized public Void call() throws Exception {
int i = 0;
while (true) {
if (run) {
Platform.runLater(() -> {
nameLvl.setText(list.get(count).getName());
rollLvl.setText("Roll: " + list.get(count).getRoll());
batchLvl.setText("Batch: " + list.get(count).getBatch());
depLvl.setText("Department: " + list.get(count).getDepartment());
uniLvl.setText(list.get(count).getUniversity());
circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));
fd.play();
rt.play();
tm.play();
count++;
if (count >= list.size())
count = 0;
});
sleep(10000);
} else
wait();
}
}
};
thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
void setStage(Stage stage) {
stage.setOnCloseRequest(event -> {
thread.interrupt();
});
}
public void playThread(){
if (run) {
run = false;
} else {
if(!run){
synchronized (task) {
task.notify();
}
}
run = true;
}
}
}
run is not volatile and is written to outside of synchronized blocks. This means the task may never see the updated value.
Using Thread.sleep(10000) you do not release the lock on the Task which means the following could happen:
The task starts sleeping
The playThread method changes run to false
The playThread method is invoked again and tries to aquire a lock on the task object which the task still keeps itself leading to the calling thread to be blocked for up to 10 sec
To fix these issues, modify the run field only from a synchronized block and use wait with a timeout instead of sleep:
while (true) {
if (run) {
Platform.runLater(() -> {
nameLvl.setText(list.get(count).getName());
rollLvl.setText("Roll: " + list.get(count).getRoll());
batchLvl.setText("Batch: " + list.get(count).getBatch());
depLvl.setText("Department: " + list.get(count).getDepartment());
uniLvl.setText(list.get(count).getUniversity());
circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));
fd.play();
rt.play();
tm.play();
count++;
if (count >= list.size())
count = 0;
});
wait(10000);
} else
wait();
}
public void playThread(){
synchronized (task) {
run = !run;
if (run) {
task.notify();
}
}
}
This means however starting and stoping the task may speed up the update frequency...
Alternative:
Use a ScheduledExecutorService to schedule updates regularly:
// TODO: shut this down after you're done with it???
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
});
#Override
public void initialize(URL location, ResourceBundle resources) {
...
startTask();
}
private final Runnable updateRunnable = () -> {
Platform.runLater(() -> {
nameLvl.setText(list.get(count).getName());
rollLvl.setText("Roll: " + list.get(count).getRoll());
batchLvl.setText("Batch: " + list.get(count).getBatch());
depLvl.setText("Department: " + list.get(count).getDepartment());
uniLvl.setText(list.get(count).getUniversity());
circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));
fd.play();
rt.play();
tm.play();
count++;
if (count >= list.size())
count = 0;
}
});
};
private ScheduledFuture scheduledFuture;
private void startTask() {
scheduledFuture = executor.scheduleWithFixedDelay(updateRunnable, 0, 10000, TimeUnit.MILLISECONDS);
}
public void playThread() {
if (scheduledFuture == null) {
// nothing running currently
startTask();
} else {
scheduledFuture.cancel();
scheduledFuture = null;
}
}
Or in a way more suitable to JavaFX
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(10), evt -> {
nameLvl.setText(list.get(count).getName());
rollLvl.setText("Roll: " + list.get(count).getRoll());
batchLvl.setText("Batch: " + list.get(count).getBatch());
depLvl.setText("Department: " + list.get(count).getDepartment());
uniLvl.setText(list.get(count).getUniversity());
circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));
fd.play();
rt.play();
tm.play();
count++;
if (count >= list.size())
count = 0;
}
});
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
if (timeline.getStatus == Animation.Status.RUNNING) {
timeline.stop();
} else {
timeline.play();
}
Is there any way of running a handler inside a loop?
I have this code but is not working as it does not wait for the loop but executes the code right way:
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
public void run() {
// need to do tasks on the UI thread
Log.d(TAG, "runn test");
//
for (int i = 1; i < 6; i++) {
handler.postDelayed(this, 5000);
}
}
};
// trigger first time
handler.postDelayed(runnable, 0);
Of course when I move the post delayed outside the loop works but it does not iterate nor execute the times I need:
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
public void run() {
// need to do tasks on the UI thread
Log.d(TAG, "runn test");
//
for (int i = 1; i < 6; i++) {
}
// works great! but it does not do what we need
handler.postDelayed(this, 5000);
}
};
// trigger first time
handler.postDelayed(runnable, 0);
SOLUTION FOUND:
I need to use asyntask along with Thread.sleep(5000) in the doInBackground method:
class ExecuteAsyncTask extends AsyncTask<Object, Void, String> {
//
protected String doInBackground(Object... task_idx) {
//
String param = (String) task_idx[0];
//
Log.d(TAG, "xxx - iter value started task idx: " + param);
// stop
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//
Log.d(TAG, "xxx - iter value done " + param);
return " done for task idx: " + param;
}
//
protected void onPostExecute(String result) {
Log.d(TAG, "xxx - task executed update ui controls: " + result);
}
}
for(int i = 0; i < 6; i ++){
//
new ExecuteAsyncTask().execute( String.valueOf(i) );
}
Instead of using a for loop, you can let the Runnable instance call itself for a specific number of times. These calls will be posted to UI thread queue so, keep that in mind. Also, since the delay is quite large, make sure the event is still needed when you trigger it next time.
The following code should do it:
final Handler handler = new Handler();
int count = 0;
final Runnable runnable = new Runnable() {
public void run() {
// need to do tasks on the UI thread
Log.d(TAG, "Run test count: " + count);
if (count++ < 5) {
handler.postDelayed(this, 5000);
}
}
};
// trigger first time
handler.post(runnable);
Here is a simple logic I made, without moving the for loop inside runnable.
for(int i = 1; i<=5; i++){
...
new Handler().postDelayed(() -> myFunctionToExecute() , i * 1000);
}
So whenever the loop iterates, it just extends the handler delay. And this way, you may achieve. I was searching for something similar, couldn't find anything, because in my case I already did the implementation of for loop, moving it inside the run() creates a mess
My solution to this problem if anyone has simmilar issues:
int count = 0;
public static void method(param1, param2, param3) {
Runnable r = () -> { //put method inside runnable
View view = listView.getChildAt(position); //action to be complete
if (view != null) { //if action is successfully complete
view.setSelected(true); //do something with this
} else { //do a looper
if (count < 10) { //limited looper to certain number
count++;
method(param1, param2, param3); //run the method again
}
};
Handler h = new Handler(); //create a new Handler and post above thread with it
h.postDelayed(r, 300);
}
Basically, I have created an if-else statement where else statement runs the same method with postDelayed() again for a limited number of trials.
This can be another solution
final Handler handler = new Handler();
Runnable runnable = new Runnable() {
int i;
public void run() {
for (i = 1; i < 6; i++) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
// need to do tasks on the UI thread
Log.d(TAG, "runn test");
}
}, 0);
//Add some downtime
SystemClock.sleep(5000);
}
}
};
new Thread(runnable).start();
My JFrame containing an embedded single graph (Graphstream) freezes when I try to update it in a loop that calls Thread,sleep(). I have tried using the same update on a standalone-graph (displayed on it's own) and it works as expected.
I have a single graph embedded in JFrame as follows (AppGraph.java):
public static ViewPanel init(){
graph.addAttribute("ui.stylesheet", styleSheet);
graph.setAutoCreate(true);
graph.setStrict(false);
graph.addAttribute("ui.quality");
graph.addAttribute("ui.antialias");
initGraph();
initNodes(graph);
return attachViewPanel();
}
private static ViewPanel attachViewPanel() {
Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
viewer.enableAutoLayout();
return viewer.addDefaultView(false);
}
private static void initGraph(){
FileSource fs = new FileSourceDOT();
String graph_filename = "graph.gv";
String absolute_path = System.getProperty("user.home") + File.separator + graph_filename;
fs.addSink(graph);
try {
fs.readAll(absolute_path);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
} finally {
fs.removeSink(graph);
}
}
Then this is called in the JFrame class as below:
/*AppWindow.java
* Set up graph
*/
GridBagConstraints graphConstraints = new GridBagConstraints();
graphConstraints.fill = GridBagConstraints.BOTH;
graphConstraints.gridx = 0;
graphConstraints.gridy = 1;
graphConstraints.weightx = 0.5;
graphConstraints.weighty = 0.5;
graphConstraints.gridwidth = 4;
graphConstraints.gridheight = GridBagConstraints.RELATIVE;
add(AppGraph.init(), graphConstraints);`
On the JFrame are buttons for different search algorithms like BFS. During the execution of these algorithms, edges traversed are colored at fixed time intervals to create a sort of animation effect as shown below:
//BFSAlgorithm.java
private void callBFS(Node startNode, Node goalNode) {
startNode.setAttribute("parent", "null");
startNode.setAttribute("level", 0);
startNode.setAttribute("visited?");
LinkedList<Node> queueFrontier = new LinkedList<>();
int level = 1;
queueFrontier.addLast(startNode);
while (!queueFrontier.isEmpty()) {
System.out.println("Level: " + (level - 1));
LinkedList<Node> next = new LinkedList<>();
for (Node node : queueFrontier) {
if (node == goalNode) {
System.out.println(node.getId() + ": Found Found Found!!!");
if (node != startNode) {
colorEdge(node);
}
return;
}
System.out.print(node.getId() + " visited \t");
if (node != startNode) {
colorEdge(node);
}
for (Edge edge : node.getEdgeSet()) {
Node opposite = edge.getOpposite(node);
if (!opposite.hasAttribute("visited?")) {
System.out.print(opposite.getId() + " enqueued \t");
opposite.setAttribute("level", level);
opposite.setAttribute("parent", node);
opposite.setAttribute("visited?");
next.addLast(opposite);
}
}
System.out.print("\n");
}
level++;
queueFrontier = next;
sleep();
}
}
private void colorEdge(Node node) {
Edge visitedEdge = node.getEdgeBetween(node.getAttribute("parent", Node.class));
visitedEdge.setAttribute("ui.color", 0.5);
sleep();
}
private void sleep() {
try {
Thread.sleep(AppWindow.speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
This BFSAlgorithm implements DynamicAlgorithm and extends SinkAdapter. I have extended the SinkAdapter to enable it to interact with the View as the algorithm runs. When I call the BFSAlgorithm, while the algorithm runs and the various println statements are delayed by sleep(), the GUI freezes and is unresponsive until after execution before all the visited edges are then colored. I tried implementing ViewerListener in my AppGraph.java as is documented on the graphstream documentation but it only resulted in an infinite loop that crashed the application:
/*...init() method from AppGraph.java*/
ProxyPipe fromViewer = viewer.newThreadProxyOnGraphicGraph();
fromViewer.addSink(graph);
fromViewer.pump();
while(loop) {
fromViewer.pump(); //
}
Like #Frakool and #MadProgrammer suggested in the comments, if anyone is having similar issues, using SwingWorker and Swing Timer will provide the desired results. According to the documentation:
In general, we recommend using Swing timers rather than general-purpose timers for GUI-related tasks because Swing timers all share the same, pre-existing timer thread and the GUI-related task automatically executes on the event-dispatch thread. However, you might use a general-purpose timer if you don't plan on touching the GUI from the timer, or need to perform lengthy processing.
Here's how I used it to stop the gui freezing. I created a private inner SwingWorker class that uses a Swing Timer as below:
private class BFSTask extends SwingWorker<LinkedList<Node>, Node>{
private ArrayList<Node> visitedList;
private int visitedIndex = 0;
private boolean traversalDone = false;
private Timer traversal = new Timer(AppWindow.speed, new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
Node lastVisited = visitedList.get(visitedIndex);
Edge visitedEdge = lastVisited.getEdgeBetween(lastVisited.getAttribute("parent", Node.class));
visitedEdge.setAttribute("ui.color", 0.5);
visitedIndex++;
if(visitedIndex >= visitedList.size()){
traversal.stop();
traversalDone = true;
if(BFSAlgorithm.this.getPathToGoal() != null){
startTimer();
}
}
}
});
#Override
protected LinkedList<Node> doInBackground() throws Exception {
Node found = publishNodeBreadthFirst(getStartNode(), getGoalNode());
if (found != null) {
return getPathToGoal(found);
} else{
return null;
}
}
#Override
protected void process(List<Node> list) {
visitedList = (ArrayList<Node>) list;
traversal.start();
}
#Override
protected void done() {
try {
BFSAlgorithm.this.pathToGoal = get();
if(traversalDone && BFSAlgorithm.this.getPathToGoal() != null){
startTimer();
}
if(BFSAlgorithm.this.getPathToGoal() == null){
throw new NullPointerException("Goal Not Found.");
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (NullPointerException e){
JOptionPane.showMessageDialog(getAppWindow(), "Goal Node Not Found!", "Error", JOptionPane.ERROR_MESSAGE);
getAppWindow().disableExceptClear();
getAppWindow().changeStatus("Goal node not found");
}
}
private LinkedList<Node> getPathToGoal(Node found) {
LinkedList<Node> path = new LinkedList<>();
Node parent = found.getAttribute("parent");
path.addLast(found);
while (parent != getStartNode()){
path.addLast(parent);
parent = parent.getAttribute("parent");
}
return path;
}
}