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

时间:2009-09-23 23:57:17

标签: java events gwt menubar

我们最近将一个GWT MenuBar附加到我们的应用程序的一部分,以及菜单用途。

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

menubar.setAutoOpen(true);

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

这似乎没有内置,并且GWT中的MenuItem对象直接将UIObject子类化,这意味着没有相对简单的onBrowserEvent()或某处附加鼠标侦听器。可能会扩展MenuItem和沉没/解链事件会让我添加这种行为,但我不确定这是否是最好的方法。

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

谢谢。

4 个答案:

答案 0 :(得分:2)

经过非常可怕的黑客攻击尝试实现类似的东西后,我们编写了自己的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>

看起来像方法调用的URL广播CommandEvent。其他人触发正常的历史令牌更改。请查看online demo以查看正在运行的菜单。

答案 1 :(得分: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的任何一侧(子菜单保持打开状态) 当然可以改进,但我只是让它工作,想分享; - )

答案 2 :(得分:0)

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

我亲眼目睹的常见问题是运行((JMenu)e.getSource())。doClick();在mouseEntered上模拟了单击其中一个JMenu父项,但不能简单地添加到mouseExited方法,因为MouseListener需要附加到子MenuItems以及JMenu父项。 (它在正常分配给MenuBar时没有做 - 只附加到父JMenu对象)。

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

以下是从我的实时应用中获得的完整解决方案:

我解决鼠标输出关闭菜单的方法是运行一个布尔变量&#34; isMouseOut&#34;在构造函数的顶部进行跟踪,然后以更友好的方式分配MouseListener,以便在用户与菜单交互时跟踪多个MouseIn-MouseOut事件。它调用一个单独的menuClear方法作用于布尔值&#34; isMouseOut&#34;的状态。该类实现了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);

然后将下拉菜单项目子项添加到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&#39;父母&#39; (在此示例中为3x)因为它们是子菜单下拉菜单的触发器。 timerMenuClear()方法调用MenuSelectionManager来清空实际mouseOUT时所选择的路径点:

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在开发过程中可以访问的值 - 但是它有效!即使有嵌套菜单:)我希望很多人觉得这个完整的例子非常有用。

答案 3 :(得分:0)

使用此代码:

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);
}

}