移动鼠标无法取消JavaFX MenuItem

时间:2015-03-26 16:06:39

标签: java javafx

enter image description here

如果按住鼠标单击其中一个MenuItem,然后拖动鼠标,该按钮将保持选中状态。即使您将鼠标拖到NewSaveOpen按钮也会保持选中状态。如果您将鼠标放在Open之外的任何位置,命令将仍然执行。换句话说,当您按住鼠标时,即将调用Open' s onAction()

这与Mac OSX中的正常行为不同,我想是Windows本机应用程序。在它们中,即使您按住MenuItem上的单击,如果您将鼠标移开,该按钮也不会触发。但它确实发生在JavaFX上。

我该怎么做才能解决这个问题? JavaFX 8。

2 个答案:

答案 0 :(得分:2)

免责声明:这不是一个合适的解决方案,它是丑陋的,没有经过良好测试,并且有许多限制(见下文)。它也可能会破坏下一个Java版本,但是它解决了我的标准普通香草案例中的问题所以我决定分享它以防万一有人可能会发现它有用。使用它是你自己的风险! :)

我也非常希望有更好的JavaFX皮肤API知识的人可以改进它,我将不胜感激!

限制:

  • 仅适用于ContextMenu,而不适用于主菜单(这是我的用例)
  • 仅适用于单级菜单(无法确定如何扩展子菜单)
  • 不确定它是否适用于CustomMenuItem,或MenuItemGraphic一起使用,但它可能!
  • 它依赖于标准ContextMenuContent作为ContextMenu的外观。如果你有自己的皮肤,那么它将不起作用。

这是辅助类:

public class ContextMenuFixer {

public static void fix(ContextMenu contextMenu) {
    if (contextMenu.getSkin() != null) {
        fix(contextMenu, (ContextMenuContent) contextMenu.getSkin().getNode());
    } else {
        contextMenu.skinProperty().addListener((observable, oldValue, newValue) -> {
            if(newValue != null) {
                fix(contextMenu, (ContextMenuContent) contextMenu.getSkin().getNode());
            }
        });
    }
}

private static void fix(ContextMenu menu, ContextMenuContent content) {

    content.getItemsContainer().getChildren().forEach(node -> {

        EventHandler<? super MouseEvent> releaseEventFilter = event -> {
            if (!((Node) event.getTarget()).isFocused()) {
                event.consume();
                menu.hide();
            }
        };
        node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
            node.removeEventFilter(MouseEvent.MOUSE_RELEASED, releaseEventFilter);

        });

        node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> {
            node.startFullDrag();
            node.addEventFilter(MouseEvent.MOUSE_RELEASED, releaseEventFilter);
        });

        node.addEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, event -> {
            MouseEvent e = event.copyFor(event.getSource(), event.getTarget(), MouseEvent.MOUSE_ENTERED);
            node.fireEvent(e);
        });

        node.addEventHandler(MouseDragEvent.MOUSE_DRAG_RELEASED, event -> {
            Event e = event.copyFor(event.getSource(), event.getTarget(), MouseEvent.MOUSE_RELEASED);
            node.fireEvent(e);
        });

        node.addEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, event -> {
            node.getParent().requestFocus();
        });

        node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
            menu.hide();
        });

    });
}

}

用法非常简单:

    ContextMenuFixer.fix(myContextMenu);

P.S。一个更清洁的解决方案是编写你自己的适当的皮肤类(即,替换ContextMenuContent),但由于维护成本,我故意不想这样做。

答案 1 :(得分:1)

最后,我找到了JavaFX MenuBar的解决方法。非常感谢Danis his answer的ContextMenu修复方法。我所做的就是通过MenuBarSkin进行破解,直到在其中找到ContextMenu,然后应用Danis的代码。因此,“我的解决方案”仅删除了MenuBar限制,但保留了Danis的所有其他限制列表,这更加难看。

在这里:

public static void fix(MenuBar menubar) throws Exception {
    // Hack through MenuBarSkin until we get the ContextMenus
    Field container = MenuBarSkin.class.getDeclaredField("container");
    container.setAccessible(true);
    Field openMenu = MenuBarSkin.class.getDeclaredField("openMenu");
    openMenu.setAccessible(true);
    Field popup = MenuButtonSkinBase.class.getDeclaredField("popup");
    popup.setAccessible(true);

    MenuBarSkin mBarSkin = new MenuBarSkin(menubar);
    menubar.setSkin(mBarSkin);

    // Modified code from Danis
    HBox hBox = (HBox) container.get(mBarSkin);
    hBox.getChildren().forEach(child -> {
        MenuButton mButton = (MenuButton) child;
        MenuButtonSkin mButtonSkin = new MenuButtonSkin(mButton);
        mButton.setSkin(mButtonSkin);

        ContextMenu contextMenu;
        try {
            contextMenu = (ContextMenu) popup.get(mButtonSkin);
        } catch (IllegalArgumentException | IllegalAccessException e1) {
            e1.printStackTrace();
            return;
        }

        ContextMenuSkin cmSkin = new ContextMenuSkin(contextMenu);
        contextMenu.setSkin(cmSkin);
        ContextMenuContent content = (ContextMenuContent) cmSkin.getNode();

        contextMenu.setOnHiding(event -> {
            try {
                ((Menu) openMenu.get(mBarSkin)).hide();
                openMenu.set(mBarSkin, null);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
                return;
            }
        });

        content.getItemsContainer().getChildren().forEach(node -> {
            EventHandler<? super MouseEvent> releaseEventFilter = event -> {
                if (!((Node) event.getTarget()).isFocused()) {
                    event.consume();
                    contextMenu.hide();
                    mButton.hide();
                }
            };
            node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
                node.removeEventFilter(MouseEvent.MOUSE_RELEASED, releaseEventFilter);

            });

            node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> {
                node.startFullDrag();
                node.addEventFilter(MouseEvent.MOUSE_RELEASED, releaseEventFilter);
            });

            node.addEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, event -> {
                MouseEvent e = event.copyFor(event.getSource(), event.getTarget(), MouseEvent.MOUSE_ENTERED);
                node.fireEvent(e);
            });

            node.addEventHandler(MouseDragEvent.MOUSE_DRAG_RELEASED, event -> {
                Event e = event.copyFor(event.getSource(), event.getTarget(), MouseEvent.MOUSE_RELEASED);
                node.fireEvent(e);
            });

            node.addEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, event -> {
                node.getParent().requestFocus();
            });

            node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
                contextMenu.hide();
            });
        });
    });
}

并用于:

fix(urMenuBar);

希望这会有所帮助!