i have a javafx application, i need to show/edit/manipulate some objects at runtime.
For example i have an anchorPane, when i click a button i need to set the pane visible, and during the execution of the logic, set some labels to visible.
here's my code:
//MyController.java
public class MyController extends AnchorPane {
#FXML
private AnchorPane myPane;
#FXML
private Label label1;
#FXML
private Label label2;
#FXML
private Button button1;
private Main application;
public void setApp(Main application) {
this.application = application;
}
public void initialize()
{
myButton.setOnAction(new EventHandler<ActionEvent>()
{
#override
public void handle(ActionEvent arg0)
{
myPane.setVisile(true);.
//long run action
label1.setVisible(true);
//long run action
label2.setVisibile(true);
//long run action
button1.setText("myText");
//long run action
button1.setVisibile(true);
//end of operations
}
)};
}
}
I don't understand why the GUI freeze and all the elements are shown together at the end of the operations, I tried to see something about javafx thread management but I couldn't implement a satisfactory solution.. can someone help me?
Thank you all.
Here's a solution leveraging the java.util.concurrent and javafx.concurrent APIs:
Executor exec = Executors.newSingleThreadedExecutor();
// ...
button.setOnAction(event -> {
myPane.setVisible(true);
Task<Void> task1 = createTask(this::doTask1, () -> label1.setVisible(true));
Task<Void> task2 = createTask(this::doTask2, () -> label2.setVisible(true));
Task<Void> task3 = createTask(this::doTask3, () -> button1.setText("myText"));
Task<Void> task4 = createTask(this::doTask4, () -> button1.setVisible(true));
List.of(task1, task2, task3, task4).forEach(exec::execute);
});
// ...
private Task<Void> createTask(Runnable longRunningProcess, Runnable onSucceeded) {
Task<Void> task = new Task<>() {
#Override
public Void call() {
longRunningProcess.run();
return null ;
}
};
task.setOnSucceeded(e -> onSucceeded.run());
return task ;
}
private void doTask1() {
// long running operation...
}
private void doTask2() {
// long running operation...
}
private void doTask3() {
// long running operation...
}
private void doTask4() {
// long running operation...
}
Here is a quick runnable example in writing my comment I realized I glossed over the fact that it is not already on a different thread and was reminded when I read VGR's comment here is a runnable example I used just a new Thread for a simple demonstration but would recommend a Task or a Service. Note you can see that the Main Thread is not freezing by the fact that you can still click the RadioButtons
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
vBox.setPrefSize(200, 200);
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().add(new Label("Main APP"));
Pane pane = new Pane(new Label("THIS IS INSIDE THE PANE"));
vBox.getChildren().add(pane);
Button button = new Button("Activate Pane Cloaking");
button.setOnAction(event -> {
new Thread(() -> {
simulateCodeRunning();//5 sec
Platform.runLater(() -> pane.setVisible(!pane.isVisible()));
if (pane.isVisible())
Platform.runLater(() ->button.setText("Deactivate Cloaking"));
else
Platform.runLater(() ->button.setText("Activate Pane Cloaking"));
}).start();
});
vBox.getChildren().add(button);
ToggleGroup radioGroup = new ToggleGroup();
RadioButton radioButton1 = new RadioButton("Click Me");
radioButton1.setToggleGroup(radioGroup);
RadioButton radioButton2 = new RadioButton("Click Me");
radioButton2.setToggleGroup(radioGroup);
vBox.getChildren().addAll(radioButton1, radioButton2);
primaryStage = new Stage();
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
private void simulateCodeRunning() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Related
I am making a simple JavaFX college course project and I need a good way of dealing with threads, mainly running them while a certain flag is activated.
This is a simple sketch I came up with:
public class ListenerService extends Thread {
private static ArrayList<ListenerService> listeners = new ArrayList<>();
private ToggleButton button;
private File folder;
private SimpleBooleanProperty active = new SimpleBooleanProperty();
ListenerService(ToggleButton button, String pathname) {
this.button = button;
this.folder = new File(pathname);
button.setOnAction(event -> active.set(button.isSelected()));
active.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
-> {if (newValue.booleanValue()) start();});
listeners.add(this);
}
#Override
public void run() {
while(active.get())
System.out.print(".");
}
The process is as following:
The user dynamically creates a ToggleButton on the form. A
ListenerService object is created, to which a button and a directory
are assigned.
A listener is assigned to the button - if it's clicked - activate
the flag. Otherwise, deactivate. The flag here is a
SimpleBooleanProperty instance.
If the flag is switched on, run the thread. The thread will run
while the flag is active. If the user toggles the button again and
deactivates it, the condition in the while loop would fail and
thread should stop running.
As soon as I run the program, it freezes. I tried making the flag volatile, but nothing changed. Since the flag is controlled externally (from GUI), there isn't a way to make this method synchronized.
What am I doing wrong?
You basically create a new Thread that runs as long as the button is selected and exits when the button is not selected.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ThreadApp extends Application {
public class Worker implements Runnable{
private boolean active;
public void setActive(boolean active) {
this.active = active;
}
#Override
public void run() {
while(active)
{
System.out.println("Active! " + System.currentTimeMillis());
}
}
}
public class WorkerToggle extends ToggleButton {
Worker worker;
public WorkerToggle(String text) {
super(text);
this.worker = new Worker();
setOnAction((event) -> {
if(isSelected())
{
worker.setActive(true);
Thread thread = new Thread(worker);
thread.setDaemon(true);
thread.start();
}else
{
worker.setActive(false);
}
});
}
}
#Override
public void start(Stage primaryStage) {
BorderPane rootPane = new BorderPane();
Scene scene = new Scene(rootPane);
rootPane.setCenter(new WorkerToggle("toggle me"));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This should work fine, but creating Threads can be expensive, so you might want to look into ThreadPoolExecutor if you notice some performance problems there.
In JavaFX you have the ability to use a scheduled service to run things off the main FX thread. Here is simple sample that might help.
public class JavaFXApplication3 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
PollingService service = new PollingService();
service.setPeriod(Duration.millis(1000)); // sysout every second
ToggleButton tb = new ToggleButton("Start Process");
tb.setOnAction(event -> {
System.out.println(tb.isSelected());
if(tb.isSelected()){
service.reset();
service.start();
}else {
service.cancel();
}
});
VBox vbox = new VBox(tb);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();;
}
private class PollingService extends ScheduledService<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() {
System.out.print(".#.");
return null;
}
};
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I want to close my JavaFX application if the user is inactive for a period of time. I have this code in Swing and I want to do the same in JavaFX. This class redirect the user to the login panel if no event happens during the time indicated.
import javax.swing.Timer;
public class AutoClose {
private Timer timer;
public AutoClose(JFrame centralpanel) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
Object source = event.getSource();
if (source instanceof Component) {
Component comp = (Component) source;
Window win = null;
if (comp instanceof Window) {
win = (Window) comp;
} else {
win = SwingUtilities.windowForComponent(comp);
}
if (win == centralpanel) {
timer.restart();
}
}
}
}, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
timer = new Timer(3600000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
centralpanel.dispose();
//Redirect to the login panel.
Login login = new Login();
login.setVisible(true);
timer.stop();
JOptionPane.showMessageDialog(null,"Connection closed due to inactivity");
}
});
timer.start();
}
});
}
}
Create a PauseTransition to trigger a delayed exit and add a event filter for InputEvents to all your scenes that restart the transition:
#Override
public void start(Stage primaryStage) throws IOException {
Button button = new Button("abcde");
StackPane root = new StackPane(button);
Scene scene = new Scene(root, 400, 400);
// create transition for logout
Duration delay = Duration.seconds(10);
PauseTransition transition = new PauseTransition(delay);
transition.setOnFinished(evt -> logout());
// restart transition on user interaction
scene.addEventFilter(InputEvent.ANY, evt -> transition.playFromStart());
primaryStage.setScene(scene);
primaryStage.show();
transition.play();
}
private void logout() {
// TODO: replace with logout code
Platform.exit();
}
Note: It's important to use an event filter since events can be consumed before they reach an event handler.
I have done this code and now it works correctly as I wanted.
public class AutoClose {
private Timeline timer;
public AutoClose(VBox mainPanel) {
timer = new Timeline(new KeyFrame(Duration.seconds(3600), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Inactivity");
alert.setHeaderText("Connection closed due to inactivity");
alert.show();
try {
Stage mainWindowStage = Login.getPrimaryStage();
Parent root = FXMLLoader.load(getClass().getResource("/view/Login.fxml"));
Scene scene = new Scene(root);
mainWindowStage.setScene(scene);
mainWindowStage.show();
timer.stop();
} catch (IOException ex) {
}
}
}));
timer.setCycleCount(Timeline.INDEFINITE);
timer.play();
mainPanel.addEventFilter(MouseEvent.ANY, new EventHandler<Event>() {
#Override
public void handle(Event event) {
timer.playFromStart();
}
});
}
}
I have A.fxml and B.fxml. A runing with Java Application override start method. I want to every 40 min in loop(5 times) { open new stage B.fxml and wait stage.close, if stage close continue loop open new stage B fxml. Loop this five times. I try timer timertask i could not. I try JavaFX Service i could not. I create Mythread extend Thread object. This time i could not control loop for next stage. When for statement start opening 5 stage. But i want to loop wait for currentstage is close then go next loop. This is my fail code;
public class Driver extends Application {
public static Stage stage;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource(View.SETTINGS));
Parent root = loader.load();
Scene scene = new Scene(root);
stage = primaryStage;
stage.setScene(scene);
stage.setTitle("Info Library");
stage.setResizable(false);
stage.show();
RandomQuestionThread thread = new RandomQuestionThread();
if (DBContext.settings.isAbbreviation() || DBContext.settings.isTranslation()) {
thread.start();
}
}
public static void main(String[] args) throws InterruptedException {
DBContext.settings = DBContext.getInstance().settings().getSettings();
launch(args);
HibernateUtil.getSessionFactory().close();
}
}
public class RandomQuestionThread extends Thread {
Thread randomThread = new Thread(this);
private String fxml;
private static String TITLE;
#Override
public void run() {
while (true) {
try {
Thread.sleep(DBContext.settings.getAutoQuestionTime() * 6000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for (int i = 0; i<DBContext.settings.getAutoQuestionCount(); i++) {
randomFxml();
Platform.runLater(()->{
Parent root = null;
try {
root = new FXMLLoader(getClass().getResource(fxml)).load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setTitle(TITLE);
stage.show();
System.out.println(currentThread().getName());
});
}
}
}
private void randomFxml() {
int start = 0;
if (DBContext.settings.isTranslation() && DBContext.settings.isAbbreviation()) {
start = new Random().nextInt(2);
} else if (DBContext.settings.isTranslation()) {
start = 1;
}
switch (start) {
case 0:
fxml = View.ABBREVIATION;
break;
case 1:
fxml = View.TRANSLATION;
break;
default:
break;
}
if (start == 0) {
TITLE = "KISALTMA SORUSU";
} else TITLE = "ÇEVİRİ SORUSU";
}
}
I need to work more Java multi threads. But after fix this problem. Please explain where I'm doing wrong. In loop write console currentThread name console result "Java Apllication Thread". But i set my thread name "MyThread". I'm so confused.My brain gave blue screen error.
You've put your System.out.println(currentThread().getName()) statement into Platform.runLater(), which means that it will be executed on JavaFX Application Thread (see JavaDoc).
Regarding your question about scheduling some task to repeat fixed number of times with predefined rate, this post could help you.
In loop write console currentThread name console result "Java Apllication Thread". But i set my thread name "MyThread". I'm so confused.
Using Platform.runLater you schedule the Runnable to be executed on the javafx application thread instead of the current thread which allows you to modify the UI, but also results in the current thread being the javafx application thread instead of the thread you call Platform.runLater from...
If you want to continue the "loop" after the window has been closed, you should schedule opening the next window after the last one has been closed. Stage.showAndWait() is a convenient way to wait for the stage to be closed.
For scheduling I'd recommend using a ScheduledExecutorService:
private ScheduledExecutorService executor;
#Override
public void stop() throws Exception {
// stop executor to allow the JVM to terminate
executor.shutdownNow();
}
#Override
public void init() throws Exception {
executor = Executors.newSingleThreadScheduledExecutor();
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Start");
btn.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// just display a "empty" scene
Scene scene = new Scene(new Pane(), 100, 100);
Stage stage = new Stage();
stage.setScene(scene);
// schedule showing the stage after 5 sec
executor.schedule(new Runnable() {
private int openCount = 5;
#Override
public void run() {
Platform.runLater(() -> {
stage.showAndWait();
if (--openCount > 0) {
// show again after 5 sec unless the window was already opened 5 times
executor.schedule(this, 5, TimeUnit.SECONDS);
}
});
}
}, 5, TimeUnit.SECONDS);
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I fix this. I used Timer and TimeTask in my main controller init method. And its work. But same code in app start method or in mian method stage didnt wait. I used stageshowandwait() method but thread didnt wait. But same code woked in main controller init method. Why i dont know.
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
Platform.runLater(()->{
for (int i = 0; i<4; i++) {
Parent root = null;
try {
root = new FXMLLoader(getClass().getResource(View.ABBREVIATION)).load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setTitle("deneme");
stage.showAndWait();
}
});
}
};
timer.schedule(timerTask, 6000);
I have a method that read values from the the database and returns a Map<Integer,String>. This method takes some time to return the map.
Till the time values are getting read I want a progress indicator(only loading ring like indicator will be enough,no need for progress bar) to be displayed on screen and all other components should be disabled till the time progress bar is shown.
public void scanDevice() {
ObservableList<TextField> list = FXCollections.observableArrayList(vehicleId, vehicleName, deviceType,
offboardBroker1, offboardBroker2, postfixQueue, pKIServer);
editedValuesMap.clear();
// devicePlugged = true;
if (cbChooseProject.getSelectionModel().getSelectedItem() != null) {
try {
devicePlugged = dsAdapter.getAdapter();
if (devicePlugged) {
if (bScanDevice.isFocused()) {
readMap = new HashMap<Integer, String>();
//Process Start
readMap = dsAdapter.initScan();
//Process End
if (!readMap.isEmpty() && readMap != null) {
isWritten = true;
isDeviceSideEnabled();
editDeviceContents.setDisable(false);
vehicleId.setText(readMap.get(0));
vehicleName.setText(readMap.get(1));
deviceType.setText(readMap.get(2));
offboardBroker1.setText(readMap.get(3));
offboardBroker2.setText(readMap.get(4));
postfixQueue.setText(readMap.get(5));
pKIServer.setText(readMap.get(6));
lContentsSerialNo.setText(readMap.get(7));
}
}
}
You could disabled all nodes with a method like the following but if you are also wanting to wait while something is happening an overlay using StackPanes may be the preferred choice.
public void setNodesDiabled(boolean disable, Node... nodes) {
for(Node node : nodes) {
node.setDisable(disable);
}
}
With an arbitrary node count, you can disable and re-enable as many nodes that are relevant to the process. It also helps to clean up as you won't have several node.setDisable(true); node2.setDisable(true); and so on.
Here in this example you won't need setNodesDisabled() because the StackPane overlay prevents clicking anything other than what's inside it. The background color is gray with 70% alpha so that you can tell it's an overlay.
public class ProgressExample extends Application {
public StackPane layout, main, progress;
public StackPane createProgressPane() {
ProgressIndicator indicator = new ProgressIndicator();
indicator.setMaxHeight(50);
indicator.setMaxWidth(50);
StackPane pane = new StackPane();
pane.setAlignment(Pos.CENTER);
pane.setStyle("-fx-background-color: rgba(160,160,160,0.7)");
pane.getChildren().add(indicator);
Task<Void> task = new Task<Void>(){
protected Void call() throws Exception {
// Your process here.
// Any changes to UI components must be inside Platform.runLater() or else it will hang.
Thread.sleep(2000);
Platform.runLater(() -> {
layout.getChildren().remove(pane);
});
return null;
}
};
new Thread(task).start();
return pane;
}
public StackPane createMainPane() {
Label label = new Label("Hello World!");
label.setFont(Font.font("Tahoma", FontWeight.SEMI_BOLD, 16));
Button start = new Button("Start Process");
start.setOnAction(action -> {
progress = createProgressPane();
layout.getChildren().add(progress);
});
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().addAll(label, start);
vbox.setPadding(new Insets(10,10,10,10));
StackPane pane = new StackPane();
pane.getChildren().add(vbox);
return pane;
}
public void start(Stage stage) throws Exception {
main = createMainPane();
layout = new StackPane();
layout.getChildren().add(main);
Scene scene = new Scene(layout, 900, 550);
stage.setScene(scene);
stage.setTitle("Progress Example");
stage.setOnCloseRequest(e -> {
Platform.exit();
System.exit(0);
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I believe the problem is that you are trying to change the values of TextFields inside the Task which is not the FX application thread which is why you are getting Not on FX application thread. To fix this you need to put your lines that modify nodes inside a Platform.runLater() like the following to your if statement.
if (readMap != null && !readMap.isEmpty()) { // Swap the order, can't check empty if it's null.
isWritten = true;
isDeviceSideEnabled();
Platform.runLater(() -> {
editDeviceContents.setDisable(false);
vehicleId.setText(readMap.get(0));
vehicleName.setText(readMap.get(1));
deviceType.setText(readMap.get(2));
offboardBroker1.setText(readMap.get(3));
offboardBroker2.setText(readMap.get(4));
postfixQueue.setText(readMap.get(5));
pKIServer.setText(readMap.get(6));
lContentsSerialNo.setText(readMap.get(7));
});
}
Here is an SSCCE:
It uses a Service that can be started more than once. It is not completebut something to start with.
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
Service<Void> serv = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
int maxWork = 10;
for (int i = 0; i < maxWork; i++) {
Thread.sleep(1000);
updateProgress(i + 1, maxWork);
}
return null;
}
#Override
protected void succeeded() {
super.succeeded();
updateProgress(1, 1);
}
#Override
protected void cancelled() {
super.cancelled();
updateProgress(1, 1);
}
#Override
protected void failed() {
super.failed();
updateProgress(1, 1);
}
};
}
};
ProgressIndicator pi = new ProgressIndicator();
pi.progressProperty().bind(serv.progressProperty());
Button bStart = new Button("Start");
bStart.setOnAction(e -> {
serv.reset();
serv.start();
});
root.setCenter(bStart);
root.setBottom(pi);
primaryStage.setScene(scene);
primaryStage.show();
pi.getScene().getRoot().disableProperty().bind(serv.runningProperty());
}
public static void main(String[] args) {
launch(args);
}
}
In CSS I added:
.progress-indicator:disabled {
-fx-opacity: 1;
}
This question already has an answer here:
JavaFx - Updating GUI
(1 answer)
Closed 6 years ago.
Can you explain me how can I refresh value in Label?
In initialize I bind the text of the Label to a StringProperty. Here it is ok.
I have Button, and on button press I want to update the Label value in every iteration step.
But I can see only the final value. Why?
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) throws InterruptedException {
for(int i=0;i<1001;i++){
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
//Handle exception
}
this.value.setValue(i+"");
}
}
// Bind
private StringProperty value = new SimpleStringProperty("0");
#Override
public void initialize(URL url, ResourceBundle rb) {
// Bind label to value.
this.label.textProperty().bind(this.value);
}
When you call Thread.sleep(1); you actually stop the JavaFX Application Thread (GUI Thread), therefore you prevent it to update the GUI.
What you basically need is a background Task which actually stops for a certain amount of time, then updates the GUI on the JavaFX Application Thread by calling Platform.runLater before it goes to sleep again.
Example:
public class MyApplication extends Application {
private IntegerProperty value = new SimpleIntegerProperty(0);
#Override
public void start(Stage primaryStage) {
try {
HBox root = new HBox();
Scene scene = new Scene(root, 400, 400);
Label label = new Label();
Button button = new Button("Press Me");
button.setOnAction(event -> {
// Background Task
Task<Void> task = new Task<Void>() {
#Override
protected Void call() {
for (int i = 0; i < 1001; i++) {
int intVal = i;
try {
Thread.sleep(1);
} catch (InterruptedException ignored) {
}
// Update the GUI on the JavaFX Application Thread
Platform.runLater(() -> value.setValue(intVal));
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
});
label.textProperty().bind(value.asString());
root.getChildren().addAll(button, label);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Only thing left is to update the button callback.