单击边框时出现JComboBox弹出窗口并立即隐藏(糟糕的用户体验)

时间:2016-12-09 14:44:16

标签: java swing jcombobox user-experience visual-glitch

当你有一个摆动JComboBox并点击它的边框时,弹出窗口会立即显示并消失。当我说点击时,我的意思是按下鼠标左键并立即释放。

enter image description here

它可能被认为是糟糕的用户体验,因为没有用户会期望它发生。单击组合框的边框时,任何用户都会期望以下行为之一:

  1. 要打开并保持打开的弹出窗口,
  2. 或者根本不打开。
  3. 当然没有用户会期望弹出窗口立即打开和关闭。

    用户不会故意点击边框。但是当组合框很小并且他试图快速点击它时,它可能经常发生。

    在2000年,有人将此行为注册为openjdk网站中的错误:https://bugs.openjdk.java.net/browse/JDK-4346918

    他们已经认识到它是一个错误,但是通过以下观察结果解决了它:“无法解决”:

      

    我已经能够重现这个问题但是并不重要   我不打算解决它。问题是下拉部分   单击后释放鼠标时,组合框将隐藏   边界。这个bug没有太大的影响。

    我同意他们的观点,认为它没有太大的影响。但我仍然认为这会导致糟糕的用户体验,我想知道是否有一个简单的解决方法是让用户点击其边框时弹出窗口要么保持打开状态,要么根本不打开。

    可以通过单击任何JComboBox边框上的鼠标左键来重现所描述的行为。请参阅下面的简单代码,可以复制:

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

2 个答案:

答案 0 :(得分:5)

问题似乎在:

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

通过从size.widthsize.height中减去一个,鼠标落在箭头按钮的边界之外,弹出菜单将被隐藏。

解决问题是有问题的。 Handler课程为private,因此我们无法对其进行扩展,getHandler()private,因此我们也无法覆盖BasicComboPopup中的MetalComboBoxUI

可以扩展createPopup()并覆盖ComboPopup以返回自定义BasicComboPopup,例如扩展createMouseListener(),但扩展Handler以返回类似的类上面的MetalComboBoxButton,但没有减去的。

哦,为你想支持的每个LAF做同样的事情。育。

从另一个方向攻击问题,可以扩展e.getSource()(由getSize()返回)并覆盖MetalComboBoxUI方法以返回两个方向上大一个像素的维度,当显示菜单时。当然,您仍然需要扩展和覆盖Select data.BlockingSessionID,data.VictimSessionID,LTRIM(RTRIM(data.BlockingQuery)),LTRIM(RTRIM(data.VictimQuery)),data.WaitDurationSecond,data.WaitType,data.BlockingQueryCompletePercent from( SELECT blocking_session_id AS BlockingSessionID, session_id AS VictimSessionID, (SELECT [text] FROM sys.sysprocesses CROSS APPLY sys.dm_exec_sql_text([sql_handle]) WHERE spid = blocking_session_id) AS BlockingQuery, [text] AS VictimQuery, wait_time/1000 AS WaitDurationSecond, wait_type AS WaitType, percent_complete AS BlockingQueryCompletePercent FROM sys.dm_exec_requests CROSS APPLY sys.dm_exec_sql_text([sql_handle]) WHERE blocking_session_id > 0) data 以创建和安装此自定义按钮。

同样,你需要为你想支持的每个LAF做同样的事情。再次,yuk。

不幸的是,Swing似乎没有必要的钩子来轻松覆盖所需的功能,并且已经将各种类标记为私有内部实现细节,从而阻止了它们的重用(为了防止以后如果他们想要更改内部的话)。

答案 1 :(得分:1)

AJNeufeld的建议完美无缺。谢谢!

以下是代码,如果有人需要它。

<强> JComboBoxGoodBorder.java:

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;

public class JComboBoxGoodBorder<T> extends JComboBox<T> {

    public JComboBoxGoodBorder(){
        super();
    }

    public JComboBoxGoodBorder(ComboBoxModel<T> aModel){
        super(aModel);
    }

    public JComboBoxGoodBorder(T[] items){
        super(items);
    }

    public JComboBoxGoodBorder(Vector<T> items){
        super(items);
    }

    @Override
    public void updateUI(){
        setUI(MetalComboBoxUIGoodBorder.createUI(this));
    }

    private static class MetalComboBoxUIGoodBorder extends MetalComboBoxUI {
        public static ComponentUI createUI(JComponent c) {
            return new MetalComboBoxUIGoodBorder();         
        }

        @Override
        protected ComboPopup createPopup() {
            return new BasicComboPopup(comboBox) {
                @Override
                protected MouseListener createMouseListener(){
                    return new MouseAdapter(){
                        @Override
                        public void mousePressed(MouseEvent e) {
                            if (e.getSource() == list) {
                                return;
                            }
                            if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
                                return;

                            if ( comboBox.isEditable() ) {
                                Component comp = comboBox.getEditor().getEditorComponent();
                                if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
                                    comp.requestFocus();
                                }
                            }
                            else if (comboBox.isRequestFocusEnabled()) {
                                comboBox.requestFocus();
                            }
                            togglePopup();
                        }

                        @Override
                        public void mouseReleased(MouseEvent e) {
                            if (e.getSource() == list) {
                                if (list.getModel().getSize() > 0) {
                                    // JList mouse listener
                                    if (comboBox.getSelectedIndex() != list.getSelectedIndex()) {
                                        comboBox.setSelectedIndex( list.getSelectedIndex() );
                                    } else {
                                        comboBox.getEditor().setItem( list.getSelectedValue() );
                                    }
                                }
                                comboBox.setPopupVisible(false);
                                // workaround for cancelling an edited item (bug 4530953)
                                if (comboBox.isEditable() && comboBox.getEditor() != null) {
                                    comboBox.configureEditor(comboBox.getEditor(),
                                            comboBox.getSelectedItem());
                                }
                                return;
                            }
                            // JComboBox mouse listener
                            Component source = (Component)e.getSource();
                            Dimension size = source.getSize();
                            Rectangle bounds = new Rectangle( 0, 0, size.width, size.height);
                            if ( !bounds.contains( e.getPoint() ) ) {
                                MouseEvent newEvent = convertMouseEvent( e );
                                Point location = newEvent.getPoint();
                                Rectangle r = new Rectangle();
                                list.computeVisibleRect( r );
                                if ( r.contains( location ) ) {
                                    if (comboBox.getSelectedIndex() != list.getSelectedIndex()) {
                                        comboBox.setSelectedIndex( list.getSelectedIndex() );
                                    } else {
                                        comboBox.getEditor().setItem( list.getSelectedValue() );
                                    }
                                }
                                comboBox.setPopupVisible(false);
                            }
                            hasEntered = false;
                            stopAutoScrolling();
                        }
                    };
                }
            };         
        }
    }
}

<强> Test.java:

import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

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

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

                JFrame frame = new JFrame("JComboBox Good Border");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setContentPane(panel);
                frame.setSize(300, 300);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}