除非有菜单项,否则无法单击菜单

Can not click on menu unless it has menu item

我正在尝试使用 javafx 在菜单栏中的菜单上的单击事件上添加一个动作。 事情是,我看到了很多关于它的帖子,但没有一个答案对我有用。 我设法使用菜单上的 "On Showing" 来做到这一点,这很好, 但是 如果菜单至少有一个菜单项,此事件只会触发(与其他事件一样) . 这不是我想要的,但我现在别无选择

这是 fxml:

    <?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.HangmanGameFXViews.view.MenuesActionsControlleur">
   <top>
      <MenuBar fx:id="menusBar" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" onMouseReleased="#switchToAbout" prefWidth="400.0" BorderPane.alignment="CENTER">
         <menus>
            <Menu mnemonicParsing="false" text="Fichiers">
               <items>
                  <MenuItem mnemonicParsing="false" text="Nouveau" />
                  <MenuItem mnemonicParsing="false" onAction="#switchToScore" text="Scores" />
                  <MenuItem mnemonicParsing="false" onAction="#switchToRules" text="Règles" />
                  <MenuItem mnemonicParsing="false" onAction="#exit" text="Quitter" />
               </items>
            </Menu>
            <Menu fx:id="about" mnemonicParsing="false" onShowing="#switchToAbout" text="À propos">
               <items>
<!--                THIS THE DUMMY MENU I USE TO BE ABLE TO TRIGGER THE EVENT ON THE PARENT -->
                  <MenuItem fx:id="dummyMenuItem" mnemonicParsing="false" />
               </items></Menu>
         </menus>
      </MenuBar>
   </top>
</BorderPane>

视图控制器的代码:

package org.HangmanGameFXViews.view;
import org.HangmanGameFXViews.Main;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.stage.Stage;

public class MenuesActionsControlleur {
    private Stage stageDialogue;
    private Main main;

    @FXML
    private Menu about;
    @FXML
    private MenuBar menusBar;
    @FXML
    private MenuItem dummyMenuItem;

    @FXML
    private void initialize() {

        about.addEventHandler(Event.ANY, new EventHandler<Event>() {

            @Override
            public void handle(Event event) {
                System.out.println("Showing Menu 1");
                System.out.println(event.getTarget().toString());
                System.out.println(event.getEventType().toString());

            }
        });
    }

    @FXML
    public void switchToRules() {
        main.switchToRules();
    }

    @FXML
    public void switchToAbout() {

        dummyMenuItem.setDisable(true);
        main.switchToAbout();
        dummyMenuItem.setDisable(false);

    }
    @FXML
    public void clickableMenu(ActionEvent e){
        System.out.println("Menu clicked");
    }

    @FXML
    public void switchToNew() {
        main.switchToNew();
    }
    @FXML
    public void switchToScore() {
        main.switchToScore();
    }

    public Stage getStageDialogue() {
        return stageDialogue;
    }

    public void setStageDialogue(Stage stageDialogue) {
        this.stageDialogue = stageDialogue;
    }

    @FXML
    public void exit() {
        stageDialogue.close();
    }

    public void setMainClass(Main m) {
        main = m;
        stageDialogue = main.getStagePrincipal();
    }
}

感谢您的帮助。

正如评论中已经指出的那样:菜单并不意味着像按钮(或 MenuItem)一样 - 它的 "action" 是打开一个显示其项目的 ContextMenu。实现它以在该上下文中触发是可能的(注意:它可能会使用户感到困惑,并且在使用实现细节和内部 api 时需要一些技巧)。

就是说:安装自定义处理程序的基本思路是在表示菜单的 styleableNode 上执行此操作。默认情况下,仅当菜单是 ContextMenu 中的项目而不是在 MenuBar 中由 MenuButton 表示时才实现对该节点的访问(我认为这是一个错误,但那是另一回事)。

所以我们必须自己动手

  • 扩展菜单
  • 添加api设置一个包含MenuBar
  • 实现 getStyleableNode 以访问 MenuButton
  • 添加一些连线以将 mouseReleased(或其他)重定向到触发菜单的操作

自定义菜单可能有点像(显然不是生产质量:):

public class MyMenu extends Menu {

    private MenuBar parentMenuBar;
    private Parent menuBarContainer;
    private MenuButton menuButton;

    private EventHandler<MouseEvent> redirector = this::redirect;

    public MyMenu(String string) {
        super(string);
    }

    public void setParentMenuBar(MenuBar menuBar) {
        this.parentMenuBar = menuBar;
        // tbd: cleanup if menuBar and/or its skin disposed/changed 
        if (menuBar != null) {
            menuBar.skinProperty().addListener((src, ov, nv) -> {
                if (nv instanceof MenuBarSkin && menuBar.getChildrenUnmodifiable().size() == 1) {
                    menuBarContainer = (Parent) menuBar.getChildrenUnmodifiable().get(0);
                    menuBarContainer.getChildrenUnmodifiable().addListener((ListChangeListener)c -> {
                        updateEventRedirector();
                    });
                    updateEventRedirector();
                }
            });
        }
    }

    protected void redirect(MouseEvent e) {
        // fire only if there are no items 
        if (getItems().size() == 0) fire();
    }

    /**
     * Rewire eventHandler when our styleable node is changed
     */
    private void updateEventRedirector() {
        if (menuButton != null) {
            menuButton.removeEventHandler(MouseEvent.MOUSE_RELEASED, redirector);
        }
        menuButton = getParentMenuButton();
        if (menuButton != null) {
            menuButton.addEventHandler(MouseEvent.MOUSE_RELEASED, redirector);
        }
    }

    @Override
    public Node getStyleableNode() {
        if (parentMenuBar != null && parentMenuBar.getChildrenUnmodifiable().size() == 1) {
            return menuButton;
        }
        return super.getStyleableNode();
    }

     private MenuButton getParentMenuButton() {
        if (parentMenuBar == null || parentMenuBar.getChildrenUnmodifiable().size() != 1) return null;
        // beware: implementation detail of menuBarSkin!
        Parent parent = (Parent) parentMenuBar.getChildrenUnmodifiable().get(0);
        for (Node child : parent.getChildrenUnmodifiable()) {
            // beware: internal api!
            if (child instanceof MenuBarButton) {
                MenuBarButton menuButton = (MenuBarButton) child;
                if (menuButton.menu == this) {
                    return menuButton;
                }
            }
        }
        return null;
    }
}

使用:

bar = new MenuBar();
first = new MyMenu("dummy");
first.setParentMenuBar(bar);
first.setOnAction(e -> System.out.println("menu handler"));
bar.getMenus().addAll(first, new Menu("other"));