gpt4 book ai didi

java - JavaFX TableView不会在初始化方法之外更新

转载 作者:行者123 更新时间:2023-12-01 16:56:30 24 4
gpt4 key购买 nike

我有一个应用程序(出于这个问题的目的)包含3个组件。


有问题的主视图(带有控制器),其中包含一个TableView
该视图的表项的模型(TableMessage)
线程侦听器,用于侦听要添加到表中的消息


将项目添加到连接的ObservableList时,TableView无法更新时遇到了问题。如果在控制器的initialize方法中添加示例数据,则数据显示良好。但是,当我从程序的其他位置(在本例中为侦听器)调用相同的方法进行添加时,TableView不会更新。调试时,我可以看到数据已添加到连接的List中(并且示例数据在那里,因此我知道它是正确的对象)。

控制器:

@FXML
private TableView<TableMessage> messageTable;
@FXML
private TableColumn<TableMessage, String> messageIDColumn;
@FXML
private TableColumn<TableMessage, String> timestampColumn;
@FXML
private TableColumn<TableMessage, String> reportTypeColumn;
@FXML
private TableColumn<TableMessage, String> tNumberColumn;

private ObservableList<TableMessage> tableContent = FXCollections.observableArrayList();

@FXML
public void initialize() {

linkColumns();

// this works
addRow(new TableMessage("001", "today", "1", "10"));

}

private void linkColumns() {
messageIDColumn.setCellValueFactory(new PropertyValueFactory<TableMessage, String>("messageID"));
timestampColumn.setCellValueFactory(new PropertyValueFactory<TableMessage, String>("timestamp"));
reportTypeColumn.setCellValueFactory(new PropertyValueFactory<TableMessage, String>("reportType"));
tNumberColumn.setCellValueFactory(new PropertyValueFactory<TableMessage, String>("tNumber"));
messageTable.setItems(tableContent);
}

public void addRow(TableMessage row) {
tableContent.add(row);
}


模型:

public class TableMessage{
private SimpleStringProperty messageID = new SimpleStringProperty ("");
private SimpleStringProperty timestamp = new SimpleStringProperty ("");
private SimpleStringProperty reportType = new SimpleStringProperty ("");
private SimpleStringProperty tNumber = new SimpleStringProperty ("");

// all my constructors, getters, setters below
...
}


听众:

// same sample code as before, doesn't work here (reference to myController is set separately)
myController.addRow(new TableMessage("001", "today", "1", "10"));


我看不到TableView为什么在初始化后停止监视。如前所述,我确认正在更新正确的tableContent引用。

谢谢

编辑1:

对于下面的每个问题,我上面的视图(称为MainController)的父级通过以下方式获取对上述控制器的引用:

FXMLLoader loader = new FXMLLoader(getClass().getResource("MessageView.fxml"));
loader.load();
messageViewController= (MessageViewController) loader.getController();


然后将 SpringContext(用于侦听器)向下传递到INTO messageViewController中,这将从该 SpringContext创建侦听器。

然后给听众参考我调用的 messageViewController

myListener.setReferenceToController(this);  


看起来像这样

public void setReferenceToController(MessageController ref) {
this.messageController = ref;
}


顺带一提,如果对控制器的引用有误,当我跟踪来自控制器的调用时,为什么我会在可观察列表中看到示例数据(回想它是在 initialize中调用的)。听众?

最佳答案

FXMLLoader在FXML文件的根元素中遇到fx:controller属性时,其默认行为是通过调用其无参数构造函数来创建该属性指定的控制器类的新实例,并将其用作该FXML定义的视图的控制器。

因此,当您通过代码获得对控制器的引用时

FXMLLoader loader = new FXMLLoader(getClass().getResource("MessageView.fxml"));
loader.load();
messageViewController= (MessageViewController) loader.getController();


FXMLLoader创建 MessageViewController的新实例,并将其与 MessageView.fxml定义的视图的新实例相关联。由于您放弃了该视图(对返回值 loader.load()不执行任何操作),因此您具有引用的控制器与未显示的视图相关联。

(请注意,在该控制器实例上 FXMLLoader仍将调用 initialize(...),因此 initialize()方法的任何效果将在您获得的引用中可见。)

根据您的评论,通过在另一个FXML文件中包含 MessageView.fxml来创建您实际显示的视图。当 FXMLLoader使用 Nested Controllers技术加载包含的FXML文件时,可以注入对创建的控制器的引用。简要地说,将 fx:id添加到 <fx:include>元素。通过将 "Controller"附加到 fx:id注释字段名称中的 @FXML属性的值,可以将包含文件中的控制器从包含FXML文件注入到控制器中。例如:

