JavaFX TableView不会在initialize方法之外更新

kfr*_*ede 0 java javafx

我有一个应用程序(为了这个问题的目的)有3个组件.

  1. 有问题的主视图(带控制器),其中包含一个TableView
  2. 此视图的表项的模型(TableMessage)
  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);
}
Run Code Online (Sandbox Code Playgroud)

模型:

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
...
}
Run Code Online (Sandbox Code Playgroud)

监听器:

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

我无法理解为什么TableView在初始化后停止观看.如前所述,我确认正在更新正确的tableContent引用.

谢谢

编辑1:

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

FXMLLoader loader = new FXMLLoader(getClass().getResource("MessageView.fxml"));
        loader.load();
        messageViewController= (MessageViewController) loader.getController();
Run Code Online (Sandbox Code Playgroud)

然后将SpringContext(用于监听器)传递给INTO messageViewController,从而创建监听器SpringContext.

然后给听众引用messageViewController我的电话

myListener.setReferenceToController(this);  
Run Code Online (Sandbox Code Playgroud)

看起来像这样

public void setReferenceToController(MessageController ref) {
    this.messageController = ref;
}
Run Code Online (Sandbox Code Playgroud)

一方面认为,如果对控制器的错误引用出错,为什么initialize在跟踪侦听器的调用时,我会在可观察列表中看到示例数据(回想它被调用)?

Jam*_*s_D 5

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

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

FXMLLoader loader = new FXMLLoader(getClass().getResource("MessageView.fxml"));
loader.load();
messageViewController= (MessageViewController) loader.getController();
Run Code Online (Sandbox Code Playgroud)

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

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

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

MainView.fxml:

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

    <!-- ... -->

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

    <!-- ... -->

</BorderPane>
Run Code Online (Sandbox Code Playgroud)

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

        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能足以满足您的使用需求.

还有其他几种方法可以修改创建控制器的默认机制.最直接的(在<fx:include>s 的情况下实际上没有帮助)是fx:controller从FXML文件中删除属性并将控制器直接设置在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();
Run Code Online (Sandbox Code Playgroud)

这个的主要用例是使用需要传递给构造函数的参数的控制器.您可以使用此技术重复使用单个控制器实例来多次加载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 ;
    }
}
Run Code Online (Sandbox Code Playgroud)

我做

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();
Run Code Online (Sandbox Code Playgroud)

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

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());
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

public class MessageViewController {
    private final Model model ;

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

    @FXML
    public void addMessage() {
        model.getMessages().add(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

(与您的应用程序不同的结构,但您明白了).

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

既然你提到你使用Spring,你可以考虑将你的控制器定义为Spring管理的bean.那你可以做

ApplicationContext applicationContext = ... ;
FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(applicationContent::getBean);
Parent view = loader.load();
Run Code Online (Sandbox Code Playgroud)

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