如何使用 Focus 子系统

许多组件-甚至主要由鼠标操作的组件,例如按钮-都可以通过键盘进行操作。为了使按键受到影响,该组件必须具有键盘焦点。

从用户的角度来看,具有键盘焦点的组件通常很突出-例如带有虚线或黑色边框。包含该组件的窗口也比屏幕上的其他窗口突出。这些视觉提示使用户知道任何键入将与哪个组件相关。窗口系统中一次只能有一个组件具有键盘焦点。

窗口究竟如何获得焦点取决于窗口系统。在所有平台上,没有万无一失的方法来确保窗口获得焦点。在某些 os(例如 Microsoft Windows)上,前窗口通常成为焦点窗口。在这种情况下,Window.toFront方法将窗口移到最前面,从而使其成为焦点。但是,在其他 os(例如 Solaris™os)上,窗口 管理 器可能会根据光标位置选择聚焦窗口,在这种情况下,Window.toFront方法的行为是不同的。

当用户单击某个组件时,或者当用户在组件之间进行制表或与组件进行交互时,组件通常会获得焦点。也可以通过编程方式给组件一个焦点,例如当使其包含的框架或对话框可见时。此代码段显示了每次窗口获得焦点时如何赋予特定组件焦点:

//Make textField get the focus whenever frame is activated.
frame.addWindowFocusListener(new WindowAdapter() {
    public void windowGainedFocus(WindowEvent e) {
        textField.requestFocusInWindow();
    }
});

如果要确保特定组件在第一次激活窗口时获得焦点,则可以在实现该组件之后但在显示框架之前在该组件上调用requestFocusInWindow方法。以下示例代码显示了如何完成此操作:

//...Where initialization occurs...
    JFrame frame = new JFrame("Test");
    JPanel panel = new JPanel(new BorderLayout());

    //...Create a variety of components here...

    //Create the component that will have the initial focus.
    JButton button = new JButton("I am first");
    panel.add(button);
    frame.getContentPane().add(panel);  //Add it to the panel

    frame.pack();  //Realize the components.
    //This button will have the initial focus.
    button.requestFocusInWindow(); 
    frame.setVisible(true); //Display the window.

或者,您可以将自定义FocusTraversalPolicy应用于框架并调用getDefaultComponent方法以确定哪个组件将获得焦点。

本节的其余部分包括以下主题:

Focus 子系统简介

焦点子系统旨在尽可能不可见地做正确的事情。在大多数情况下,它以合理的方式运行,如果不运行,则可以通过各种方式调整其行为。一些常见的情况可能包括:

  • Sequences 正确,但未设置焦点所在的第一个组件。如上一节中的代码片段所示,当窗口变为可见时,可以使用requestFocusInWindow方法将焦点设置在组件上。

  • Order 错误。要解决此问题,您可以更改容纳层次结构,可以更改组件添加到其容器的 Sequences,也可以创建自定义焦点遍历策略。有关更多详细信息,请参见自定义焦点遍历

  • 必须防止组件失去焦点,或者您需要在失去焦点之前检查组件中的值。 Input verification是解决此问题的方法。

  • 自定义组件无法获得焦点。要解决此问题,您需要确保它满足使自定义组件具有焦点中概述的所有要求。

FocusConceptsDemo示例说明了一些概念。

FocusConceptsDemo 示例

Try this:

  • 单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行 FocusConceptsDemo。另外,要自己编译和运行示例,请查阅example index

  • 如有必要,请单击窗口以使其成为焦点。

  • 使用 Tab 键将焦点从一个组件移到另一个组件。
    您会注意到,当焦点移到文本区域时,它将停留在文本区域中。

  • 使用 Control-Tab 将焦点移到文本区域之外。

  • 使用 Shift-Tab 键向相反方向移动焦点。

  • 使用 Control-Shift-Tab 键将焦点沿相反方向移出文本区域。

KeyboardFocusManager是焦点子系统的关键元素。它 管理 状态并启动更改。键盘 管理 器跟踪焦点所有者-从键盘接收键入的组件。焦点窗口是包含焦点所有者的窗口。

