Scrolling on a JComboBox popup hide it
Asked Answered
M

4

5

My client is complaining that JComboBox popups often close when the scroll is being used over a JComboBox popup with no vertical scrollbar. (He seems to accidently use scrolling over it because he is using an Apple Magic Mouse.)

Any way to prevent this to happen ?

I know it has to do with the ComboBoxUI, but I would like a few pointer where to start. BasicComboPopup.handler is private (not reusable) and I don't see any code relative to any a MouseWhellListener in BasicComboPopup.

Magneto answered 12/4, 2011 at 18:16 Comment(2)
Could you maybe overwrite the mouse wheel listener and simply ignore/consume all events since you know the box size fits all options?Hindustan
Thanks Boro. Maybe I miss something, but BasicComboPopup does not seems to declare any MouseWheelListener. There's one in its nested JScrollPane but I don't understand how it could close the popup (and how to prevent this).Triplett
E
6

As seen in the source, BasicPopupMenuUI contains a nested class, MouseGrabber, that implements the AWTEventListener interface. The receipt of MouseEvent.MOUSE_WHEEL in eventDispatched() cancels the popup as a function of isInPopup(). I know of no simple way to defeat the behavior.

Empirically, this example invokes show() from the actionPerformed() handler of a JButton; mouse wheel events are ignored. This might be a reasonable alternative for your user, perhaps combined with a suitable ActionEvent modifier mask.

In contrast, this example invokes show() in response to isPopupTrigger() in a MouseAdapter; as expected, mouse wheel events cancel the popup.

Electrothermics answered 13/4, 2011 at 2:5 Comment(5)
Thank you for figuring that out (the mouse grabber)! I was not really looking into making a replacement of a JComboBox, but I'll see if I can do something by replacing default UIs...Triplett
I've been hitting this problem a lot lately and it's annoying the heck out of me. The issue I see when debugging this is that the popup is an instance of Popup.HeavyWeightWindow (in my case) and the mouse wheel event is targeted to that window rather than the JPopupMenu inside. Since, like noted above, the MouseGrabber code cancels the popup when the component or any parent is not an instance of JPopupMenu the popup ends up closing. This really seems like a bug in the library. If I find any workaround I will post it.Orianna
@Joshua: I think it may also be a limitation imposed by supporting disparate platforms.Electrothermics
@Electrothermics Totally possible. I could have sworn this worked in builds 1.6.0_31 and prior though. Thanks for your original answer. Glad I'm not the only one running into this issue.Orianna
I find that when there is no scollbar, there is still a jscrollpane and that the mouse wheel listener correctly identifies it as a scrollpane and tells the MouseGrabber not to dismiss the popup. However, when there is no scollbar, there is a second mouse wheel listener (the HeavyWeightWindow) and that fires after the scollpane. Since it fires second, the mousegrabber returns true to close.Photoconductivity
H
4

I have tested default behaviour of a combobox. And when I am scrolling over the popup it is fine it will not close it. But when I scroll outside it or even over the combobox itself then it disappears.

I do not know if you are after something like this but I have added the mouse wheel listener to the combobox this way if I detect the movement over the combobox there I am reshowing the popup. -- This bit only partially solves the issue that the mouse wheeling will not show the combo box when scrolling over the combobox.

import java.awt.HeadlessException;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ComboBoxMouseWheel
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createGUI();
            }
        });
    }

    private static void createGUI() throws HeadlessException
    {
        String[] items = new String[]
        {
            "oasoas", "saas", "saasas"
        };
        final JComboBox jcb = new JComboBox(items);
        jcb.addMouseWheelListener(new MouseWheelListener()
        {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e)
            {
                System.out.println("ohjasajs");
                e.consume();
                jcb.showPopup();
            }
        });
        JPanel p = new JPanel();
        p.add(jcb);
        JPanel contentPane = new JPanel();
        contentPane.add(p);
        JFrame f = new JFrame();
        f.setContentPane(contentPane);
        f.setSize(300, 300);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}

I hope this is helpful even a bit. If you manage to solve other way please do share it with us. The solution provided by @trashgod seems doable but it looks so elaborated :), thus I propose mine approach an alternative.

Good luck, Boro.

Hindustan answered 13/4, 2011 at 8:44 Comment(3)
Thank you for your answer. Maybe it does this only with Java on Mac OS X (which is not a standard Mac OS X behavior). Swing use a nested component for its popup, so the mouseWheelMoved responds only when using wheel over the JComboBox area and not its popup.Triplett
Yea. You are completely right. Just tested it on my mates Mac and I must admit that my hack actually doesn't solve anything in fact it makes it worse. First of all why the heck Mac makes the JComboBox to look like JSpinner? BTW is there a component which works like Mac's combobox, i.e. looks like spinner, allows you to use them to select an item but still lets you to chose from list of options, like combo box?Hindustan
It looks like that because of the Mac Aqua look and feel.Triplett
M
4

Thanks to your suggestion, I've got an idea an found a solution by hacking AWTEventListeners.

    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener()
    {
        public void eventDispatched(AWTEvent event)
        {
            if (event instanceof MouseWheelEvent)
            {
                Object source = event.getSource();
                if ((source instanceof JScrollPane) &&
                    (((JScrollPane) source).getParent().getClass().
                       getName().equals("com.apple.laf.AquaComboBoxPopup")))
                {
                    JViewport viewport = ((JScrollPane) source).getViewport();
                    if (viewport.getViewSize().height <= viewport.getHeight())
                        // prevent consuming if there is a vertical scrollbar
                        ((MouseWheelEvent) event).consume();
                }
            }
        }
    }, AWTEvent.MOUSE_WHEEL_EVENT_MASK);

Thanks guys !

Magneto answered 13/4, 2011 at 18:27 Comment(0)
N
1

Here is a solution that will work in most cases

Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
            public void eventDispatched(AWTEvent event) {
                if (event instanceof MouseWheelEvent) {
                    Object source = event.getSource();

                    if (source instanceof JScrollPane) {
                        JScrollPane scroll = (JScrollPane) source;
                        if (scroll.getName().equals("ComboBox.scrollPane")) {
                            MouseWheelEvent sourceEvent = ((MouseWheelEvent) event);

                            for (MouseWheelListener listener : scroll.getListeners(MouseWheelListener.class)) {
                                listener.mouseWheelMoved(sourceEvent);
                            }

                            sourceEvent.consume();
                        }
                    }


                }
            }
        }, AWTEvent.MOUSE_WHEEL_EVENT_MASK);
Nieberg answered 28/4, 2015 at 17:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.