I have a simple login window in JavaFX. When the user inserts his username and password I want to make a simple string "progress bar" in another thread while the main thread processes the inputs.
When the main thread gets to the if statement (let's say the passwords don't match) I want the progress to stop when the alert is thrown. But with this code it continues even after the alert is thrown.
public void validateLogin(ActionEvent actionEvent) throws Exception {
Thread thread = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(loading_txt.getText().length() < 10)loading_txt.setText(loading_txt.getText() + "|");
else loading_txt.setText("|");
}
});
thread.start();
String username = username_field.getText();
String password = password_field.getText();
if (!(BCrypt.checkpw(password_field.getText(), dbHandler.getLoginByUsername(username_field.getText()).getPassword()))) {
throwAlert(Alert.AlertType.ERROR,"Login problem", "Password doesn't match.", "Wrong password. Please, check out and try it again. ");
thread.join();
return;
}
thread.join();
//other code
}
So I made a little change in the if statement and put the thread.join() before the alert. Now the progress can't be even seen.
if (!(BCrypt.checkpw(password_field.getText(), dbHandler.getLoginByUsername(username_field.getText()).getPassword()))) {
thread.join();
throwAlert(Alert.AlertType.ERROR,"Login problem", "Password doesn't match.", "Wrong password. Please, check out and try it again. ");
return;
}
How does this little change cause the progress to be seen or not to be seen? What do I have to change to stop the progressing when the alert is thrown? Could it be caused by some functionality in JavaFX?
Here is an Example, you may take the idea and apply it to your program (Explanation in comments).
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextField;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.scene.control.ProgressBar;
public class ProgressBarExample extends Application{
ProgressBar pb = new ProgressBar(); // your progress bar
#Override
public void start(Stage primaryStage) throws Exception {
// The structure and components are for example only
TextField password = new TextField();
Button test = new Button("Test");
HBox container = new HBox();
container.getChildren().addAll(password, test);
container.setAlignment(Pos.CENTER);
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(container);
// add action listener to the button
test.setOnAction(e->{
// when it's pressed add Progress bar and other stuff that are concerned with the GUI!
Platform.runLater(new Runnable(){ // always use this to update GUI components
#Override
public void run() {
root.getChildren().add(pb);
// you can add label to the root...etc
// or update your progress bar ..etc
// in a nutshell: anything needs to be updated in GUI.
}
});
Task<Boolean> validatePassword = new Task<Boolean>(){ // always use Task to do complex-long calculations
#Override
protected Boolean call() throws Exception {
return validatePassword(password); // method to validate password (see later)
}
};
validatePassword.setOnSucceeded(ee->{ // when Task finishes successfully
System.out.println("Finished");
root.getChildren().remove(pb); // remove the progress bar
if(!validatePassword.getValue()){
Alert alert = new Alert(AlertType.ERROR, "Wrong Password", ButtonType.OK);
alert.showAndWait();
}
});
validatePassword.setOnFailed(eee->{ // if it fails
System.out.println("Failed");
root.getChildren().remove(pb); // remove it anyway
});
new Thread(validatePassword).start(); // add the task to a thread and start it
});
Scene scene = new Scene(root, 300,300);
primaryStage.setScene(scene);
primaryStage.show();
}
// validate here in this method
public static boolean validatePassword(TextField password){
for(int i=0; i<99999; i++){ // suppose it is a long process
System.out.println("Processing");
}
if(password.getText().equalsIgnoreCase("Invalid")){ // suppose it's invalid, just for testing
return false
}
return true;
}
public static void main(String[] args) {
launch();
}
}
Test:
Related
I am trying to raise a custom loading dialog in java and then execute some synchronous function which takes a few seconds.
I would like the dialog to be present as long as the function executes and once it finishes I would close the dialog.
My Dialog looks as follows:
public abstract class LoaderControl extends Control implements SimpleDialogInfo {
private static final StyleablePropertyFactory<LoaderControl> FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData());
private LoaderDialogResponse response;
private final DialogInfo dialogInfo;
private final SimpleStringProperty text = new SimpleStringProperty("");
private final SimpleBooleanProperty spinnerVisible = new SimpleBooleanProperty(true);
private UpdaterStates state;
private CloseDialogFunction onClose;
#Override
public void closeDialog(){
onClose.closeDialog();
}
#Override
public void setCloseDialog(CloseDialogFunction onClose){
this.onClose = onClose;
}
}
This is how I create it and show it:
public void createIndependentDialog(SimpleDialogInfo content, EventHandler<MouseEvent> onClose) {
Platform.runLater(() -> {
Stage stage = new Stage();
Parent p = new StackPane();
Scene s = new Scene(p);
stage.setScene(s);
MFXGenericDialog dialogContent = MFXGenericDialogBuilder.build()
.makeScrollable(true)
.setShowAlwaysOnTop(false)
.get();
MFXStageDialog dialog = MFXGenericDialogBuilder.build(dialogContent)
.toStageDialogBuilder()
.initModality(Modality.APPLICATION_MODAL)
.setDraggable(true)
.initOwner(stage)
.setTitle("Dialogs Preview")
.setOwnerNode(grid)
.setScrimPriority(ScrimPriority.WINDOW)
.setScrimOwner(true)
.get();
dialogContent.setMinSize(350, 200);
MFXFontIcon infoIcon = new MFXFontIcon(content.getDialogInfo().getIcon(), 18);
dialogContent.setHeaderIcon(infoIcon);
dialogContent.setHeaderText(content.getDialogInfo().getHeader());
dialogContent.setContent((Node) content);
MFXGenericDialog finalDialogContent = dialogContent;
MFXStageDialog finalDialog = dialog;
content.setCloseDialog(dialog::close);
convertDialogTo(String.format("mfx-%s-dialog", content.getDialogInfo().getDialogType()));
if(onClose != null)
dialogContent.setOnClose(onClose);
dialog.showAndWait();
});
}
This is how it looks like in the calling class:
DialogLoaderControlImpl preloader = new DialogLoaderControlImpl(new LoaderDialogInfo("Searching For New Versions"));
DialogsController.getInstance().createIndependentDialog(preloader);
someSynchronousMethod();
preloader.closeDialog();
The issue is that when I get to the "preloader.closeDialog()" line, the closeDialog function which should close the dialog is null (the onClose field is null).
In short:
The createIndependentDialog() method should raise a dialog and I would like to proceed to execute the method "someSynchronousMethod()" while the dialog is still shown and close it once the method finishes.
Please note that I use a Skin for the dialog which is not shown here but it works if I remove the Platform.runLater, but then it is stuck in the showAndWait() without advancing which is expected
Is there a way or a known design of some sort that will help to run tasks/methods with custom dialogs?
This can be done, but as pointed out in the comments, it is probably better to use some type of progress node. I used Alert in this example but Dialog should be very similar.
The key is closing the Alert/Dialog after the task is complete using the task's setOnSucceeded.
longRunningTask.setOnSucceeded((t) -> {
System.out.println("Task Done!");
alert.close();
});
Full Code
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application
{
public static void main(String[] args)
{
launch();
}
#Override
public void start(Stage stage)
{
Scene scene = new Scene(new StackPane(new Label("Hello World!")), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
Task<Integer> longRunningTask = new Task<Integer>() {
#Override protected Integer call() throws Exception {
int iterations;
for (iterations = 0; iterations < 100000; iterations++) {
if (isCancelled()) {
break;
}
System.out.println("Iteration " + iterations);
}
return iterations;
}
};
Alert alert = new Alert(Alert.AlertType.INFORMATION);
Button okButton = (Button)alert.getDialogPane().lookupButton(ButtonType.OK);
okButton.setDisable(true);
longRunningTask.setOnSucceeded((t) -> {
System.out.println("Task Done!");
alert.close();
});
new Thread(longRunningTask).start();
alert.setTitle("Hello World");
alert.setHeaderText("Hello");
alert.setContentText("I will close when the long running task ends!");
alert.showAndWait();
}
}
Altered code from https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm.
One pitfall I can see is someone closing the Alert/Dialog before the task finishes.
Here is a small sample from my custom dialog, which is meant to display the progress of a running javafx.concurrent.Task.
DialogPane pane = this.getDialogPane()
pane.getButtonTypes().addAll(ButtonType.CANCEL);
pane.headerTextProperty().bind(task.titleProperty());
pane.contentTextProperty().bind(task.messageProperty());
For some reason, the buttons in the button bar disappeared, but only after some text updated. After further investigation, I found that binding the header text of a dialog seems to remove all the buttons in the button bar. Why would this happen, and what would I do to stop the buttons from being hidden?
EDIT: Here's an MCVE demonstrating the problem.
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class MCVE extends Application {
public static class CustomDialog extends Dialog<ButtonType> {
public CustomDialog(Task<?> task) {
this.initModality(Modality.APPLICATION_MODAL);
DialogPane pane = this.getDialogPane();
{
pane.getButtonTypes().addAll(ButtonType.CANCEL);
pane.headerTextProperty().bind(task.titleProperty());
}
setOnCloseRequest(event -> {
if (task.isRunning()) event.consume();
});
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
StackPane root = new StackPane();
Button starter = new Button("Showcase");
starter.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
root.getChildren().add(starter);
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
starter.setOnAction(event -> {
Task<Void> task = new Task<>() {
#Override
protected Void call() throws Exception {
updateTitle("Before loop");
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
if (isCancelled()) return null;
}
for (int i = 1; i <= 20; i++) {
updateTitle("loop " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
if (isCancelled()) return null;
}
}
return null;
}
};
Thread worker = new Thread(task);
worker.start();
new CustomDialog(task).showAndWait();
});
}
}
It appears that the issue needs fixing on JavaFX's end. In fact, the button bar is simply pushed out of view. There is a relatively decent workaround though. Dialog internally uses a styled GridPane to display header text and graphics, so I simply replicated that using an external GridPane and instead bound to the textProperty() of a Label.
public static class CustomDialog extends Dialog<ButtonType> {
public CustomDialog(Task<?> task) {
DialogPane pane = this.getDialogPane(); {
pane.getButtonTypes().addAll(ButtonType.CANCEL);
//construct custom header
GridPane headerRoot = new GridPane(); {
Label headerText = new Label();
//headerText is the label containing the header text
headerText.textProperty().bind(task.titleProperty());
headerRoot.add(headerText, 0, 0);
}
headerRoot.getStyleClass().addAll("header-panel");
pane.setHeader(headerRoot);
pane.setContentText("Placeholder content");
pane.getScene().getWindow().setOnCloseRequest(event -> {
if (!task.isDone()) {
event.consume();
}
});
}
}
It looks exactly the same as a default dialog, without the issue mentioned in the question above.
I have a JavaFX app that runs two threads at startup. One is the UI thread that must not be blocked. The other is a thread that prepares a large table (it takes about 20 seconds). I want to signal the UI thread when the second thread is done, so it can change the color of a rectangle from red to green. I have tried solutions using the synchronized keyword, but they all caused the UI thread to be blocked.
I used the following resources to obtain the below code.
Concurrency in JavaFX
Execute task in background in JavaFX
The below app simply displays a red rectangle which, after five seconds, turns to green. Explanations after the code.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class JfxTask0 extends Application {
private Task<Void> task;
#Override
public void init() throws Exception {
task = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(5000L);
}
catch (InterruptedException xInterrupted) {
if (isCancelled()) {
System.out.println("CANCELLED!");
}
}
return null;
}
};
}
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(25.0d, 25.0d, 50.0d, 50.0d);
rect.setFill(Color.RED);
task.stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends State> workerStateProperty,
Worker.State oldValue,
Worker.State newValue) {
if (newValue == Worker.State.SUCCEEDED) {
rect.setFill(Color.GREEN);
}
}
});
new Thread(task).start();
Group root = new Group();
ObservableList<Node> children = root.getChildren();
children.add(rect);
Scene scene = new Scene(root, 100.0D, 100.0D);
primaryStage.setScene(scene);
primaryStage.setTitle("Task");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Method init() is declared in class javafx.application.Application. It is executed before method start() and, as its name suggests, is used to initialize the JavaFX application. In this method I create the background task. The background task merely sleeps for five seconds.
In method start() I create the red rectangle and then launch the background task but before launching the task, I register a listener with one of the task's properties. This property will be set to a particular value once the task completes.
After the task is launched, I build the rest of the GUI and display it.
Once the task terminates, then listener is invoked and it sets the rectangle color to green.
You can use a handler for this problem.
there is example
Add this in your main activity and create handler.
Handler h = new Handler(){
#Override public void handleMessage(Message msg){
switch(msg.what){
case 1:
// what you want when complete
break;
default:
break;
}
}
}
MyThread thread = new MyThread(new Messenger(h));
thread.start();
Now add this in your thread file.
public class MyThread{
Messenger m;
public MyThread(Messenger m){
this.m = m;
}
public void run(){
super.run();
// your codes
//
//when your task complete
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "";
try{
m.send(msg);
}catch(IOException e){
e.printStackTrace();
}
}
}
I use a background-thread that should not stop immediately when JavaFX stops (as it does when no stage is open anymore as configured with setImplicitExit), so I do not use setDaemon for this one. But how can I check if JavaFX is shutting down? (This thread should just finish some things and stop itself)
I know I could put an setOnCloseRequest to all stages, but I'd prefer not doing that.
Runtime.getRuntime().addShutdownHook() does not work in this case as the machine does not go down as long as this thread is running).
Override Application.stop() and set a flag. Your thread will need to periodically check that flag.
SSCCE:
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ExitThreadGracefullyOnExit extends Application {
AtomicBoolean shutdownRequested = new AtomicBoolean();
#Override
public void start(Stage primaryStage) {
Label countLabel = new Label();
Thread thread = new Thread(() -> {
try {
int count = 0 ;
while (! shutdownRequested.get()) {
count++ ;
final String message = "Count = "+count ;
Platform.runLater(() -> countLabel.setText(message));
Thread.sleep(1000);
}
System.out.println("Shutdown... closing resources");
Thread.sleep(1000);
System.out.println("Almost done...");
Thread.sleep(1000);
System.out.println("Exiting thread");
} catch (InterruptedException exc) {
System.err.println("Unexpected Interruption");
}
});
VBox root = new VBox(10, countLabel);
root.setAlignment(Pos.CENTER);
thread.start();
Scene scene = new Scene(root, 350, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() {
shutdownRequested.set(true);
}
public static void main(String[] args) {
launch(args);
}
}
i am trying to influence a UI-Element during an event in javaFX.
void buttonClicked(ActionEvent e) {
labelInfo.setText("restarting - might take a few seconds");
jBoss.restart();
labelInfo.setText("JBoss successfully restarted");
}
The action "jBoss.restart()" waits till the JBoss is restarted.
The problem:
the text "restarting - ..." is not displayed. The application waits till the JBoss is restarted and then it shows the Text "JBoss successfully restarted".
My thoughts:
the scene is refreshed AFTER the event is completed. So the first label-change will not happen.
How can i show a info message during an event?
The problem it's that the FX Thread has no safe operations. So I'm guessing that jBoss.restart() it's taking a lot of time. So you have to put this command in a Service. Also I recommend to you a progress indicator to show to the user you are making a long operation.
Here it is an example but I encourage you to go to Concurrency in JavaFX and take a deep look on it. Maybe there are other things that can help you.
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
private Label labelInfo;
private Button button;
private ProgressIndicator progressIndicator;
#Override
public void start(Stage stage) throws Exception {
VBox vbox = new VBox(5);
vbox.setAlignment(Pos.CENTER);
labelInfo = new Label();
button = new Button("Restart");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
buttonClicked(event);
}
});
progressIndicator = new ProgressIndicator(-1);
progressIndicator.setVisible(false);
vbox.getChildren().addAll(labelInfo, progressIndicator, button);
Scene scene = new Scene(vbox, 300, 200);
stage.setScene(scene);
stage.show();
}
void buttonClicked(ActionEvent e) {
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
updateMessage("restarting - might take a few seconds");
// Here the blocking operation
// jBoss.restart();
Thread.sleep(10000);
updateMessage("JBoss successfully restarted");
return null;
}
};
}
};
// Make the progress indicator visible while running
progressIndicator.visibleProperty().bind(service.runningProperty());
// Bind the message of the service to text of the label
labelInfo.textProperty().bind(service.messageProperty());
// Disable the button, to prevent more clicks during the execution of
// the service
button.disableProperty().bind(service.runningProperty());
service.start();
}
}