JWindow and focus:

要在 GUI 中使用JWindow组件,您应该知道JWindow组件的所属框架必须是可见的,以便窗口中的任何组件都能获得焦点。默认情况下,如果未为JWindow组件指定所有权框架,则会为其创建不可见的所有权框架。结果是JWindow组件中的组件可能无法获得焦点。解决方案是在创建JWindow组件时指定可见的拥有框架,或者改用未装饰的JFrame组件。

焦点循环(或焦点遍历循环)是在包含层次结构中共享共同祖先的一组组件。焦点循环根是容器,它是特定焦点遍历循环的根。默认情况下,每个JWindowJInternalFrame组件都可以是焦点循环的根。聚焦循环根本身可以包含一个或多个聚焦循环根。以下 Swing 对象可以是焦点循环的根:JAppletJDesktopPaneJDialogJEditorPaneJFrameJInternalFrameJWindow。尽管JTableJTree对象似乎是聚焦循环的根,但它们不是。

焦点遍历策略确定一组组件的导航 Sequences。 Swing 提供了LayoutFocusTraversalPolicy类,该类根据与布局 管理 器相关的因素(例如组件的大小,位置和方向)确定导航 Sequences。在一个聚焦周期内,组件可以向前或向后导航。在焦点循环根的层次结构中,向上遍历会将焦点从当前循环移到父循环中。

在大多数外观模型中,使用 Tab 和 Shift-Tab 键浏览组件。这些键是默认的焦点遍历键,可以通过编程更改。例如,您可以使用以下四行代码将 Enter 添加为前向遍历键:

Set forwardKeys = getFocusTraversalKeys(
    KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
Set newForwardKeys = new HashSet(forwardKeys);
newForwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
    newForwardKeys);

Tab 可以将焦点向前移动。 Shift-Tab 键可将焦点向后移动。例如,在 FocusConceptsDemo 中,第一个按钮具有初始焦点。 Tab 键将焦点通过按钮移动到文本区域。额外的制表符会将光标移动到文本区域内,但不能移出文本区域,因为在文本区域内,Tab 不是焦点遍历键。但是,Control-Tab 键可将焦点移出文本区域并移至第一个文本字段。同样,Control-Shift-Tab 键可将焦点移出文本区域并移至上一个组件。按照惯例,使用 Control 键将焦点移出以特殊方式处理 Tab 的任何组件,例如JTable

您刚刚收到了有关焦点体系结构的简短介绍。如果需要更多详细信息,请参见Focus Subsystem的规范。

Validating Input

GUI 设计的一个常见要求是限制用户 Importing 的组件,例如,一个文本字段仅允许使用十进制格式的数字 Importing(例如,钱),或者一个文本字段仅允许 5 位邮政编码。易于使用的格式化文本字段组件,可将 Importing 限制为各种可本地化的格式。您还可以为文本字段指定custom formatter,它可以执行特殊检查,例如确定值是否不仅格式正确,而且合理。

您可以使用 Importing 验证程序来替代自定义格式程序,或者当您使用的组件不是文本字段时。Importing 验证程序允许您拒绝特定值,例如格式正确但无效的邮政编码,或超出所需范围的值,例如体温高于 110°F。要使用 Importing 验证器,请创建一个InputVerifier的子类,创建该子类的实例,然后将该实例设置为一个或多个组件的 Importing 验证器。

每当组件即将失去焦点时,都会咨询组件的 Importing 验证程序。如果组件的值不可接受,则 Importing 验证程序可以采取适当的措施,例如拒绝将注意力集中在组件上,或者将用户的 Importing 替换为最后一个有效值,然后将焦点转移到下一个组件。但是,将焦点转移到另一个顶级组件时,不会调用InputVerifier

下面的两个示例显示抵押计算器。一种使用格式化的文本字段,另一种使用标准文本字段的 Importing 验证。

InputVerificationDemo 和示例,演示

