如何使用 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 子系统简介

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

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 验证。

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示例演示了如何自定义焦点行为。

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以下示例演示如何通过在键盘焦点 管理 器上安装属性更改侦听器来跟踪焦点所有者。

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 添加:

启动应用程序时,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方法会启动焦点转移,但是不会立即将焦点移到“取消”按钮。当“开始”按钮被禁用时,焦点将转移到下一个组件(因此始终有焦点的组件),在这种情况下,它将把焦点移动到文本字段,而不是“取消”按钮。

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

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为这些属性注册一个侦听器。

Property Purpose
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 当前焦点周期根的容器。

使用焦点的示例

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

Example Where Described Notes
FocusConceptsDemo This section 演示基本的默认焦点行为。
FocusTraversalDemo This section 演示如何覆盖默认的焦点 Sequences。
TrackFocusDemo This section 演示如何使用PropertyChangeListener跟踪焦点所有者。还实现自定义可聚焦组件。
InputVerificationDemo This section 演示如何实现InputVerifier来验证用户 Importing。
InputVerificationDialogDemo This section 演示如何实现当用户 Importing 无效时创建一个对话框的InputVerifier
FocusEventDemo 如何编写焦点侦听器 报告发生在多个组件上的所有焦点事件,以演示触发焦点事件的情况。
首页