Java 具有可编辑JComboxes的奇怪选项卡顺序

Java 具有可编辑JComboxes的奇怪选项卡顺序,java,swing,focus,jcombobox,Java,Swing,Focus,Jcombobox,我有一个JFrame看起来像这样: 它有两个JTextFields,一个JComboBox在它们之间,一个JPanel在底部(你看不到) JComboBox的一个特性是可以为它提供一个自定义编辑器。它们实现了ComboBoxEditor接口。在以下三种情况中,GUI看起来都完全相同,我希望它们的行为都完全相同: 我没有指定自定义编辑器,而是使用默认编辑器 我创建了一个自定义编辑器,其编辑器组件是JTextField 我创建了一个自定义编辑器,其编辑器组件是一个JPanel,上面有一个JTex

我有一个
JFrame
看起来像这样:

它有两个
JTextField
s,一个
JComboBox
在它们之间,一个
JPanel
在底部(你看不到)

JComboBox
的一个特性是可以为它提供一个自定义编辑器。它们实现了
ComboBoxEditor
接口。在以下三种情况中,GUI看起来都完全相同,我希望它们的行为都完全相同:

  • 我没有指定自定义编辑器,而是使用默认编辑器
  • 我创建了一个自定义编辑器,其编辑器组件是
    JTextField
  • 我创建了一个自定义编辑器,其编辑器组件是一个
    JPanel
    ,上面有一个
    JTextField
    (使用
    BorderLayout
当可编辑组合框的编辑器设置为默认值时,按Tab键将焦点从顶部的
JTextField
移动到
JComboBox
上的编辑区域,然后移动到另一个
JTextField
。如果我创建了一个自定义编辑器,它的编辑器组件是
JTextField
,如果我不这样做,同样的事情也会发生

但是,如果我创建了一个自定义编辑器,其编辑器组件是一个
JPanel
,其中有一个
JTextField
ed,那么焦点会额外停止一次。如果焦点位于顶部
JTextField
,则按Tab键将焦点移动到可编辑组合框右侧的小箭头,然后再移动到文本区域

为什么会这样?焦点永远不会移到帧底部的
JPanel
,那么为什么按住
JTextField
JPanel
会影响组合框上的选项卡顺序

以下是一个S(-ish)SCCE,其中有一个文本字段和所有三种类型的组合框:

import javax.swing.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;

public class ComboBoxTest extends JFrame
{
  private JPanel layoutPanel;
  private JTextField meaninglessTextField;
  private JComboBox defaultEditorComboBox;
  private JComboBox textFieldEditorComboBox;
  private JComboBox panelEditorComboBox;

  public ComboBoxTest()
  {
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    layoutPanel = new JPanel();
    layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
    meaninglessTextField = new JTextField();

    defaultEditorComboBox = new JComboBox(); // Just a default JComboBox.
    defaultEditorComboBox.setEditable(true);

    textFieldEditorComboBox = new JComboBox();
    textFieldEditorComboBox.setEditable(true);
    textFieldEditorComboBox.setEditor(new TextFieldEditor());

    panelEditorComboBox = new JComboBox();
    panelEditorComboBox.setEditable(true);
    panelEditorComboBox.setEditor(new PanelEditor());

    layoutPanel.add(Box.createRigidArea(new Dimension(500,0)));
    layoutPanel.add(meaninglessTextField);
    layoutPanel.add(defaultEditorComboBox);
    layoutPanel.add(textFieldEditorComboBox);
    layoutPanel.add(panelEditorComboBox);

    Container contentPane = getContentPane();
    contentPane.add(layoutPanel, BorderLayout.CENTER);

    pack();
  }

  public static void main(String[] args)
  {
    java.awt.EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run()
                    {
                      new ComboBoxTest().setVisible(true);
                    }
    });
  }

  private class PanelEditor extends JPanel implements ComboBoxEditor
  {
    public JTextField inputTextField = new JTextField();

    public PanelEditor()
    {
      setLayout(new BorderLayout());
      add(inputTextField, BorderLayout.CENTER);
    }

    @Override
    public String getItem()
    {
      return inputTextField.getText();
    }

    @Override
    public void setItem(Object newText)
    {
      if (newText != null) {
        inputTextField.setText(newText.toString());
      }
      else {
        inputTextField.setText("");
      }
    }

    @Override
    public Component getEditorComponent()
    {
      return this;
    }

    @Override
    public void removeActionListener(ActionListener listener)
    {
      inputTextField.removeActionListener(listener);
    }

    @Override
    public void addActionListener(ActionListener listener)
    {
      inputTextField.addActionListener(listener);
    }

    @Override
    public void selectAll()
    {
      inputTextField.selectAll();
    }
  }

  private class TextFieldEditor extends PanelEditor implements ComboBoxEditor
  {
    // The same, except that the editor component is now just the JTextField
    // rather than the whole panel.
    public TextFieldEditor()
    {
    }

    @Override
    public JTextField getEditorComponent()
    {
      return inputTextField;
    }
  }
} 
注意:如果要将
JLabel
添加到编辑器中,此行为将成为一个问题。然后我必须在那里放置一个
JPanel
来保存标签和文本字段