Try this:

  • 单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行 FormattedTextFieldDemo。另外,要自己编译和运行示例,请查阅example index

  • 单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行 InputVerificationDemo。另外,要自己编译和运行示例,请查阅example index

  • 并排比较两个抵押计算器。您将看到 Importing 验证演示在关联的标签中为每个可编辑文本字段指定了有效的 Importing 值。try在两个示例中 Importing 格式错误的值,以观察行为。然后try Importing 格式正确但不可接受的值。

您可以在InputVerificationDemo.java中找到 Input Verification 演示的代码。这是InputVerifier子类MyVerifier的代码:

class MyVerifier extends InputVerifier
                 implements ActionListener {
    double MIN_AMOUNT = 10000.0;
    double MAX_AMOUNT = 10000000.0;
    double MIN_RATE = 0.0;
    int MIN_PERIOD = 1;
    int MAX_PERIOD = 40;

   public boolean shouldYieldFocus(JComponent input) {
        boolean inputOK = verify(input);
        makeItPretty(input);
        updatePayment();

        if (inputOK) {
            return true;
        } else {
            Toolkit.getDefaultToolkit().beep();
            return false;
        }
    }

    protected void updatePayment() {
        double amount = DEFAULT_AMOUNT;
        double rate = DEFAULT_RATE;
        int numPeriods = DEFAULT_PERIOD;
        double payment = 0.0;

        //Parse the values.
        try {
            amount = moneyFormat.parse(amountField.getText()).
                              doubleValue();
        } catch (ParseException pe) {pe.printStackTrace();}
        try {
            rate = percentFormat.parse(rateField.getText()).
                                 doubleValue();
        } catch (ParseException pe) {pe.printStackTrace();}
        try {
            numPeriods = decimalFormat.parse(numPeriodsField.getText()).
                              intValue();
        } catch (ParseException pe) {pe.printStackTrace();}

        //Calculate the result and update the GUI.
        payment = computePayment(amount, rate, numPeriods);
        paymentField.setText(paymentFormat.format(payment));
    }

    //This method checks input, but should cause no side effects.
    public boolean verify(JComponent input) {
        return checkField(input, false);
    }

    protected void makeItPretty(JComponent input) {
        checkField(input, true);
    }

    protected boolean checkField(JComponent input, boolean changeIt) {
        if (input == amountField) {
            return checkAmountField(changeIt);
        } else if (input == rateField) {
            return checkRateField(changeIt);
        } else if (input == numPeriodsField) {
            return checkNumPeriodsField(changeIt);
        } else {
            return true; //should not happen
        }
    }

    //Checks that the amount field is valid.  If it is valid,
    //it returns true; otherwise, returns false.  If the
    //change argument is true, this method sets the
    //value to the minimum or maximum value if necessary and
    // (even if not) sets it to the parsed number so that it
    // looks good -- no letters, for example.
    protected boolean checkAmountField(boolean change) {
        boolean wasValid = true;
        double amount = DEFAULT_AMOUNT;

        //Parse the value.
        try {
            amount = moneyFormat.parse(amountField.getText()).
                              doubleValue();
        } catch (ParseException pe) {
            pe.printStackTrace();
            wasValid = false;
        }

        //Value was invalid.
        if ((amount < MIN_AMOUNT) || (amount > MAX_AMOUNT)) {
            wasValid = false;
            if (change) {
                if (amount < MIN_AMOUNT) {
                    amount = MIN_AMOUNT;
                } else { // amount is greater than MAX_AMOUNT
                    amount = MAX_AMOUNT;
                }
            }
        }

        //Whether value was valid or not, format it nicely.
        if (change) {
            amountField.setText(moneyFormat.format(amount));
            amountField.selectAll();
        }

        return wasValid;
    }

    //Checks that the rate field is valid.  If it is valid,
    //it returns true; otherwise, returns false.  If the
    //change argument is true, this method reigns in the
    //value if necessary and (even if not) sets it to the
    //parsed number so that it looks good -- no letters,
    //for example.
    protected boolean checkRateField(boolean change) {
        ...//Similar to checkAmountField...
    }

    //Checks that the numPeriods field is valid.  If it is valid,
    //it returns true; otherwise, returns false.  If the
    //change argument is true, this method reigns in the
    //value if necessary and (even if not) sets it to the
    //parsed number so that it looks good -- no letters,
    //for example.
    protected boolean checkNumPeriodsField(boolean change) {
        ...//Similar to checkAmountField...
    }

    public void actionPerformed(ActionEvent e) {
        JTextField source = (JTextField)e.getSource();
        shouldYieldFocus(source); //ignore return value
        source.selectAll();
    }
}

