【问题标题】:Strange tab-order with editable JComboBoxes带有可编辑 JComboBoxes 的奇怪标签顺序
【发布时间】:2013-08-28 14:38:06
【问题描述】:

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

上面有两个JTextFields,一个JComboBox在它们之间,一个JPanel在底部(你看不到)。

JComboBox 的一个特点是它可以被赋予一个自定义编辑器。这些实现了ComboBoxEditor 接口。在以下三种情况下,GUI 看起来完全一样,我希望它们的行为完全相同:

  • 我没有指定自定义编辑器,而是使用默认编辑器。
  • 我创建了一个自定义编辑器,其编辑器组件为JTextField
  • 我创建了一个自定义编辑器,其编辑器组件是JPanel,上面带有JTextField(使用BorderLayout)。

当可编辑组合框的编辑器设置为默认值时,按 Tab 将焦点从顶部 JTextField 移到 JComboBox 上的编辑区域,然后移到另一个 @ 987654335@。如果我创建一个自定义编辑器,其编辑器组件是JTextField,否则会按照您的预期进行,同样的事情也会发生。

但是,如果我改为创建一个自定义编辑器,其编辑器组件是 JPanel 并带有 JTextField added 到它,焦点会额外停止。如果焦点位于顶部JTextField,则按下 Tab 会将焦点移至可编辑组合框右侧的小箭头,然后再移入文本区域。

为什么会这样?焦点永远不会移动到框架底部的JPanel,那么为什么存在JPanel 持有JTextField 会影响组合框上的Tab 顺序?

以下是一个 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 来保存标签和文本字段。

【问题讨论】:

  • 是的,请显示SSCCE。只是好奇:为什么你需要编辑器中的标签?小组开始的唯一原因是什么?
  • 我现在添加了 SSCCE。我想要编辑器中的标签只是因为我认为这样看起来更好,并且更适合组合框的外观。这是一个对话框,允许您选择溶液中四种不同离子的浓度:标签显示您指定的离子,您可以在文本字段中输入浓度。使用下拉列表可以选择不同的离子,我宁愿将标签直接放在下拉列表上方,而不是放在一边。
  • 确保特定选项卡顺序的最佳方法是使用FocusTraversalPolicy,而不是依赖于布局管理器的隐式顺序。使用它,您可以确保客户编辑器之后的下一个组件确实是所需的JTextField

标签: java swing focus jcombobox


【解决方案1】:

基本问题是组合的 ui 委托无法处理复合编辑器组件。有几个地方假设编辑器组件是它需要做的任何配置的目标。这里具体的错误行为是它明确地将编辑器的可聚焦性设置为组合本身的可聚焦性

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

影响

  • 默认情况下,面板的可聚焦为真,因为组合为真
  • 在其构造函数中强制面板的可聚焦为 false 没有任何效果(用户界面稍后会在 LAF 切换时重置它)
  • 禁用组合的焦点功能也会禁用面板的焦点

要修复编辑器的级别,可以实现它的isFocusable无条件返回false:

private class PanelEditor extends JPanel implements ComboBoxEditor

    public boolean isFocusable() {
        return false;
    }
    ...
}

顺便说一句:为了代码卫生,最好不要扩展一个视图来实现它作为 ComboBoxEditor 的角色(即使在这里你需要一个子类 JPanel 来避免这个问题,所以它可以说是边界:-) -而是实现编辑器并让它使用调整后的面板。

另外请注意,您可能会在使用复合编辑器时遇到更多问题(查看 BasicComboUI 的代码以了解更多假定纯无子组件的地方),因此您可能会考虑根本不这样做,而是想一种不同的方法达到您的要求。

【讨论】:

    【解决方案2】:

    试试这个:

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

    【讨论】:

    • 感谢您的修复,但您知道为什么会这样吗? JPanels 无法获得焦点。
    • 此修复程序还会阻止您将选项卡移出组合框。
    • @Donkey_2009 是的,我忘记将其添加为免责声明。您可能需要亲自动手使用容器的 FocusTraversalPolicy。
    • Focus whateverXxxFocus 是相当异步的,invokeLater 需要包装
    【解决方案3】:

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

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

    setFocusable(false)
    

    到上面例子中PanelEditor的构造函数,那么奇怪的行为仍然存在,因为PanelEditorimplements JPanel,所以JPanelsetFocusable方法覆盖了@987654330的方法@。由于JPanelsetFocusable 方法基本上什么都不做,所以没有任何变化。

    如果改为添加该行

    panelEditorComboBox.setFocusable(false)
    

    对于JFrame 本身的构造函数,那么JComboBox 将无法获得焦点,但编辑器内部的JTextField 会。这不是一个完美的解决方案,因为如果编辑器本身负责关闭JComboBox 的可聚焦性会更好,因此您始终可以将父JComboBox 作为参数传递给编辑器的构造函数,并在此处关闭可聚焦性。

    我不知道为什么当你有一个 JTextField 作为编辑器时行为会有所不同。一些奇怪的 Swing 东西。

    【讨论】:

    • 您是否尝试过将代码链接为@splunebob 答案的注释,有趣的是外观、java 版本和本机操作系统,
    • 请注意,我看到了第三个。 JComponent, JComboBoc 需要两个选项卡才能将焦点留给下一个 JComponent, JTextField
    猜你喜欢
    • 2020-01-27
    • 1970-01-01
    • 2018-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-28
    • 2023-04-08
    • 2017-10-23
    相关资源
    最近更新 更多