如何自动隐藏 GWT MenuBar 子菜单?

Posted

技术标签:

【中文标题】如何自动隐藏 GWT MenuBar 子菜单?【英文标题】:How does one autohide a GWT MenuBar submenu? 【发布时间】:2009-09-23 23:57:16 【问题描述】:

我们最近在应用程序的一部分中附加了一个 GWT MenuBar,用于菜单目的。

基本上我希望当您将鼠标悬停在***菜单上时打开子菜单,这很容易做到:

menubar.setAutoOpen(true);

我还希望在用户鼠标离开子菜单时自动隐藏子菜单。理想情况下有一些延迟,以防止它突然消失,但我会满足于隐藏。

这似乎不是内置的,GWT 中的 MenuItem 对象直接子类化 UIObject,这意味着没有相对琐碎的 onBrowserEvent() 或附加鼠标侦听器的地方。可能扩展 MenuItem 和 sinking/unsinking 事件可以让我添加这种行为,但我不确定这是否是最好的方法。

那么自动隐藏 GWT 子菜单的最佳方法是什么?

谢谢。

【问题讨论】:

【参考方案1】:

在经历了许多可怕的黑客攻击之后,我们编写了自己的cascading menu 作为GWT Portlets framework 的一部分。它显示来自 html 模板的菜单项和子菜单,如下所示:

<a href="#home">Home</a>
<a href="#submenu1()">Sub Menu 1</a>
<a href="#away">Away</a>

<div id="submenu1">
    <a href="#hello_world">Hello World</a>
    <a href="#free_memory">Free Memory</a>
    <a href="#submenu2()">Sub Menu 2</a>
</div>

<div id="submenu2">
    <a href="#command_demo">Command Demo</a>
    <a href="#command1()">Command1</a>
    <a href="#command2(arg1,arg2)">Command2</a>
</div>

看起来像方法调用广播 CommandEvent 的 URL。其他人像往常一样触发历史令牌更改。查看online demo 以查看实际使用的菜单。

【讨论】:

唉,可怕的黑客行为是我们不得不做的。我喜欢演示的外观,但目前还不会向我们的项目添加另一个框架。不过,感谢您的建议,很高兴知道它就在那里。【参考方案2】:

这是一个相当完整的解决方案,并不完美,代码后解释:

public class MyMenuBar extends Composite 

    private class OpenTab implements ScheduledCommand 
        private String wid;

        public OpenTab(String windowId) 
            wid = windowId;
        

        @Override
        public void execute() 
            WinUtl.newAppTab(wid);
        
    

    interface MyMenuBarUiBinder extends UiBinder<Widget, MyMenuBar> 

    private static MyMenuBarUiBinder uiBinder =
                                GWT.create(MyMenuBarUiBinder.class);

    @UiField MenuBar mainMenu;

    @UiField MenuBar subsMenu;
    @UiField MenuItem subsChoice1;
    @UiField MenuItem subsChoice2;
    @UiField MenuItem subsChoice3;

    @UiField MenuBar svcPrvdrMenu;
    @UiField MenuItem svcPrvdrChoice1;
    @UiField MenuItem svcPrvdrChoice2;

    @UiField MenuBar netMgtMenu;
    @UiField MenuItem netMgtChoice1;

    @UiField MenuBar reportsMenu;
    @UiField MenuItem reportsChoice1;

    @UiField MenuBar auditsMenu;
    @UiField MenuItem auditsChoice1;

    @UiField MenuBar securityMenu;
    @UiField MenuItem securityChoice1;

    @UiField MenuBar helpMenu;
    @UiField MenuItem helpChoice1;

    private boolean subMenuPopped = false;
    private boolean subMenuEntered = false;

    private static Type<MouseOverHandler> OVR_EVT = MouseOverEvent.getType();
    private static Type<MouseOutHandler> OUT_EVT = MouseOutEvent.getType();

    private MouseOverHandler mainOverHandler = new MouseOverHandler() 
        @Override
        public void onMouseOver(MouseOverEvent event) 
            subMenuPopped = true;
        
    ;

    private MouseOutHandler mainOutHandler = new MouseOutHandler() 
        @Override
        public void onMouseOut(MouseOutEvent event) 
            Element e = event.getRelativeElement()
            boolean movedUp = (event.getRelativeY(e) < 0);
            if ((movedUp && subMenuPopped) || subMenuEntered) 
                subMenuPopped = false;
                subMenuEntered = false;
                mainMenu.closeAllChildren(true);
            
        
    ;

    private MouseOverHandler subOverHandler = new MouseOverHandler() 
        @Override
        public void onMouseOver(MouseOverEvent event) 
            subMenuEntered = true;
        
    ;

    private MouseOutHandler subOutHandler = new MouseOutHandler() 
        @Override
        public void onMouseOut(MouseOutEvent event) 
            subMenuPopped = false;
            subMenuEntered = false;
            mainMenu.closeAllChildren(true);
        
    ;

    public MyMenuBar() 
        initWidget(uiBinder.createAndBindUi(this));

        mainMenu.addStyleName("npac-MenuBar");
        mainMenu.setAutoOpen(true);
        mainMenu.setAnimationEnabled(true);
        mainMenu.setFocusOnHoverEnabled(true);

        subsChoice1.setScheduledCommand(new OpenTab(Names.Wid.NPA));

        mainMenu.addDomHandler(mainOverHandler, OVR_EVT);
        mainMenu.addDomHandler(mainOutHandler, OUT_EVT);

        addHandlers(subsMenu);
        addHandlers(svcPrvdrMenu);
        addHandlers(netMgtMenu);
        addHandlers(reportsMenu);
        addHandlers(auditsMenu);
        addHandlers(securityMenu);
        addHandlers(helpMenu);
    

    private void addHandlers(MenuBar m) 
        m.addDomHandler(subOverHandler, OVR_EVT);
        m.addDomHandler(subOutHandler, OUT_EVT);
    