请注意,实现verify方法是为了检测无效值,但不会执行其他任何操作。 verify方法仅用于确定 Importing 是否有效-绝不应该弹出对话框或引起任何其他副作用。 shouldYieldFocus方法调用verify,如果值无效,则将其设置为最小值或最大值。允许shouldYieldFocus方法引起副作用,在这种情况下,它总是格式化文本字段,并且还可能更改其值。在我们的示例中,shouldYieldFocus方法始终返回 true,因此实际上不会阻止焦点的转移。这只是可以执行验证的一种方式。查找该演示的另一个版本InputVerificationDialogDemo,当用户 Importing 无效并要求用户 Importing 合法值时,该对话框会弹出一个对话框。

Importing 验证程序是使用JComponent类的setInputVerifier方法安装的。例如,InputVerificationDemo具有以下代码:

private MyVerifier verifier = new MyVerifier();
...
amountField.setInputVerifier(verifier);

使自定义组件具有焦点

为了使组件获得焦点,它必须满足三个要求:它必须是可见的,启用的和可聚焦的。也可以给出 Importing 图。有关 ImportingMap 的更多信息,请阅读如何使用键绑定

TrackFocusDemo示例定义了简单组件Picture。其构造函数如下所示:

public Picture(Image image) {
    this.image = image;
    setFocusable(true);
    addMouseListener(this);
    addFocusListener(this);
}

setFocusable(true)方法的调用使该组件具有焦点。如果您在其WHEN_FOCUSEDImportingMap 中显式提供了组件键绑定,则无需调用setFocusable方法。

为了直观地显示焦点的变化(仅当组件具有焦点时才绘制红色边框),Picture具有focus listener

为了在用户单击图片时获得焦点,组件具有mouse listener。侦听器的mouseClicked方法请求将焦点转移到图片上。这是代码:

public void mouseClicked(MouseEvent e) {
    //Since the user clicked on us, let us get focus!
    requestFocusInWindow();
}

有关 TrackFocusDemo 示例的更多讨论,请参见跟踪焦点更改到多个组件

自定义焦点遍历

焦点子系统确定使用焦点遍历键(例如 Tab)进行导航时所应用的默认 Sequences。 Swing 应用程序的策略由LayoutFocusTraversalPolicy确定。您可以使用setFocusCycleRoot方法在任何Container上设置焦点遍历策略。但是,如果容器不是焦点循环的根,则可能没有明显的作用。或者,您可以将焦点遍历策略提供程序传递给FocusTraversalPolicy方法,而不是焦点循环根。使用isFocusTraversalPolicyProvider()方法来确定Container是否是焦点遍历策略提供者。使用setFocusTraversalPolicyProvider()方法来设置用于提供焦点遍历策略的容器。

FocusTraversalDemo示例演示了如何自定义焦点行为。

焦点遍历演示,演示了自定义的 FocusTraversalPolicy。

Try this:

  • 单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行 FocusTraversalDemo。另外,要自己编译和运行示例,请查阅example index

  • 如有必要,请单击窗口以使其成为焦点。

  • 当您浏览组件时,请注意焦点 Sequences。焦点 Sequences 由组件添加到内容窗格的 Sequences 确定。还请注意,复选框永远不会获得焦点;我们从焦点周期中将其删除。

  • 要将焦点移到表外,请使用 Control-Tab 或 Control-Shift-Tab。

  • 单击“自定义 FocusTraversalPolicy”复选框。此框在框架上安装自定义焦点遍历策略。

  • try再次浏览各个组件。请注意,现在焦点 Sequences 是从左到右,从上到下的 Sequences。

