在JavaFX中关闭选项卡上的双选项卡更改事件

时间:2017-10-16 17:42:16

标签: java javafx

我有以下程序:

public class MainClass extends Application {

    public static void main(String[] arg) {
        launch(arg);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        TabPane tabPane = new TabPane();
        tabPane.getSelectionModel().selectedItemProperty().addListener((ov, oldTab, newTab) -> {
            System.out.println("Tab change: " + oldTab + "/" + newTab);
        });
        Tab tab = new Tab("Test tab");
        tab.setOnCloseRequest((event) -> {
            System.out.println("Removing tab");
            event.consume();
            //I need to remove tab manually
            tabPane.getTabs().remove(tab);
        });
        System.out.println("Adding tab");
        tabPane.getTabs().add(tab);
        Group root = new Group(tabPane);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}

当我运行它并单击选项卡上的关闭图标时,我有以下程序输出:

Adding tab
Tab change: null/javafx.scene.control.Tab@70b1aa69
Removing tab
Tab change: javafx.scene.control.Tab@70b1aa69/null
Tab change: null/javafx.scene.control.Tab@70b1aa69

如你所见,当我关闭标签时,我会收到两个Tab change个事件,但我只需要一个。如何解决?

2 个答案:

答案 0 :(得分:2)

有趣的错误 - 令人困惑的是,即使不再在选项卡列表中,删除的选项卡仍然可以作为选定选项卡的原因/方式。

第一个问题是,确切选择的位置:在TabHeaderSkin安装的mousePressedHandler中完成

setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override public void handle(MouseEvent me) {
        if (getTab().isDisable()) {
            return;
        }
        if (me.getButton().equals(MouseButton.MIDDLE)) {
            if (showCloseButton()) {
                Tab tab = getTab();
                if (behavior.canCloseTab(tab)) {
                    removeListeners(tab);
                    behavior.closeTab(tab);
                }
            }
        } else if (me.getButton().equals(MouseButton.PRIMARY)) {
            behavior.selectTab(getTab());
        }
    }
});

但是接下来:在删除标签后,这个处理程序仍然处于活动状态(希望它的视觉效果也是如此)?视觉部分的清理是TabPaneSkin的任务,它监听选项卡列表并删除TabHeaderSkin(也就是:显示其内容上方的选项卡的组件)。但由于两个原因,清理工作并未立即完成:

  • 淡出动画使标题保持活动状态,直到动画准备就绪,这很好
  • 标头的内部清理(通过header.removeListeners发送消息)不完整,因为它删除了子节点和侦听器,但无法删除mouseHandler - 这就是错误。

TabHeaderSkin的代码:

private void removeListeners(Tab tab) {
    listener.dispose();
    inner.getChildren().clear();
    getChildren().clear();
    // following line is missing:
    setOnMousePressed(null)   
}

一种黑客攻击的方法是在选项卡上注册我们自己的侦听器,并在删除时强制处理程序为null。注意:必须在核心完成其工作后通知侦听器,因此要么安装在自定义皮肤中,要么在设置tabPane的皮肤之后。

为了说明,我相应地修改了你的例子:

public class TabPaneRemoveSelected extends Application {

    public static void main(String[] arg) {
        launch(arg);
    }

    public static class MyTabSkin extends TabPaneSkin {

        public MyTabSkin(TabPane pane) {
            super(pane);
            pane.getTabs().addListener(this::tabsChanged);
        }

        protected void tabsChanged(Change<? extends Tab> c) {
            while (c.next()) {
                if (c.wasRemoved()) {
                    // lookup all TabHeaderSkins
                    Set<Node> tabHeaders = getSkinnable().lookupAll(".tab");
                    tabHeaders.stream()
                        .filter(p -> p instanceof Parent)
                        .map(p -> (Parent) p)
                        .forEach(p -> {
                            // all children removed indicates being in the process
                            // of being removed
                            if (p.getChildrenUnmodifiable().size() == 0) {
                                // complete removeListeners
                                p.setOnMousePressed(null);
                            }
                        }
                    );

                }
            }
        }

    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        TabPane tabPane = new TabPane() {

            @Override
            protected Skin<?> createDefaultSkin() {
                return new MyTabSkin(this);
            }

        };
        tabPane.getSelectionModel().selectedItemProperty().addListener((ov, oldTab, newTab) -> {
            System.out.println("Tab change: " + oldTab + "/" + newTab );
        });
        Tab tab = new Tab("Test tab");
        Tab second = new Tab("second");
        installHandler(tabPane, tab, second);
        installHandler(tabPane, second);
        System.out.println("Adding tab");
        tabPane.getTabs().addAll(tab, second);
        Group root = new Group(tabPane);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    protected void installHandler(TabPane tabPane, Tab... tab) {
        for (Tab tab2 : tab) {
            tab2.setOnCloseRequest((event) -> {
                System.out.println("Removing tab");
                event.consume();
                //I need to remove tab manually
                tabPane.getTabs().remove(tab2);
            });

        }
    }

}

答案 1 :(得分:1)

这似乎是一个错误所以我打开了一个错误问题http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8189424https://bugs.openjdk.java.net/browse/JDK-8189424)并接受了这个答案(只要SO允许我这样做)。