这处理了 mouseOver 打开 subMenu,用户然后鼠标向上,关闭 mainMenu(subMenu 关闭)的情况。它不处理鼠标沿对角线向下移动,越过 subMenu 的任一侧(子菜单保持打开状态) 当然可以改进,但我刚刚开始工作并想分享;-)

【讨论】:

这个答案对我有帮助。我已经使用这个想法和提供的代码来构建非常相似的东西。不过,我的菜单有嵌套的子菜单(MenuBar 中的 MenuBar),您的代码没有考虑到这些子菜单,我仍在处理它......再次感谢。【参考方案3】:

不需要可怕的黑客攻击或依赖 JAVA 中的 CSS 来实现带有子菜单的 MenuBar 的自动隐藏。我创建了一个完整的 Parent+Children 下拉菜单示例,其中 mouseover 打开和 mouseOut 关闭,并解释了每个部分供其他人使用。

我见过的常见问题是运行 ((JMenu)e.getSource()).doClick();在 mouseEntered 上模拟单击到 JMenu 父级之一,但不能简单地添加到 mouseExited 方法,因为 MouseListener 需要附加到子 MenuItems 以及 JMenu 父级。 (在对 MenuBar 的正常分配中它不会这样做 - 仅附加到父 JMenu 对象)。

此外,由于试图让 MouseExit 侦听器仅在鼠标离开整个菜单结构(即子菜单下拉菜单)时触发“关闭”方法而出现问题。

以下是从我的实时应用中获取的完整答案:

我解决鼠标退出菜单关闭的方法是在构造函数的顶部运行一个布尔变量“isMouseOut”来跟踪,然后以更OO友好的方式分配MouseListener来跟踪多个MouseIn -用户与菜单交互时的MouseOut 事件。它调用一个单独的 menuClear 方法,作用于布尔“isMouseOut”的状态。该类实现 MouseListener。这就是它的完成方式。

首先创建一个 ArrayList,将所有菜单项添加到该数组中。像这样:

    Font menuFont = new Font("Arial", Font.PLAIN, 12);
    JMenuBar menuBar = new JMenuBar();
    getContentPane().add(menuBar, BorderLayout.NORTH); 

// Array of MenuItems
    ArrayList<JMenuItem> aMenuItms = new ArrayList<JMenuItem>();
    JMenuItem mntmRefresh = new JMenuItem("Refresh");
    JMenuItem mntmNew = new JMenuItem("New");
    JMenuItem mntmNormal = new JMenuItem("Normal");
    JMenuItem mntmMax = new JMenuItem("Max");
    JMenuItem mntmStatus = new JMenuItem("Status");
    JMenuItem mntmFeedback = new JMenuItem("Send Feedback");
    JMenuItem mntmEtsyTWebsite = new JMenuItem("EtsyT website");
    JMenuItem mntmAbout = new JMenuItem("About");

    aMenuItms.add(mntmRefresh);
    aMenuItms.add(mntmNew);
    aMenuItms.add(mntmNormal);
    aMenuItms.add(mntmMax);
    aMenuItms.add(mntmStatus);
    aMenuItms.add(mntmFeedback);
    aMenuItms.add(mntmEtsyTWebsite);
    aMenuItms.add(mntmAbout);

