Archives

Meta

Subscribe

Menu Scroller

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

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

Like this:

LikeLoading...

Related

This entry was posted on February 1, 2009 at 12:19 pm and is filed under Classes, Swing.
You can follow any responses to this entry through the RSS 2.0 feed.
You can skip to the end and leave a response. Pinging is currently not allowed.

72 Responses to “Menu Scroller”

Kleopatrasaid

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?

Darryl Burkesaid

Darryl Burkesaid

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.

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.

Darryl Burkesaid

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.

Shaun Kalleysaid

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.

Darryl Burkesaid

Axel Dörflersaid

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.

Shyluxsaid

Beirtisaid

Oncu Altuntassaid

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.

Anonymoussaid

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.

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?

Darryl Burkesaid

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

Stevesaid

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.

Anonymoussaid

Huwsaid

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

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 Burkesaid

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.

Dave Bensonsaid

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.

Darryl Burkesaid

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.

Simply works right out of the box!
JPopupMenu popupMenu = new JPopupMenu(); // existed, showing a very long context menu with languages selection
MenuScroller.setScrollerFor( popupMenu); // one new line added

Milos Kleintsaid

Darryl Burkesaid

I don’t have a Mac nor access to one. Perhaps you could try and let us know here?

There’s no license, nor any guarantee. All code on the blog is offered on a ‘take it, use it, change it — just don’t cry about it!’ basis. If used in commercial software, we do request a credit in the doc or other code comments, but that is by no means mandatory.

GrassEhsaid

Thank you very much, exactly what I was looking for. However, have you ever been working on a simple implementation of a scrollable JPopupMenu using JScrollPane ? I’ve been trying to be it but it’s not as easy as I was thinking. I’ve found some works on the web, but they are a bit complicated, usin JPanel or JWindow.
I’m looking for an easy and clean implementation of it, by starting to implement the Scrollable interface from the JPopupMenu for example.

myMenu, // The JMenu instance
20, // visible number of sub items
30, // delay at scrolling with the top/down buttons
0, // visible number of the first visible items out of the scrolling box
0 // visible number of the last visible items out of the scrolling box

Sethsaid

Thank you so much for this class. It really fills a needed gap in Swing for us.

I just wanted to mention that in refreshMenu(), you call revalidate() on the parent. I haven’t looked to deeply into why, but with JRE7, this is not sufficient anymore. The positions of the menu items do not get recalculated during scrolling, so it becomes a mess. My quickfix involves instead calling validate() instead, which fires the necessary doLayout() call.

Thank you Seth. I’m presently in the throes of moving house, and down to a laptop with older versions of everything, but once I unpack and set up my computer I’ll see if I can identify where the relevant code changes are between JRE6 and 7.

RedTulipssaid

I am having an issue where the scrollable menu is not laying out its components properly. The menu contains items with different heights. As I scroll down the menu, the height of the popup is not getting adjusted properly.

I tried the following in the MenuScroller.refreshMenu() method, but it did not work:

Steve Cohensaid

Very nice work. However, my requirement is basically to pretend the mouse doesn’t exist and just use the keyboard (up-arrow and down-arrow). I’d rather disable the whole timer business and make it work like page down. Is there anything in here for that or is that an enhancement (dehancement?) I should roll on my own.

Steve Cohensaid

Key Bindings or a Key Listener? I went with the latter approach. I also wound up stripping out most of the logic associated with top and bottom fixed items. I also got rid of the timer and the scroll items. The Key Listener handles the up and down arrows and correctly arms the element it should. All I’ve winded up keeping is the logic of repopulating the menu when I scroll. That part works quite nicely. But there is one big problem – the Enter key doesn’t function. My key listener is not consuming the event, but nothing happens when the user presses Enter. I can listen for Enter in the KeyPressed event and perform the action associated with the armed menu item, but then the menu doesn’t disappear! I can make the menu disappear, but it has parents that also should disappear. All this is handled by normal menus, but I can’t figure out to do it here. Aargh!

Steve Cohensaid

I’m reasonably familiar with Key Bindings. I looked at the KeyBindings app looking for what actions the Enter or Escape keys might be bound to, I didn’t find anything I could bind to. And the comment in #6 you referred to also used a listener, which is why I went down the KeyListener path.

So I suppose you might have been suggesting binding the Up and Down Arrow keys to actions I would have to write and avoiding listeners (which I tend to dislike in general). Is that right?

Steve Cohensaid

Got this working with a big assist from your associate Rob Camick over at Stack Overflow (thanks, Rob!). The key was to call MenuSelectionManager.defaultManager().clearSelectedPath(); on the Enter keypress after performing the action.

So I now have a nice little stripped down version of your system that is perfect for my needs. There is no scrolling with the mouse, which probably is a show-stopper for most situations but not for mine. Do you suppose anyone would be interested in it? It does sort of illustrate your basic concept (the refreshMenu() logic), which I have retained. If so, I guess I could post it.

Gigelsaid

Jim Niksaid

I have a jMenu that gets populated several times during a session, with different jMenuItems each time (i.e. menu empties with jMenu.removeAll() and its jMenuItem content is regenerated).
The first time my jMenu gets populated, the MenuScroller works perfectly!

However, for every subsequent re-population of the same menu, the MenuScroller works completely erratically: for example double arrows may appear on both top and bottom, response is bad, scrolling arrows appear and disappear when trying to scroll one direction, and most often the menu content will get stuck at some point with no arrows to click on.

I have no idea why this happens in my case, any thoughts? Thanks again.

Jim Niksaid

Darryl thank you very much for your response. It helped me realize that I was actually re-adding a MenuScroller with each repopulation of the menu.
Problem solved by adding the MenuScroller only once, upon the initialization of the empty jMenu. I tried it out with 4-5 repopulations: no problems at all.
Thank you once more for the excellent scroller and your prompt response.

Jim

PS. Previously, while experimenting with your code, I removed completely this part:

@Override
public void finalize() throws Throwable {
dispose();
}

And it still works fine. I left it out ever since. I really don’t know much about finalize() usage, I just read that it isn’t very good practice in general, although I couldn’t figure all the details.
Is it ok to leave this out or do you absolutely recommend including it?

Hi
I’m the beginner of JAVA who have taught my self with your code.
I want to ask you how can I use this Menu Scroller on JPanel.
It is not component so I can’t find the way to add it on JPanel.
Sorry to ask you simple question but I really really want to know…
Please answer my question…