8

In the past, when one made a JPopupMenu visible it's first item would get selected by default: http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html

Nowadays the default behavior is to pop up the menu without any item selected. I would like create a JPopupMenu with a single item that will pop up selected and centered under the mouse pointer. I have managed to get the item to pop up centered under the mouse but I the JMenuItem refuses to render as if it is selected. If I move the mouse out of the item and back in it selects properly.

Any ideas?

Here is my testcase:

import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

public class Test extends JFrame
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        frame.setSize(800, 600);
        frame.getContentPane().addMouseListener(new MouseAdapter()
        {
            @Override
            public void mousePressed(MouseEvent e)
            {
                if (e.isPopupTrigger())
                    popupTriggered(e);
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                if (e.isPopupTrigger())
                    popupTriggered(e);
            }

            private void popupTriggered(MouseEvent e)
            {
                JPopupMenu menu = new JPopupMenu();
                final JMenuItem item = new JMenuItem("This is a JMenuItem");
                menu.add(item);
                Point point = e.getPoint();
                int x = point.x - (item.getPreferredSize().width / 2);
                int y = point.y - (item.getPreferredSize().height / 2);
                menu.show((Component) e.getSource(), x, y);
            }
        });
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setVisible(true);
    }
}
Troyseph
  • 4,960
  • 3
  • 38
  • 61
Gili
  • 86,244
  • 97
  • 390
  • 689

4 Answers4

9

Secret turns out to be MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{menu, ...});

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;

/**
 * Demonstrates programmatic {@link JMenuItem} selection;
 * specifically how to make the first item selected by default
 */
public class TestPopup extends JFrame {
  public static void main(String[] args) {
    final JFrame frame = new JFrame("TestPopup");
    frame.setSize(640, 480);
    frame.getContentPane().addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent e) {
        if (e.isPopupTrigger()) {
          popupTriggered(e);
        }
      }
      private void popupTriggered(MouseEvent e) {
        final JPopupMenu menu = new JPopupMenu();
        final JMenuItem item0 = new JMenuItem("JMenuItem 0");
        final JMenuItem item1 = new JMenuItem("JMenuItem 1");
        menu.add(item0);
        menu.add(item1);
        menu.pack();
        // use invokeLater or just do this after the menu has been shown
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{menu, item0});
          }
        });
        int x = (int) ((int) (frame.getSize().width - (menu.getPreferredSize().width / 2.)) / 2.);
        int y = (int) ((int) (frame.getSize().height - (menu.getPreferredSize().height / 2.)) / 2.);
        menu.show(frame, x, y);
        // doesn't work:
        //item0.setSelected(true);
        // doesn't work:
        //menu.getSelectionModel().setSelectedIndex(0);
        // bingo; see also MenuKeyListener / MenuKeyEvent
//        MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{menu, item0});
      }
    });
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}
1

Nowadays the default behavior is to pop up the menu without any item selected.

Actually, I would argue that this is the correct behavior, at least in Windows. Other non-Java applications do this too. I don't think it's worth breaking this convention even if there is only one item in the menu. If you feel otherwise, you can set the selection index as in sean.bright's answer.


So, I finally got the chance to try it out on Java 1.6.0_11, and found some inconsistent behavior: If the popup menu juts out of the parent frame, the item is automatically selected; if the popup menu appears entirely within the parent frame, nothing is selected. Sounds like a Swing bug, which at least warrants an RFE for consistent behavior.

Community
  • 1
  • 1
Zach Scrivena
  • 29,073
  • 11
  • 63
  • 73
  • +1: The right behavior is whatever the particular platform does for other (non-Java) programs. – Lawrence Dol Jan 27 '09 at 02:34
  • Zach, I agree with your general assessment but unfortunately this doesn't answer my question. I *want* to break away from the conventional behavior but I'm finding it impossible to do so. I'm trying to find out if this is a Swing bug or user error. – Gili Jan 27 '09 at 04:20
  • I filed a bug report with Sun: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6799989 – Gili Feb 28 '09 at 17:39
1

MenuSelectionManager.defaultManager() is indeed a good solution, but it won't work when you'll try to pre-select your JPopupMenu's sub menus (it will hide the parent menu). Also, it messes up other keyboard navigation behaviors (you can't press left to hide the sub-menu etc.)

Unfortunatelly, there's no good solution for this question in Swing... My solution is ugly, but sadly does the job perfect:

public static void setMenuSelectedIndex(final JPopupMenu popupMenu, final int index) {
    SwingUtilities.invokeLater(new Runnable(){public void run()
    {
        for (int i=0; i < index+1; i++) {
            popupMenu.dispatchEvent(new KeyEvent(popupMenu, KeyEvent.KEY_PRESSED, 0, 0, KeyEvent.VK_DOWN, '\0'));
        }
    }});
}

As you can see, I'm basically simulating 'Down' Keyboard Key-Presses on the popupmenu...

A better solution might be not to Hardcodedly simulate VK_DOWN, but to read the Popup's input map and determine which KeyCode means "select next menu item" - but I think most of us will get along with this hack...

You might also want to look at this method, which selects a menu's item once it gets selected It utilizes the previous method

public static void setSelectedIndexWhenVisible(final JMenu menu, final int index) {
    menu.getPopupMenu().addPopupMenuListener(new PopupMenuListener() {
        @Override
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            PopupUtils.setMenuSelectedIndex(menu.getPopupMenu(), index);
            menu.getPopupMenu().removePopupMenuListener(this);
        }

        @Override
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        }

        @Override
        public void popupMenuCanceled(PopupMenuEvent e) {
        }
    });
}
Eyal Katz
  • 41
  • 5
0

This is weird.

I tried it with Windows, and with Java 1.5.0_08 and even 1.6.0_07 the first Element is selected automatically, as you expected it to be.

So I tried it with 1.6.0_11, and it does not work any more, the first element is not selected initially. Selecting the element in the selectionModel does not seem to help.

One workaround (that I'm not at all proud of) is to move the mouse automatically after displaying the popup menu, using the coordinates of the MouseEvent. Maybe someone's got a better idea?

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

public class SelectedPopupMenu extends JFrame {

    public SelectedPopupMenu() {
        addMouseListener(new MouseAdapter() {
            public void mouseClicked(final MouseEvent e) {
                JPopupMenu popupMenu = new JPopupMenu();
                popupMenu.add(new JMenuItem("Test-Item"));
                popupMenu.add(new JMenuItem("Test-Item-2"));
                // do not care to really hit the center of the popup
                popupMenu.show(SelectedPopupMenu.this, e.getX() - 30, e.getY() - 10);
                try {
                    // shake mouse, so that first element is selected even in Java 1.6.0_11
                    Robot robot = new Robot();
                    robot.mouseMove(e.getX() + 1, e.getY());
                    robot.mouseMove(e.getX(), e.getY());
                } catch (AWTException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public static void main(String[] args) {
        JFrame frame = new SelectedPopupMenu();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);
        frame.setVisible(true);
    }
}
Peter Lang
  • 54,264
  • 27
  • 148
  • 161