JavaFX TableView不会在初始化方法之外进行更新

弗里德

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

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

将项目添加到已连接的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在跟踪来自侦听器的调用时,为什么在可观察列表中会看到示例数据(记得在中调用了该示例数据)?

詹姆斯·D

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

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

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

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>

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>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();

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

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

另一种机制是controllerFactory在加载程序上设置a 控制器工厂本质上是一个Class<?>将a映射到控制器实例的函数(大概是该类的实例,但没有强制执行)。这里要注意的一个重要功能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是一个非常轻量级的框架,它使用控制器工厂来允许@Inject在FX控制器类中使用,因此您只需注入共享模型实例即可。

既然您提到使用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配置选择接口的实现。由于上述原因,强烈建议给控制器beanprototype范围(尽管可以对注入的模型bean进行singleton范围设置)。只是一些想法...

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章