Java 教程是为 JDK 8 编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请参阅Java 语言更改,了解 Java SE 9 及其后续版本中更新的语言特性的摘要。
请参阅JDK 发行说明,了解有关所有 JDK 版本的新功能、增强功能以及已删除或已弃用选项的信息。
许多组件,甚至那些主要通过鼠标操作的组件(例如按钮),也可以通过键盘操作。要使按键对组件产生影响,该组件必须具有键盘焦点。
从用户的角度来看,具有键盘焦点的组件通常很突出,例如带有点状或黑色边框。包含该组件的窗口也比屏幕上的其他窗口更突出。这些视觉提示使用户知道键入将与哪个组件相关。窗口系统中一次只能有一个组件具有键盘焦点。
窗口如何获得焦点取决于窗口系统。在所有平台上,没有绝对可靠的方法来确保窗口获得焦点。在某些操作系统(如Microsoft Windows)中,通常前置窗口成为焦点窗口。在这些情况下,Window.toFront
方法将窗口移到前面,从而使其获得焦点。然而,在其他操作系统(如Solaris™操作系统)中,窗口管理器可能根据光标位置选择焦点窗口,而在这些情况下,Window.toFront
方法的行为是不同的。
当用户单击组件、在组件之间切换标签或以其他方式与组件交互时,组件通常会获得焦点。还可以通过编程方式给组件设置焦点,例如在其包含的框架或对话框框架可见时。以下代码段显示了如何在窗口获得焦点时每次都给特定组件设置焦点:
//当框架被激活时,使textField获取焦点。 frame.addWindowFocusListener(new WindowAdapter() { public void windowGainedFocus(WindowEvent e) { textField.requestFocusInWindow(); } });
如果要确保在窗口激活时第一次获得焦点的是特定组件,可以在组件被实例化后、框架显示之前调用组件的requestFocusInWindow
方法。以下示例代码显示了如何执行此操作:
//...初始化代码... JFrame frame = new JFrame("Test"); JPanel panel = new JPanel(new BorderLayout()); //...在此处创建各种组件... //创建将具有初始焦点的组件。 JButton button = new JButton("我是第一个"); panel.add(button); frame.getContentPane().add(panel); //将其添加到面板中 frame.pack(); //实例化组件。 //此按钮将具有初始焦点。 button.requestFocusInWindow(); frame.setVisible(true); //显示窗口。
或者,您可以将自定义FocusTraversalPolicy
应用于框架,并调用getDefaultComponent
方法来确定哪个组件将获得焦点。
本节的其余部分涵盖以下主题:
焦点子系统旨在尽可能隐形地执行正确的操作。在大多数情况下,它的行为是合理的,如果不是,您可以以各种方式调整其行为。一些常见的场景可能包括:
requestFocusInWindow
方法在窗口可见时将焦点设置在组件上。FocusConceptsDemo
示例说明了一些概念。
KeyboardFocusManager
是焦点子系统的一个关键组件。它管理状态并发起更改。键盘管理器跟踪焦点所有者 - 从键盘接收输入的组件。焦点窗口是包含焦点所有者的窗口。
JWindow
组件时,您应该知道JWindow
组件的拥有框架必须可见,以便窗口中的任何组件都可以获得焦点。默认情况下,如果不为JWindow
组件指定拥有框架,则会为其创建一个不可见的拥有框架。结果是JWindow
组件中的组件可能无法获得焦点。解决方案是在创建JWindow
组件时指定一个可见的拥有框架,或者改用一个无装饰的JFrame
组件。
焦点循环(或焦点遍历循环)是一组在包含层次结构中共享共同祖先的组件。 焦点循环根是一种用于特定焦点遍历循环的容器。默认情况下,每个JWindow
和JInternalFrame
组件都可以是焦点循环根。焦点循环根本身可以包含一个或多个焦点循环根。以下Swing对象可以作为焦点循环根: JApplet
, JDesktopPane
, JDialog
, JEditorPane
, JFrame
, JInternalFrame
和JWindow
。虽然JTable
和JTree
对象看起来可能是焦点循环根,但它们实际上不是。
焦点遍历策略确定了一组组件的导航顺序。Swing提供了
类,它基于布局管理器相关因素(例如组件的大小,位置和方向)来决定导航顺序。在焦点循环中,组件可以以正向或反向方向导航。在焦点循环根的层次结构中,向上遍历将焦点从当前循环带入父级循环。 LayoutFocusTraversalPolicy
在大多数外观模型中,使用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键将光标移动在文本区域内,但不会移出文本区域,因为在文本区域内,Tab键不是焦点遍历键。然而,Control-Tab键将焦点从文本区域移出,并移入第一个文本字段。同样,Control-Shift-Tab键将焦点从文本区域移出,并移入前一个组件。按照惯例,Control键用于将焦点从任何将Tab键视为特殊方式的组件移出,例如JTable
。
您刚刚接触了焦点架构的简要介绍。如果您想了解更多细节,请参阅焦点子系统规范。
GUI设计的常见需求是限制用户的输入,例如,只允许数字输入的文本字段(例如货币)或者只允许5位数字的邮政编码。易于使用的格式化文本字段组件允许输入被限制为各种本地化格式。您还可以为文本字段指定一个自定义格式化器,该格式化器可以执行特殊检查,例如确定值不仅格式正确,而且合理。
您可以使用输入验证器作为自定义格式化器的替代方法,或者当您有一个不是文本字段的组件时。输入验证器允许您拒绝特定的值,例如格式正确但无效的邮政编码,或者超出期望范围的值,例如高于110°F的体温。要使用输入验证器,您需要创建
的子类,创建该子类的实例,并将实例设置为一个或多个组件的输入验证器。InputVerifier
当一个组件即将失去焦点时,会请示该组件的输入验证器。如果组件的值不可接受,输入验证器可以采取适当的动作,例如拒绝将焦点移出组件,或者用上一个有效值替换用户的输入,然后允许焦点转移到下一个组件。然而,当焦点转移到另一个顶级组件时,InputVerifier
不会被调用。
以下两个示例展示了房贷计算器。一个使用格式化文本字段,另一个使用标准文本字段进行输入验证。
你可以在InputVerificationDemo.java
中找到输入验证演示的代码。这里是InputVerifier
子类MyVerifier
的代码:
...//类似于checkAmountField......//类似于checkAmountField...
请注意,verify
方法的实现用于检测无效的值,但不执行任何其他操作。verify
方法只用于确定输入是否有效 - 它不应该弹出对话框或引起任何其他副作用。shouldYieldFocus
方法调用verify
,如果值无效,则将其设置为最小值或最大值。shouldYieldFocus
方法允许引起副作用,在本例中,它总是对文本字段进行格式化,并可能更改其值。在我们的示例中,shouldYieldFocus
方法始终返回true,以便实际上从不阻止焦点的转移。这只是一种实现验证的方式。找到名为InputVerificationDialogDemo
的此演示的另一个版本,当用户输入无效时会弹出对话框,并要求用户输入合法值。
可以使用JComponent
类的setInputVerifier
方法来安装输入验证器。例如,InputVerificationDemo
具有以下代码:
private MyVerifier verifier = new MyVerifier(); ... amountField.setInputVerifier(verifier);
要使组件获得焦点,它必须满足三个要求:可见,启用和可获得焦点。还可以提供输入映射。有关输入映射的更多信息,请阅读如何使用键绑定。
TrackFocusDemo示例定义了简单的组件Picture
。其构造函数如下所示:
public Picture(Image image) { this.image = image; setFocusable(true); addMouseListener(this); addFocusListener(this); }
调用setFocusable(true)
方法使组件可获得焦点。如果在WHEN_FOCUSED
输入映射中显式为组件提供键绑定,则不需要调用setFocusable
方法。
为了在焦点发生变化时可视化显示变化(仅在组件具有焦点时绘制红色边框),Picture
具有焦点监听器。
为了在用户点击图片时获得焦点,组件具有鼠标监听器。监听器的mouseClicked
方法请求将焦点转移到图片。以下是代码:
public void mouseClicked(MouseEvent e) { //由于用户点击了我们,让我们获取焦点! requestFocusInWindow(); }
有关TrackFocusDemo示例的更多讨论,请参见跟踪多个组件的焦点更改。
焦点子系统确定了在使用焦点遍历键(例如Tab键)进行导航时应用的默认顺序。Swing应用程序的策略由LayoutFocusTraversalPolicy
决定。您可以通过使用setFocusCycleRoot
方法在任何Container
上设置焦点遍历策略。然而,如果容器不是焦点遍历根,则可能没有明显的效果。或者,您可以将焦点遍历策略提供者传递给FocusTraversalPolicy
方法,而不是焦点遍历根。使用isFocusTraversalPolicyProvider()
方法确定一个Container
是否是焦点遍历策略提供者。使用setFocusTraversalPolicyProvider()
方法设置用于提供焦点遍历策略的容器。
FocusTraversalDemo
示例演示了如何自定义焦点行为。
你可以在FocusTraversalDemo.java
中找到演示代码。
通过这行代码将复选框从焦点循环中移除:
togglePolicy.setFocusable(false);
下面是应用程序的自定义FocusTraversalPolicy
:
... JTextField tf1 = new JTextField("字段1"); JTextField tf2 = new JTextField("更大的字段2"); JTextField tf3 = new JTextField("字段3"); JTextField tf4 = new JTextField("更大的字段4"); JTextField tf5 = new JTextField("字段5"); JTextField tf6 = new JTextField("更大的字段6"); JTable table = new JTable(4,3); ... public FocusTraversalDemo() { super(new BorderLayout()); JTextField tf1 = new JTextField("字段1"); JTextField tf2 = new JTextField("更大的字段2"); JTextField tf3 = new JTextField("字段3"); JTextField tf4 = new JTextField("更大的字段4"); JTextField tf5 = new JTextField("字段5"); JTextField tf6 = new JTextField("更大的字段6"); JTable table = new JTable(4,3); togglePolicy = new JCheckBox("自定义FocusTraversalPolicy"); togglePolicy.setActionCommand("toggle"); togglePolicy.addActionListener(this); togglePolicy.setFocusable(false); //从焦点循环中移除 //注意HTML是允许的,会将这行文本分为两行 label = new JLabel("<html>使用Tab(或Shift-Tab)从组件导航到另一个组件。<p>Control-Tab(或Control-Shift-Tab)允许你跳出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属性表。
以下示例演示了通过在键盘焦点管理器上安装属性更改监听器来跟踪焦点所有者。
Picture
组件显示。具有焦点的Picture
以红色边框标识。窗口底部的标签描述了具有焦点的Picture
。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");
焦点转移是异步的。这个特性可能会导致一些奇怪的定时相关问题和假设,特别是在焦点的自动转移过程中。例如,想象一个包含开始按钮、取消按钮和文本字段的窗口应用程序。这些组件的添加顺序如下:
当应用程序启动时,LayoutFocusTraversalPolicy
确定了焦点遍历策略 — 在这个例子中,它是组件被添加到容器的顺序。在这个例子中,期望的行为是开始按钮具有初始焦点,当点击开始按钮后,它将被禁用,然后取消按钮获得焦点。实现这个行为的正确方法是按照期望的顺序将组件添加到容器中,或者创建一个自定义的焦点遍历策略。如果由于某些原因不可能实现这一点,那么可以使用以下代码片段来实现这个行为:
public void actionPerformed(ActionEvent e) { //这样可以实现。 start.setEnabled(false); cancel.requestFocusInWindow(); }
按照要求,焦点从“开始”按钮转移到“取消”按钮,而不是文本字段。但是,如果以相反的顺序调用相同的方法,则会产生不同的结果,如下所示:
public void actionPerformed(ActionEvent e) { //这样是不行的。 cancel.requestFocusInWindow(); start.setEnabled(false); }
在这种情况下,在离开“开始”按钮之前,请求在“取消”按钮上设置焦点。调用requestFocusInWindow
方法会启动焦点转移,但它不会立即将焦点转移到“取消”按钮。当“开始”按钮被禁用时,焦点会转移到下一个组件(所以始终有一个组件具有焦点),在这种情况下,焦点将移动到文本字段,而不是“取消”按钮。
有几种情况下,您需要在所有可能影响焦点的其他更改之后进行焦点请求:
removeNotify
方法。以下表格列出了与焦点相关的常用构造函数和方法。焦点 API 可分为四个类别:
有关焦点架构的更详细信息,请参阅焦点子系统规范。您还可以查看如何编写焦点侦听器。
Component
isFocusOwner()true
setRequestFocusEnabled(boolean)
JComponent
setRequestFocusEnabled
false
JButton
JPanel
setFocusable
辅助技术setFocusable(boolean)
setFocusable(false)
setRequestFocusEnabled
辅助技术requestFocusInWindow()JComponent
FOCUS_GAINED
requestFocus
setFocusTraversalKeys(int, Set)
java.awt.Container
中KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS
KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS
KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS
UP_CYCLE_TRAVERSAL_KEYS
DOWN_CYCLE_TRAVERSAL_KEYS
setImplicitDownCycleTraversal(false)
javax.swing.SortingFocusTraversalPolicy
中)getFirstComponent(Container)getInitialComponent(Container)getLastComponent(Container)setFocusTraversalPolicy(FocusTraversalPolicy)
java.awt.Container
中null
isFocusCycleRoot()
java.awt.Container
中isFocusTraversalPolicyProvider()
java.awt.Container
中
类或方法 | 目的 |
---|---|
InputVerifier | 抽象类,通过焦点机制允许输入验证。当尝试从包含输入验证器的组件中移出焦点时,焦点将不会释放,直到验证器满足条件。 |
shouldYieldFocus(JComponent) (在 InputVerifier 中) |
当组件有一个输入验证器时,系统将调用此方法来确定焦点是否可以离开该组件。此方法可能会引起副作用,例如弹出对话框。如果此方法返回false ,焦点将保留在传递给该方法的组件上。 |
verify(JComponent) (在 InputVerifier 中) |
您需要重写此方法来检查组件的输入是否有效。如果有效,应返回true ,否则返回false 。此方法不应引起任何副作用,例如弹出对话框。此方法由shouldYieldFocus 调用。 |
setInputVerifier(inputVerifier) getInputVerifier() (在 JComponent 中) |
设置或获取分配给组件的输入验证器。默认情况下,组件没有输入验证器。 |
setVerifyInputWhenFocusTarget(boolean) getVerifyInputWhenFocusTarget() (在 JComponent 中) |
设置或获取当前焦点所有者的输入验证器是否在该组件请求焦点之前调用。默认值为true 。对于需要接收焦点的组件(如取消按钮或滚动条),此方法应设置为false ,即使输入无效。 |
该表定义了KeyboardFocusManager
的绑定属性。可以通过调用addPropertyChangeListener
来为这些属性注册监听器。
属性 | 用途 |
---|---|
focusOwner | 当前接收键盘事件的组件。 |
permanentFocusOwner | 最近接收到永久性FOCUS_GAINED 事件的组件。通常与focusOwner 相同,除非当前生效的是临时性焦点更改。 |
focusedWindow | 拥有焦点所有者的窗口。 |
activeWindow | 该组件必须始终是一个Frame 或Dialog 。活动窗口要么是焦点窗口,要么是焦点窗口的第一个框架或对话框的所有者。 |
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 | 当前的焦点循环根容器。 |
以下表格列出了使用焦点的示例:
示例 | 所述位置 | 备注 |
---|---|---|
FocusConceptsDemo |
本节 | 演示基本的默认焦点行为。 |
FocusTraversalDemo |
本节 | 演示如何覆盖默认的焦点顺序。 |
TrackFocusDemo |
本节 | 演示如何使用 PropertyChangeListener 来跟踪焦点所有者。还实现了一个自定义的可聚焦组件。 |
InputVerificationDemo |
本节 | 演示如何实现一个 InputVerifier 来验证用户输入。 |
InputVerificationDialogDemo |
本节 | 演示如何实现一个 InputVerifier ,当用户输入无效时弹出对话框。 |
FocusEventDemo |
如何编写焦点监听器 | 报告发生在多个组件上的所有焦点事件,以演示焦点事件被触发的情况。 |