您可以在FocusTraversalDemo.java中找到该演示的代码。

使用以下代码行将代码从焦点循环中删除:

togglePolicy.setFocusable(false);

这是应用程序的自定义FocusTraversalPolicy

...
JTextField tf1 = new JTextField("Field 1");
JTextField tf2 = new JTextField("A Bigger Field 2");
JTextField tf3 = new JTextField("Field 3");
JTextField tf4 = new JTextField("A Bigger Field 4");
JTextField tf5 = new JTextField("Field 5");
JTextField tf6 = new JTextField("A Bigger Field 6");
JTable table = new JTable(4,3);
...
public FocusTraversalDemo() {
    super(new BorderLayout());

    JTextField tf1 = new JTextField("Field 1");
    JTextField tf2 = new JTextField("A Bigger Field 2");
    JTextField tf3 = new JTextField("Field 3");
    JTextField tf4 = new JTextField("A Bigger Field 4");
    JTextField tf5 = new JTextField("Field 5");
    JTextField tf6 = new JTextField("A Bigger Field 6");
    JTable table = new JTable(4,3);
    togglePolicy = new JCheckBox("Custom FocusTraversalPolicy");
    togglePolicy.setActionCommand("toggle");
    togglePolicy.addActionListener(this);
    togglePolicy.setFocusable(false);  //Remove it from the focus cycle.
    //Note that HTML is allowed and will break this run of text
    //across two lines.
    label = new JLabel("<html>Use Tab (or Shift-Tab) to navigate from component to component.<p>Control-Tab 
    (or Control-Shift-Tab) allows you to break out of the JTable.</html>");

    JPanel leftTextPanel = new JPanel(new GridLayout(3,2));
    leftTextPanel.add(tf1, BorderLayout.PAGE_START);
    leftTextPanel.add(tf3, BorderLayout.CENTER);
    leftTextPanel.add(tf5, BorderLayout.PAGE_END);
    leftTextPanel.setBorder(BorderFactory.createEmptyBorder(0,0,5,5));
    JPanel rightTextPanel = new JPanel(new GridLayout(3,2));
    rightTextPanel.add(tf2, BorderLayout.PAGE_START);
    rightTextPanel.add(tf4, BorderLayout.CENTER);
    rightTextPanel.add(tf6, BorderLayout.PAGE_END);
    rightTextPanel.setBorder(BorderFactory.createEmptyBorder(0,0,5,5));
    JPanel tablePanel = new JPanel(new GridLayout(0,1));
    tablePanel.add(table, BorderLayout.CENTER);
    tablePanel.setBorder(BorderFactory.createEtchedBorder());
    JPanel bottomPanel = new JPanel(new GridLayout(2,1));
    bottomPanel.add(togglePolicy, BorderLayout.PAGE_START);
    bottomPanel.add(label, BorderLayout.PAGE_END);

    add(leftTextPanel, BorderLayout.LINE_START);
    add(rightTextPanel, BorderLayout.CENTER);
    add(tablePanel, BorderLayout.LINE_END);
    add(bottomPanel, BorderLayout.PAGE_END);
    setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
    Vector<Component> order = new Vector<Component>(7);
    order.add(tf1);
    order.add(tf2);
    order.add(tf3);
    order.add(tf4);
    order.add(tf5);
    order.add(tf6);
    order.add(table);
    newPolicy = new MyOwnFocusTraversalPolicy(order);
}

要使用自定义FocusTraversalPolicy,请在任何焦点循环根目录上实现以下代码。

MyOwnFocusTraversalPolicy newPolicy = new MyOwnFocusTraversalPolicy();
    frame.setFocusTraversalPolicy(newPolicy);

您可以通过将FocusTraversalPolicy设置为null来删除自定义焦点遍历策略,这将恢复默认策略。

跟踪焦点到多个组件的更改

在某些情况下,应用程序可能需要跟踪哪个组件具有焦点。此信息可能用于动态更新菜单或状态栏。如果您只需要关注特定组件,则可以实现焦点事件监听器

如果焦点监听器不合适,则可以在KeyboardFocusManager上注册PropertyChangeListener。向属性更改侦听器通知涉及焦点的每个更改,包括对焦点所有者,焦点窗口和默认焦点遍历策略的更改。有关完整列表,请参见KeyboardFocusManager Properties表。

The以下示例演示如何通过在键盘焦点 管理 器上安装属性更改侦听器来跟踪焦点所有者。

TrackFocusDemo 示例,该示例演示如何跟踪焦点所有者。

Try this:

  • 单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行 TrackFocusDemo。另外,要自己编译和运行示例,请查阅example index

  • 如有必要,请单击窗口以使其成为焦点。

  • 该窗口显示六个图像,每个图像由Picture组件显示。具有焦点的Picture带有红色边框。窗口底部的标签描述了具有焦点的Picture

  • 通过使用 Tab 或 Shift-Tab 或单击图像,将焦点移至另一个Picture。因为属性更改侦听器已在键盘焦点 管理 器上注册,所以可以检测到焦点更改并适当地更新标签。

您可以在TrackFocusDemo.java中查看演示的代码。用于绘制图像的自定义组件可以在Picture.java中找到。以下是定义和安装属性更改侦听器的代码:

KeyboardFocusManager focusManager =
    KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.addPropertyChangeListener(
    new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
            String prop = e.getPropertyName();
            if (("focusOwner".equals(prop)) &&
                  ((e.getNewValue()) instanceof Picture)) {
                Component comp = (Component)e.getNewValue();
                String name = comp.getName();
                Integer num = new Integer(name);
                int index = num.intValue();
                if (index < 0 || index > comments.length) {
                    index = 0;
                }
                info.setText(comments[index]);
            }
        }
    }
);

