开发者

Auto-hide JMenuBar

开发者 https://www.devze.com 2023-01-01 15:30 出处:网络
When I run the following code, the menu b开发者_如何学Car shows when the cursor moves to the upper part of the window. The problem is, when I move the cursor up to open the menu but do not select anyt

When I run the following code, the menu b开发者_如何学Car shows when the cursor moves to the upper part of the window. The problem is, when I move the cursor up to open the menu but do not select anything, and then move the cursor out of the the menu bar area, it becomes invisible but the menu's elements stay on screen.

What I'm try to achieve is an "auto-hide" menu bar that becomes visible when the mouse enters a certain region in the JFrame.

public class Test extends JFrame {

    public Test() {
        setLayout(new BorderLayout());
        setSize(300, 300);

        JMenuBar mb = new JMenuBar();
        setJMenuBar(mb);
        mb.setVisible(false);


        JMenu menu = new JMenu("File");
        mb.add(menu);

        menu.add(new JMenuItem("Item-1"));
        menu.add(new JMenuItem("Item-2"));

        addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                getJMenuBar().setVisible(e.getY() < 50);
            }
        });
    }

    public static void main(String args[]) {
        new Test().setVisible(true);
    }
}

I think I found a workaround: if the menu bar is visible and the JFrame receives a mousemove event then send the ESC key to close any open menu.

 addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                if (getJMenuBar().isVisible()) {
                    try {
                        Robot robot = new Robot();
                        robot.keyPress(KeyEvent.VK_ESCAPE);
                    } catch (AWTException ex) {
                    }

                }
                getJMenuBar().setVisible(e.getY() < 50);
            }
        });

This workaround depends on the look and feel (meaning of the ESC key). Anyway, for me it is ok.


You can probably make it work by checking is any menu is selected, from the JMenuBar:

public void mouseMoved(MouseEvent e) {
    JMenuBar lMenu = getJMenuBar();
    boolean hasSelectedMenu = false;
    for (int i=0 ; i< lMenu.getMenuCount() ; ++i)
    {
        if (lMenu.getMenu(i).isSelected())
        {
            hasSelectedMenu = true;
            break;
        }
    }

    if(!hasSelectedMenu)
        lMenu.setVisible(e.getY() < 50);
}

In this case, it would disappear as soon as you click somewhere else in the JFrame.

However, not exactly, as it would update only on mouseMoved. I would recommend you to do the same check on mouseClicked, to be sure it disappears when clicking without moving.


Also works:

frame.addMouseMotionListener(new MouseAdapter() {
        @Override
        public void mouseMoved(MouseEvent e) {
            if (menuBar.isSelected()) {
                try {
                    Robot robot = new Robot();
                    robot.keyPress(KeyEvent.VK_ESCAPE);
                    SingleSelectionModel sm = menuBar.getSelectionModel();
                    sm.clearSelection();
                } catch (AWTException ex) {
                }

            }
        }
    });


You could also add a transparent panel area into your window and attach a separate MouseListener to that area panel so that you can make an invisible menu appear! but this idea is not included in my example.

Below is a fully working AutoHide Hierarchical MenuBar 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.


It's better late than never. I suggest that you use setJMenuBar(null) instead of setting the visibility.

0

精彩评论

暂无评论...
验证码 换一张
取 消