Popup intended to display ProgressBar not showing in JavaFX - java

I want to display a ProgressBar using a Popup in JavaFX so that I will be able to make the ProgressBar disappear by calling the PopUp's hide() method. However, rather than disappearing when the background task is complete, the ProgessBar doesn't show at all.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) {
StackPane root = new StackPane();
Popup popup = new Popup();
ProgressBar bar = new ProgressBar();
popup.getContent().add(bar);
Task<Void> task = new Task<>() {
#Override
protected Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
updateProgress(i, 100);
} catch (Exception ignored) {
}
}
return null;
}
};
bar.progressProperty().bind(task.progressProperty());
task.setOnSucceeded(workerStateEvent -> popup.hide());
popup.show(stage);
new Thread(task).start();
stage.setScene(new Scene(root, 100, 100));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When I run the above code, all I see is a blank stage, shown here:
If I add the ProgressBar to the Stage manually using root.getChildren().add(bar), the bar displays correctly, with its progressProperty properly bonded, so I know that the issue is with the Popup. However, using that method, the bar doesn't disappear when the background task is complete.

I can't find this documented anywhere, but I think the owner of a Popup must be showing for the Popup.show(owner) method to succeed. So the following reordering of your code (calling stage.show() before popup.show(stage)) works:
#Override
public void start(Stage stage) {
StackPane root = new StackPane();
Popup popup = new Popup();
ProgressBar bar = new ProgressBar();
popup.getContent().add(bar);
Task<Void> task = new Task<>() {
#Override
protected Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
updateProgress(i, 100);
} catch (Exception ignored) {
}
}
return null;
}
};
bar.progressProperty().bind(task.progressProperty());
task.setOnSucceeded(workerStateEvent -> popup.hide());
stage.setScene(new Scene(root, 100, 100));
stage.show();
popup.show(stage);
new Thread(task).start();
}
Note your other approach works: you just have to remove the progress bar from the scene graph when the task is complete:
#Override
public void start(Stage stage) {
StackPane root = new StackPane();
// Popup popup = new Popup();
ProgressBar bar = new ProgressBar();
// popup.getContent().add(bar);
root.getChildren().add(bar);
Task<Void> task = new Task<>() {
#Override
protected Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
updateProgress(i, 100);
} catch (Exception ignored) {
}
}
return null;
}
};
bar.progressProperty().bind(task.progressProperty());
// task.setOnSucceeded(workerStateEvent -> popup.hide());
task.setOnSucceeded(event -> root.getChildren().remove(bar));
stage.setScene(new Scene(root, 100, 100));
stage.show();
// popup.show(stage);
new Thread(task).start();
}

Related

JavaFX Simple Update Label (Threading)

I'm trying to demonstrate to a few beginner programmers how to set a label on a JavaFX app to auto update. Basically they would like the value to decrease every minute or so on the label without any user interaction.
Java isn't my strong point and looking through some previous questions I get that I need to deal with threads and Runnable().
I have put the code together below that works, but I was just wondering if there is a better way of doing this or an easier way to demonstrate the same outcome with simpler code.
public class MainTimer2 extends Application {
private int count = 100;
private Label response = new Label(Integer.toString(count));
public static void main(String[] args) {
launch(args);
}
//Update function
private void decrementCount() {
count--;
response.setText(Integer.toString(count));
}
#Override
public void start(Stage myStage) {
myStage.setTitle("Update Demo");
//Vertical and horizontal gaps set to 10px
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 200, 100);
myStage.setScene(myScene);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Runnable updater = new Runnable() {
#Override
public void run() {
decrementCount();
}
};
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("Timer error");
}
// UI update is run on the Application thread
Platform.runLater(updater);
}
}
});
// don't let thread prevent JVM shutdown
thread.setDaemon(true);
thread.start();
rootNode.getChildren().addAll(response);
myStage.show();
}
}
Count down by using PauseTransition:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MainTimer2 extends Application {
private int count = 100;
private Label response = new Label(Integer.toString(count));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage myStage) {
myStage.setTitle("Update Demo");
//Vertical and horizontal gaps set to 10px
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 200, 100);
myStage.setScene(myScene);
rootNode.getChildren().addAll(response);
myStage.show();
update();
}
private void update() {
PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(event ->{
decrementCount();
pause.play();
});
pause.play();
}
//Update function
private void decrementCount() {
count = (count > 0) ? count -1 : 100;
response.setText(Integer.toString(count));
}
}
Alternatively you could use Timeline:
private void update() {
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> {
decrementCount();
}
);
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
//if you want to limit the number of cycles use
//timeline.setCycleCount(100);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}

