我有一个应用程序(为了这个问题的目的)有3个组件。
我遇到一个问题,TableView在将项目添加到连接的ObservableList时不会更新。如果我在控制器的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
中被调用)跟踪来自听众的电话?
答案 0 :(得分:5)
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
的{{1}}属性值,将包含文件中的控制器从包含 FXML文件注入控制器 - 注释字段。例如:
MainView.fxml:
@FXML
MainController.java:
<!-- xml headers and imports etc -->
<BorderPane fx:controller="com.example.MainController" ... >
<!-- ... -->
<fx:include source="MessageView.fxml" fx:id="messageView"/>
<!-- ... -->
</BorderPane>
这对您的用例来说已经足够了。
还有其他几种方法可以修改创建控制器的默认机制。最直接的(在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
// ...
}
}
s的情况下实际上没有帮助)是从FXML文件中删除<fx:include>
属性并直接在fx:controller
上设置控制器:
FXMLLoader
这个的主要用例是使用需要传递给构造函数的参数的控制器。您可以使用此技术重复使用单个控制器实例来多次加载FXML文件:我不建议这样做,就好像您真的希望视图的两个实例处于活动状态一样,事情会很快出错。
请注意,如果设置控制器,然后加载设置了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
。控制器工厂本质上是一个将controllerFactory
映射到控制器实例的函数(可能是该类的,但没有强制执行)。这里要注意的一个重要特征是Class<?>
向下传播到controllerFactory
;换句话说,当加载FXML并包含<fx:include>
标签时,相同的控制器工厂用于加载包含的FXML,就像加载周围的FXML一样。
我经常使用控制器工厂来实例化具有共享模型实例的控制器。即给出一个模型类:
<fx:include>
我做
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());
// ...
}
}
(与您的应用程序不同的结构,但您明白了。)
控制器工厂机制非常强大。例如,afterburner.fx是一个非常轻量级的框架,它使用控制器工厂允许在FX控制器类中使用public class MessageViewController {
private final Model model ;
public MessageViewController(Model model) {
this.model = model ;
}
@FXML
public void addMessage() {
model.getMessages().add(...);
}
}
,因此您只需注入共享模型实例。
既然你提到你使用Spring,你可以考虑将你的控制器定义为Spring管理的bean。然后就可以了
@Inject
现在ApplicationContext applicationContext = ... ;
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(applicationContent::getBean);
Parent view = loader.load();
将通过调用FXMLLoader
来获取控制器实例,并传递applicationContext.getBean(Class<?>)
属性指定的类。这样,您可以使用弹簧注入将模型实例(或任何您需要的)注入控制器。您可以在fx:controller
属性中使用接口名称,并让spring配置选择接口的实现。由于上面提到的原因,最好给控制器bean fx:controller
范围(尽管注入的模型bean可能是prototype
范围的)。只是一些想法...