如何在javaFX中将光标更改为WAIT时线程化后台任务?
Posted
技术标签:
【中文标题】如何在javaFX中将光标更改为WAIT时线程化后台任务?【英文标题】:How to thread a background task whilst changing the cursor to WAIT in javaFX? 【发布时间】:2016-09-12 00:03:35 【问题描述】:我有一个应用程序,它在登录时通过接口类访问数据库。登录过程导致应用程序在访问数据库时没有响应一段时间,因此我一直在研究线程和等待光标以使其顺利运行。我试图通过网络上的许多示例和堆栈溢出来使用线程,但我的方法似乎不起作用,我收到 java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4 异常,我不确定如何从这里开始。我试图做的是在这个后台线程运行 loginLoadEverything() 方法时将光标更改为等待模式(尽管我没有在其中包含代码,因为它太长了)。这是我的控制器类:
package main.java.gui;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import main.java.databaseInterface.BackendInterface;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.concurrent.CountDownLatch;
public class LoginController implements Initializable
private BackendInterface backendInterface;
private DashboardController dashboardController;
private StudentsController studentsController;
private ConsultationController consultationController;
private CreateStudentController createStudentController;
private CreateConsultationController createConsultationController;
@FXML
TextField username;
@FXML
PasswordField password;
@FXML
Button loginButton;
@FXML
Label loginLabel;
@FXML
public void loginButtonPress(ActionEvent event)
Service<Void> service = new Service<Void>()
@Override
protected Task<Void> createTask()
return new Task<Void>()
@Override
protected Void call() throws Exception
loginLoadEverything();
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(new Runnable()
@Override
public void run()
try
Scene s1 = loginLabel.getScene();
s1.setCursor(Cursor.WAIT);
finally
latch.countDown();
);
latch.await();
return null;
;
;
service.start();
public void loginLoadEverything()
//chance to true when complete
if (username.getText().isEmpty() == false || password.getText().isEmpty() == false)
loginLabel.setText("Please enter data in the fields below");
else
username.setText("-----");
password.setText("-----");
//initialises backend interface with username and password
backendInterface = new BackendInterface(username.getText(), password.getText().toCharArray());
// Open a connection to the database
if (backendInterface.openConnection())
//return and print response
System.out.println(backendInterface.getConnectionResponse());
//directs the user to the dashboard after successful login
try
if (backendInterface.getAllStudents() &&
backendInterface.getAllConsultations() &&
backendInterface.getCourses() &&
backendInterface.getConsultationCategories() &&
backendInterface.getConsultationPriorities())
FXMLLoader loader1 = new FXMLLoader();
loader1.setLocation(getClass().getResource("/main/res/dashboard.fxml"));
loader1.load();
Parent p = loader1.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(p));
stage.show();
//set instances to the dashboard controller
dashboardController = loader1.getController();
dashboardController.setBackendInterface(backendInterface); //pass backendInterface object to controller
dashboardController.setDashboardController(loader1.getController()); //pass dashboard as reference
//load images
Image logoutImage = new Image(getClass().getResourceAsStream("images/logout.png"));
Image userImage = new Image(getClass().getResourceAsStream("images/users.png"));
Image calendarImage = new Image(getClass().getResourceAsStream("images/calendar.png"));
Image leftArrowImage = new Image(getClass().getResourceAsStream("images/leftArrow.png"));
Image notepadImage = new Image(getClass().getResourceAsStream("images/notepad.png"));
//set images
dashboardController.studentLabel.setGraphic(new ImageView(userImage));
dashboardController.logoutLabel.setGraphic(new ImageView(logoutImage));
dashboardController.consultationLabel.setGraphic(new ImageView(notepadImage));
else
system.out.println(backendInterface.getExceptionMessage);
@Override
public void initialize(URL location, ResourceBundle resources)
【问题讨论】:
call()
方法是在后台线程上执行的方法。它应该执行需要很长时间才能执行的工作,并且不得执行任何 UI 工作。现在,它似乎只在 UI 上工作。实际的数据库连接工作在哪里完成,从您的代码中不清楚它在哪里。还有其他问题 - 例如。您将光标更改为WAIT
after call()
方法中的其他所有内容都已完成,这肯定不是您想要的。但是您需要先弄清楚实际耗时的工作在哪里。
感谢詹姆斯的反馈,我已经添加了 loginLoadEverything 方法的其余代码。此方法提供从 BackendInterface 类对数据库的访问。正如您所见,BackendInterface 类的方法在这里被调用,创建了耗时的工作。然后我尝试在 call() 方法中调用这个 loginLoadEverything 方法。是的,这是正确的,我将不得不更改光标以更合适的方式等待以反映耗时的过程,但不知道如何实现这一点。
查看答案。看起来您需要重组一些代码,因为数据库访问和 UI 之间没有足够清晰的分离。您需要能够在一段代码中完成所有后台工作,并将结果封装在某种对象中——尚不清楚您的 backendInterface
对象是否适用于此。然后任务可以完成数据库工作并返回该对象,onSucceeded
处理程序可以显示 UI,从对象进行配置。一旦你开始工作,光标就变得微不足道了。
使用您添加的代码更新了答案。您的某些代码不是很容易理解。 dashboardController.setDashboardController(loader1.getController());
是做什么的?当然这相当于dashboardController.setDashboardController(dashboardController);
。你为什么给一个对象引用它自己???大概你真的不想总是传递"-----"
作为用户名和密码?你的很多实例变量也应该是局部变量。
dashboardController.setDashboardController(loader.getController());相当于把控制器传给自己。当我尝试其他事情时,它是留在那里的代码。是的,我实际上并没有使用“------”作为用户名和密码,而是替换它,因为我的代码中有我的实际用户名和密码
【参考方案1】:
您可能在这里不需要Service
:您只需要Task
。
call()
方法是在后台线程上执行的方法。它应该完成需要很长时间才能执行的工作(即连接到数据库并从中获取数据)并且它不得做任何 UI 工作,因为 UI 的更改必须 在 FX 应用程序线程上进行。您收到异常的原因是您正在从后台线程创建并显示 Stage
。
所以基本思路是让任务从数据库中获取数据并返回;然后使用任务的onSucceeded
处理程序来显示UI,使用任务的结果。 (onSucceeded
处理程序在 FX 应用程序线程上执行,允许您在此处安全地修改 UI。)
我不确切知道您的类是如何实现的,等等,但以下几行可能会起作用。重要的是您不要在与 UI 交互的后台线程中执行任何操作。
@FXML
public void loginButtonPress(ActionEvent event)
if (( ! username.getText().isEmpty()) || (! password.getText().isEmpty()) )
loginLabel.setText("Please enter data in the fields below");
else
// I assume you want these values before you set them to "-----", no???
final String uName = username.getText();
final char[] pw = password.getText().toCharArray();
username.setText("-----");
password.setText("-----");
// create task for retrieving data:
Task<BackendInterface> loadDataTask = new Task<BackendInterface>()
@Override
public BackendInterface call() throws Exception
BackendInterface backendInterface = new BackendInterface(uName, pw);
if (backendInterface.openConnection())
if (backendInterface.getAllStudents() &&
backendInterface.getAllConsultations() &&
backendInterface.getCourses() &&
backendInterface.getConsultationCategories() &&
backendInterface.getConsultationPriorities())
return backendInterface ;
// maybe throw an exception here, depending on your requirements...
return null ;
;
// show UI on task completion:
loadDataTask.setOnSucceeded(e ->
BackendInterface backendInterface = loadDataTask.getValue();
if (backendInterface == null)
// something went wrong... bail, or probably show error message...
return ;
FXMLLoader loader1 = new FXMLLoader();
loader1.setLocation(getClass().getResource("/main/res/dashboard.fxml"));
Parent p = loader1.load();
DashboardController controller = loader.getController();
controller.setBackendInterface(backendInterface);
Stage stage = new Stage();
stage.setScene(new Scene(p));
stage.show();
// etc etc with your Images, etc (not sure why this isn't done in DashboardController though...)
// set cursor back to default:
loginLabel.getScene().setCursor(Cursor.DEFAULT);
);
loadDataTask.setOnFailed(e ->
// show error message or otherwise handle database exception here
);
// set cursor to WAIT:
loginLabel.getScene().setCursor(Cursor.WAIT);
// and run task in a background thread:
Thread t = new Thread(loadDataTask);
t.start();
【讨论】:
James_D 你是个传奇人物,已经为此苦苦挣扎了好几天。数据库方法的返回值都是布尔值,表示数据库方法是否有效或是否有错误。所以我把Task和call()方法中的数据类型改成了Boolean,call方法返回调用这些数据库连接方法的值。如果这是真的,则继续加载仪表板页面。光标也变为 WAIT 并返回 DEFAULT :D以上是关于如何在javaFX中将光标更改为WAIT时线程化后台任务?的主要内容,如果未能解决你的问题,请参考以下文章
将鼠标光标更改为等待光标,然后启动工作线程并在线程完成时改回
使用 Windows 窗体应用程序时如何将鼠标光标更改为自定义光标?