JavaFX: Disable all components while a process is running and show progress indicator

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;
}

How to listen focusing child event

here is my code:
#Override
void start(Stage stage) throws Exception {
def root = new VBox() {
{
children.add(new TextArea() {
{
setId("ta1")
}
})
children.add(new TextArea() {
{
setId("ta2")
}
})
}
}
root.setOnFocus(new OnFocus() {
void onFocus(Node focusedTarget) {
// handle focusedTarget
}
})
def scene = new Scene(root, 800, 600)
stage.setScene(scene)
stage.show()
}
I hope implement following code to handle focusing child events
root.setOnFocus(new OnFocus() {
void onFocus(Node focusedTarget) {
// handle focusedTarget
}
})
if i set #ta1 and #ta2's focusedProperty, if child are large, it hard to do it, so I hope directly listen the parent, how to do it?
The standard event dispatching can be used to fire a custom event on the Scene. A listener atached to the focusOwner property of the Scene can be used to trigger the event.
Example (java)
public class FocusEvent extends Event {
public static final EventType FOCUS_EVENT_TYPE = new EventType(EventType.ROOT);
public FocusEvent(Object source, EventTarget target) {
super(source, target, FOCUS_EVENT_TYPE);
}
}
#Override
public void start(Stage primaryStage) {
TextArea ta1 = new TextArea();
ta1.setId("ta1");
TextArea ta2 = new TextArea();
ta2.setId("ta2");
VBox root = new VBox(ta1, ta2);
root.addEventHandler(FocusEvent.FOCUS_EVENT_TYPE, evt -> {
System.out.println("focused "+ evt.getTarget());
});
ta1.addEventHandler(FocusEvent.FOCUS_EVENT_TYPE, evt -> {
System.out.println("You focused the first TextArea");
evt.consume();
});
Scene scene = new Scene(root);
scene.focusOwnerProperty().addListener((o, old, newValue) -> {
if (newValue != null) {
FocusEvent event = new FocusEvent(scene, newValue);
Event.fireEvent(newValue, event);
}
});
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX update text from Task

I want to change text I create a task and increment i, but I want to set a new text on this same place when i is changed, but old text doesn't disappear. It's my code. On swing I will be use repaint()
Task task = new Task<Void>() {
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
final int finalI = i;
Platform.runLater(new Runnable() {
#Override
public void run() {
String a = "aaa";
if(finalI>4){
a = "sadsa";
}
if(finalI>10){
a = "sadsadsadsadsad";
}
gc.fillText(a, 150, 250+10);
}
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
As I mentioned in my comment, the problem is that Canvas really acts like a drawing board. You have drawn some text on it then you have drawn another text without erasing the previous text.
In your case, when you want to store a reference to the text to be able to update it, it is more reasonable to use a Pane and put a Text instance on it.
I have created an example for you:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
Pane pane = new Pane();
Text text = new Text("");
pane.getChildren().add(text);
Task<Void> task = new Task<Void>() {
String a = "Initial text";
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
if (i > 4)
a = "I is bigger than 4";
if (i > 10)
a = "I is bigger than 10";
Platform.runLater(() -> {
text.setText(a);
// If you want to you can also move the text here
text.relocate(10, 10);
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
root.setCenter(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Note: You can also eliminate the Platform.runlater(...) block by updating the messageProperty of the task inside call() then binding the textProperty of the Text to this property.
Example:
Pane pane = new Pane();
Text text = new Text("");
text.relocate(10, 10);
pane.getChildren().add(text);
Task<Void> task = new Task<Void>() {
{
updateMessage("Initial text");
}
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
if (i > 4)
updateMessage("I is bigger than 4");
if (i > 10)
updateMessage("I is bigger than 10");
i++;
Thread.sleep(1000);
}
}
};
text.textProperty().bind(task.messageProperty());
Thread th = new Thread(task);
th.setDaemon(true);
th.start();

JavaFX - Refresh Label [duplicate]

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.

Categories