Javafx 在哪里将标签绑定到 StringProperty
Javafx where to bind labels to StringProperty
几天来我一直在努力解决这个问题,我已经阅读了有关线程、MVC、绑定、接口和许多有趣的东西,但我只是无法以适当的方式将它们放在一起来完成这项工作。
我只想列出 mi c:\ 中的所有文件并将它们显示在不断变化的标签上
但我得到的只是:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
这是我的 FXML:
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane alignment="center" hgap="10" prefHeight="200.0" prefWidth="401.0" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.60" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="368.0">
<children>
<Button fx:id="start" layoutX="159.0" layoutY="35.0" mnemonicParsing="false" onAction="#displayFiles" text="Start" />
<Label fx:id="fileLabel" layoutX="20.0" layoutY="100.0" prefHeight="21.0" prefWidth="329.0" text="This label must change on iteration" />
</children>
</AnchorPane>
</children>
</GridPane>
我的主要:
import javafx.application.Application;
import javafx.fxml.FXML;
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 Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Dummy App");
primaryStage.setScene(new Scene(root));
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
我的控制器:
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class Controller {
@FXML
Button start;
@FXML
Label fileLabel;
@FXML
void displayFiles(ActionEvent event) throws Exception{
Model model = new Model();
//BINDING
fileLabel.textProperty().bind(model.status);
Thread thread = new Thread(model);
thread.start();
}
}
以及模特:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.io.File;
/**
* Created by R00715649 on 16-Nov-16.
*/
public class Model implements Runnable {
File rootDirectory = new File("C:/");
StringProperty status = new SimpleStringProperty("Starting scan...");
@Override
public void run() {
try{
File[] fileList = rootDirectory.listFiles();
for (File f:fileList){
processDirectory(f);
}
}catch (Exception e){
}
}
void processDirectory (File directory){
if (directory.isDirectory()){
File[] fileList = directory.listFiles();
for (File f:fileList){
processDirectory(f);
}
}else{
System.out.println(directory.getAbsolutePath());
status.set(directory.getAbsolutePath());
}
}
}
status.set(directory.getAbsolutePath());
应该在主线程中。应该是这样的:
Platform.runLater(() -> status.set(directory.getAbsolutePath()));
由于标签的文本与模型的状态绑定,更改模型的状态会导致 UI 发生变化(标签的文本会发生变化)。因此,您只能在 FX 应用程序线程上更改模型的状态。
您可以使用 Platform.runLater(...)
在 FX 应用程序线程上将代码安排到 运行。您可以直接在模型中执行此操作:
void processDirectory (File directory){
if (directory.isDirectory()){
File[] fileList = directory.listFiles();
for (File f:fileList){
processDirectory(f);
}
}else{
System.out.println(directory.getAbsolutePath());
Platform.runLater(() -> status.set(directory.getAbsolutePath()));
}
}
或者您可以使用模型的状态(而不是绑定)注册一个侦听器,然后委托给那里的 FX 应用程序线程:
@FXML
void displayFiles(ActionEvent event) throws Exception{
Model model = new Model();
ChangeListener<String> listener = (obs, oldStatus, newStatus) -> fileLabel.setText(newStatus);
model.status.addListener(listener);
Thread thread = new Thread(model);
thread.start();
}
在后一种解决方案中,您可能希望在线程结束时删除侦听器(这需要一些额外的工作),否则在标签仍然显示时无法对模型进行垃圾回收。为此,您可以考虑使用 Task
:
@FXML
void displayFiles(ActionEvent event) throws Exception{
Model model = new Model();
Task<Void> task = new Task<Void>() {
@Override
public Void call() {
model.status.addListener((obs, oldStatus, newStatus) -> updateMessage(newStatus));
model.run();
return null ;
}
};
fileLabel.textProperty().bind(task.messageProperty());
task.setOnSucceeded(e -> fileLabel.textProperty().unbind());
new Thread(task).start();
}
几天来我一直在努力解决这个问题,我已经阅读了有关线程、MVC、绑定、接口和许多有趣的东西,但我只是无法以适当的方式将它们放在一起来完成这项工作。
我只想列出 mi c:\ 中的所有文件并将它们显示在不断变化的标签上 但我得到的只是:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
这是我的 FXML:
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane alignment="center" hgap="10" prefHeight="200.0" prefWidth="401.0" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.60" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="368.0">
<children>
<Button fx:id="start" layoutX="159.0" layoutY="35.0" mnemonicParsing="false" onAction="#displayFiles" text="Start" />
<Label fx:id="fileLabel" layoutX="20.0" layoutY="100.0" prefHeight="21.0" prefWidth="329.0" text="This label must change on iteration" />
</children>
</AnchorPane>
</children>
</GridPane>
我的主要:
import javafx.application.Application;
import javafx.fxml.FXML;
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 Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Dummy App");
primaryStage.setScene(new Scene(root));
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
我的控制器:
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class Controller {
@FXML
Button start;
@FXML
Label fileLabel;
@FXML
void displayFiles(ActionEvent event) throws Exception{
Model model = new Model();
//BINDING
fileLabel.textProperty().bind(model.status);
Thread thread = new Thread(model);
thread.start();
}
}
以及模特:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.io.File;
/**
* Created by R00715649 on 16-Nov-16.
*/
public class Model implements Runnable {
File rootDirectory = new File("C:/");
StringProperty status = new SimpleStringProperty("Starting scan...");
@Override
public void run() {
try{
File[] fileList = rootDirectory.listFiles();
for (File f:fileList){
processDirectory(f);
}
}catch (Exception e){
}
}
void processDirectory (File directory){
if (directory.isDirectory()){
File[] fileList = directory.listFiles();
for (File f:fileList){
processDirectory(f);
}
}else{
System.out.println(directory.getAbsolutePath());
status.set(directory.getAbsolutePath());
}
}
}
status.set(directory.getAbsolutePath());
应该在主线程中。应该是这样的:
Platform.runLater(() -> status.set(directory.getAbsolutePath()));
由于标签的文本与模型的状态绑定,更改模型的状态会导致 UI 发生变化(标签的文本会发生变化)。因此,您只能在 FX 应用程序线程上更改模型的状态。
您可以使用 Platform.runLater(...)
在 FX 应用程序线程上将代码安排到 运行。您可以直接在模型中执行此操作:
void processDirectory (File directory){
if (directory.isDirectory()){
File[] fileList = directory.listFiles();
for (File f:fileList){
processDirectory(f);
}
}else{
System.out.println(directory.getAbsolutePath());
Platform.runLater(() -> status.set(directory.getAbsolutePath()));
}
}
或者您可以使用模型的状态(而不是绑定)注册一个侦听器,然后委托给那里的 FX 应用程序线程:
@FXML
void displayFiles(ActionEvent event) throws Exception{
Model model = new Model();
ChangeListener<String> listener = (obs, oldStatus, newStatus) -> fileLabel.setText(newStatus);
model.status.addListener(listener);
Thread thread = new Thread(model);
thread.start();
}
在后一种解决方案中,您可能希望在线程结束时删除侦听器(这需要一些额外的工作),否则在标签仍然显示时无法对模型进行垃圾回收。为此,您可以考虑使用 Task
:
@FXML
void displayFiles(ActionEvent event) throws Exception{
Model model = new Model();
Task<Void> task = new Task<Void>() {
@Override
public Void call() {
model.status.addListener((obs, oldStatus, newStatus) -> updateMessage(newStatus));
model.run();
return null ;
}
};
fileLabel.textProperty().bind(task.messageProperty());
task.setOnSucceeded(e -> fileLabel.textProperty().unbind());
new Thread(task).start();
}