2

我有一个 GUI 应用程序,它使用 InputVerifier 在产生焦点之前检查文本字段的内容。这一切都很正常。然而,昨天发现了一个问题——它似乎是一个错误,但我在任何地方都找不到任何提及它的内容。在我将此报告为错误之前,我想我会问:我在这里遗漏了一些明显的东西吗?

情况:

  • 一组带有 InputVerifiers 的文本字段。
  • 所有控件上的 FocusLost 和 FocusGained 侦听器,所以我可以看到发生了什么。
  • 一个单独的线程使用 DefaultKeyboardFocusManager 报告(每 2 秒)哪个控件具有焦点。
  • 我将无效数据放在表单中间的 JTextField 中,并尝试离开控件。

如果我尝试使用鼠标或使用 tab 键离开此控件,我不能。FocusLost 事件不会触发并且控件正确地保留焦点。

但是,如果我尝试使用 Shift-Tab 以相反的制表顺序离开控件,有时会触发 FocusLost 事件。如果发生这种情况,单独的线程会报告没有控件具有焦点,即 getFocusOwner() 返回 null。


编辑:下面是一个显示问题的小示例程序。问题与额外的线程无关 - 线程只是为了使问题更加明显。如果存在竞争条件,则它在 Swing 中的某个地方。

要查看问题,请转到第二个文本框并将其清空。控件应该保留焦点,除非您通过按 shift-tab 离开它,否则会保留焦点。与完整的应用程序不同,错误似乎 100% 发生在这里。在 OpenJDK 6 和 Oracle Java 7 下都是如此。

这几乎太明显了,不可能是一个错误,而且它发生在多个 Java 环境中。因此,我怀疑我遗漏了一些明显的东西。任何人?

public class FocusBugDemo extends JFrame {
    static JTextField txtOne = new JTextField("Ignore this control");
    static JTextField txtTwo = new JTextField("Delete this text, then press shift-tab");
    static JLabel lblFocus = new JLabel("");
    static KeyboardFocusManager kfm = new DefaultKeyboardFocusManager();

    public static void main(String[] args) {
        new FocusBugDemo();
        Thread t = new Thread() {
            @Override
            public void run() {
                while(true) {
                    Component c = kfm.getFocusOwner();
                    String focusInfo = "elsewhere";
                    if (c == null) {                        focusInfo = "null";
                    } else if (c == txtOne) {       focusInfo = "txtOne";
                    } else if (c == txtTwo) {       focusInfo = "txtTwo";
                    }
                    lblFocus.setText(System.currentTimeMillis() + " - Focus owner " + focusInfo);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        t.start();
    }

    private FocusBugDemo() {
        super("Focus bug demo");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setPreferredSize(new Dimension(300,100));
        setLayout(new GridLayout(3,1));
        NotEmpty validator = new NotEmpty();
        txtOne.setInputVerifier(validator);
        txtTwo.setInputVerifier(validator);
        add(txtOne);
        add(txtTwo);
        add(lblFocus);
        pack();
        setVisible(true);
    }

    private class NotEmpty extends InputVerifier {
        @Override
        public boolean verify(JComponent input) {
            JTextField txtField = (JTextField) input;
            return (txtField.getText().length() > 0);
        }
    }
}
4

2 回答 2

2

现在作为错误7167871报告给 Oracle 。

于 2012-05-12T06:24:44.820 回答
1

使用您的sscce,我无法在支持@CatalinaIsland 观察的 Mac OS X、Java 6 上重现您描述的效果。特别是,焦点永远不会使用tabor离开一个空的文本字段shift-tabnull只有当框架被停用时,焦点才会变为。

我看到两个线程访问多个字段而根本没有同步。至少,您应该使用EventQueue.invokeLater()int来更新 GUI,如Swing 中的并发中所述并在下面显示。

更广泛的问题是:您要使用什么来解决焦点问题t

import java.awt.Component;
import java.awt.DefaultKeyboardFocusManager;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class FocusDemo {

    private static final JTextField txtOne =
        new JTextField("Ignore this control");
    private static final JTextField txtTwo =
        new JTextField("Delete this text, then press shift-tab");
    private static final JLabel lblFocus = new JLabel("");

    public static void main(String[] args) {
        new FocusDemo();
        Thread t = new Thread() {

            @Override
            public void run() {
                while (true) {
                    EventQueue.invokeLater(new Runnable() {

                        KeyboardFocusManager kfm =
                            new DefaultKeyboardFocusManager();

                        @Override
                        public void run() {
                            Component c = kfm.getFocusOwner();
                            String focusInfo = "elsewhere";
                            if (c == null) {
                                focusInfo = "null";
                            } else if (c == txtOne) {
                                focusInfo = "txtOne";
                            } else if (c == txtTwo) {
                                focusInfo = "txtTwo";
                            }
                            lblFocus.setText(System.currentTimeMillis()
                                + " - Focus owner " + focusInfo);
                        }
                    });
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace(System.err);
                    }
                }
            }
        };
        t.start();
    }

    private FocusDemo() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame("Focus bug demo");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setPreferredSize(new Dimension(300, 100));
                f.setLayout(new GridLayout(3, 1));
                NotEmpty validator = new NotEmpty();
                txtOne.setInputVerifier(validator);
                txtTwo.setInputVerifier(validator);
                f.add(txtOne);
                f.add(txtTwo);
                f.add(lblFocus);
                f.pack();
                f.setVisible(true);
            }
        });
    }

    private class NotEmpty extends InputVerifier {

        @Override
        public boolean verify(JComponent input) {
            JTextField txtField = (JTextField) input;
            return (txtField.getText().length() > 0);
        }
    }
}
于 2012-05-10T21:42:32.620 回答