Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JComboBox popup appears and hide immediately when clicking on its border (Bad User Experience)

When you have a swing JComboBox and click on its border, the popup appears and disappears immediately. When I say click, I mean press the left button of the mouse and release immediately.

enter image description here

It may be considered a bad user experience because no user would expect it to happen. Any user would expect one of the following behaviors when clicking on a border of a combobox:

  1. The popup to open and remain opened,
  2. Or it not to open at all.

Surely no user would expect the popup to be opened and closed immediately.

The user does not click on the border on purpose. But it may happen frequently when the combobox is small and he tries to click on it quickly.

In the year 2000 somebody registered this behavior as a bug in openjdk site: https://bugs.openjdk.java.net/browse/JDK-4346918

They've recognized it as a bug, but closed it with the resolution: "Won't fix", with the following observation:

I've been able to reproduce the problem but it's not significant so I'm not going to fix it. The problem is that the drop down portion of the combo box will hide when the mouse is released after clicking on the border. This bug doesn't have a very major impact.

I agree with them, that it doesn't have a very major impact. But I still think that it leads to a bad user experience and I would like to know if there is a simple workaround to make the popup either to remain opened or not to open at all when the user clicks on its border.

The described behavior can be reproduced by clicking the left mouse button on the border of any JComboBox. See below a simple code where it can be reproduced:

import java.awt.FlowLayout;
import javax.swing.*;

public class JComboBoxUX{
    public static void main(String[] args){
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run(){
                JComboBox<String> combobox = new JComboBox<String>(
                        new String[]{"aaaaaaaaaa","bbbbbbbb","ccccccccc"});

                JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
                panel.add(combobox);

                JFrame frame = new JFrame("JComboBox UX");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setContentPane(panel);
                frame.setSize(300, 150);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
like image 714
viniciussss Avatar asked Dec 09 '16 14:12

viniciussss


1 Answers

The problem seems to be in:

class BasicComboPopup extends ... {

    private Handler getHandler() {
        if (handler == null) {
            handler = new Handler();
        }
        return handler;
    }

    private class Handler implements ... MouseListener ... {

        public void mouseReleased(MouseEvent e) {
            //...
            Component source = (Component)e.getSource();
            Dimension size = source.getSize();
            Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
            if ( !bounds.contains( e.getPoint() ) ) {
                //...
                comboBox.setPopupVisible(false);
            }
        }
    }
}

By subtracting one from size.width and size.height, the mouse falls outside of the bounds of the arrow button, and the popup menu is hidden.

Fixing the issue is problematic. The Handler class is private, so we can't extend it, the getHandler() is private, so we can't override that in BasicComboPopup either.

One could extend MetalComboBoxUI and override createPopup() to return a custom ComboPopup, such as one extending BasicComboPopup but extending createMouseListener() to return a similar class to the Handler above, but without the subtract ones.

Oh, and do the same thing for each LAF you wish to support. Yuk.

Attacking the problem from the other direction, one could extend the MetalComboBoxButton (which is returned by e.getSource()) and override the getSize() method to return a dimension one pixel larger in both directions, when the menu is displayed. Of course, you'd still need to extend and override the MetalComboBoxUI to create and install this custom button.

And again, you'd need to do the same thing for each LAF you wish to support. Again, yuk.

Unfortunately, it does not appear that Swing has the needed hooks to easily override the needed functionalities, and has marked various classes as private internal implementation details, preventing their reuse (in order to prevent breakage later if they want to change the internals).

like image 104
AJNeufeld Avatar answered Sep 20 '22 10:09

AJNeufeld