如何创建弹出菜单

14
我是javafx的新手。我想在鼠标右键单击时显示弹出菜单。我找到了一个教程这里这里,但不理解。我想创建一个弹出菜单,它显示在此链接中的图像上。
现在我正在创建舞台,但我不想要舞台。我需要显示在右键单击时显示并在单击任何地方时关闭的弹出菜单。
这是我的代码,其中我正在使用舞台,但我需要像上面链接中那样打开弹出菜单。
 public void MouseClickedOnTree(MouseEvent event) {
if (event.isSecondaryButtonDown()) {

        System.out.println("secondary press");
        final Stage optionstage = new Stage();

        VBox vBox = new VBox(5);
        vBox.setMinHeight(50);
        vBox.setMinWidth(50);
        Button btnNewTestBed = new Button("New Testbed");
        btnNewTestBed.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                try {
                     optionstage.close();
                    stage.show();
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        });
        Button btnOpenTestbed = new Button("Open Testbed");
        btnOpenTestbed.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                optionstage.close();

            }
        });
        optionstage.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent t) {
                if (t.getCode() == KeyCode.ESCAPE) {
                    System.out.println("click on escape");
                    //Stage sb = (Stage) label.getScene().getWindow();//use any one object
                    if(optionstage.isShowing())
                        optionstage.close();
                }
            }
        });

        vBox.getChildren().addAll(btnNewTestBed, btnOpenTestbed);
        optionstage.setScene(new Scene(vBox, 100, 100));
        //stage.setScene(new Scene(new Group(new Text(50,50, "my second window")))); 
        optionstage.setX(event.getSceneX());
        optionstage.setY(event.getScreenY());
        optionstage.initStyle(StageStyle.UNDECORATED);
        optionstage.show();

    }

请提供任何链接或参考资料。
3个回答

28

你的代码上下文不是非常清晰:这是在事件处理程序内吗?如果是,它是为什么事件处理程序?如果不是,那么在开头的if语句中的event是什么?

你提供的两个链接相当复杂;在JavaFX中(与Swing不同),您应该只考虑子类化现有节点类以进行非常高级的用例。您无需达到此级别的复杂性就可以创建弹出菜单。

创建弹出菜单的最简单方法是为Control(或子类)创建一个ContextMenu,向其中添加MenuItem,并将其设置为您控件上的contextMenu

TextField textField = new TextField("Type Something"); // we will add a popup menu to this text field
final ContextMenu contextMenu = new ContextMenu();
MenuItem cut = new MenuItem("Cut");
MenuItem copy = new MenuItem("Copy");
MenuItem paste = new MenuItem("Paste");
contextMenu.getItems().addAll(cut, copy, paste);
cut.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        System.out.println("Cut...");
    }
});
// ...
textField.setContextMenu(contextMenu);
如果您想在非控件节点(例如PaneShape)上使用ContextMenu,则没有setContextMenu(...) 方法可用。因此,您需要进行一些额外的工作:
final AnchorPane pane = new AnchorPane();
// fill pane with nodes, etc
// create context menu and menu items as above
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        if (event.isSecondaryButtonDown()) {
            contextMenu.show(pane, event.getScreenX(), event.getScreenY());
        }
    }
});

查看Javadocs了解有关ContextMenu的更多详细信息,或查看教程


但我想在TreeView上显示它。当我右键单击TreeView时,我想在弹出窗口中显示。 - Sandip Armal Patil
我使用了这段代码,但弹出窗口没有显示:contextMenu.show(soariteTree, event.getScreenX(), event.getScreenY()); - Sandip Armal Patil
抱歉..现在它可以工作了...我将声明代码放在事件外面,然后它就可以工作了...谢谢。 - Sandip Armal Patil
TreeView是一个控件,所以您只需要执行treeView.setContextMenu(contextMenu)即可,无需注册鼠标处理程序或调用show(...)。 - James_D
这个答案不是正确的解决方案。@fuzzyBSc的答案非常好! - Neuron
显示剩余2条评论

16

James_D已经提供了一个与教程一致的可工作示例,但是我在它上面遇到了问题。James正确地指出,对于任何类型为Control的节点,打开上下文菜单的正确方式是使用Control.setContextMenu()。然而,与教程相反,在非Control节点上注册上下文菜单的正确方法如下(Java 8):

    pane.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
        contextMenu.show(pane, event.getScreenX(), event.getScreenY());
        event.consume();
    });
    pane.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
        contextMenu.hide();
    });

这与setContextMenu背后的实现一致。Controls的setContextMenu实现使用和消耗ContextMenuEvent,但不会消耗鼠标事件。这意味着,如果您在Pane上注册了MouseEvent的监听器,并在Pane内的Control上使用setContextMenu,则右键单击控件实际上将打开两个上下文菜单:Control将监听并消费ContextMenuEvent,而Pane则监听并消费MouseEvent。我发现在我的openjdk-8代码中,在Pane上注册一个ContextMenuEvent监听器可以解决这个重复上下文菜单问题。

我还发现,注册在Pane上的菜单默认情况下在用户单击其他位置时不会关闭。未消耗其事件的MOUSE_PRESSED监听器可确保在应该关闭菜单时关闭菜单。


2
在Node中也有一个setOnContextMenuRequested()方法。 - Andrew
@Andrew实际上很多时候需要这个,以及fuzzyBsc的回答的第二部分。 - trilogy

1

James_D的上面的回答是错误的。如果您右键单击非控制面板,但单击其他地方,则上下文菜单不会自动隐藏。

我喜欢fuzzyBSc的回答,但我发现这种组合在所有边缘情况下都有效:

pane.setOnContextMenuRequested(e -> contextMenu.show(pane, e.getScreenX(), e.getScreenY()));
pane.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> contextMenu.hide());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接