Hello!
I am trying to implement a scrollable JPopupMenu for an application
where the contents of the popup menu are variable and occasionally too
large for the screen.
almost ... LOL (how do you know *anything* works at all except for it
appearing?).
Every time I mouse over one of the menu items contained in the
scrolling area, the JPopupMenu disappears! I have tried everything I
can think of to no avail. Please help!!!
There is the selected path of the MenuSelectionManager (which may be
empty).
When a (standalone) popup menu is shown, it sets itself (and the first
of its sub-MenuElements if any), to selected, so the selected then is
[ menu, first element ]
or
[ menu ]
When a JMenuItem becomes "pointed to", it depends on its parent (container)
being part of the selected path so it known where it belongs. Otherwise
it will just do nothing (if nothing is selected) or become the selected
path alone (possibly unintentionally, see BasicMenuItemUI.getPath). Then
the popup menu become deselected and hides itself. As the parent of the
menu item here is just a panel, this happens.
So the parent of the menu items must be made a MenuElement and become
selected when the popup menu is shown. This is easiest (and cleanest) made
by making it a sub element of the JPopupMenu so it happens automatically
and have the selected be actually valid in terms of the MenuElement tree.
Some thoughts:
* The fact that keyboard navigation works is because BasicPopupMenuUI
probably handling more than it is actually responsible for.
* Probably the JScrollPane should be made Scrollable to have decent
scroll offsets.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
public class ScrollPopupTest2 extends JFrame
{
private static class MenuPanel
extends JPanel
implements MenuElement
{
public MenuPanel()
{
super(null, false);
}
public void menuSelectionChanged(boolean value)
{
}
public Component getComponent()
{
return this;
}
public MenuElement[] getSubElements()
{
// only works if all children *are* MenuElements (why are the separators not?)
// otherwise, explicit loop
MenuElement[] result = new MenuElement[getComponentCount()];
Arrays.asList(getComponents()).toArray(result);
return result;
}
public void processKeyEvent(KeyEvent e, MenuElement[] path, MenuSelectionManager m)
{
}
public void processMouseEvent(MouseEvent e, MenuElement[] path, MenuSelectionManager m)
{
}
}
private MenuPanel menuPanel = new MenuPanel();
private final JPopupMenu _popup = new JPopupMenu()
{
public MenuElement[] getS2ubElements()
{
return new MenuElement[] { menuPanel };
}
};
private JMenuItem[] _menuElements =
{
new JCheckBoxMenuItem("Element 1"),
new JCheckBoxMenuItem("Element 2"),
new JCheckBoxMenuItem("Element 3"),
new JCheckBoxMenuItem("Element 4"),
new JCheckBoxMenuItem("Element 5"),
new JCheckBoxMenuItem("Element 6"),
new JCheckBoxMenuItem("Element 7"),
new JCheckBoxMenuItem("Element 8"),
new JCheckBoxMenuItem("Element 9")
};
public ScrollPopupTest2()
{
setBounds(100, 100, 250, 250);
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent e)
{
popup(e);
}
public void mouseReleased(MouseEvent e)
{
popup(e);
}
private void popup(MouseEvent e)
{
if (_popup.isPopupTrigger(e))
_popup.show((Component)e.getSource(), e.getX(), e.getY());
}
});
menuPanel.setLayout(new BoxLayout(menuPanel, BoxLayout.Y_AXIS));
// not optimal for mouse actions?
final ChangeListener showIfArmed = new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
JMenuItem m = (JMenuItem)e.getSource();
if (m.isArmed())
m.scrollRectToVisible(new Rectangle(0, 0, m.getWidth(), m.getHeight()));
}
};
for(int i = 0; i < _menuElements.length; i++)
{
_menuElements[i].addChangeListener(showIfArmed);
menuPanel.add(_menuElements[i]);
}
// Create a container for the menu panel and a scrollpane to scroll it
// What is scrollContainer needed for?
JPanel scrollContainer = new JPanel(new GridLayout(1, 1, 0, 0));
JScrollPane scrollPane = new JScrollPane(menuPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
scrollContainer.add(scrollPane);
// Set the size of the popup to make the scrollbar appear
Dimension scrollAreaSize = scrollContainer.getPreferredSize();
scrollAreaSize.height = 80;
scrollContainer.setPreferredSize(scrollAreaSize);
// Add the container to the popup menu
_popup.add(scrollContainer);
}
public static void main(String[] args)
{
new ScrollPopupTest2().show();
}
}