Search code examples
javajavafxjavafx-2fxmlprogress-indicator

How to validate setVisible property for progress indicator


I'm writing a javafx program in the program i need to put a progress indicator after login system it should wait for specific time until it load the needed objects from the server. I'm new in javafx i want a way to validate the setVisible(boolean) property from the controller, in initialize it should be invisible and there's no problem to set it to false in initialize method but in controller after initializing i think i should validate the changes. is there a method that i could use to validate this property change?

     //////pseudocode
@FXML ProgressIndicator pi;
public void initialize(...){
 PI.setVisible(false); 
  }
@FXML public void buttonOnClick(){
Thread t1=new Thread(new Runnable(....)) // A thread to load data
 t1.start
pi.setVisible(true); //HERE IS MY PROBLEM
Thread t2;//a thread to increase progress
  t2.start();
t1.join();
}

Solution

  • Why the concept of validate() is usually invalid in JavaFX, plus solving threading issues

    To explain some issues with your original question and why this answer may not seem to directly answer it:

    1. You usually don't have to validate() (i.e. force the layout of the subcomponents) components in JavaFX, as JavaFX will do that for you automatically each pulse (study the linked doc to more fully understand this).
    2. Sometimes you might want to generate a layout pass (which is, I guess, somewhat similar in concept to an explicit call to validate for a JFrame). But, that is usually just because you want to measure what the dimensions of something would be, once it has had css applied and has been completely laid out (which isn't what you want to do here).
    3. You should not try to change things in the scene graph off the JavaFX application thread as that will cause exceptions and race conditions (i.e. your call to setVisible on the progress indicator within a thread that you have created is wrong).
    4. It is better to use built-in JavaFX concurrency mechanisms for some concurrent tasks, such as submitting a login to a login service and interfacing with the UI based upon the task progress and results.

    What to do instead

    What I think you are trying to do is:

    1. To create a login prompt where the login logic occurs in another thread
    2. Have an indefinite progress indicator spin while the login is progressing in the other thread.
    3. The progress indicator is only shown while the login is in progress and is not shown once the login attempt completes (regardless of whether or not it succeeds or fails).

    not logged in logging in logged in

    Sample App

    A login Service handles the asynchronous login process. A progress indicator in the login pane indicates that a login is ongoing. Once login is complete, the login pane is replaced with the application pane for the logged in user.

    The following line ensures that the progress indicator is only displayed while the login service is executing:

    progressIndicator.visibleProperty().bind(loginService.runningProperty());
    

    Full code:

    import javafx.application.Application;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.concurrent.Service;
    import javafx.concurrent.Task;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.layout.*;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class LoginServiceApp extends Application {
        private LoginService loginService = new LoginService();
    
        @Override
        public void start(Stage stage) throws IOException {
            Pane loginPane = createLoginPane();
            loginService.setOnSucceeded(event ->
                    stage.getScene().setRoot(createAppPane(stage))
            );
    
            stage.setScene(new Scene(new StackPane(loginPane)));
            stage.show();
        }
    
        private Pane createLoginPane() {
            GridPane credentialsGrid = new GridPane();
            credentialsGrid.setHgap(10);
            credentialsGrid.setVgap(10);
            TextField usernameField = new TextField("frobozz");
            PasswordField passwordField = new PasswordField();
            credentialsGrid.addRow(0, new Label("Username"), usernameField);
            credentialsGrid.addRow(1, new Label("Password"), passwordField);
    
            Button loginButton = new Button("Login");
            loginButton.setOnAction(event -> {
                loginService.setUsername(usernameField.getText());
                loginService.setPassword(passwordField.getText());
                loginService.restart();
            });
            loginButton.disableProperty().bind(loginService.runningProperty());
    
            ProgressIndicator progressIndicator = new ProgressIndicator();
            progressIndicator.visibleProperty().bind(loginService.runningProperty());
            progressIndicator.setPrefSize(20, 20);
    
            HBox loginControl = new HBox(10, loginButton, progressIndicator);
            VBox loginPane = new VBox(10, credentialsGrid, loginControl);
            loginPane.setPadding(new Insets(10));
    
            return loginPane;
        }
    
        private Pane createAppPane(Stage stage) {
            Button logoutButton = new Button("Logout");
            logoutButton.setOnAction(event -> stage.getScene().setRoot(createLoginPane()));
            HBox appPane = new HBox(logoutButton);
            appPane.setPadding(new Insets(10));
    
            return appPane;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    
        private static class LoginService extends Service<Void> {
            private StringProperty username = new SimpleStringProperty(this, "username");
            public final void setUsername(String value) { username.set(value); }
            public final String getUsername() { return username.get(); }
            public final StringProperty usernameProperty() { return username; }
    
            private StringProperty password = new SimpleStringProperty(this, "password");
            public final void setPassword(String value) { password.set(value); }
            public final String getPassword() { return password.get(); }
            public final StringProperty passwordProperty() { return password; }
    
            @Override
            protected Task<Void> createTask() {
                final String _username = getUsername();
                final String _password = getPassword();
                return new Task<Void>() {
                    @Override
                    protected Void call() throws Exception {
                        // Simulate a long call to a login service,
                        // using the username and password we saved when the task was created.
                        // If login fails, an exception can be raised to report it and the
                        // caller starting the service can monitor setOnException to handle it.
                        // Or the Task could return a result value instead of void and the caller
                        // could monitor the value property of the task in addition to the exception handler.
                        Thread.sleep(1_000);
                        return null;
                    }
                };
            }
        }
    }