1 回答
TA贡献1805条经验 获得超9个赞
为什么 JavaFX 中的概念validate()
通常无效,以及解决线程问题
要解释您的原始问题的一些问题以及为什么这个答案似乎无法直接回答它:
您通常不必
validate()
(即强制子组件的布局)JavaFX 中的组件,因为 JavaFX 会在每次脉冲时自动为您执行此操作(研究链接的文档以更充分地理解这一点)。有时您可能想要生成一个布局过程(我猜这在概念上有点类似于对 JFrame 进行验证的显式调用)。但是,这通常只是因为您想测量某物的尺寸,一旦它应用了 css 并且已经完全布局(这不是您想要在这里做的)。
您不应该尝试在 JavaFX 应用程序线程之外更改场景图中的内容,因为这会导致异常和竞争条件(即您对
setVisible
已创建的线程内的进度指示器的调用是错误的)。一些并发任务最好使用内置的 JavaFX 并发机制,例如将登录提交到登录服务并根据任务进度和结果与 UI 进行交互。
该怎么做
我认为你正在尝试做的是:
创建登录提示,其中登录逻辑发生在另一个线程中
当登录在另一个线程中进行时,有一个不确定的进度指示器旋转。
进度指示器仅在登录过程中显示,一旦登录尝试完成(无论成功或失败)就不会显示。
示例应用
登录服务处理异步登录过程。登录窗格中的进度指示器指示登录正在进行中。登录完成后,登录窗格将替换为已登录用户的应用程序窗格。
以下行确保仅在登录服务执行时显示进度指示器:
progressIndicator.visibleProperty().bind(loginService.runningProperty());
完整代码:
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;
}
};
}
}
}
添加回答
举报