MainView.fxml:

<!-- xml headers and imports etc -->
<BorderPane fx:controller="com.example.MainController" ... >

<!-- ... -->

<fx:include source="MessageView.fxml" fx:id="messageView"/>

<!-- ... -->

</BorderPane>


MainController.java:

public class MainController {

@FXML
private MessageViewController messageViewController ;

public void initialize() {
// messageViewController will be initialized and be a reference to the controller
// for the included messageView

// ...
}
}


对于您的用例,这可能就足够了。

还有其他两种方法可以修改用于创建控制器的默认机制。最直接的方法(在 <fx:include>的情况下实际上无济于事)是从FXML文件中删除 fx:controller属性,然后直接在 FXMLLoader上设置控制器:

FXMLLoader loader = new FXMLLoader(getClass().getResource("MessageView.fxml"));
MessageViewController myController = new MessageViewController();
loader.setController(myController);
// calling load will now inject @FXML-annotated fields and call initialize() on myController
Parent view = loader.load();


主要用途是使用需要将参数传递给构造函数的控制器。您可以使用此技术来重用单个控制器实例多次加载FXML文件:我不建议这样做,因为您确实希望视图的两个实例处于活动状态,所以事情很快就会出错。

请注意,如果您设置一个控制器,然后加载已设置 fx:controller属性的FXML文件,则会发生运行时异常,并且加载将失败。

另一种机制是在加载程序上设置 controllerFactory。控制器工厂本质上是一个将 Class<?>映射到控制器实例的函数(可能是该类的实例,但没有强制执行)。这里要注意的一个重要功能是 controllerFactory会向下传播到 <fx:include> s。换句话说,当加载FXML并包含 <fx:include>标记时,将使用与加载周围FXML相同的控制器工厂来加载包含的FXML。

我经常使用控制器工厂来实例化具有共享模型实例的控制器。即给定一个模型类:

public class Model { 
private ObservableList<TableMessage> messages = FXCollections.observableArrayList();

public ObservableList<TableMessage> getMessages() {
return messages ;
}
}


我做

Model model = new Model() ;
Callback<Class<?>, Object> controllerFactory = clazz -> {
try {
// see if controller class has a constructor taking a Model:
for (Constructor<?> constructor : class.getConstructors()) {
if (constructor.getParameterCount() == 1
&& constructor.getParameterTypes()[0] == Model.class) {
return constructor.newInstance(model);
}
}
// no suitable constructor, just invoke no-arg constructor:
return clazz.newInstance();
} catch (RuntimeException exc) {
throw exc ;
} catch (Exception exc) {
throw new RuntimeException(exc);
}
};
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(controllerFactory);
Parent mainView = loader.load();


共享模型实例通常避免了传递控制器引用的任何需要,因为控制器可以只更新共享数据模型:

public class MainController {
private final Model model ;

@FXML
private TableView<TableMessage> messageTable ;

public MainController(Model model) {
this.model = model ;
}

public void initialize() {
messageTable.setItems(model.getMessages());
// ...
}
}




public class MessageViewController {
private final Model model ;

public MessageViewController(Model model) {
this.model = model ;
}

@FXML
public void addMessage() {
model.getMessages().add(...);
}
}


(与您的应用程序结构不同,但是您可以理解)。

控制器工厂机制非常强大。例如, afterburner.fx是一个非常轻量级的框架,它使用控制器工厂来允许在FX控制器类中使用 @Inject,因此您只需注入共享模型实例即可。

由于您提到使用Spring,因此可以考虑将控制器定义为Spring管理的Bean。那你就可以

ApplicationContext applicationContext = ... ;
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(applicationContent::getBean);
Parent view = loader.load();


现在, FXMLLoader将通过调用 applicationContext.getBean(Class<?>)并传递 fx:controller属性指定的类来获取控制器实例。这样,您可以使用弹簧注入将模型实例(或任何您需要的)注入控制器。您可以在 fx:controller属性中使用接口名称,并让您的spring配置选择接口的实现。由于上述原因,强烈建议将控制器bean的范围设为 prototype(尽管注入的模型bean的范围可以设为 singleton)。只是一些想法...

关于java - JavaFX TableView不会在初始化方法之外更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32099724/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com