JavaFX 避免在使用混合 UI 而不是 UI 元素的长时间运行操作期间锁定 UI

问题描述 投票:0回答:1

在我的控制器中的 JavaFX 中,单击“播放”按钮启动方法“createTTV()”后,创建嵌套的 TreeTableView(此 TreeTableView 会更大,但我不想粘贴太多不必要的代码)。该方法有许多 UI/非 UI 元素,无法将其拆分为 UI/非 UI。有没有一种方法可以在后台运行此类方法,并且当一切准备就绪时更新 UI 而不会锁定整个 UI?我下面的代码可以工作,但 UI 显然没有响应,直到 createTTV() 方法完成运行。

public void buildUi() {
        vBoxForProgressIndicator.setVisible(false);
        fuses_ttv.setRoot(root_FRCs);

        btnPlay.setOnAction(event ->{
            clear_hook();
            vBoxForTableView.setVisible(false);
            vBoxForProgressIndicator.setVisible(true);

            new Thread(()->{
                createTTV();
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        vBoxForProgressIndicator.setVisible(false);
                        vBoxForTableView.setVisible(true);
                    }
                });
            }).start();
        });
}
private void createTTV(){
tdoStructure = model.getDataStructure();
            allFRCs = tdoStructure.getAllFRCsAndCandidates();
            allWires = tdoStructure.getAllWires();
            allParts = tdoStructure.collectAllParts();
            allFusesAndRelays = tdoStructure.getAllFusesAndRelais();

Platform.runLater(()->{
            root_FRCs.getChildren().clear();
            partDescription_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("description"));
            partIdentification_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("identification"));
            partNo_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("partNumber"));
            slot_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("slot"));
            pin_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("pin"));
            moduleFamilyName_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("module"));
            partType_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("type"));
            variants_ttc.setCellValueFactory(new TreeItemPropertyValueFactory<>("variants"));
if (frcElement != null) {
                        TreeItem frcTreeItem = new TreeItem<>(new FuseValidationTreeObject(frcElement));
                        if (!frcElement.getFrcSlots().isEmpty()) {
                            for (Map.Entry<String, ArrayList<String>> entrySlot : frcElement.getFrcSlots().entrySet()) {
                                String slot = entrySlot.getKey();
                                TreeItem slotItem = new TreeItem<>(new FuseValidationTreeObject(slot));
                                slotItem.setExpanded(true);
                                frcTreeItem.getChildren().add(slotItem);
                                for (Part fuseOrRelay : allFusesAndRelays) {
                                    LinkedList<String> fuseOrRelayVariants = AppUtils.getAllVariantsForPart(fuseOrRelay);
                                    if (fuseOrRelay.getReferencePartShortName().equals(frcElement.getShortName()) && AppUtils.getSlotIdFromFuseOrRelayName(fuseOrRelay.getShortName()).equals(slot)) {
                                        TreeItem fuseRelayItem = new TreeItem(new FuseValidationTreeObject(fuseOrRelay));
                                        boolean[] fuseOrRelayHasNoWires = {true};
                                        //assign wires to fuse or relay
                                        allWires.forEach(wire -> {
                                            LinkedList<String> wireVariants = AppUtils.getAllVariantsForPart(wire);
                                            if (fuseOrRelay.getReferencePartShortName().equals(wire.getWireToShortName())
                                                    && entrySlot.getValue().contains(wire.getWireToPin()) && fuseOrRelay.getMemberModuleFamily().getDescription().equals(wire.getMemberModuleFamily().getDescription())) {
                                                if (AppUtils.containsWireVariantsAnyOfFuseVariant(wireVariants, fuseOrRelayVariants)) {
                                                    TreeItem wireToItem = new TreeItem<>(new FuseValidationTreeObject(wire));
                                                    wireToItem.setExpanded(true);
                                                    fuseRelayItem.getChildren().add(wireToItem);
                                                    fuseOrRelayHasNoWires[0] = false;

                                                    if (!fuseOrRelayVariants.containsAll(wireVariants)) {
                                                        if (!wiresWithWrongVariants.contains(wire.getWireNumber())) {
                                                            wiresWithWrongVariants.add(wire.getWireNumber());
                                                        }
                                                    }
                                                }
                                            }
                                            if (fuseOrRelay.getReferencePartShortName().equals(wire.getWireFromShortName())
                                                    && entrySlot.getValue().contains(wire.getWireFromPin()) && fuseOrRelay.getMemberModuleFamily().getDescription().equals(wire.getMemberModuleFamily().getDescription())) {
                                                if (AppUtils.containsWireVariantsAnyOfFuseVariant(wireVariants, fuseOrRelayVariants)) {
                                                    TreeItem wireFromItem = new TreeItem<>(new FuseValidationTreeObject(wire));
                                                    wireFromItem.setExpanded(true);
                                                    fuseRelayItem.getChildren().add(wireFromItem);
                                                    fuseOrRelayHasNoWires[0] = false;

                                                    if (!fuseOrRelayVariants.containsAll(wireVariants)) {
                                                        if (!wiresWithWrongVariants.contains(wire.getWireNumber())) {
                                                            wiresWithWrongVariants.add(wire.getWireNumber());
                                                        }
                                                    }
                                                }
                                            }
                                        });
                                        fuseRelayItem.setExpanded(true);
                                        if (fuseOrRelayHasNoWires[0]) {
                                            emptyFusesOrRelays.add(fuseOrRelay.getShortName());
                                        }
                                        slotItem.getChildren().add(fuseRelayItem);
                                    }
                                }
                                if (slotItem.isLeaf()) {
                                    //insert empty slot into emptySlots Map
                                    if (emptySlots.containsKey(frcElement.getShortName())) {
                                        emptySlots.get(frcElement.getShortName()).add(0, slot);
                                    } else {
                                        emptySlots.put(frcElement.getShortName(), new ArrayList<>(Arrays.asList(slot)));
                                    }
                                    //some more conditions
                                    allWires.forEach(wire -> {
                                        //if there is no fuse in the slot but there is still wire to fuse in this slot
                                        if (wire.getWireToShortName().equals(frcElement.getShortName()) && entrySlot.getValue().contains(wire.getWireToPin())) {
                                            wiresWithNoFuseOrRelay.add(wire);
                                            TreeItem emptyWireTo = new TreeItem<>(new FuseValidationTreeObject(wire));
                                            slotItem.getChildren().add(emptyWireTo);
                                        }
                                        if (wire.getWireFromShortName().equals(frcElement.getShortName()) && entrySlot.getValue().contains(wire.getWireFromPin())) {
                                            wiresWithNoFuseOrRelay.add(wire);
                                            TreeItem emptyWireFrom = new TreeItem<>(new FuseValidationTreeObject(wire));
                                            slotItem.getChildren().add(emptyWireFrom);
                                        }
                                    });
                                }
                            }
                        }
                        frcTreeItem.setExpanded(true);
                        root_FRCs.getChildren().add(frcTreeItem);
                    }
}
}
javafx background-task ui-thread treetableview
1个回答
0
投票

