JavaFX TableView没有';t在初始化方法之外更新
我有一个应用程序(就本问题而言)有3个部分JavaFX TableView没有';t在初始化方法之外更新,java,javafx,Java,Javafx,我有一个应用程序(就本问题而言)有3个部分 包含TableView的主视图(带控制器) 此视图的表项的模型(TableMessage) 线程侦听器侦听要添加到表中的消息 我面临一个问题,当项目添加到连接的ObservableList时,TableView没有更新。如果我在控制器的initialize方法中添加样本数据,数据显示良好。但是,当我从程序中的其他地方(本例中的侦听器)调用相同的方法进行添加时,TableView不会更新。在调试过程中,我可以看到数据被添加到连接列表中(示例数据在那里,因
initialize
方法中添加样本数据,数据显示良好。但是,当我从程序中的其他地方(本例中的侦听器)调用相同的方法进行添加时,TableView不会更新。在调试过程中,我可以看到数据被添加到连接列表中(示例数据在那里,因此我知道它是正确的对象)
控制器:
@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);
}
听众:
// 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
(用于侦听器)向下传递到messageViewController
,从而从该SpringContext
创建侦听器
然后给侦听器引用我的messageViewController
I调用
myListener.setReferenceToController(this);
看起来像这样
public void setReferenceToController(MessageController ref) {
this.messageController = ref;
}
另一方面,如果对控制器的引用错误,那么当我跟踪来自侦听器的调用时,为什么我会在可观察列表中看到样本数据(回想一下它是在
初始化
中调用的?FXMLLoader的默认行为,当它在FXML文件的根元素中遇到fx:controller
属性时,它将通过调用其无参数构造函数来创建该属性指定的控制器类的新实例,并将其用作该FXML定义的视图的控制器
因此,当您通过代码获得控制器的引用时
FXMLLoader loader = new FXMLLoader(getClass().getResource("MessageView.fxml"));
loader.load();
messageViewController= (MessageViewController) loader.getController();
fxmloader
创建MessageViewController
的新实例,并将其与MessageView.fxml
定义的视图的新实例相关联。由于您放弃了该视图(您不会对loader.load()
的返回值执行任何操作),因此您所引用的控制器与未显示的视图相关联
(请注意,FXMLLoader
仍将在该控制器实例上调用initialize(…)
,因此initialize()
方法的任何效果都将在您获得的引用中可见。)
根据您的注释,您实际显示的视图是通过在另一个fxml文件中包含MessageView.fxml
创建的。当FXMLLoader
使用该技术加载包含的FXML文件时,可以向创建的控制器注入引用。简单地说,在
元素中添加一个fx:id
。通过将@FXML
-注释字段名称中的fx:id
属性值追加“controller”
,可以将包含文件中的控制器从包含的FXML文件注入控制器。例如:
MainView.fxml:
<!-- xml headers and imports etc -->
<BorderPane fx:controller="com.example.MainController" ... >
<!-- ... -->
<fx:include source="MessageView.fxml" fx:id="messageView"/>
<!-- ... -->
</BorderPane>
这对于您的用例来说已经足够了
有两种其他方法可以修改创建控制器的默认机制。最直接的方法(对于
s实际上没有帮助)是从FXML文件中删除fx:controller
属性,并直接在fxmloader
上设置控制器:
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
向下传播到
s;换句话说,当加载FXML并包含
标记时,加载包含的FXML时使用的控制器工厂与加载周围FXML时使用的控制器工厂相同
我经常使用控制器工厂来实例化具有共享模型实例的控制器。即,给定一个模型类:
public class Model {
private ObservableList<TableMessage> messages = FXCollections.observableArrayList();
public ObservableList<TableMessage> getMessages() {
return messages ;
}
}
(与应用程序的结构不同,但您可以理解)
控制器工厂机制非常强大。例如,是一个非常轻量级的框架,它使用控制器工厂允许在FX控制器类中使用@Inject
,因此您可以只注入共享模型实例
由于您提到使用Spring,可以考虑将控制器定义为Spring托管bean。那你就可以了
ApplicationContext applicationContext = ... ;
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(applicationContent::getBean);
Parent view = loader.load();
现在,fxmloader
将通过调用applicationContext.getBean(Class)
,传递由fx:controller
属性指定的类来获取控制器实例。通过这种方式,您可以使用spring注入将模型实例(或任何您需要的)注入控制器。您可以在fx:controller
属性中使用接口名称,并让spring配置选择接口的实现。出于上述原因,最好给出
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(...);
}
}
ApplicationContext applicationContext = ... ;
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(applicationContent::getBean);
Parent view = loader.load();