定制组件Picture负责绘制图像。通过以下方式定义所有六个组件:

pic1 = new Picture(createImageIcon("images/" +
            mayaString + ".gif", mayaString).getImage());
pic1.setName("1");

时间重点转移

焦点转移是异步的。这种质量可能会导致一些与计时相关的奇怪问题和假设,尤其是在焦点自动转移期间。例如,假设一个应用程序具有一个包含“开始”按钮,“取消”按钮和文本字段的窗口。组件按以下 Sequences 添加:

  • Start button

  • Text field

  • Cancel button

启动应用程序时,LayoutFocusTraversalPolicy确定焦点遍历策略-在这种情况下,这是将组件添加到其容器的 Sequences。在此示例中,所需的行为是“开始”按钮具有初始焦点,并且在单击“开始”按钮时将其禁用,然后“取消”按钮获得了焦点。实现此行为的正确方法是按照所需 Sequences 将组件添加到容器中,或者创建自定义焦点遍历策略。如果由于某种原因无法执行此操作,则可以使用以下代码段实现此行为:

public void actionPerformed(ActionEvent e) {
    //This works.
    start.setEnabled(false);
    cancel.requestFocusInWindow();
}

根据需要,焦点从“开始”按钮转到“取消”按钮,而不是文本字段。但是,如果以相反的 Sequences 调用相同的方法,则会产生不同的结果,如下所示:

public void actionPerformed(ActionEvent e) {
    //This does not work.
    cancel.requestFocusInWindow();
    start.setEnabled(false);
}

在这种情况下,焦点在离开“开始”按钮之前就需要在“取消”按钮上进行。调用requestFocusInWindow方法会启动焦点转移,但是不会立即将焦点移到“取消”按钮。当“开始”按钮被禁用时,焦点将转移到下一个组件(因此始终有焦点的组件),在这种情况下,它将把焦点移动到文本字段,而不是“取消”按钮。

