Would it be possible to wrap an entire JavaFX application into a while loop to trigger automatic events? For example in a auction house simulator:
package main;
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 {
// Standard JavaFX boilerplate
primaryStage.show();
while(true){
// Get price of this item
// Update table of listings
}
}
public static void main(String[] args) {
launch(args);
}
I know that the loop would block the main thread for the GUI so I was thinking of using the system time + a few seconds in the while loop instead:
double systemTime = systemTime;
double executeTime = systemTime + 5;
while(systemTime != executeTime){
//Do things
executeTime = systemTime + 5;
}
At any rate I know what I need, I just don't know what it's called or implemented.
Well you were right this would most likely block the JavaFX thread, but so would your second statement. As its still looping blocking the thread. What you could do is use
the ScheduledExecutorService to run a Runnable or thread to periodically refresh the gui and update it with what ever information. But you should make sure to wrap the parts which change the GUI within the JavaFX Thread which you can simply do so by using Platform.runLater method. Or for more heavy duty background tasks, use the Task class for JavaFX.
I will use the runLater method for simplicity sakes.
Example:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.lang.management.PlatformManagedObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class Example extends Application {
private Scene myscene;
private TextArea exampleText;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//JavaFX boilerplate
VBox rootVBox = new VBox();
exampleText = new TextArea();
VBox.setVgrow(exampleText, Priority.ALWAYS);
myscene = new Scene(rootVBox);
rootVBox.getChildren().add(exampleText);
//End of JavaFX boilerplate
// Scheduler to update gui periodically
ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor();
Random r = new Random();
Runnable addNewNumber = () -> {
Platform.runLater(()->{
System.out.println("I Just updated!!!");
String newNumber = Integer.toString(r.nextInt(100));
System.out.println("adding "+ newNumber +" to textfield ");
exampleText.appendText(newNumber+"\n");
});
};
executor.scheduleAtFixedRate(addNewNumber, 0, 500, TimeUnit.MILLISECONDS);
primaryStage.setScene(myscene);
primaryStage.show();
}
}
Related
I am having problems with javafx (surprise lol). For some reason my code seems to be running, but the actual gui never shows up on my screen, and it is stuck in the bottom right. When I take out the "implements initializable" in the Sample Controller class, then the gui shows up. I would greatly appreciate any help anyone could give me !!
Thanks
Main Class
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml"));
Scene scene = new Scene(root,400,400);
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);
}
}
Sample Controller Class
package application;
import java.awt.Label;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ListView;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class SampleController{
#FXML
private ListView<String>mainListView;
/**
* Initializing the class
*/
public void initialize(URL url, ResourceBundle rb) {
ObservableList<String>thisMainListView = FXCollections.observableArrayList("SPY","QQQ","Rus2000");
mainListView.setItems(thisMainListView);
}
}
I posted a question similar to this earlier but I wasn't specific enough. Here is a simplified version of my code:
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.scene.control.ProgressBar;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Button;
import javafx.scene.control.MenuBar;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Separator;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.geometry.Pos;
import java.net.URL;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Slider;
public class ProgressTest extends Application {
boolean play = true;
int x = 0;
#Override
public void start(Stage stage) {
GridPane pane = new GridPane(); //pane and Hboxes
HBox hbox = new HBox();
Button update = new Button("Start");
update.setOnAction( e -> {
while(play == true)
{
System.out.println(++x);
}
});
Button pause = new Button("Pause");
pause.setOnAction( e -> {
if(play == true)
{
pause.setText("Play");
play = false;
}
else
{
pause.setText("Pause");
play = true;
}
});
hbox.getChildren().addAll(update, pause);
pane.add(hbox, 0, 1);
Scene scene = new Scene(pane);
stage.setMaxWidth(655);
stage.setMaxHeight(620);
stage.setTitle("Gallery!");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The idea with this code is that, when the user clicks the "Start" button, the program should print out x going up, and pause whenever the user hits "Pause", and resume again when the user hits "Play".
The problem is that whenever I click the "Play" button, the program goes into an infinite loop and I am unable to press the pause button to stop it. Is there something wrong with the way I am going about this? Are there any tricks to getting this to work? Any help would be very appreciated.
Also, I know that some of this may have syntax errors but I know it's correct on my copy of the code, I'm more considered with the logic behind how to get this to work.
There are 2 big problems with your implementation:
The GUI runs on the JavaFX thread. For it to remain responsive any operations in it must finish quickly. When you are running a long computation like your loop on this thread the whole GUI is blocked.
After you manage to set play to false the loop will just exit and clicking on resume will do nothing.
There are several ways to approach this. I will demonstrate one using Thread and CountDownLatch.
I can't explain what a thread is in the scope of the question (tons of material on SO and everywhere), but what is relevant here is that executing the costly operation on a thread which is not the JavaFX thread will solve point 1.
A CountDownLatch is used to block a thread's (or more than one) execution until the latch is released/broken, upon which the thread will continue. It is initialized with an int representing the number of times it needs to count down before it is released. A thread reaching the latch's await method blocks until the latch's countDown method is called the specified amount of times.
Here is a sample code:
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ProgressTest extends Application {
volatile CountDownLatch cl = new CountDownLatch(1);
#Override
public void start(Stage stage) {
Thread thread = new Thread(() -> {
int x = 0;
while (true) {
try {
cl.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(++x);
}
});
thread.setDaemon(true);
Button update = new Button("Start");
update.setOnAction(e -> {
if (!thread.isAlive()) {
cl.countDown();
thread.start();
}
});
BooleanProperty running = new SimpleBooleanProperty(true);
Button pause = new Button("Pause");
pause.textProperty().bind(Bindings.when(running).then("Pause").otherwise("Play"));
pause.setOnAction(e -> {
if (running.get()) {
cl = new CountDownLatch(1);
}
else {
cl.countDown();
}
running.set(!running.get());
});
HBox hbox = new HBox(update, pause);
Scene scene = new Scene(hbox);
stage.setScene(scene);
stage.setTitle("Gallery!");
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The thread spits out numbers "as fast as possible" with the while(true) loop as you did, only it can be paused by reaching the await method. When you press Start the latch is broken and the thread starts and executes continuously. When you press Pause a new latch is created in its place (a latch is a 1-time thing, it can't be reset), which causes the thread to wait in await until someone breaks it with the countDown method (one call is enough because we instantiated it with 1). That is what pressing Resume does.
Calling setDaemon(true) on the thread makes sure it allows the JVM to exit without waiting for it. If the thread must finish before the JVM exists (e.g., it is not a background thread), you can remove it.
Iv'e made the latch volatile which guarantees that different threads will see the same value for it. See also Do you ever use the volatile keyword in Java? and other available sources. In this specific case you don't need poinpoint thread synchronization so it won't have a noticeable effect, but it should be there nonetheless.
Note that I've added a small check on Start that the thread is not already running because starting a thread while it's running throws an exception. You didn't specify what to do if Start is pressed during execution.
While irrelevant to your question, Iv'e demonstrated how you can utilize the JavaFX binding API to have the button's text update automatically with the boolean's value. I "promoted" the control boolean to a property and bound the button's text to its value. It might not be so useful for this situation though.
Notes:
You're setting the height and width of the scene and then call sizeToScene, which makes the previous calls redundant.
You don't need to check a boolean with ==, you can use it directly: if (b == true) is equivalent to if (b) and if (b == false) is equivalent to if (!b).
A better name for your boolean would be one that represents a state ("running"/"paused") rather than an action ("run"/"pause").
I wanted to give SceneBuilder a try because its pain to center objects manually (in code). Unfortunately root is taken from me when I use FXML, so I can't set group as root. What I want to do is to operate on canvas (that is added to root group) while still having SceneBuilder FXML file working.
For example when Ive set root sceneBuilder FXML file then i couldnt add sprites to it in code. Vice versa when I have set root as group then the application wasnt making use of FXML file.
How to connect these? Below is code with use of group as root. I have added sprite, which is not visible in SceneBuilder. Also the button made in SceneBuilder is not visible after compiling the application.
Code:
package zegelardo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.application.Application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.animation.AnimationTimer;
import javafx.event.EventHandler;
import javafx.scene.input.KeyEvent;
import javafx.scene.control.Button;
import java.util.ArrayList;
import java.util.Iterator;
public class Zegelardo extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Group rootx = new Group();
Scene scene = new Scene(rootx);
scene.setFill(Color.BLACK);
stage.setScene(scene);
stage.setTitle("Zegelardo");
Sprite tlo = new Sprite();
tlo.setImage("test55.gif");
tlo.setPosition(0, 0);
Canvas canvas = new Canvas( 800, 400 );
rootx.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
// gc.clearRect(0, 0, 1000,800);
// briefcase.render( gc );
tlo.render(gc);
// Group leaf = new Group();
// root.getChildren().add(canvas);
// Canvas canvas = new Canvas( 800, 400 );
//leaf.getChildren().add( canvas );
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I have the following code in Controller.java:
package sample;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.TabPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.awt.*;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller{
#FXML
HBox fontBox;
Stage stage = (Stage) fontBox.getScene().getWindow();
Scene scene = stage.getScene();
scene.widthProperty().addListener((obs, oldVal, newVal) -> {
stage.setTitle(newVal.toString()); //test
int newSize = Integer.parseInt(newVal.toString());
});
}
IntelliJ doesn't recognize widthProperty and says addListener is invalid method declaration. Does anyone know how to fix that.
The block of code below needs to go in the constructor or initialize method of your controller to be recognized by Intellij:
scene.widthProperty().addListener((obs, oldVal, newVal) -> {
stage.setTitle(newVal.toString()); //test
int newSize = Integer.parseInt(newVal.toString());
});
Intellij does not recognize it because statements need to appear inside of a block of code.
I am working on a clock program in Java and I am unsure how to make the time update secondly. I have tried using a for loop with thread.sleep(1000); but that did not work. Also, if anybody knows how to stop the Stage from opening in white then having a small delay before turning black, that would be really appreciated.
Here is my code:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.scene.paint.Color;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import java.text.DateFormat;
import java.util.Date;
import java.text.SimpleDateFormat;
public class Clock extends Application {
#Override public void start(Stage stage) {
DateFormat df = new SimpleDateFormat("EEE,MMM d yyyy - h:mm:ss a");
Date date = new Date();
String stringDate = df.format(date);
Text text = new Text(10, 60, stringDate);
text.setFont(Font.font ("Digital Dream Fat", 30f));
text.setFill(Color.RED);
HBox hbox = new HBox();
Scene scene = new Scene(new Group(text));
scene.setFill(Color.BLACK);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.setWidth(710);
stage.setHeight(80);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
First of all, you have to remember that UI's components are not thread-safe thus any update operations on the ui must be done within the ui's thread itself and not from background threads otherwise they will block the UI's thread.
To update it you can use thread safe mechanisms, one of them is Timeline class. Let's now implement it in your code.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.scene.paint.Color;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import java.text.DateFormat;
import java.util.Date;
import java.text.SimpleDateFormat;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.util.Duration;
public class Clock extends Application {
#Override public void start(Stage stage) {
DateFormat df = new SimpleDateFormat("EEE,MMM d yyyy - h:mm:ss a");
Date date = new Date();
String stringDate = df.format(date);
Text text = new Text(10, 60, stringDate);
text.setFont(Font.font ("Digital Dream Fat", 30f));
text.setFill(Color.RED);
HBox hbox = new HBox();
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
Date update = new Date();
String stringNewDate = df.format(update);
text.setText(stringNewDate);
}
}
), new KeyFrame(Duration.seconds(1)));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play(); // timeline.stop()
Scene scene = new Scene(new Group(text));
scene.setFill(Color.BLACK);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.setWidth(710);
stage.setHeight(80);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The sleep/pause part is controlled on this line .. new KeyFrame(Duration.seconds(1))) so every minute will be Duration.minutes(1), to stop it just take Timeline's instance then do timeline.stop()
You have to setup a TimerTask and run it in a separate thead.
https://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
TimerTask can't update the JavaFX Label text