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

发布于 2024-08-05 04:14:04 字数 389 浏览 4 评论 0原文

我们最近将 GWT 菜单栏附加到我们应用程序的一部分,用于菜单目的。

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

menubar.setAutoOpen(true);

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

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

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

谢谢。

We have recently attached a GWT MenuBar to a part of our application for, well, menu purposes.

Basically I want the sub menus to open when you mouse over the top level menu, which is easy enough to do:

menubar.setAutoOpen(true);

I would also like to have the sub menu automatically hide when the user's mouse leaves the sub menu. Ideally with some sort of delay to prevent it vanishing too abruptly, but I'd settle for just the hiding.

This doesn't seem to be built in and the MenuItem object in GWT directly subclasses UIObject which means there isn't a relatively trivial onBrowserEvent() or somewhere to attach mouse listeners. Possibly extending MenuItem and sinking/unsinking events would let me add this behavior, but I am unsure if that is the best approach.

So what would be the best approach to autohiding a GWT submenu?

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

°如果伤别离去 2024-08-12 04:14:04

经过多次可怕的黑客尝试实现类似的目标后,我们编写了自己的 层叠菜单,作为 GWT Portlet 的一部分框架。它显示来自 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。其他人像平常一样触发历史令牌更改。请查看在线演示以查看实际菜单。

After much horrible hacking trying to achieve something similar we wrote our own cascading menu as part of the GWT Portlets framework. It displays menu items and submenus from an HTML template looking something like this:

<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>

The URLs that look like method calls broadcast CommandEvent's. The others trigger a history token change like normal. Have a look at the online demo to see the menu in action.

会发光的星星闪亮亮i 2024-08-12 04:14:04

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

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

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

Here's a fairly complete solution, not perfect, explained after code:

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

This handles the case where mouseOver opens subMenu, user then mouses UP, off mainMenu (subMenu closes). It does not handle the mouse moving diagonally down, past either side of subMenu (submenu stays open)
Certainly can be improved, but I just got it to work and wanted to share ;-)

毁梦 2024-08-12 04:14:04

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

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

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

下面是从我的实时应用程序中获取的完整工作答案:

我解决鼠标移出时菜单关闭的方法是在构造函数顶部运行一个布尔变量“isMouseOut”来跟踪,然后以更加面向对象的方式分配 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);

然后将下拉菜单项子级添加到 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() 实例化中的对象类型非常重要,以便在鼠标悬停时自动打开菜单(在本例中为 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“父级”(本例中为 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 中访问哪些值 - 但它确实有效!即使使用嵌套菜单:)我希望很多人发现这个完整的示例非常有用。

There's no need for horrible hacking or relying on CSS within JAVA to achieve autohiding of a MenuBar with submenus. I created a fully working example of a Parent+Children dropdown menu with mouseover opening and mouseOut closing with explanations of each part for others to use.

The common problem I've witnessed folks having is Running a ((JMenu)e.getSource()).doClick(); on the mouseEntered simulates the click into one of the JMenu parents but can't be simply added to the mouseExited method as the MouseListener needs to be attached to the child MenuItems as well as the JMenu parents. (Which it doesn't do in the normal assignment to the MenuBar - only attaching to the parent JMenu objects).

Additionally, a problem arises due to trying to get the MouseExit listener to fire a "close" method ONLY when the mouse has left the entire Menu structure (ie the Child menu dropdowns).

Below is a fully working answer taken from my live app:

The way I solved the menu close on mouse out was to run a boolean variable "isMouseOut" in the top of the constructor to keep track, and then allocate the MouseListener in a more OO friendly way to keep track of the multiple MouseIn-MouseOut events as a user interacts with the menu. Which calls a separate menuClear method acting upon the state of the boolean "isMouseOut". The class implements MouseListener. This is how its done.

Create an ArrayList adding all the menu items to this array first. Like so:

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

then iterate over the arrayList at this stage adding a MouseListener using the for() loop:

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

Now set JMenu parents for the MenuBar:

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

Then add the dropdown menuItems children to the JMenu parents:

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

Add the mouseListeners to the JMenu parents as a separate step:

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

Now that the child menuItem elements all have their own listeners that are separate to the parent JMenu elements and the MenuBar itself - It is important to identify the object type within the MouseListener() instantiation so that you get the menu auto opening on mouseover (in this example the 3x JMenu parents) BUT ALSO avoids child exception errors and allows clean identification of mouseOUT of the menu structure without trying to monitor where the mouse position is. The MouseListener is as follows:

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

The above only simulates the mouse click into the JMenu 'parents' (3x in this example) as they are the triggers for the child menu dropdowns. The timerMenuClear() method calls on the MenuSelectionManager to empty whatever selectedpath point was live at the time of real 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();
}

It took me a little testing, monitoring what values I could access within the JVM during its development - but it Works a treat! even with nested menus :) I hope many find this full example very useful.

呆头 2024-08-12 04:14:04

使用此代码:

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

}

Use this code:

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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文