在可能影响焦点的所有其他更改应用于以下情况后,需要在几种情况下发出焦点请求:

  • 隐藏焦点所有者。

  • 使焦点所有者无法聚焦。

  • 在焦点所有者上调用removeNotify方法。

  • 对焦点所有者的容器执行上述任何操作,或对焦点策略进行更改,以使容器不再接受该组件作为焦点所有者。

  • 处理包含焦点所有者的顶层窗口。

Focus API

下表列出了与焦点相关的常用构造函数和方法。焦点 API 分为四类:

有关焦点体系结构的更多详细信息,请参见Focus Subsystem的规范。您可能还会发现如何编写焦点侦听器有用。

有用的组件方法

方法(在Component中)Purpose
isFocusOwner()如果组件是焦点所有者,则返回true
setRequestFocusEnabled(boolean)

isRequestFocusEnabled()
(* in JComponent)*
设置或检查此组件是否应获得焦点。将setRequestFocusEnabled设置为false通常可以防止鼠标单击为组件提供焦点,同时仍允许键盘导航为组件提供焦点。此方法仅适用于接收鼠标事件的组件。例如,可以在JButton上使用此方法,但不能在JPanel上使用。如果编写自定义组件,则由您决定是否使用此属性。与setFocusable方法相比,建议使用此方法,它将使您的程序对于使用assistive technologies的用户更好地工作。
setFocusable(boolean)
isFocusable()
设置或获取组件的可聚焦状态。组件必须是可聚焦的才能获得焦点。使用setFocusable(false)从焦点循环中删除某个组件后,将无法再使用键盘进行导航。建议使用setRequestFocusEnabled方法,以便使用assistive technologies的用户可以运行您的程序。
requestFocusInWindow()请求此组件应获得焦点。组件的窗口必须是当前的焦点窗口。为使此请求被授予,JComponent的子类必须是可见的,启用的和可聚焦的,并且具有要授予该请求的 ImportingMap。在触发FOCUS_GAINED事件之前,不应假定组件具有焦点。该方法优于requestFocus方法,后者取决于平台。
setFocusTraversalKeys(int, Set)
getFocusTraversalKeys(int)
areFocusTraversalKeysSet(int)
(* in java.awt.Container *)
设置或获取特定方向的焦点遍历键,或确定是否已在此容器上显式设置任何焦点遍历键。如果未设置焦点遍历键,则它们是从祖先或键盘焦点 管理 器继承的。可以将焦点遍历键设置为以下方向:KeyboardFocusManager.FORWARD_TRAVERSAL_KEYSKeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYSKeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS。如果设置UP_CYCLE_TRAVERSAL_KEYSDOWN_CYCLE_TRAVERSAL_KEYS,则还必须在焦点遍历策略上调用setImplicitDownCycleTraversal(false)

创建和使用自定义 FocusTraversalPolicy

类或方法Purpose
LayoutFocusTraversalPolicy默认情况下,该类确定 Swing 组件的焦点遍历策略。
getComponentAfter(Container, Component)给定作为 Importing 传递的组件,返回接下来应该具有焦点的组件。
getComponentBefore(Container, Component)给定作为 Importing 传递的组件,返回在该组件之前应具有焦点的组件。该方法用于后退制表。
getDefaultComponent(Container)

(javax.swing.SortingFocusTraversalPolicy中)
返回应具有默认焦点的组件。
getFirstComponent(Container)返回遍历循环中的第一部分。
getInitialComponent(Container)返回第一次使窗口可见时应获得焦点的组件。
getLastComponent(Container)返回遍历循环中的最后一个分量。
setFocusTraversalPolicy(FocusTraversalPolicy)
getFocusTraversalPolicy(FocusTraversalPolicy)
(* in java.awt.Container *)
设置或获取焦点遍历策略,或确定是否已设置策略。请注意,在不是焦点循环根的容器上设置焦点遍历策略可能没有明显效果。值null表示尚未显式设置策略。如果未设置策略,则从父焦点循环根继承策略。
isFocusCycleRoot()
setFocusCycleRoot(boolean)
(*在java.awt.Container *中)
检查或设置容器是否为焦点遍历循环的根。
isFocusTraversalPolicyProvider()
setFocusTraversalPolicyProvider(boolean)
(*在java.awt.Container *中)
检查或设置是否使用容器提供焦点遍历策略。