如果 UI 元素未附加到 active 场景,则可以在另一个线程上创建。

对于大多数 JavaFX 控件(有一些例外,例如

WebView
),仅在 JavaFX 线程上创建或修改它们的规则仅适用于对附加到 Stage
live
场景图的修改,其中正在显示,或者正在为其制作
snapshot
。只能在 JavaFX 线程上创建的控件在其文档中已列出。

例如,在另一个线程(例如在任务中)异步加载 FXML 文件并稍后将其附加到 JavaFX 应用程序线程上的场景图是完全可以的。

您可以使用

runLater
将创建的
TreeTableView
添加到活动场景中。

引用

Task
文档,部分:“修改场景图的任务”

一般来说,任务不应该直接与 UI 交互。这样做会在特定任务实现和 UI 的特定部分之间创建紧密耦合。但是,当您确实想要创建此类耦合时,必须确保使用

Platform.runLater
,以便场景图的任何修改都发生在 FX 应用程序线程上。

在提供的示例中,UI 元素是在任务线程上创建的,

Platform.runLater()
仅用于将创建的元素添加到场景图中。

final Group group = new Group();
Task<Void> task = new Task<Void>() {
    @Override protected Void call() throws Exception {
        for (int i=0; i<100; i++) {
            if (isCancelled()) break;
            final Rectangle r = new Rectangle(10, 10);
            r.setX(10 * i);
            Platform.runLater(new Runnable() {
                @Override public void run() {
                    group.getChildren().add(r);
                }
            });
        }
        return null;
    }
};

或者,您可以在任务中创建

TreeTableView
并将其添加到场景
onSucceeded

所以你可以这样写:

Task<TreeTableView> task = new Task<TreeTableView>() {
    @Override protected TreeTableView call() {
        return createTreeTableView();
    }

    private TreeTableView createTreeTableView() {
        // Create and return a new TreeTableView with all new TreeItems initialized.
    }
};

task.setOnSucceeded(e ->
        myParentNode.getChildren().add(
                task.getValue()
        )
);

或者,您可以在

TreeItems
 中仅创建 Task

如果您仍然对在另一个线程中创建 UI 元素感到不舒服,那么您可以按照 James_D 的评论建议:

TreeItem
实例不是UI组件;它们只包含数据。您可以在后台线程上创建所有
TreeItem
实例。然后只需创建树表、列,配置它们,并在 FX 应用程序线程上设置树表的根。后面的操作并不太耗时。

© www.soinside.com 2019 - 2024. All rights reserved.