当你有一个摆动JComboBox并点击它的边框时,弹出窗口会立即显示并消失。当我说点击时,我的意思是按下鼠标左键并立即释放。
它可能被认为是糟糕的用户体验,因为没有用户会期望它发生。单击组合框的边框时,任何用户都会期望以下行为之一:
当然没有用户会期望弹出窗口立即打开和关闭。
用户不会故意点击边框。但是当组合框很小并且他试图快速点击它时,它可能经常发生。
在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);
}
});
}
}
答案 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.width
和size.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);
}
});
}
}