Javafx запускает только один поток, даже если кнопка нажата несколько раз - PullRequest
0 голосов
/ 22 февраля 2019

У меня есть кнопка, которая берет мое имя пользователя и пароль из полей и отправляет запрос бэкэнду на аутентификацию.Я использую поток, чтобы моя анимация кнопки и сцена не зависали примерно так:

Service<Void> service = new Service<Void>() {
            @Override
            protected Task<Void> createTask() {
                return new Task<Void>() {
                    @Override
                    protected Void call() throws Exception {
                        //do authentication
                        if(responseStatus != 200){
                            authenticated = false 
                        }else{
                            authenticate = true
                        }
                        Platform.runLater(() -> {
                        try{
                            if(authenticated) {
                                changeScene();
                            }
                        }finally{
                            latch.countDown();
                        }
                    });
                    latch.await();
                    return null;
                }
            };
        }
    };
    service.start();

private void changeScene(){
    try {
        Stage window = (Stage)loginPane.getScene().getWindow();
        LoggedFirstStyle.displayLoggedScene();
        window.close();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

, но дело в том, что запуск платформы позже выполняется несколько раз, если я нажимаю кнопку несколько раз, как и ChangeSceneи несколько сцен открыты.Хорошо, как я это делаю, и если да, то как я могу предотвратить открытие нескольких потоков одним и тем же методом?

1 Ответ

0 голосов
/ 23 февраля 2019

A Service предоставляет возможность «повторного использования» Task.Я помещаю «повторное использование» в кавычки, потому что на самом деле Service создает новый Task при каждом запуске.Для получения дополнительной информации о различиях между Service и Task и их использованием вы можете:

Поскольку Service предназначено для повторного использования, вам следует создать только один экземпляр.Он также поддерживает состояние, поэтому его можно выполнять только «по одному за раз».Другими словами, один и тот же Service не может выполняться несколько раз параллельно.По завершении Service он будет находиться в состояниях SUCCEEDED, CANCELLED или FAILED;чтобы снова запустить Service, вы должны либо позвонить restart() (отменит выполнение Service), либо позвонить reset() перед повторным вызовом start().

Пока Service работает, вы 'Я хочу отключить определенные компоненты пользовательского интерфейса, чтобы пользователь не мог запустить его несколько раз.Вы сделали бы это через слушателей и / или привязок.При необходимости вы также можете поставить чеки, чтобы ваш код не пытался запустить Service, если он уже запущен.Необходимость этих проверок зависит от того, какой код может запустить Service и как он может быть выполнен.

Вот небольшой пример.Он использует FXML для создания интерфейса, но важными частями являются классы LoginController и LoginService.В зависимости от вашего приложения вы также можете добавить способ отмены входа в систему.

Main.java

package com.example;

import java.io.IOException;
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 {

    @Override
    public void start(Stage primaryStage) throws IOException {
        // Login.fxml is in the same package as this class
        Parent root = FXMLLoader.load(getClass().getResource("Login.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Service Example");
        primaryStage.show();
    }

}

LoginService.java

package com.example;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class LoginService extends Service<Boolean> {

    private final StringProperty username = new SimpleStringProperty(this, "username");
    public final void setUsername(String username) { this.username.set(username); }
    public final String getUsername() { return username.get(); }
    public final StringProperty usernameProperty() { return username; }

    private final StringProperty password = new SimpleStringProperty(this, "password");
    public final void setPassword(String password) { this.password.set(password); }
    public final String getPassword() { return password.get(); }
    public final StringProperty passwordProperty() { return password; }

    @Override
    protected Task<Boolean> createTask() {
        return new LoginTask(getUsername(), getPassword());
    }

    private static class LoginTask extends Task<Boolean> {

        private final String username;
        private final String password;

        public LoginTask(String username, String password) {
            this.username = username;
            this.password = password;
        }

        @Override
        protected Boolean call() throws Exception {
            Thread.sleep(3_000L); // simulate long running work...
            return !isCancelled() && "root".equals(username) && "root".equals(password);
        }

    }

}

LoginController.java

package com.example;

import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Cursor;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

public class LoginController {

    @FXML private GridPane root;
    @FXML private TextField userField;
    @FXML private PasswordField passField;
    @FXML private Button loginBtn;

    private LoginService service;

    @FXML
    private void initialize() {
        service = new LoginService();
        service.usernameProperty().bind(userField.textProperty());
        service.passwordProperty().bind(passField.textProperty());

        // Don't let user interact with UI while trying to login
        BooleanBinding notReadyBinding = service.stateProperty().isNotEqualTo(Worker.State.READY);
        userField.disableProperty().bind(notReadyBinding);
        passField.disableProperty().bind(notReadyBinding);
        loginBtn.disableProperty().bind(notReadyBinding);

        root.cursorProperty().bind(
                Bindings.when(service.runningProperty())
                        .then(Cursor.WAIT)
                        .otherwise(Cursor.DEFAULT)
        );

        service.setOnSucceeded(event -> serviceSucceeded());
        service.setOnFailed(event -> serviceFailed());
    }

    private void serviceSucceeded() {
        if (service.getValue()) {
            /*
             * Normally you'd change the UI here to show whatever the user needed to
             * sign in to see. However, to allow experimentation with this example
             * project we simply show an Alert and call reset() on the LoginService.
             */
            showAlert(Alert.AlertType.INFORMATION, "Login Successful", "You've successfully logged in.");
            service.reset();
        } else {
            showAlert(Alert.AlertType.ERROR, "Login Failed", "Your username or password is incorrect.");
            service.reset();
        }
    }

    private void serviceFailed() {
        showAlert(Alert.AlertType.ERROR, "Login Failed", "Something when wrong while trying to log in.");
        service.getException().printStackTrace();
        service.reset();
    }

    private void showAlert(Alert.AlertType type, String header, String content) {
        Alert alert = new Alert(type);
        alert.initOwner(root.getScene().getWindow());
        alert.setHeaderText(header);
        alert.setContentText(content);
        alert.showAndWait();
    }

    @FXML
    private void handleLogin(ActionEvent event) {
        event.consume();

        // isBlank() is a String method added in Java 11
        boolean blankUsername = userField.textProperty().getValueSafe().isBlank();
        boolean blankPassword = passField.textProperty().getValueSafe().isBlank();

        if (blankUsername || blankPassword) {
            showAlert(Alert.AlertType.ERROR, null, "Both username and password must be specified.");
        } else {
            service.start();
        }
    }

}

Login.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>

<GridPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="20.0"
          xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/10.0.1"
          fx:controller="com.example.LoginController">

    <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" minWidth="-Infinity" percentWidth="50.0"/>
        <ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="-Infinity" percentWidth="50.0"/>
    </columnConstraints>

    <rowConstraints>
        <RowConstraints minHeight="-Infinity" percentHeight="25.0" vgrow="SOMETIMES"/>
        <RowConstraints minHeight="-Infinity" percentHeight="25.0" vgrow="SOMETIMES"/>
        <RowConstraints minHeight="-Infinity" percentHeight="25.0" vgrow="SOMETIMES"/>
        <RowConstraints minHeight="-Infinity" percentHeight="25.0" vgrow="SOMETIMES"/>
    </rowConstraints>

    <children>
        <Button fx:id="loginBtn" defaultButton="true" mnemonicParsing="false" onAction="#handleLogin" text="Login"
                GridPane.columnIndex="1" GridPane.rowIndex="3"/>
        <Label minWidth="-Infinity" text="Welcome">
            <font>
                <Font name="Segoe UI" size="32.0"/>
            </font>
        </Label>
        <TextField fx:id="userField" prefColumnCount="20" promptText="Username" GridPane.columnSpan="2"
                   GridPane.rowIndex="1"/>
        <PasswordField fx:id="passField" prefColumnCount="20" promptText="Password" GridPane.columnSpan="2"
                       GridPane.rowIndex="2"/>
    </children>

    <padding>
        <Insets bottom="50.0" left="50.0" right="50.0" top="50.0"/>
    </padding>

</GridPane>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...