Menu Scroller
Posted by Darryl Burke on February 1, 2009
New: keepVisible(JMenuItem) and keepVisible(int) methods scroll the menu to the specified item or index.
A drawback of the JMenu API is that it does not provide a method to limit the number of items displayed at a time, something on the lines of the JComboBox method setMaximumRowCount. This means that adding a large number of items to a menu can and does result in a menu too tall for the screen. The usual workaround is to nest the items in submenus, which can make it tedious for the user to find and select the desired item, and lead to quite some extra code for the programmer, especially if the menu items are added dynamically at run time.
MenuScroller addresses this shortcoming by deterministically adding up and down arrows to the top and bottom of the menu. A number of items can optionally be frozen at the top and/or bottom of a scrolling subset of menu items.
There are two ways in which a MenuScroller can be attached to a JMenu/JPopupMenu. The recommended method, which provides better readability for your code, is via a call to the class’s static method
public static MenuScroller
setScrollerFor(JMenu|JPopupMenu menu[,
int rowCount[,
int interval[,
int topFixedCount, int bottomFixedCount]]]);
where the square brackets [ ] indicate optional parameters.
As a glance at the code will show, it is also sufficient to construct a MenuScroller with the JMenu/JPopupMenu as the first (or only) parameter.
This illustration shows the MenuScroller in action. The line of code to set this scroller was
MenuScroller.setScrollerFor(menu, 8, 125, 3, 1);
If a reference to the MenuScroller is retained, the number of items to scroll, the scrolling interval in milliseconds and the number of items to freeze at the top or bottom can be retrieved and set via the accessors and mutators provided. An item or an index into the menu can also be specified to be made visible whenever the menu is shown.
To restore the default, non-scrolling behavior of the JMenu, invoke the MenuScroller‘s dispose() method.
menuScroller.dispose();
Try The Demo
– Using Java™ Web Start (JRE 6 required)
Get The Code
Related Reading
The Java™ Tutorials:

Kleopatra said
Hi Darryl,
that got me rolling (or should I say scrolling :-) Especially since there aren’t any – at least no free, open, don’t know about commercial – reliably working scrolling menu. We tried over at SwingX (Karl Schaefer’s incubator section has some code), but were not overly successful.
Unfortunately, your implementation seems to be easily confused as well: click on any of the scroll arrows then the next time it opens behaves erratically. And couldn’t find a way to control it by keyboard only (probably overlooking the obvious :-)
Another question – again probably due to my ignorance :-) – is how to apply that to a JPopupMenu?
Cheers
Jeanette
Darryl Burke said
Support for JPopupMenu has been added to MenuScroller.
Darryl
Darryl Burke said
Jeanette, thank you for testing this so thoroughly!
I’ve cured the erratic behavior by the simple expedient of removing the original MouseListener(s) while retaining a reference to them, cross referenced to the menu item from which they are removed. I then forward only mouseEntered and mouseExited events from my own listener. No mouseClicked, no problem :D
I’ve also taken care of the keyboard control with a ChangeListener that tests isArmed().
The new code is available now, but a further update will be uploaded in a day or two*, as the up/down menu items now have so much added functionality that I feel it’s time for a new inner class extending JMenuItem.
I hadn’t thought about using this for a JPopupMenu, I’ll have to study it.
And if you claim ignorance, then I haven’t yet emerged from the primordial ooze ;-)
regards, Darryl
* edit That took less time than I thought it would. The update is uploaded now.
Ben Leers said
Darryl, what you’ve said about removing the original mouselisteners from the menuitems “up/down” isn’t implemented in the sourcecode provided. You just keep the mouselisteners in a seperate variable, and that’s it.
other than that, nice work! worked straight out of the box for me :)
Darryl Burke said
Thanks Ben for noticing that. You’re right, of course, and while it was an oversight that I hadn’t removed the MouseListeners as planned, it’s now apparent to me that using a ChangeListener to start/stop the scroll timer instead of my earlier approach using the mouseEntered/Exited methods of a MouseListener provided the real fix for the erratic behavior Jeanette noted. So … it’s neither required to remove the MouseListeners nor to add a custom MouseListener.
I’ve shortened the code appropriately and as soon as the new version is available I’ll post a comment on the Recent Updates page.
I’m happy you found this utility useful.
cheers, Darryl
Shaun Kalley said
Hello, Darryl,
This is a neat little controller class, one which I’ve been looking for for a while and which I’ve already incorporated into a test branch of one of my projects. I’ve been working with your code a bit and would like to offer a couple of suggestions if I may.
My first suggestion is to have the MenuScroller just manage a JPopupMenu rather than both a JPopupMenu and a JMenu. Since JMenu instances really just delegate their functionality to an internal JPopupMenu which you can access using getPopupMenu(), you can simply manage that JPopupMenu instead of the JMenu itself. This will help to clean up the MenuScroller code and make it easier to test and maintain.
Second, assuming the previous suggestion, you need to add a PropertyChangeListener to the JPopupMenu to listen to the “visible” property and repaint the JPopupMenu the new value is True. This will solve a painting issue when managing the JPopupMenus of JMenus which themselves are children of JPopupMenus. I’ll include a quick-and-dirty test program to visualize the three use cases I’ve tested for so far below.
Cheers,
Shaun
public class MenuScrollerTest { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { // initialize menu-bar-test components JMenuBar menuBar = new JMenuBar(); for (int i = 1; i <= 3; i++) { JMenu menu1 = new JMenu("Menu Bar Test (" + i + ")"); new MenuScroller(menu1, 15, 125, 5, 5); for (int j = 1; j <= 100; j++) { menu1.add(new JMenuItem("Item " + j + " (" + i + ")")); } menuBar.add(menu1); } // initialize popup-menu-test components final JPopupMenu popupMenu1 = new JPopupMenu(); new MenuScroller(popupMenu1, 15, 125, 5, 5); for (int i = 1; i <= 100; i++) { popupMenu1.add(new JMenuItem("Item " + i)); } JPanel panel1 = new JPanel(null); panel1.setBorder(BorderFactory.createTitledBorder("Popup Menu Test")); panel1.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { popupMenu1.show(e.getComponent(), e.getX(), e.getY()); } } }); // initialize popup-sub-menu-test components final JPopupMenu popupMenu2 = new JPopupMenu(); for (int i = 1; i <= 3; i++) { JMenu menu2 = new JMenu("Menu Bar Test " + i); new MenuScroller(menu2, 15, 125, 5, 5); for (int j = 1; j <= 100; j++) { menu2.add(new JMenuItem("Item " + j + " (" + i + ")")); } popupMenu2.add(menu2); } JPanel panel2 = new JPanel(null); panel2.setBorder(BorderFactory.createTitledBorder("Popup Sub-Menu Test")); panel2.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { popupMenu2.show(e.getComponent(), e.getX(), e.getY()); } } }); JPanel contentPane = new JPanel(new GridLayout(1, 2, 20, 20)); contentPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); contentPane.add(panel1); contentPane.add(panel2); JFrame frame = new JFrame("MenuScroller Test"); frame.setJMenuBar(menuBar); frame.setContentPane(contentPane); frame.setSize(800, 600); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }Darryl Burke said
Thank you Shaun for your suggestions. I’ll definitely look into them.
Darryl
Axel Dörfler said
Thanks for sharing this! However, I just tested using JDK 6 under Windows Vista (with “native” L&F), and I see two problems:
1. the scroll items often move during scrolling.
2. the popup window often (especially with a full screen window) opens at the wrong position (ie. the one it would have needed to open with the original number of items).
Note that I only used the MenuScroller on sub-menus of JPopupMenus so far, though. Unfortunately, these problems make it unusable at least in this case.
Chris Yates said
I found this to be just what I needed, but my users requested mouse scroll wheel ability. It was easy enough to add, but I’ve pasted it just to help out:
Darryl Burke said
Thank you, Chris.
Darryl
Simon said
That sounds interesting! To which object do you add this listener?
Thanks in advance!
Simon
April said
This was just what I was looking for. Thanks you for putting this out there.
Shylux said
Thanks for your great work! :D
Beirti said
Sweet as a nut! Cheers for that
Oncu Altuntas said
Thanks very good work, but there is a problem with Menus width. Menus with long labels not viewed properly. For example if the first menu label smaller than the 20th one than 20th is croped according to first one.
Darryl Burke said
Thanks for pointing that out. If I develop a solution, I’ll update the code.
Darryl
Anonymous said
Thanks..a good Utility class for JPopupMenu.
had one question, is it possible to have similar functionality on Mouse click event instead of mouse over scroll event.
Darryl Burke said
You would do that by taking the code in the MenuScrollTimer’s actionPerformed and putting it in an ActionListener added to the MenuScrollItem. Feel free to modify the code to suit your requirements.
Anonymous said
Darryl: Our group would like to use your MenuScroller code in our SIFT application. SIFT is a tsunami forecast system being built for the US Tsunami Warning Centers. Would you grant us permission to do so? If so, would you like to be acknowledged in our “About SIFT” window. Thanks in advance. I can be contacted at John.Osborne@noaa.gov.
Darryl Burke said
Like all code published on the blog, MenuScroller is free to use, at your own risk . A link to the blog page in the doc comments or elsewhere would be nice, but is by no means mandatory.
Please do read the earlier comments for some limitations in the usage of this class. Please also feel free to modify the code (including doc comments) in any way that suits your application.
Darryl
Steve said
Hi Darryl. First let me thank you VERY much for creating this utility! I am on a tight schedule and the lack of a scrolling popup menu is the last item stopping us from shipping. To say the least I was very pleased to find your website.
I do have an issue however to report. In my application the popup menu items vary significantly in the number of characters per menu item. When the popup first appears (after a user clicking a control) the popup menu’s width is wide enough to accommodate the displayed number of menu items. As the user scrolls down however some menu items are longer than the popup’s width and are truncated.
So I guess my question is: is there someway the popup menu width can be autosized to accommodate the longest menu item width?
Again thanks for your efforts! I look forward to your reply.
Steve
Darryl Burke said
Thank you for the kind words, Steve. The size problem had already been reported by Oncu Altuntas at No. 10 above, but I never did get round to finding a workaround.
The only way I’m aware of to force a popup menu to resize to accommodate new or changed content is to hide and redisplay it, and the way to do that is to save the selected menu path, then clear and restore it. The code would be something like
final MenuElement[] path = manager.getSelectedPath(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { manager.clearSelectedPath(); manager.setSelectedPath(path); } });This will cause a noticeable flicker when the menu extends beyond the bounds of its parent window, though.
Darryl
Steve said
Darryl – thanks for the quick response. In the meantime I found a workaround. As I add items to the menu I track the number of characters in each menu item. Right before displaying the menu I call “menu.setPopupSize(numChars * 10, menu.getHeight());”. Assuming that 10 pixels is the width of the largest character is admittedly a hack but works for my needs.
Simon said
The following patch should tackle the issue concerning the menu width.
--- MenuScroller.java.orig 2011-09-18 18:57:22.519438516 +0200 +++ MenuScroller.java 2011-09-18 19:01:44.051355009 +0200 @@ -456,20 +460,26 @@ } menu.add(downItem); if (bottomFixedCount > 0) { menu.add(new JSeparator()); } for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { menu.add(menuItems[i]); } + int preferredWidth = 0; + for (Component item : menuItems) { + preferredWidth = Math.max(preferredWidth, item.getPreferredSize().width); + } + menu.setPreferredSize(new Dimension(preferredWidth, menu.getPreferredSize().height)); + JComponent parent = (JComponent) upItem.getParent(); parent.revalidate(); parent.repaint(); } } private class MenuScrollListener implements PopupMenuListener { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {Anonymous said
Thanks Darryl, that is just what I want now.
Huw said
Great post!
Couple of comments…
1. I found that to get the scroll wheel support in response 6 I had to include a event.consume(); in the handler. The listener should be added to the (JPopupMenu) menu.
2. When looking at an app like Chrome, its scrolling menus fill the available vertical space. I have achieved something like this with the following static helper that computes a scrollCount…
/** * Calculates the number for scrollCount such that the menu fills the available * vertical space from the point (mouse press) to the bottom of the screen. * * @param c The component on which the point parameter is based * @param pt The point at which the top of the menu will appear (in component coordinate space) * @param item A menuitem of prototypical height off of which the average height is determined * @param bottomFixedCount Needed to offset the returned scrollCount * @return the scrollCount */ public static int scrollCountForScreen(Component c, Point pt, JMenuItem item, int bottomFixedCount) { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Point ptScreen = new Point(pt); SwingUtilities.convertPointToScreen(ptScreen, c); int height = screenSize.height - ptScreen.y; int miHeight = item.getPreferredSize().height; int scrollCount = (height / miHeight) - bottomFixedCount - 2; // 2 just takes the menu up a bit from the bottom which looks nicer return scrollCount; }Paul said
Thanks Darryl :-)
Is it ok to use your code in a project which is distributed by the GNU GPU license?
I’d like to integrate this into Arduino, which is an open source program. It has a bug where a dynamically built JMenu becomes too tall if the user adds too many files. This looks like a perfect solution. I didn’t see any licensing info, so it seems safest to ask first. Thanks!
Darryl Burke said
Like I said in my reply to #12, all code published on the blog is free to use, at your own risk :) Do first read the comments about its limitations and the improvements suggested by some of the responders.
Darryl
Dave Benson said
Hey Darryl,
Thanks so much for writing this — it’s been a huge time saver for me.
BTW, my IDE (Eclipse) complained about a dozen or so override annotations toward the bottom of the file. To make it compile I had to comment them out. I’m not sure whether that due to a problem with your code, or my IDE’s finnickyness. I just thought I’d mention it for the benefit of future users.
Cheers,
D
Darryl Burke said
I’m glad the class was useful to you. The @Override annotation on interface method implementations is valid from Java 6 aka 1.6. Your IDE or project settings (compiler compliance level? I don’t use Eclipse) may be for an earlier version.
There is no problem with the @Override annotations in the code.
Darryl