试试这个:

public PanelEditor()
{
  // other code...

  addFocusListener(new FocusAdapter()
  {
    @Override
    public void focusGained(FocusEvent e)
    {
      inputTextField.requestFocusInWindow();
    }
  });
}

焦点没有转移到
JPanel
;它正在传输到
JComboBox
本身

您可以使用组件的
setFocusable
方法阻止组件接收焦点。如果你加一行

setFocusable(false)
panelEditorComboBox.setFocusable(false)
对于上例中
PanelEditor
的构造函数来说,奇怪的行为仍然存在,因为
PanelEditor
实现了JPanel
,所以
JPanel
setFocusable
方法覆盖了
JComboBox
的方法。由于
JPanel
setFocusable
方法基本上不起任何作用,因此不会发生任何变化

如果改为添加行

setFocusable(false)
panelEditorComboBox.setFocusable(false)
对于
JFrame
本身的构造函数,则
JComboBox
将无法接收焦点,但编辑器中的
JTextField
将接收焦点。这不是一个完美的解决方案,因为如果编辑器本身负责关闭
JComboBox
的聚焦性,那么会更好,因此您可以始终将父
JComboBox
作为参数传递给编辑器的构造函数,并在那里关闭聚焦性


我不知道为什么当你有一个
JTextField
作为编辑器时,行为会有所不同。一些奇怪的摇摆运动

基本问题是组合的ui委托无法处理复合编辑器组件。在一些地方,它假设编辑器组件是它需要执行的任何配置的目标。这里具体的错误行为是,它显式地将编辑器的焦点设置为组合本身的焦点

// in BasicComboBoxUI
protected void configureEditor() {
    ....
    editor.setFocusable(comboBox.isFocusable());
    ....
]
影响

  • 默认情况下,面板的焦点为true,因为组合的焦点为true
  • 在其构造函数中将面板的focusable强制为false没有任何效果(ui稍后以及在LAF切换时将其重置)
  • 禁用组合的可聚焦也会禁用面板的
要修复编辑器的级别,可以实现其isFocusable以无条件返回false:

private class PanelEditor extends JPanel implements ComboBoxEditor

    public boolean isFocusable() {
        return false;
    }
    ...
}
旁白:为了代码卫生,最好不要扩展视图来实现其作为ComboBoxEditor的角色(尽管这里需要一个子类的JPanel来避免问题,所以它可以说是边界:-),而是实现编辑器,让它使用经过调整的面板


也要注意,你可能会遇到复合编辑器的更多问题(检查BasiCouBi的代码,在更多的地方假设它是一个简单的无子组件),所以你可以考虑不做它,但是想一想实现你的需求的另一种方式。只是好奇:为什么需要编辑器中的标签?这是小组开始的唯一原因吗?我现在添加了SSCCE。我想要编辑器中的标签,只是因为我认为这样看起来更好,更适合组合框的外观。这是一个对话框,允许您选择溶液中四种不同离子的浓度:标签显示您指定的离子,您可以在文本字段中键入浓度。使用下拉列表可以选择不同的选项,我更希望标签直接位于下拉列表上方,而不是位于旁边。确保特定选项卡顺序的最佳方法是使用

FocusTraversalPolicy
,而不是依赖布局管理器的隐式顺序。使用它,您可以确保客户编辑器之后的下一个组件确实是所需的
JTextField
,谢谢您的修复,但是您知道为什么会发生这种情况吗
JPanel
s无法接收焦点。此修复程序还阻止您将选项卡移出组合框。@Docky_2009是的,我忘记了