Importing 验证 API

类或方法Purpose
InputVerifier允许通过焦点机制进行 Importing 验证的抽象类。try将焦点从包含 Importing 验证程序的组件上移开时,直到满足验证程序,才会放弃焦点。
shouldYieldFocus(JComponent)

(在InputVerifier中)
当组件具有 Importing 验证程序时,系统将调用此方法以确定焦点是否可以离开此组件。此方法可能会导致副作用,例如弹出对话框。如果此方法返回false,则焦点将停留在传递给该方法的组件上。
verify(JComponent)
(在InputVerifier中)
您需要重写此方法以检查组件的 Importing 是否有效。如果有效,它将返回true,否则返回false。此方法不应引起任何副作用,例如弹出对话框。此方法由shouldYieldFocus调用。
setInputVerifier(inputVerifier)
getInputVerifier()
(在JComponent中)
设置或获取分配给组件的 Importing 验证程序。默认情况下,组件没有 Importing 验证程序。
setVerifyInputWhenFocusTarget(boolean)
getVerifyInputWhenFocusTarget()
(在JComponent中)
设置或获取是否在此组件请求焦点之前调用当前焦点所有者的 Importing 验证程序。默认值为true

KeyboardFocusManager Properties

该表定义了KeyboardFocusManager的绑定属性。可以通过调用addPropertyChangeListener为这些属性注册一个侦听器。

PropertyPurpose
focusOwner当前接收关键事件的组件。
permanentFocusOwner最近收到永久FOCUS_GAINED事件的组件。通常与focusOwner相同,除非当前正在进行临时焦点更改。
focusedWindow是或包含焦点所有者的窗口。
activeWindow组件必须始终为FrameDialog。活动窗口可以是焦点窗口,也可以是焦点窗口的所有者的第一帧或对话框。
defaultFocusTraversalPolicy默认的焦点遍历策略,可以通过Container类的setFocusTraversalPolicy方法设置。
forwardDefaultFocusTraversalKeys向前遍历的默认焦点键集。对于多行文本组件,这些键默认为 Control-Tab。对于所有其他组件,这些键默认为 Tab 和 Control-Tab。
backwardDefaultFocusTraversalKeys向后遍历的默认焦点键集。对于多行文本组件,这些键默认为 Control-Shift-Tab。对于所有其他组件,这些键默认为 Shift-Tab 和 Control-Shift-Tab。
upCycleDefaultFocusTraversalKeys向上循环的默认聚焦键集。对于 Swing 组件,这些键默认为 null。如果将这些键设置在KeyboardFocusManager上,或者将downCycleFocusTraversalKeys设置在焦点循环根目录上,则还必须在焦点遍历策略上调用setImplicitDownCycleTraversal(false)方法。
downCycleDefaultFocusTraversalKeys向下循环的默认聚焦键集。对于 Swing 组件,这些键默认为 null。如果将这些键设置在KeyboardFocusManager上,或者将upCycleFocusTraversalKeys设置在焦点循环根目录上,则还必须在焦点遍历策略上调用setImplicitDownCycleTraversal(false)方法。
currentFocusCycleRoot当前焦点周期根的容器。

使用焦点的示例

下表列出了操纵焦点的示例:

ExampleWhere DescribedNotes
FocusConceptsDemoThis section演示基本的默认焦点行为。
FocusTraversalDemoThis section演示如何覆盖默认的焦点 Sequences。
TrackFocusDemoThis section演示如何使用PropertyChangeListener跟踪焦点所有者。还实现自定义可聚焦组件。
InputVerificationDemoThis section演示如何实现InputVerifier来验证用户 Importing。
InputVerificationDialogDemoThis section演示如何实现当用户 Importing 无效时创建一个对话框的InputVerifier
FocusEventDemo如何编写焦点侦听器报告发生在多个组件上的所有焦点事件,以演示触发焦点事件的情况。