JavaFX UI 在事件侦听器中的 JavaFX 应用程序线程中冻结,但可与 Platform.runLater 一起使用
Posted
技术标签:
【中文标题】JavaFX UI 在事件侦听器中的 JavaFX 应用程序线程中冻结,但可与 Platform.runLater 一起使用【英文标题】:JavaFX UI Freezes in JavaFX Application Thread in Event Listener but works with Platform.runLater 【发布时间】:2019-04-10 15:14:30 【问题描述】:在我的 JavaFX 程序中,我目前的所有代码都在 JavaFX 应用程序线程上运行(包括不一定会更新 UI 的游戏逻辑代码)。我读过你应该在单独的后台线程中运行非 UI 代码,然后使用 Platform.runLater
或 Tasks
在 JavaFX 应用程序线程中相应地更新它,这样 UI 就不会被阻塞,但我仍然感到困惑至于为什么当我在 JavaFX 应用程序线程上调用 myFlowPane.getChildren().remove()
时 UI 会冻结。
以下代码有望提供足够的上下文
hand.getTiles().addListener((ListChangeListener<ObservableTile>) change ->
while (change.next())
if (change.wasAdded())
for (Tile t : change.getAddedSubList())
var btn = tileButtonFactory.newTileButton(t);
fpHand.getChildren().add(change.getFrom(), btn);
else if (change.wasRemoved())
// Player can only remove 1 tile at a time
Tile tile = change.getRemoved().get(0);
fpHand.getChildren().removeIf(node -> ((TileButton)node).getTile().equals(tile));
);
我有一个包含一堆按钮的 FlowPane,我正在收听一个包含一堆图块的 ObservableList
。当此图块列表更改时,我想相应地更新 FlowPane,以便任何新图块,我在 FlowPane 中创建一个按钮。同样,当从该图块列表中删除图块时,我希望将包含此图块的按钮从 FlowPane 中删除。
所以根据我在网上找到的信息,事件侦听器在 JavaFX 应用程序线程上运行。这意味着上面的代码在 JavaFX 应用程序线程上运行。当我向我的 FlowPane 添加一个按钮时,UI 确实不 冻结/被阻止 fpHand.getChildren().add()
。另一方面,当我从 FlowPane 中删除一个按钮时,UI 冻结在 fpHand.getChildren.remove
。
但是,当我使用 Platform.runLater()
包装 fpHand.getChildren.remove()
调用时,UI 会响应并且不会冻结。为什么是这样?所有代码当前都在 JavaFX 应用程序线程上运行。
另外,我的代码的另一部分也有类似的问题。我有一个包含 128 个单元格的 GridPane(每个单元格都有一个 HBox),我需要更新表格。我有以下代码:
private void updateTable()
Platform.runLater(() ->
clearTable();
table.forEach(this::addTilesToTable);
);
private void clearTable()
for (int row = 0; row < gpTable.getRowCount(); row++)
for (int col = 0; col < gpTable.getColumnCount(); col++)
getCellFromGridPane(row, col).ifPresent(hbox -> hbox.getChildren().clear());
如果我删除Platform.runLater()
,UI 不会完全冻结,但是我之前在现场添加的任何图块,就好像桌子上有一个不可见的图块,因此我无法添加图块在那里,即使它应该是空的。 updateTable()
函数也在 JavaFX 应用程序线程和事件侦听器中调用。如果我删除Platform.runLater()
,似乎hbox.getChildren.clear()
没有被正确调用。
【问题讨论】:
要检查是否在 FX 线程上调用了侦听器,请使用Platform.isFxApplicationThread
。监听器(例如InvalidationListener
、ChangeListener
、ListChangeListener
等...)被调用无论Thread
进行了更改。也就是说,您是否尝试过调试/分析您的应用程序以查看它卡在哪里?即使是一些简单的打印语句也可能会告诉您是否进入了循环。如果您仍然无法找出问题所在,您需要提供minimal reproducible example;您提供的代码没有提供足够的上下文。
【参考方案1】:
我遇到的问题是,我将setMouseTransparent(true)
用于根窗格,其中包含用于单击和拖动的 Tile 按钮,而不是消耗鼠标事件。
node.addEventHandler(MouseEvent.MOUSE_PRESSED, e ->
root.setMouseTransparent(true);
dragContext.setMouseX(e.getSceneX());
dragContext.setMouseY(e.getSceneY());
dragContext.setInitialTranslateX(node.getTranslateX());
dragContext.setInitialTranslateY(node.getTranslateY());
e.consume();
);
当你释放鼠标时,它会重新设置为 false
node.addEventHandler(MouseEvent.MOUSE_RELEASED, e ->
root.setMouseTransparent(false);
node.setTranslateX(0);
node.setTranslateY(0);
e.consume();
);
这里的问题是,当我从场景中移除按钮时,它会破坏所有事件处理程序,即使我释放鼠标,它也不会运行此代码。
【讨论】:
以上是关于JavaFX UI 在事件侦听器中的 JavaFX 应用程序线程中冻结,但可与 Platform.runLater 一起使用的主要内容,如果未能解决你的问题,请参考以下文章