然后在此阶段迭代 arrayList,使用 for() 循环添加 MouseListener:

  for (Component c : aMenuItms) 
        if (c instanceof JMenuItem) 
            c.addMouseListener(ml);
        
    

现在为 MenuBar 设置 JMenu 父项:

// Now set JMenu parents on MenuBar
    final JMenu mnFile = new JMenu("File");
    menuBar.add(mnFile).setFont(menuFont);
    final JMenu mnView = new JMenu("View");
    menuBar.add(mnView).setFont(menuFont);
    final JMenu mnHelp = new JMenu("Help");
    menuBar.add(mnHelp).setFont(menuFont);

然后将下拉 menuItems 子项添加到 JMenu 父项:

// Now set menuItems as children of JMenu parents
    mnFile.add(mntmRefresh).setFont(menuFont);
    mnFile.add(mntmNew).setFont(menuFont);
    mnView.add(mntmNormal).setFont(menuFont);
    mnView.add(mntmMax).setFont(menuFont);
    mnHelp.add(mntmStatus).setFont(menuFont);
    mnHelp.add(mntmFeedback).setFont(menuFont);
    mnHelp.add(mntmEtsyTWebsite).setFont(menuFont);
    mnHelp.add(mntmAbout).setFont(menuFont);

将 mouseListeners 作为单独的步骤添加到 JMenu 父级:

    for (Component c : menuBar.getComponents()) 
        if (c instanceof JMenu) 
            c.addMouseListener(ml);
        
    

现在子 menuItem 元素都有自己的侦听器,这些侦听器与父 JMenu 元素和 MenuBar 本身是分开的 - 在 MouseListener() 实例化中识别对象类型很重要,这样您就可以自动打开菜单mouseover(在本例中为 3x JMenu 父级)但也避免了子异常错误,并允许清晰地识别菜单结构的 mouseOUT,而无需尝试监视鼠标位置的位置。 MouseListener 如下:

MouseListener ml = new MouseListener() 
        public void mouseClicked(MouseEvent e) 
        

        public void mousePressed(MouseEvent e) 
        

        public void mouseReleased(MouseEvent e) 
        

        public void mouseExited(MouseEvent e) 
            isMouseOut = true;
            timerMenuClear();
        

        public void mouseEntered(MouseEvent e) 
            isMouseOut = false;
            Object eSource = e.getSource();
            if(eSource == mnHelp || eSource == mnView || eSource == mnFile)
                ((JMenu) eSource).doClick();
            
        
    ; 

上面只模拟了鼠标点击到 JMenu 'parents'(在本例中为 3x),因为它们是子菜单下拉菜单的触发器。 timerMenuClear() 方法调用 MenuSelectionManager 以清空实际 mouseOUT 时处于活动状态的任何 selectedpath 点:

public void timerMenuClear()
    ActionListener task = new ActionListener() 
      public void actionPerformed(ActionEvent e) 
          if(isMouseOut == true)
              System.out.println("Timer");
          MenuSelectionManager.defaultManager().clearSelectedPath();
          
      
  ;        
    //Delay timer half a second to ensure real mouseOUT
  Timer timer = new Timer(1000, task); 
  timer.setInitialDelay(500);        
  timer.setRepeats(false);
  timer.start();

我进行了一些测试,监控在开发过程中我可以在 JVM 中访问哪些值 - 但它真的很管用!即使是嵌套菜单 :) 我希望很多人觉得这个完整的例子非常有用。

【讨论】:

【参考方案4】:

使用此代码:

public class MenuBarExt extends MenuBar 

public MenuBarExt()

    super();


@Override
public void onBrowserEvent(Event event)

    switch (DOM.eventGetType(event))
    
        case Event.ONMOUSEOUT:
            closeAllChildren(false);
            break;
        default:
            super.onBrowserEvent(event);
            break;

     
    super.onBrowserEvent(event);



【讨论】:

效果很好!除非您想实际选择一个菜单项并且菜单消失...

以上是关于如何自动隐藏 GWT MenuBar 子菜单?的主要内容,如果未能解决你的问题,请参考以下文章

如何禁用 GWT MenuBar 中子菜单后面的阴影?

gwt 中的菜单栏

在 GWT 中,如何调整菜单项的大小以使其仅限于菜单栏

需要 MenuBar 的 MouseOutEvent 来检测点击:GWT

如何在屏幕点击时删除 GWT 菜单栏?

如何在悬停时增加 gwt menuitem