Java-中文官方教程-2022-版-三-
Java 中文官方教程 2022 版(三)
解决常见组件问题
原文:
docs.oracle.com/javase/tutorial/uiswing/components/problems.html
本节讨论您在使用组件时可能遇到的问题。如果在本节中找不到您的问题,请参考以下章节:
-
解决使用其他 Swing 功能的常见问题
-
解决常见布局问题
-
解决常见事件处理问题
-
解决常见绘制问题
问题: 我在实现一个模型(或者类似于 Java SE 平台标准版中的某些代码)时遇到了困难。
- 查看 Java SE 源代码。它随 JDK 一起分发,是一个很好的资源,可以找到实现模型、触发事件等代码示例。
问题: 每当文本字段中的文本更新时,文本字段的大小会发生变化。
- 您应该通过指定文本字段应具有的列数来指定文本字段的首选宽度。为此,您可以使用
JTextField构造函数的int参数或setColumns方法。
问题: 当重新绘制时,内容窗格的某些区域看起来很奇怪。
-
如果设置内容窗格,请确保它是不透明的。您可以通过在内容窗格上调用
setOpaque(true)来实现。请注意,尽管在大多数外观和感觉中JPanel是不透明的,但在 GTK+外观和感觉中并非如此。有关详细信息,请参阅将组件添加到内容窗格。 -
如果您的一个或多个组件执行自定义绘制,请确保您正确实现了它。查看解决常见绘制问题以获取帮助。
-
您可能遇到了线程安全问题。请参阅下一条目。
问题: 我的程序表现出一些奇怪的症状,有时似乎与时间有关。
- 确保您的代码是线程安全的。有关详细信息,请参阅 Swing 中的并发性。
问题: 我的模态对话框被其他窗口遮挡。
-
如果对话框没有父组件,请在创建时尝试将其设置为有效的框架或组件。
-
此错误已在 6.0 版本中修复。有关更多信息,请参阅4255200。
问题: 滚动条策略似乎没有按照广告中描述的那样工作。
-
一些 Swing 版本中的实现可能存在
VERTICAL_SCROLLBAR_AS_NEEDED和HORIZONTAL_SCROLLBAR_AS_NEEDED策略的错误。如果您的项目可行,尽量使用最新版本的 Swing。 -
如果滚动窗格的客户端可以动态更改大小,则程序应设置客户端的首选大小,然后在客户端上调用
revalidate。 -
确保您为您打算的方向指定了所需的策略。
问题: 我的滚动窗格没有滚动条。
-
如果要始终显示滚动条,请根据需要指定
VERTICAL_SCROLLBAR_ALWAYS或HORIZONTAL_SCROLLBAR_ALWAYS作为滚动条策略。 -
如果希望根据需要显示滚动条,并且希望在创建滚动窗格时强制需要滚动条,有两种选择:要么设置滚动窗格或其容器的首选大小,要么实现一个具有滚动功能的类,并从
getPreferredScrollableViewportSize方法返回小于组件标准首选大小的值。有关信息,请参阅 调整滚动窗格的大小。
问题: 我的分割窗格中的分隔符不移动!
- 您需要设置分割窗格中至少一个组件的最小大小。有关信息,请参阅 定位分隔符并限制其范围。
问题: JSplitPane 的 setDividerLocation 方法无效。
- 如果分割窗格没有大小(通常在尚未显示在屏幕上时为真),则
setDividerLocation(double)方法无效。您可以使用setDividerLocation(int)或指定分割窗格包含组件的首选大小和分割窗格的调整权重。有关信息,请参阅 定位分隔符并限制其范围。
问题: 嵌套分割窗格上的边框看起来太宽。
- 如果嵌套分割窗格,则边框会累积 内部分割窗格的边框会显示在外部分割窗格的边框旁边,导致边框看起来特别宽。当嵌套许多分割窗格时,问题尤为明显。解决方法是在放置在另一个分割窗格中的任何分割窗格上将边框设置为 null。有关详细信息,请参阅 Java Bug 数据库中的 bug # 4131528。
问题: 我的工具栏中的按钮太大。
-
尝试减少按钮的边距。例如:
button.setMargin(new Insets(0,0,0,0));
问题: 我的分层窗格中的组件层次不正确。实际上,层次似乎是相反的 深度越低,组件越高。
-
如果在分层窗格中添加组件时使用
int而不是Integer,就会出现这种情况。要查看发生了什么,在LayeredPaneDemo类中更改layeredPane.add(label, new Integer(i));为
layeredPane.add(label, **i**);.
问题: 调用方法 *colorChooser*.setPreviewPanel(null) 未按预期删除颜色选择器的预览面板。
- 使用
null参数指定默认预览面板。要删除预览面板,请指定一个没有大小的标准面板,如此:*colorChooser*.setPreviewPanel(new JPanel());
问题和练习:使用 Swing 组件
原文:
docs.oracle.com/javase/tutorial/uiswing/QandE/questions-ch3.html
使用本课程中的信息和组件使用说明部分来帮助你完成这些问题和练习。
问题
1. 找到最适合以下需求的组件。写下组件的通用名称(如“框架”)并在线找到组件的使用说明页面。
a. 一个让用户选择颜色的组件。
b. 一个显示图标但不对用户点击做出反应的组件。
c. 一个看起来像按钮的组件,当按下时,会弹出一个菜单供用户选择。
d. 一个看起来像框架的容器,但实际上(通常与其他类似容器一起)出现在真实框架内部。
e. 一个容器,让用户确定两个组件如何共享有限的空间。
2. 你使用哪种方法将菜单栏添加到顶层容器(如JFrame)?
3. 你使用哪种方法来指定顶层容器(如JFrame或JDialog)的默认按钮?
4. 你使用哪种方法来启用和禁用诸如JButton之类的组件?它是在哪个类中定义的?
5. a. 哪些 Swing 组件使用ListSelectionModel?[提示:每个接口和类规范顶部的“Use”链接会带你到一个页面,展示了该接口或类在 API 中的引用位置。]
b. 这些组件是否使用其他模型来处理组件状态的其他方面?如果是,请列出其他模型的类型。
6. 哪种类型的模型保存文本组件的内容?
练习
1. 实现一个 GUI 程序,外观如下所示。将主方法放在名为MyDemo1的类中。

2. 复制MyDemo1.java并命名为MyDemo2.java。在MyDemo2中添加一个菜单栏。
3. 将MyDemo1.java复制到MyDemo3.java。在MyDemo3.java中添加一个按钮(JButton)。将其设置为默认按钮。
检查你的答案。
课程:Swing 中的并发
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html
示例索引
本课程讨论了并发如何应用于 Swing 编程。它假定您已经熟悉 Essential Java Classes 路径中 Concurrency 课程的内容。
慎重使用并发对于 Swing 程序员尤为重要。一个编写良好的 Swing 程序利用并发创建一个用户界面,永远不会“冻结” —— 无论正在做什么,程序始终对用户交互做出响应。为了创建一个响应灵敏的程序,程序员必须了解 Swing 框架如何使用线程。
Swing 程序员处理以下类型的线程:
-
初始线程,执行初始应用程序代码的线程。
-
事件分发线程,执行所有事件处理代码。大多数与 Swing 框架交互的代码也必须在此线程上执行。
-
工作线程,也称为后台线程,执行耗时的后台任务。
程序员不需要提供明确创建这些线程的代码:它们由运行时或 Swing 框架提供。程序员的工作是利用这些线程创建一个响应灵敏、易于维护的 Swing 程序。
与在 Java 平台上运行的任何其他程序一样,Swing 程序可以使用并发课程中描述的工具创建额外的线程和线程池。但是对于基本的 Swing 程序,这里描述的线程就足够了。
本课程依次讨论了三种线程。工作线程需要更多讨论,因为在其上运行的任务是使用javax.swing.SwingWorker创建的。这个类具有许多有用的功能,包括工作线程任务与其他线程上的任务之间的通信和协调。
初始线程
译文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
每个程序都有一组应用程序逻辑开始的线程。在标准程序中,只有一个这样的线程:调用程序类的main方法的线程。在 applet 中,初始线程是构造 applet 对象并调用其init和start方法的线程;这些操作可能发生在单个线程上,也可能发生在两个或三个不同的线程上,这取决于 Java 平台的实现。在本课程中,我们称这些线程为初始线程。
在 Swing 程序中,初始线程没有太多事情要做。它们最重要的工作是创建一个初始化 GUI 的Runnable对象,并将该对象调度到事件分发线程上执行。一旦 GUI 创建完成,程序主要由 GUI 事件驱动,每个事件都会导致在事件分发线程上执行一个短任务。应用程序代码可以在事件分发线程(如果它们快速完成,以免干扰事件处理)或工作线程(用于长时间运行的任务)上调度额外的任务。
初始线程通过调用javax.swing.SwingUtilities.invokeLater或javax.swing.SwingUtilities.invokeAndWait来调度 GUI 创建任务。这两种方法都接受一个参数:定义新任务的Runnable。它们的唯一区别由它们的名称表示:invokeLater仅调度任务并返回;invokeAndWait在任务完成之前等待返回。
您可以在 Swing 教程中看到这种情况的示例:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
在 applet 中,GUI 创建任务必须从init方法中使用invokeAndWait启动;否则,init可能在 GUI 创建之前返回,这可能会导致 Web 浏览器启动 applet 时出现问题。在任何其他类型的程序中,调度 GUI 创建任务通常是初始线程做的最后一件事情,因此无论是使用invokeLater还是invokeAndWait都无所谓。
为什么初始线程不直接创建 GUI 呢?因为几乎所有创建或与 Swing 组件交互的代码都必须在事件分发线程上运行。这个限制在下一节中进一步讨论。
事件分发线程
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html
Swing 事件处理代码在一个称为事件分发线程的特殊线程上运行。大多数调用 Swing 方法的代码也在此线程上运行。这是必要的,因为大多数 Swing 对象方法都不是“线程安全”的:从多个线程调用它们会导致线程干扰或内存一致性错误。API 规范中标记为“线程安全”的一些 Swing 组件方法可以从任何线程安全地调用。所有其他 Swing 组件方法必须从事件分发线程中调用。忽略此规则的程序可能大部分时间都能正常运行,但会出现难以复现的不可预测错误。
有用的是将在事件分发线程上运行的代码视为一系列短任务。大多数任务是调用事件处理方法,例如ActionListener.actionPerformed。其他任务可以由应用程序代码使用invokeLater或invokeAndWait调度。事件分发线程上的任务必须快速完成;如果不这样做,未处理的事件会积压,用户界面会变得无响应。
如果您需要确定您的代码是否在事件分发线程上运行,请调用javax.swing.SwingUtilities.isEventDispatchThread。
Worker Threads 和 SwingWorker
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
当 Swing 程序需要执行长时间运行的任务时,通常会使用其中一个 worker 线程,也称为 后台线程。在 worker 线程上运行的每个任务都由一个 javax.swing.SwingWorker 实例表示。SwingWorker 本身是一个抽象类;您必须定义一个子类才能创建一个 SwingWorker 对象;匿名内部类通常用于创建非常简单的 SwingWorker 对象。
SwingWorker 提供了许多通信和控制功能:
-
SwingWorker子类可以定义一个方法done,当后台任务完成时,该方法会自动在事件分发线程上调用。 -
SwingWorker实现了java.util.concurrent.Future。这个接口允许后台任务向其他线程提供返回值。此接口中的其他方法允许取消后台任务,并发现后台任务是否已完成或已取消。 -
后台任务可以通过调用
SwingWorker.publish提供中间结果,导致从事件分发线程调用SwingWorker.process。 -
后台任务可以定义绑定属性。对这些属性的更改会触发事件,导致事件处理方法在事件分发线程上被调用。
这些功能将在以下小节中讨论。
注意:
javax.swing.SwingWorker 类是在 Java SE 6 中添加到 Java 平台的。在此之前,另一个类,也称为 SwingWorker,被广泛用于一些相同的目的。旧的 SwingWorker 不是 Java 平台规范的一部分,也不作为 JDK 的一部分提供。
新的 javax.swing.SwingWorker 是一个全新的类。其功能不是旧的 SwingWorker 的严格超集。这两个类中具有相同功能的方法没有相同的名称。此外,旧的 SwingWorker 类的实例是可重用的,而每个新的后台任务都需要一个新的 javax.swing.SwingWorker 实例。
在 Java 教程中,任何提到 SwingWorker 现在都指的是 javax.swing.SwingWorker。
简单的后台任务
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
让我们从一个非常简单但潜在耗时的任务开始。TumbleItem applet 加载一组用于动画的图形文件。如果图形文件是从初始线程加载的,GUI 出现之前可能会有延迟。如果图形文件是从事件分派线程加载的,GUI 可能会暂时无响应。
为了避免这些问题,TumbleItem从其初始线程创建并执行SwingWorker的一个实例。对象的doInBackground方法在工作线程中执行,将图像加载到ImageIcon数组中,并返回对其的引用。然后,在事件分派线程中执行的done方法调用get来检索此引用,并将其分配给名为imgs的 applet 类字段。这允许TumbleItem立即构造 GUI,而不必等待图像加载完成。
这里是定义和执行SwingWorker对象的代码。
SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {
@Override
public ImageIcon[] doInBackground() {
final ImageIcon[] innerImgs = new ImageIcon[nimgs];
for (int i = 0; i < nimgs; i++) {
innerImgs[i] = loadImage(i+1);
}
return innerImgs;
}
@Override
public void done() {
//Remove the "Loading images" label.
animator.removeAll();
loopslot = -1;
try {
imgs = get();
} catch (InterruptedException ignore) {}
catch (java.util.concurrent.ExecutionException e) {
String why = null;
Throwable cause = e.getCause();
if (cause != null) {
why = cause.getMessage();
} else {
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
};
所有SwingWorker的具体子类都实现doInBackground;done的实现是可选的。
请注意,SwingWorker是一个泛型类,有两个类型参数。第一个类型参数指定doInBackground的返回类型,也指定由其他线程调用以检索doInBackground返回的对象的get方法的返回类型。SwingWorker的第二个类型参数指定在后台任务仍处于活动状态时返回的中间结果的类型。由于此示例不返回中间结果,使用Void作为占位符。
您可能会想知道设置imgs的代码是否过于复杂。为什么让doInBackground返回一个对象并使用done来检索它?为什么不直接让doInBackground直接设置imgs?问题在于imgs引用的对象是在工作线程中创建并在事件分派线程中使用的。当对象在这种方式在线程之间共享时,您必须确保在一个线程中进行的更改对另一个线程可见。使用get可以保证这一点,因为使用get会在创建imgs的代码和使用它的代码之间创建一个happens before关系。有关happens before关系的更多信息,请参考内存一致性错误中的并发性课程。
实际上有两种方法可以检索doInBackground返回的对象。
-
使用没有参数的
SwingWorker.get。如果后台任务尚未完成,get会阻塞直到完成。 -
调用带有指示超时的参数的
SwingWorker.get。如果后台任务尚未完成,get会阻塞直到完成 — 除非超时先到,此时get会抛出java.util.concurrent.TimeoutException。
在事件分发线程中调用get的任一重载时要小心;在get返回之前,不会处理任何 GUI 事件,GUI 会"冻结"。除非你确信后台任务已经完成或接近完成,否则不要调用不带参数的get。
要了解更多关于TumbleItem示例的内容,请参考如何使用 Swing 定时器中的课程使用其他 Swing 功能。
具有中间结果的任务
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/interim.html
在后台任务仍在运行时提供中间结果通常是有用的。任务可以通过调用SwingWorker.publish来实现这一点。该方法接受可变数量的参数。每个参数必须是由SwingWorker的第二个类型参数指定的类型。
要收集publish提供的结果,重写SwingWorker.process。此方法将从事件分发线程中调用。通常会将多次调用publish的结果累积到单次调用process中。
让我们看看Flipper.java示例如何使用publish提供中间结果。单击启动按钮以使用Java™ Web Start运行Flipper(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
该程序通过在后台任务中生成一系列随机的boolean值来测试java.util.Random的公平性。这相当于抛硬币;因此命名为Flipper。为了报告其结果,后台任务使用了类型为FlipPair的对象。
private static class FlipPair {
private final long heads, total;
FlipPair(long heads, long total) {
this.heads = heads;
this.total = total;
}
}
heads字段是随机值为true的次数;total字段是随机值的总数。
后台任务由FlipTask的一个实例表示:
private class FlipTask extends SwingWorker<Void, FlipPair> {
由于任务不返回最终结果,第一个类型参数是什么并不重要;Void被用作占位符。任务在每次“抛硬币”后调用publish:
@Override
protected Void doInBackground() {
long heads = 0;
long total = 0;
Random random = new Random();
while (!isCancelled()) {
total++;
if (random.nextBoolean()) {
heads++;
}
publish(new FlipPair(heads, total));
}
return null;
}
(isCancelled方法将在下一节中讨论。)由于publish被非常频繁地调用,很可能在事件分发线程中调用process之前会积累很多FlipPair值;process只对每次报告的最后一个值感兴趣,用它来更新 GUI:
protected void process(List<FlipPair> pairs) {
FlipPair pair = pairs.get(pairs.size() - 1);
headsText.setText(String.format("%d", pair.heads));
totalText.setText(String.format("%d", pair.total));
devText.setText(String.format("%.10g",
((double) pair.heads)/((double) pair.total) - 0.5));
}
如果Random是公平的,那么devText中显示的值应该在Flipper运行时越来越接近 0。
注意: 在Flipper中使用的setText方法实际上符合其规范中定义的“线程安全”。这意味着我们可以在工作线程中直接设置文本字段,而不需要使用publish和process。为了提供SwingWorker中间结果的简单演示,我们选择忽略这一事实。
取消后台任务
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/cancel.html
要取消正在运行的后台任务,请调用SwingWorker.cancel。任务必须配合自身的取消。有两种方式可以做到这一点:
-
通过在接收到中断时终止。这个过程在中断中有描述,在并发中有介绍。
-
通过在短时间间隔内调用
SwingWorker.isCancelled。如果为这个SwingWorker调用了cancel,此方法将返回true。
cancel方法接受一个boolean参数。如果参数为true,cancel会向后台任务发送中断信号。无论参数是true还是false,调用cancel都会将对象的取消状态更改为true。这是isCancelled返回的值。一旦更改,取消状态就无法再更改。
前一节中的Flipper示例使用了仅检查状态的习惯用法。在doInBackground中的主循环在isCancelled返回true时退出。当用户点击“取消”按钮时,将触发调用带有false参数的cancel的代码。
仅检查状态的方法对于Flipper是有意义的,因为其SwingWorker.doInBackground的实现不包含可能引发InterruptedException的代码。要响应中断,后台任务必须在短时间间隔内调用Thread.isInterrupted。使用SwingWorker.isCancelled来实现相同的目的同样简单。
注意: 如果在取消后台任务后对SwingWorker对象调用get,将抛出java.util.concurrent.CancellationException。
绑定属性和状态方法
原文:
docs.oracle.com/javase/tutorial/uiswing/concurrency/bound.html
SwingWorker支持绑定属性,这对与其他线程通信非常有用。两个绑定属性已预定义:progress和state。与所有绑定属性一样,progress和state可用于在事件分发线程上触发事件处理任务。
通过实现属性更改监听器,程序可以跟踪progress、state和其他绑定属性的更改。有关更多信息,请参阅如何编写属性更改监听器中的编写事件监听器。
progress绑定变量
progress绑定变量是一个int值,范围从 0 到 100。它有一个预定义的设置方法(受保护的SwingWorker.setProgress)和一个预定义的获取方法(公共SwingWorker.getProgress)。
ProgressBarDemo示例使用progress从后台任务更新ProgressBar控件。有关此示例的详细讨论,请参阅如何使用进度条中的使用 Swing 组件。
state绑定变量
state绑定变量指示SwingWorker对象在其生命周期中的位置。绑定变量包含SwingWorker.StateValue类型的枚举值。可能的值包括:
PENDING
从对象构造到doInBackground调用之前的状态。
STARTED
从doInBackground调用之前的短暂时期到done调用之前的状态。
DONE
对象存在期间的状态。
SwingWorker.getState返回state绑定变量的当前值。
状态方法
作为Future接口的一部分,还有两个方法报告后台任务的状态。正如我们在取消后台任务中看到的,如果任务已取消,isCancelled返回true。此外,如果任务已完成(无论是正常完成还是被取消),isDone返回true。
问题和练习:Swing 中的并发
原文:
docs.oracle.com/javase/tutorial/uiswing/QandE/questions-concurrency.html
问题
-
对于以下每个任务,请指定应在哪个线程中执行以及原因。
-
初始化 GUI。
-
加载一个大文件。
-
调用
javax.swing.JComponent.setFont来更改组件的字体。 -
调用
javax.swing.text.JTextComponent.setText来更改组件的文本。
-
-
一组线程不用于前面问题中提到的任何任务。命名此线程并解释为什么其应用如此有限。
-
SwingWorker有两个类型参数。解释这些类型参数如何使用,以及为什么通常不重要。
练习
- 修改
Flipper示例,使其在“抛硬币”之间暂停 5 秒。如果用户点击“取消”,则立即终止抛硬币循环。
检查你的答案。
课程:使用其他 Swing 功能
示例索引
本课程包含一系列如何页面,帮助您使用各种 Swing 功能。
如何与桌面类集成
使用Desktop类,您可以使您的 Java 应用程序与主机平台上与特定文件类型关联的默认应用程序进行交互。
如何创建半透明和形状窗口
从 Java 平台标准版 6 更新 10 版本开始,您可以向 Swing 应用程序添加半透明和形状窗口。本课程向您展示如何操作。
如何使用 JLayer 装饰组件
JLayer是 Swing 组件的灵活而强大的装饰器。它使您能够在组件上绘制并响应组件事件,而无需直接修改底层组件。
如何使用操作
使用Action对象,您可以协调生成动作事件的两个或多个组件的状态和事件处理。例如,您可以使用单个Action来创建和协调执行相同功能的工具栏按钮和菜单项。
如何使用 Swing 计时器
使用 Swing Timer类,您可以实现在延迟后执行操作的线程,并可选择继续重复执行操作。操作在事件分发线程中执行。
如何支持辅助技术
Swing 组件内置支持辅助技术。通过遵循一些规则,您的程序可以提供更好的支持。
如何使用焦点子系统
一些程序需要操作焦点 — 例如,验证输入,或更改组件的选项卡顺序。本节描述了您可以使用的一些技术来定制程序中的焦点。
如何使用键绑定
使用键绑定,您可以指定组件如何响应用户输入。
如何在对话框中使用模态
本节描述了在 Java™ SE 版本 6 中出现的新模态模型,并使您能够将不同的模态类型应用于对话框。
如何打印表格
本节描述了表格的打印功能,并解释了如何将打印支持添加到您的程序中。
如何打印文本
本节描述了文本组件的打印功能,并解释了如何将打印支持添加到您的程序中。
如何创建启动画面
使用SplashScreen类,您可以关闭启动画面,更改启动画面图像,获取图像位置或大小,并在启动画面上绘制。
如何使用系统托盘
本节描述如何向系统托盘添加一个托盘图标,并应用文本工具提示、弹出菜单、气球消息以及与之关联的一组监听器。
使用其他 Swing 功能解决常见问题
本节告诉您在尝试使用本课程中的信息时可能遇到的问题如何解决。
如果您有兴趣使用 JavaFX 创建您的 GUI,请参阅使用 JavaFX 属性和绑定、在 JavaFX 中创建视觉效果、在 JavaFX 中应用变换以及在 JavaFX 中创建过渡和时间轴动画。
如何与 Desktop 类集成
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/desktop.html
Java™ 标准版 6 缩小了本机应用程序和 Java 应用程序之间性能和集成之间的差距。除了新的 系统托盘 功能、启动画面 支持和增强的 JTables 打印 功能外,Java SE 版本 6 还提供了 Desktop API (java.awt.Desktop) API,允许 Java 应用程序与主机平台上与特定文件类型关联的默认应用程序进行交互。
Desktop 类提供了新的功能。该 API 源自 JDesktop Integration Components (JDIC) 项目。JDIC 项目的目标是使基于 Java 技术的应用程序成为桌面的“一等公民”,实现无缝集成。JDIC 为 Java 应用程序提供了访问本机桌面提供的功能和设施。关于新的 Desktop API,这意味着 Java 应用程序可以执行以下操作:
-
启动主机系统的默认浏览器并打开特定的统一资源标识符(URI)
-
启动主机系统的默认电子邮件客户端
-
启动应用程序以打开、编辑或打印与这些应用程序关联的文件
注意:
Desktop API 使用主机操作系统的文件关联来启动与特定文件类型关联的应用程序。例如,如果 OpenDocument 文本(.odt)文件扩展名与 OpenOffice Writer 应用程序关联,Java 应用程序可以启动 OpenOffice Writer 来打开、编辑或甚至打印具有该关联的文件。根据主机系统,不同的应用程序可能与不同的操作关联。例如,如果特定文件无法打印,请首先检查其扩展名在给定操作系统上是否具有打印关联。
使用 isDesktopSupported() 方法来确定 Desktop API 是否可用。在 Solaris 操作系统和 Linux 平台上,此 API 依赖于 Gnome 库。如果这些库不可用,该方法将返回 false。在确定 Desktop API 受支持后,即 isDesktopSupported() 返回 true 后,应用程序可以使用静态方法 getDesktop() 来检索一个 Desktop 实例。
如果一个应用在没有键盘、鼠标或显示器的环境中运行(即“无头”环境),getDesktop() 方法会抛出一个 java.awt.HeadlessException 异常。
一旦检索到,Desktop 实例允许应用程序浏览、发送邮件、打开、编辑,甚至打印文件或 URI,但前提是检索到的 Desktop 实例支持这些活动。每个活动称为一个操作,每个操作表示为 Desktop.Action 枚举实例:
-
BROWSE— 代表主机默认浏览器执行的浏览操作。 -
MAIL— 代表主机默认电子邮件客户端执行的邮件操作。 -
OPEN— 代表应用程序执行的打开操作,与打开特定文件类型相关联。 -
EDIT— 代表应用程序执行的编辑操作,与编辑特定文件类型相关联。 -
PRINT— 代表应用程序执行的打印操作,与打印特定文件类型相关联。
即使在相同的文件类型上,不同的应用程序也可以注册这些不同的操作。例如,Firefox 浏览器可能会在 OPEN 操作中启动,Emacs 在 EDIT 操作中启动,而打印操作可能使用不同的应用程序。您主机桌面的关联用于确定应该调用哪个应用程序。在 JDK 6 中,使用当前版本的 Desktop API 无法操作桌面文件关联,这些关联只能通过特定于平台的工具在此时创建或更改。
下面的示例展示了上述提到的功能。
试一试:
-
编译并运行示例,参考 示例索引。
-
DesktopDemo 对话框将出现。
![DesktopDemo 应用程序。]()
-
在 URI 文本字段中输入一个 URI 值,例如 –
https://docs.oracle.com/javase/tutorial。 -
按下启动浏览器按钮。
-
确保默认浏览器窗口打开并加载教程主页。
-
将 URI 更改为任意值,按下启动浏览器按钮,并确保所请求的网页成功加载。
-
切换回 DesktopDemo 对话框,在电子邮件文本字段中输入邮件接收者名称。你也可以使用支持 CC、BCC、SUBJECT 和 BODY 字段的
mailto方案,例如 –duke@example.com?SUBJECT=Hello Duke!。 -
按下启动邮件按钮。
-
默认电子邮件客户端的合成对话框将出现。确保收件人和主题字段如下。
![DesktopDemo 应用程序。]()
-
你可以继续撰写消息或尝试在电子邮件字段中输入不同的邮件方案组合。
-
切换回 DesktopDemo 对话框,按下省略号按钮选择任何文本文件。
-
选择打开、编辑或打印以设置操作类型,然后按下启动应用程序按钮。
-
确保操作完成正确。尝试其他文件格式,例如
.odt、.html、.pdf。注意:如果尝试编辑.pdf文件,则 DesktopDemo 返回以下消息:无法对<文件名>文件执行给定操作
以下代码片段提供了有关 DeskDemo 应用程序实现的更多细节。DesktopDemo 构造函数在实例化 UI 后立即禁用少数组件,并检查 Desktop API 是否可用。
public DesktopDemo() {
// init all GUI components
initComponents();
// disable buttons that launch browser, email client,
// disable buttons that open, edit, print files
disableActions();
// before any Desktop APIs are used, first check whether the API is
// supported by this particular VM on this particular host
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
// now enable buttons for actions that are supported.
enableSupportedActions();
}
...
/**
* Disable all graphical components until we know
* whether their functionality is supported.
*/
private void disableActions() {
txtBrowserURI.setEnabled(false);
btnLaunchBrowser.setEnabled(false);
txtMailTo.setEnabled(false);
btnLaunchEmail.setEnabled(false);
rbEdit.setEnabled(false);
rbOpen.setEnabled(false);
rbPrint.setEnabled(false);
txtFile.setEnabled(false);
btnLaunchApplication.setEnabled(false);
}
...
一旦获得 Desktop 对象,您可以查询该对象以找出支持的具体操作。如果 Desktop 对象不支持特定操作,或者 Desktop API 本身不受支持,DesktopDemo 将简单地保持受影响的图形组件禁用。
/**
* Enable actions that are supported on this host.
* The actions are the following: open browser,
* open email client, and open, edit, and print
* files using their associated application.
*/
private void enableSupportedActions() {
if (desktop.isSupported(Desktop.Action.BROWSE)) {
txtBrowserURI.setEnabled(true);
btnLaunchBrowser.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.MAIL)) {
txtMailTo.setEnabled(true);
btnLaunchEmail.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.OPEN)) {
rbOpen.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.EDIT)) {
rbEdit.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.PRINT)) {
rbPrint.setEnabled(true);
}
if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {
txtFile.setEnabled(true);
btnLaunchApplication.setEnabled(true);
}
}
browse(uri)方法可能会抛出各种异常,包括如果 URI 为 null 则抛出 NullPointerException,如果不支持 BROWSE 操作则抛出 UnsupportedOperationException。如果默认浏览器或应用程序无法找到或启动,则此方法可能会抛出 IOException,如果安全管理器拒绝调用,则可能会抛出 SecurityException。
private void onLaunchBrowser(ActionEvent evt) {
URI uri = null;
try {
uri = new URI(txtBrowserURI.getText());
desktop.browse(uri);
} catch(IOException ioe) {
System.out.println("The system cannot find the " + uri +
" file specified");
//ioe.printStackTrace();
} catch(URISyntaxException use) {
System.out.println("Illegal character in path");
//use.printStackTrace();
}
}
应用程序可以通过调用此 Desktop 实例的mail(uriMailTo)方法启动主机的默认电子邮件客户端(如果支持该操作)。
private void onLaunchMail(ActionEvent evt) {
String mailTo = txtMailTo.getText();
URI uriMailTo = null;
try {
if (mailTo.length() > 0) {
uriMailTo = new URI("mailto", mailTo, null);
desktop.mail(uriMailTo);
} else {
desktop.mail();
}
} catch(IOException ioe) {
ioe.printStackTrace();
} catch(URISyntaxException use) {
use.printStackTrace();
}
}
Java 应用程序可以使用Desktop类的open()、edit()和print()方法从其关联的应用程序打开、编辑和打印文件。
private void onLaunchDefaultApplication(ActionEvent evt) {
String fileName = txtFile.getText();
File file = new File(fileName);
try {
switch(action) {
case OPEN:
desktop.open(file);
break;
case EDIT:
desktop.edit(file);
break;
case PRINT:
desktop.print(file);
break;
}
} catch (IOException ioe) {
//ioe.printStackTrace();
System.out.println("Cannot perform the given operation
to the " + file + " file");
}
}
此演示的完整代码可在DesktopDemo.java文件中找到。
Desktop API
Desktop类允许 Java 应用程序启动处理 URI 或文件的本机桌面应用程序。
| 方法 | 目的 |
|---|---|
| isDesktopSupported() | 测试当前平台是否支持此类。如果支持,使用getDesktop()来检索一个实例。 |
| getDesktop() | 返回当前浏览器上下文的Desktop实例。在某些平台上,可能不支持 Desktop API。使用isDesktopSupported()方法来确定当前桌面是否受支持。 |
| isSupported(Desktop.Action) | 测试当前平台是否支持某个操作。使用Desktop.Action枚举的以下常量:BROWSE、EDIT、MAIL、OPEN、PRINT。 |
| browse(URI) | 启动默认浏览器以显示 URI。如果默认浏览器无法处理指定的 URI,则调用注册用于处理指定类型 URI 的应用程序。应用程序是根据 URI 类定义的协议和路径确定的。 |
| 邮件(URI) | 启动用户默认邮件客户端的邮件撰写窗口,填写mailto: URI指定的消息字段。 |
| 打开(File) | 启动关联的应用程序以打开文件。 |
| 编辑(File) | 启动关联的编辑器应用程序并打开文件进行编辑。 |
| 打印(File) | 使用本机桌面打印设施打印文件,使用关联应用程序的打印命令。 |
使用 Desktop API 的示例
下表列出了使用 Desktop 类集成的示例。
| 示例 | 描述位置 | 备注 |
|---|---|---|
DesktopDemo |
本节 | 使用指定的 URI 和默认电子邮件客户端启动主机系统的默认浏览器;启动应用程序以打开、编辑或打印文件。 |
如何创建半透明和形状窗口
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html
从 Java 平台标准版 6(Java SE 6)更新 10 版本开始,您可以向您的 Swing 应用程序添加半透明和形状窗口。本页面涵盖以下主题:
-
支持的功能
-
确定平台的功能
-
如何实现统一半透明度
-
如何实现每像素半透明度
-
如何实现形状窗口
-
Java SE Release 6 Update 10 API
支持的功能
此功能作为 JDK 7 版本中的公共 AWT 包的一部分,采用三种形式,如下:
-
您可以创建一个具有统一半透明度的窗口,其中每个像素具有相同的半透明度(或 alpha 值)。以下屏幕截图显示了一个具有 45%半透明度的窗口。
![一个半透明窗口]()
试试这个:
单击“启动”按钮以使用Java™ Web Start运行 TranslucentWindowDemo 示例。此示例需要JDK 7或更高版本。或者,要执行编译和运行示例,请参考示例索引。
-
您可以创建一个具有每像素半透明度的窗口,其中每个像素都有自己的 alpha 值。使用此功能,您可以例如创建一个通过定义 alpha 值梯度而逐渐消失的窗口。以下屏幕截图显示了一个从顶部(完全半透明)到底部(完全不透明)具有渐变半透明度的窗口。
![一个具有每像素半透明度的窗口。]()
试试这个:
单击“启动”按钮以使用Java™ Web Start运行 GradientTranslucentWindowDemo 示例。此示例需要JDK 7或更高版本。或者,要自行编译和运行示例,请参考示例索引。
-
您可以创建具有任何
Shape对象的窗口,可以定义。形状窗口可以是不透明的,也可以使用统一的或逐像素的半透明度。以下屏幕截图显示了一个椭圆形状的窗口,透明度为 30%。![一个椭圆形状的窗口。]()
试试这个:
点击“启动”按钮以使用Java™ Web Start运行 ShapedWindowDemo 示例。此示例需要JDK 7或更高版本。或者,要自行编译和运行示例,请参考示例索引。
确定平台的功能
并非所有平台都支持所有这些功能。当代码尝试在不支持这些功能的平台上调用 setShape 或 setOpacity 方法时,会抛出 UnsupportedOperationException 异常。因此,最佳实践是首先检查平台是否支持您要实现的功能。GraphicsDevice 类提供了 isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency) 方法,您可以用于此目的。您将一个在 GraphicsDevice.WindowTranslucency 中定义的三个枚举值之一传递给此方法:
-
TRANSLUCENT– 底层平台支持具有统一半透明度的窗口,其中每个像素具有相同的 alpha 值。 -
PERPIXEL_TRANSLUCENT– 底层平台支持具有逐像素半透明度的窗口。此功能用于实现渐隐窗口。 -
PERPIXEL_TRANSPARENT– 底层平台支持自定义形状的窗口。
GraphicsConfiguration 类还提供了 isTranslucencyCapable 方法,用于确定给定 GraphicsConfiguration 对象是否支持 PERPIXEL_TRANSLUCENT 半透明度。
版本说明: 半透明和自定义形状窗口 API 首次添加到 Java SE 6 Update 10 版本作为私有 API。此功能在 JDK 7 版本中移至公共 AWT 包。本教程描述了 JDK 7 版本中可用的 API。请参阅 Java SE 6 Update 10 API,了解 Java SE 6 Update 10 版本中私有 API 与 JDK 7 版本中公共 API 的映射。
以下代码显示如何检查所有三种功能:
import static java.awt.GraphicsDevice.WindowTranslucency.*;
// Determine what the default GraphicsDevice can support.
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
boolean isUniformTranslucencySupported =
gd.isWindowTranslucencySupported(TRANSLUCENT);
boolean isPerPixelTranslucencySupported =
gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);
boolean isShapedWindowSupported =
gd.isWindowTranslucencySupported(PERPIXEL_TRANSPARENT);
注意: 这些功能都不适用于全屏模式下的窗口。在全屏模式下调用任何相关方法会导致抛出IllegalComponentStateException异常。
如何实现统一的透明度
你可以通过在Window类中调用setOpacity(float)方法来创建每个像素具有相同透明度的窗口。传递给该方法的float参数表示窗口的透明度,应该是介于 0 和 1 之间的值。数字越小,窗口越透明。还有一个对应的getOpacity方法。
TranslucentWindowDemo.java示例创建一个 55% 不透明(45% 半透明)的窗口。如果底层平台不支持半透明窗口,示例将退出。与不透明度相关的代码显示为粗体。
import java.awt.*;
import javax.swing.*;
import static java.awt.GraphicsDevice.WindowTranslucency.*;
public class TranslucentWindowDemo extends JFrame {
public TranslucentWindowDemo() {
super("TranslucentWindow");
setLayout(new GridBagLayout());
setSize(300,200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add a sample button.
add(new JButton("I am a Button"));
}
public static void main(String[] args) {
// Determine if the GraphicsDevice supports translucency.
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
//If translucent windows aren't supported, exit.
if (!gd.isWindowTranslucencySupported(TRANSLUCENT)) {
System.err.println(
"Translucency is not supported");
System.exit(0);
}
JFrame.setDefaultLookAndFeelDecorated(true);
// Create the GUI on the event-dispatching thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TranslucentWindowDemo tw = new TranslucentWindowDemo();
// Set the window to 55% opaque (45% translucent).
tw.setOpacity(0.55f);
// Display the window.
tw.setVisible(true);
}
});
}
}
注意按钮也受到统一的透明度影响。设置不透明度会影响整个窗口,包括窗口包含的任何组件。
如何实现每个像素的半透明效果
创建一个使用每个像素半透明的窗口涉及定义窗口占用的矩形区域上的 alpha 值。当像素的 alpha 值为零时,该像素完全透明。当像素的 alpha 值为 255 时,该像素完全不透明。当像素的 alpha 值为 128 时,该像素为 50% 半透明,依此类推。创建 alpha 值之间的平滑插值的简单方法是使用GradientPaint类。包含的示例使用了这种方法。
调用setBackground(new Color(0,0,0,0))在窗口上会导致软件使用 alpha 值来渲染每个像素的半透明效果。实际上,调用setBackground(new Color(0,0,0,alpha),其中alpha小于 255,会安装每个像素的透明度。因此,如果你调用setBackground(new Color(0,0,0,128))并且不做其他操作,窗口将以每个背景像素 50% 的半透明度渲染。然而,如果你正在创建自己的 alpha 值范围,你很可能会想要一个 alpha 值为 0。
虽然公共 API 没有禁止,但通常你会想要在无装饰窗口上启用每个像素的半透明效果。在大多数情况下,在带装饰的窗口上使用每个像素的半透明效果是没有意义的。这样做可能会禁用装饰,或导致其他依赖平台的副作用。
要确定窗口是否使用每个像素的半透明效果,可以使用isOpaque方法。
以下是实现示例所需的步骤。
-
在窗口上调用
setBackground(new Color(0,0,0,0))。 -
创建一个覆盖
paintComponent方法的JPanel实例。 -
在
paintComponent方法中,创建一个GradientPaint实例。 -
在示例中,矩形的顶部具有 alpha 值为 0(最透明),底部具有 alpha 值为 255(最不透明)。
GradientPaint类会平滑地插值矩形从顶部到底部的 alpha 值。 -
将
GradientPaint实例设置为面板的绘制方法。
这是 GradientTranslucentWindowDemo.java 示例的代码。如果底层平台不支持像素级半透明,此示例将退出。与创建渐变窗口相关的代码以粗体显示。
import java.awt.*;
import javax.swing.*;
import static java.awt.GraphicsDevice.WindowTranslucency.*;
public class GradientTranslucentWindowDemo extends JFrame {
public GradientTranslucentWindowDemo() {
super("GradientTranslucentWindow");
setBackground(new Color(0,0,0,0));
setSize(new Dimension(300,200));
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
if (g instanceof Graphics2D) {
final int R = 240;
final int G = 240;
final int B = 240;
Paint p =
new GradientPaint(0.0f, 0.0f, new Color(R, G, B, 0),
0.0f, getHeight(), new Color(R, G, B, 255), true);
Graphics2D g2d = (Graphics2D)g;
g2d.setPaint(p);
g2d.fillRect(0, 0, getWidth(), getHeight());
}
}
};
setContentPane(panel);
setLayout(new GridBagLayout());
add(new JButton("I am a Button"));
}
public static void main(String[] args) {
// Determine what the GraphicsDevice can support.
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
boolean isPerPixelTranslucencySupported =
gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);
//If translucent windows aren't supported, exit.
if (!isPerPixelTranslucencySupported) {
System.out.println(
"Per-pixel translucency is not supported");
System.exit(0);
}
JFrame.setDefaultLookAndFeelDecorated(true);
// Create the GUI on the event-dispatching thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
GradientTranslucentWindowDemo gtw = new
GradientTranslucentWindowDemo();
// Display the window.
gtw.setVisible(true);
}
});
}
}
请注意,按钮不受像素级半透明的影响。设置像素级半透明会影响背景像素。如果您想要一个窗口只对背景像素产生统一的半透明效果,可以调用 setBackground(new Color(0,0,0,alpha)),其中 alpha 指定您期望的半透明度。
如何实现形状窗口
你可以通过在 Window 类中调用 setShape(Shape) 方法来创建一个具有形状的窗口。传递给该方法的 Shape 参数决定了窗口的裁剪方式。当在窗口上设置形状时,窗口装饰不会重新形成新的形状,因此在无装饰窗口上设置形状效果最佳。
设置窗口形状的最佳实践是在组件事件监听器的 componentResized 方法中调用 setShape。这种做法将确保为窗口的实际大小正确计算形状。以下示例使用了这种方法。
ShapedWindowDemo.java 示例创建了一个椭圆形状的窗口,透明度为 70%。如果底层平台不支持形状窗口,示例将退出。如果底层平台不支持半透明性,则示例将使用标准不透明窗口。您可以修改此示例以创建一个同时使用像素级半透明的形状窗口。
与窗口形状相关的代码以粗体显示。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.geom.Ellipse2D;
import static java.awt.GraphicsDevice.WindowTranslucency.*;
public class ShapedWindowDemo extends JFrame {
public ShapedWindowDemo() {
super("ShapedWindow");
setLayout(new GridBagLayout());
// It is best practice to set the window's shape in
// the componentResized method. Then, if the window
// changes size, the shape will be correctly recalculated.
addComponentListener(new ComponentAdapter() {
// Give the window an elliptical shape.
// If the window is resized, the shape is recalculated here.
@Override
public void componentResized(ComponentEvent e) {
setShape(new Ellipse2D.Double(0,0,getWidth(),getHeight()));
}
});
setUndecorated(true);
setSize(300,200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new JButton("I am a Button"));
}
public static void main(String[] args) {
// Determine what the GraphicsDevice can support.
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
final boolean isTranslucencySupported =
gd.isWindowTranslucencySupported(TRANSLUCENT);
//If shaped windows aren't supported, exit.
if (!gd.isWindowTranslucencySupported(PERPIXEL_TRANSPARENT)) {
System.err.println("Shaped windows are not supported");
System.exit(0);
}
//If translucent windows aren't supported,
//create an opaque window.
if (!isTranslucencySupported) {
System.out.println(
"Translucency is not supported, creating an opaque window");
}
// Create the GUI on the event-dispatching thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ShapedWindowDemo sw = new ShapedWindowDemo();
// Set the window to 70% translucency, if supported.
if (isTranslucencySupported) {
sw.setOpacity(0.7f);
}
// Display the window.
sw.setVisible(true);
}
});
}
}
Java SE 6 Update 10 API
在更新版本中更改公共 API 是不允许的,因此当在 Java SE 6 Update 10 版本中添加了半透明和形状窗口功能时,它是在私有的 com.sun.awt.AWTUtilities 类中实现的。对于 JDK 7 版本,此功能已移至公共 AWT 包。以下表格显示了私有方法如何映射到公共方法。
| Java SE 6 Update 10 中的方法 | JDK 7 中的等效方法 |
|---|---|
AWTUtilities.isTranslucencySupported(Translucency) |
GraphicsDevice.isWindowTranslucencySupported(WindowTranslucency) |
AWTUtilities.isTranslucencyCapable(GraphicsConfiguration) |
GraphicsConfiguration.isTranslucencyCapable() |
AWTUtilities.setWindowOpacity(Window, float) |
Window.setOpacity(float) |
AWTUtilities.setWindowShape(Window, Shape) |
Window.setShape(Shape) |
AWTUtilities.setWindowOpaque(boolean) |
Window.setBackground(Color) 通过将new Color(0,0,0,alpha)传递给这个方法,其中alpha小于 255,可以实现逐像素的半透明。 |
如何使用JLayer类装饰组件
JLayer类是 Swing 组件的灵活而强大的装饰器。它使您能够在组件上绘制并响应组件事件,而无需直接修改底层组件。
本文档描述了展示JLayer类功能的示例。提供完整的源代码。
-
使用
JLayer类 -
使用
LayerUI类 -
在组件上绘制
-
响应事件
-
动画繁忙指示器
-
验证文本字段
要了解本页面上的材料的简要介绍,请观看以下视频。
www.youtube.com/embed/6mQYsWCkx4g
视频需要启用 JavaScript 的网络浏览器和互联网连接。如果无法查看视频,请尝试在 YouTube 上观看。
使用JLayer类
javax.swing.JLayer类是一个团队的一半。另一半是javax.swing.plaf.LayerUI类。假设您想在JButton对象上方进行一些自定义绘制(装饰JButton对象)。您想要装饰的组件是目标。
-
创建目标组件。
-
创建
LayerUI子类的实例来进行绘制。 -
创建包装目标和
LayerUI对象的JLayer对象。 -
使用
JLayer对象在用户界面中就像使用目标组件一样。
例如,要将JPanel子类的实例添加到JFrame对象中,可以类似地执行以下操作:
JFrame f = new JFrame();
JPanel panel = createPanel();
f.add (panel);
要装饰JPanel对象,可以类似地执行以下操作:
JFrame f = new JFrame();
JPanel panel = createPanel();
LayerUI<JPanel> layerUI = new MyLayerUISubclass();
JLayer<JPanel> jlayer = new JLayer<JPanel>(panel, layerUI);
f.add (jlayer);
使用泛型确保JPanel对象和LayerUI对象是兼容的类型。在上一个示例中,JLayer对象和LayerUI对象都与JPanel类一起使用。
JLayer类通常使用其视图组件的确切类型进行泛型化,而LayerUI类设计用于与其泛型参数或任何祖先的JLayer类一起使用。
例如,可以将LayerUI<JComponent>对象与JLayer<AbstractButton>对象一起使用。
LayerUI对象负责为JLayer对象进行自定义装饰和事件处理。当您创建LayerUI子类的实例时,您的自定义行为可以适用于具有适当泛型类型的每个JLayer对象。这就是为什么JLayer类是final的;所有自定义行为都封装在您的LayerUI子类中,因此不需要创建JLayer子类。
使用LayerUI类
LayerUI类大部分行为都继承自ComponentUI类。以下是最常重写的方法:
-
当目标组件需要绘制时,会调用
paint(Graphics g, JComponent c)方法。为了以与 Swing 相同的方式呈现组件,调用super.paint(g, c)方法。 -
当你的
LayerUI子类与一个组件关联时,会调用installUI(JComponent c)方法。在这里执行任何必要的初始化。传入的组件是相应的JLayer对象。使用JLayer类的getView()方法检索目标组件。 -
当你的
LayerUI子类不再与给定组件关联时,会调用uninstallUI(JComponent c)方法。如果需要,进行清理。
在组件上绘制
要使用JLayer类,你需要一个良好的LayerUI子类。最简单的LayerUI类改变了组件的绘制方式。例如,这里有一个在组件上绘制透明颜色渐变的示例。
class WallpaperLayerUI extends LayerUI<JComponent> {
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
Graphics2D g2 = (Graphics2D) g.create();
int w = c.getWidth();
int h = c.getHeight();
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .5f));
g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red));
g2.fillRect(0, 0, w, h);
g2.dispose();
}
}
paint()方法是自定义绘制发生的地方。调用super.paint()方法会绘制JPanel对象的内容。在设置了 50%透明度的合成后,绘制颜色渐变。
定义了LayerUI子类之后,使用它很简单。这里是一些使用WallpaperLayerUI类的源代码:
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
public class Wallpaper {
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createUI();
}
});
}
public static void createUI() {
JFrame f = new JFrame("Wallpaper");
JPanel panel = createPanel();
LayerUI<JComponent> layerUI = new WallpaperLayerUI();
JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI);
f.add (jlayer);
f.setSize(300, 200);
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo (null);
f.setVisible (true);
}
private static JPanel createPanel() {
JPanel p = new JPanel();
ButtonGroup entreeGroup = new ButtonGroup();
JRadioButton radioButton;
p.add(radioButton = new JRadioButton("Beef", true));
entreeGroup.add(radioButton);
p.add(radioButton = new JRadioButton("Chicken"));
entreeGroup.add(radioButton);
p.add(radioButton = new JRadioButton("Vegetable"));
entreeGroup.add(radioButton);
p.add(new JCheckBox("Ketchup"));
p.add(new JCheckBox("Mustard"));
p.add(new JCheckBox("Pickles"));
p.add(new JLabel("Special requests:"));
p.add(new JTextField(20));
JButton orderButton = new JButton("Place Order");
p.add(orderButton);
return p;
}
}
这是结果:

源代码:
Wallpaper NetBeans 项目
Wallpaper.java
使用Java Web Start运行:
LayerUI类的paint()方法让你完全控制组件的绘制方式。这里是另一个LayerUI子类,展示了如何使用 Java 2D 图像处理修改面板的整个内容:
class BlurLayerUI extends LayerUI<JComponent> {
private BufferedImage mOffscreenImage;
private BufferedImageOp mOperation;
public BlurLayerUI() {
float ninth = 1.0f / 9.0f;
float[] blurKernel = {
ninth, ninth, ninth,
ninth, ninth, ninth,
ninth, ninth, ninth
};
mOperation = new ConvolveOp(
new Kernel(3, 3, blurKernel),
ConvolveOp.EDGE_NO_OP, null);
}
@Override
public void paint (Graphics g, JComponent c) {
int w = c.getWidth();
int h = c.getHeight();
if (w == 0 || h == 0) {
return;
}
// Only create the off-screen image if the one we have
// is the wrong size.
if (mOffscreenImage == null ||
mOffscreenImage.getWidth() != w ||
mOffscreenImage.getHeight() != h) {
mOffscreenImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
}
Graphics2D ig2 = mOffscreenImage.createGraphics();
ig2.setClip(g.getClip());
super.paint(ig2, c);
ig2.dispose();
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(mOffscreenImage, mOperation, 0, 0);
}
}
在paint()方法中,面板被渲染到一个离屏图像中。离屏图像使用卷积运算符进行处理,然后绘制到屏幕上。
整个用户界面仍然活跃,只是模糊了:

源代码:
Myopia NetBeans 项目
Myopia.java
使用Java Web Start运行:
响应事件
你的LayerUI子类也可以接收其对应组件的所有事件。然而,JLayer实例必须注册对特定类型事件的兴趣。这是通过JLayer类的setLayerEventMask()方法实现的。通常情况下,这个调用是在LayerUI类的installUI()方法中进行初始化时进行的。
例如,以下摘录显示了一个LayerUI子类的部分内容,该子类注册接收鼠标和鼠标移动事件。
public void installUI(JComponent c) {
super.installUI(c);
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK
);
}
所有发送到你的JLayer子类的事件都会路由到一个事件处理方法,其名称与事件类型匹配。例如,你可以通过重写相应的方法来响应鼠标和鼠标移动事件:
protected void processMouseEvent(MouseEvent e, JLayer l) {
// ...
}
protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
// ...
}
以下是一个LayerUI子类,它在面板内鼠标移动时绘制一个半透明的圆圈。
class SpotlightLayerUI extends LayerUI<JPanel> {
private boolean mActive;
private int mX, mY;
@Override
public void installUI(JComponent c) {
super.installUI(c);
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK
);
}
@Override
public void uninstallUI(JComponent c) {
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(0);
super.uninstallUI(c);
}
@Override
public void paint (Graphics g, JComponent c) {
Graphics2D g2 = (Graphics2D)g.create();
// Paint the view.
super.paint (g2, c);
if (mActive) {
// Create a radial gradient, transparent in the middle.
java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY);
float radius = 72;
float[] dist = {0.0f, 1.0f};
Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
RadialGradientPaint p =
new RadialGradientPaint(center, radius, dist, colors);
g2.setPaint(p);
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .6f));
g2.fillRect(0, 0, c.getWidth(), c.getHeight());
}
g2.dispose();
}
@Override
protected void processMouseEvent(MouseEvent e, JLayer l) {
if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true;
if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false;
l.repaint();
}
@Override
protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
mX = p.x;
mY = p.y;
l.repaint();
}
}
mActive变量指示鼠标是否在面板坐标内。在installUI()方法中,调用setLayerEventMask()方法来指示LayerUI子类对接收鼠标和鼠标移动事件的兴趣。
在processMouseEvent()方法中,根据鼠标位置设置mActive标志。在processMouseMotionEvent()方法中,鼠标移动的坐标存储在mX和mY成员变量中,以便稍后在paint()方法中使用。
paint()方法显示了面板的默认外观,然后叠加了一个径向渐变以实现聚光灯效果:

源代码:
Diva NetBeans Project
Diva.java
使用Java Web Start运行:
动画繁忙指示器
这个示例是一个动画繁忙指示器。它展示了在LayerUI子类中的动画,并具有淡入和淡出效果。它比之前的示例更复杂,但基于相同的原则定义了一个用于自定义绘制的paint()方法。
点击下订单按钮,查看 4 秒钟的繁忙指示器。注意面板变灰并且指示器旋转。指示器的元素具有不同程度的透明度。
LayerUI子类WaitLayerUI类展示了如何触发属性更改事件以更新组件。WaitLayerUI类使用Timer对象以每秒 24 次的速度更新其状态。这是在计时器的目标方法actionPerformed()中完成的。
actionPerformed()方法使用firePropertyChange()方法指示内部状态已更新。这会触发对applyPropertyChange()方法的调用,该方法重新绘制JLayer对象:

源代码:
TapTapTap NetBeans 项目
TapTapTap.java
使用 Java Web Start 运行:
验证文本字段
本文档中的最后一个示例展示了如何使用 JLayer 类装饰文本字段,以显示它们是否包含有效数据。虽然其他示例使用 JLayer 类来包装面板或一般组件,但此示例显示了如何专门包装 JFormattedTextField 组件。它还演示了单个 LayerUI 子类实现可以用于多个 JLayer 实例。
JLayer 类用于为具有无效数据的字段提供视觉指示。当 ValidationLayerUI 类绘制文本字段时,如果字段内容无法解析,它会绘制一个红色的 X。以下是一个示例:

源代码:
FieldValidator NetBeans 项目
FieldValidator.java
使用 Java Web Start 运行:
如何使用动作
Action可用于将功能和状态与组件分离。例如,如果有两个或更多执行相同功能的组件,请考虑使用Action对象来实现该功能。Action对象是一个 action listener,不仅提供动作事件处理,还提供对动作事件触发组件的状态的集中处理,例如工具栏按钮、菜单项、常用按钮和文本字段。动作可以处理的状态包括文本、图标、助记键、启用和选定状态。
通常使用setAction方法将动作附加到组件。当在组件上调用setAction时会发生什么:
-
组件的状态会更新以匹配
Action的状态。例如,如果Action的文本和图标值已设置,则组件的文本和图标将设置为这些值。 -
Action对象在组件上注册为动作监听器。 -
如果
Action的状态发生变化,组件的状态将更新以匹配Action。例如,如果更改动作的启用状态,则所有附加到它的组件将更改其启用状态以匹配动作。
这里有一个示例,创建一个工具栏按钮和菜单项,执行相同的功能:
Action leftAction = new LeftAction(); *//LeftAction code is shown later*
...
button = new JButton(leftAction)
...
menuItem = new JMenuItem(leftAction);
要创建一个Action对象,通常创建AbstractAction的子类,然后实例化它。在子类中,必须实现actionPerformed方法,在动作事件发生时做出适当反应。这里有一个创建和实例化AbstractAction子类的示例:
leftAction = new LeftAction("Go left", anIcon,
"This is the left button.",
new Integer(KeyEvent.VK_L));
...
class LeftAction extends AbstractAction {
public LeftAction(String text, ImageIcon icon,
String desc, Integer mnemonic) {
super(text, icon);
putValue(SHORT_DESCRIPTION, desc);
putValue(MNEMONIC_KEY, mnemonic);
}
public void actionPerformed(ActionEvent e) {
displayResult("Action for first button/menu item", e);
}
}
当前面的代码创建的动作附加到按钮和菜单项时,按钮和菜单项将显示与动作关联的文本和图标。按钮和菜单项上使用L字符作为助记键,并且它们的工具提示文本设置为SHORT_DESCRIPTION字符串,后跟助记键的表示。
例如,我们提供了一个简单的示例,ActionDemo.java,定义了三个操作。每个操作都附加到一个按钮和一个菜单项。由于为每个按钮的操作设置了助记符值,按键序列Alt-L激活左按钮,Alt-M激活中间按钮,Alt-R激活右按钮。左按钮的工具提示显示这是左按钮。Alt-L. 所有这些配置都是自动完成的,程序不需要显式调用设置助记符或工具提示文本。正如我们稍后将展示的,程序确实调用设置按钮文本,但只是为了避免使用操作已设置的值。

试试这个:
-
点击“启动”按钮以使用Java™ Web Start运行 ActionDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
从左侧菜单中选择顶部项目(菜单 > 向左)。
文本区域显示一些文本,标识事件源和接收事件的操作监听器。
-
点击工具栏中最左边的按钮。
文本区域再次显示有关事件的信息。请注意,尽管事件的来源不同,但两个事件都是由相同的操作监听器检测到的:附加到组件的
Action对象。 -
从操作状态菜单中选择顶部项目。
这将禁用“向左”
Action对象,进而禁用其关联的菜单项和按钮。
当“向左”操作被禁用时,用户看到的情况如下:
![]() |
![]() |
|---|
这是禁用“向左”操作的代码:
boolean selected = ...//*true if the action should be enabled;*
//*false, otherwise*
leftAction.setEnabled(selected);
在使用Action创建组件后,您可能需要自定义它们。例如,您可能希望通过添加或删除图标或文本来自定义其中一个组件的外观。例如,ActionDemo.java的菜单中没有图标,按钮中也没有文本。以下是实现此目的的代码:
menuItem = new JMenuItem();
menuItem.setAction(leftAction);
menuItem.setIcon(null); //arbitrarily chose not to use icon in menu
...
button = new JButton();
button.setAction(leftAction);
button.setText(""); //an icon-only button
我们选择通过将图标属性设置为null,将文本设置为空字符串,从同一个操作中创建一个仅图标的按钮和一个仅文本的菜单项。然而,如果Action的属性发生变化,小部件可能会尝试再次从Action中重置图标和文本。
操作 API
以下表格列出了常用的Action构造函数和方法。 使用Action对象的 API 分为三类:
-
支持设置/获取操作的组件
-
创建和使用 AbstractAction
-
操作属性
支持设置/获取操作的组件
| 类 | 目的 |
|---|
JTextField | 这些组件及其子类可以通过setAction直接分配一个操作。 有关通常与操作关联的组件的更多信息,请参阅关于工具栏按钮、菜单项、常见按钮和文本字段的部分。 有关每个组件从Action中获取哪些属性的详细信息,请参阅相关类的 API 文档configurePropertiesFromAction方法。 还请参考buttonActions表。 |
创建和使用 AbstractAction
| 构造函数或方法 | 目的 |
|---|
| AbstractAction() AbstractAction(String)
AbstractAction(String, Icon) | 创建一个Action对象。 通过参数,您可以指定要在操作附加到的组件中使用的文本和图标。 |
| void setEnabled(boolean) boolean isEnabled() | 设置或获取操作控制的组件是否启用。 调用setEnabled(false)会禁用操作控制的所有组件。 类似地,调用setEnabled(true)会启用操作的组件。 |
|---|---|
| void putValue(String, Object) Object getValue(String) | 设置或获取与指定键关联的对象。 用于设置和获取与操作关联的属性。 |
操作属性
该表定义了可以在动作上设置的属性。第二列列出了哪些组件会自动使用这些属性(以及具体调用的方法)。例如,在附加到菜单项的动作上设置ACCELERATOR_KEY,意味着将自动调用JMenuItem.setAccelerator(KeyStroke)。
| 属性 | 自动应用于:类
(调用方法) | 目的 |
| ACCELERATOR_KEY | JMenuItem (setAccelerator) |
用作动作加速器的KeyStroke。有关加速器与助记键的讨论,请参见启用键盘操作。 |
|---|---|---|
| ACTION_COMMAND_KEY | AbstractButton, JCheckBox, JRadioButton (setActionCommand) |
与ActionEvent相关联的命令字符串。 |
| LONG_DESCRIPTION | 无 | 动作的更长描述。可用于上下文相关的帮助。 |
| MNEMONIC_KEY | AbstractButton, JMenuItem, JCheckBox, JRadioButton (setMnemonic) |
动作的助记键。有关加速器与助记键的讨论,请参见启用键盘操作。 |
| NAME | AbstractButton, JMenuItem, JCheckBox, JRadioButton (setText) |
动作的名称。您可以在使用AbstractAction(String)或AbstractAction(String, Icon)构造函数创建动作时设置此属性。 |
| SHORT_DESCRIPTION | AbstractButton, JCheckBox, JRadioButton (setToolTipText) |
动作的简短描述。 |
| SMALL_ICON | AbstractButton, JMenuItem (setIcon) |
工具栏或按钮上用于动作的图标。您可以在使用AbstractAction(name, icon)构造函数创建动作时设置此属性。 |
使用动作的示例
以下示例使用Action对象。
| 示例 | 描述位置 | 备注 |
|---|---|---|
ActionDemo |
本节 | 使用动作将按钮和菜单项绑定到同一个函数。 |
TextComponentDemo |
文本组件特性 | 使用文本操作创建菜单项,用于文本编辑命令,如剪切、复制和粘贴,并将按键绑定到插入符移动。还实现了自定义AbstractAction子类来实现撤销和重做。关于文本操作的讨论始于概念:关于编辑器工具包。 |
如何使用 Swing 计时器
Swing 计时器(javax.swing.Timer 的一个实例)在指定延迟后触发一个或多个动作事件。不要将 Swing 计时器与 java.util 包中的通用计时器设施混淆。本页仅描述 Swing 计时器。
一般来说,我们建议在 GUI 相关任务中使用 Swing 计时器而不是通用计时器,因为所有 Swing 计时器共享相同的、预先存在的计时器线程,并且 GUI 相关任务会自动在事件分发线程上执行。但是,如果您不打算从计时器中触摸 GUI,或者需要执行长时间处理,可以使用通用计时器。
您可以以两种方式使用 Swing 计时器:
-
一次性执行任务,延迟后。
例如,工具提示管理器使用 Swing 计时器来确定何时显示工具提示以及何时隐藏它。
-
重复执行任务。
例如,您可以执行动画或更新显示朝向目标进展的组件。
Swing 计时器非常容易使用。创建计时器时,您指定一个动作监听器,在计时器“触发”时通知它。此监听器中的 actionPerformed 方法应包含您需要执行的任何任务的代码。创建计时器时,还要指定计时器触发之间的毫秒数。如果希望计时器仅触发一次,可以在计时器上调用 setRepeats(false)。要启动计时器,请调用其 start 方法。要暂停它,请调用 stop。
请注意 Swing 计时器的任务是在事件分发线程中执行的。这意味着任务可以安全地操作组件,但也意味着任务应该快速执行。如果任务可能需要一段时间才能执行完毕,则考虑使用 SwingWorker 代替或与计时器一起使用。请参阅 Swing 中的并发性 了解如何使用 SwingWorker 类以及在多线程程序中使用 Swing 组件的信息。
让我们看一个使用计时器定期更新组件的示例。TumbleItem 小程序使用计时器定期更新其显示。 (要查看此小程序运行,请转到 如何制作小程序。此小程序首先创建并启动计时器:
timer = new Timer(speed, this);
timer.setInitialDelay(pause);
timer.start();
speed 和 pause 变量代表小程序参数;在另一页配置的情况下,它们分别为 100 和 1900,因此第一个计时器事件将在大约 1.9 秒后发生,并且每 0.1 秒重复一次。通过将 this 指定为 Timer 构造函数的第二个参数,TumbleItem 指定它是计时器事件的动作监听器。
启动计时器后,TumbleItem开始在后台线程中加载一系列图像。与此同时,计时器事件开始发生,导致actionPerformed方法执行:
public void actionPerformed(ActionEvent e) {
//If still loading, can't animate.
if (!worker.isDone()) {
return;
}
loopslot++;
if (loopslot >= nimgs) {
loopslot = 0;
off += offset;
if (off < 0) {
off = width - maxWidth;
} else if (off + maxWidth > width) {
off = 0;
}
}
animator.repaint();
if (loopslot == nimgs - 1) {
timer.restart();
}
}
直到图像加载完成,worker.isDone返回false,因此计时器事件实际上被忽略了。事件处理代码的第一部分只是设置了在动画控件的paintComponent方法中使用的值:loopslot(动画中下一个图形的索引)和off(下一个图形的水平偏移量)。
最终,loopslot将达到图像数组的末尾并重新开始。当这种情况发生时,actionPerformed末尾的代码重新启动计时器。这样做会导致动画序列再次开始之前有一个短暂的延迟。
如何支持辅助技术
你可能想知道辅助技术到底是什么,以及为什么你应该关心。主要来说,辅助技术存在是为了让有永久或暂时残疾的人能够使用计算机。例如,如果你患上了腕管综合症,你可以使用辅助技术完成工作而不用手。
辅助技术 语音界面、屏幕阅读器、替代输入设备等等 不仅对残疾人有用,也适用于在非办公环境中使用计算机的人群。例如,如果你被困在交通拥堵中,你可以使用辅助技术来检查你的电子邮件,只需使用语音输入和输出。支持辅助技术的信息也可以用于其他工具,比如自动化 GUI 测试工具和输入设备如触摸屏。辅助技术通过使用在javax.accessibility包中定义的辅助功能 API 从组件获取信息。
因为对辅助功能 API 的支持内置在 Swing 组件中,你的 Swing 程序可能会很好地与辅助技术配合工作,即使你没有做任何特殊处理。例如,辅助技术可以自动获取以下代码设置的文本信息:
JButton button = new JButton("I'm a Swing button!");
label = new JLabel(labelPrefix + "0 ");
label.setText(labelPrefix + numClicks);
JFrame frame = new JFrame("SwingApplication");
辅助技术还可以获取与组件关联的工具提示文本(如果有的话),并用它来向用户描述组件。
使你的程序与辅助技术顺畅配合是很容易的,而且在美国可能是联邦法律要求的。
本节的其余部分涵盖了以下主题:
-
支持辅助功能的规则
-
辅助功能测试
-
在组件上设置可访问名称和描述
-
概念:辅助功能的工作原理
-
使自定义组件可访问
-
辅助功能 API
-
使用辅助功能 API 的示例
支持辅助功能的规则
以下是一些你可以做的事情,使你的程序尽可能与辅助技术配合良好:
-
如果一个组件没有显示一个短字符串(作为其默认名称),可以使用
setAccessibleName方法指定一个名称。你可能想为仅包含图像的按钮、提供逻辑分组的面板、文本区域等设置这个名称。 -
在有意义的情况下为组件设置工具提示文本。例如:
aJComponent.setToolTipText( "Clicking this component causes XYZ to happen."); -
如果你不想为一个组件提供工具提示,可以使用
setAccessibleDescription方法提供一个描述,辅助技术可以向用户提供。例如:aJComponent.getAccessibleContext(). setAccessibleDescription( "Clicking this component causes XYZ to happen."); -
尽可能指定键盘替代方案。确保您的程序只能使用键盘。尝试隐藏鼠标!请注意,如果焦点在可编辑文本组件中,则可以使用 Shift-Tab 将焦点移动到下一个组件。
对于键盘替代方案的支持因组件而异。按钮使用
setMnemonic方法支持键盘替代方案。菜单继承按钮助记符支持,并且还支持加速键,如启用键盘操作中所述。其他组件可以使用键绑定将用户输入与程序操作关联起来。 -
为程序中的所有
ImageIcon对象分配文本描述。您可以使用setDescription方法或ImageIcon构造函数的一个String形式来设置此属性。 -
如果一组组件形成一个逻辑组,请尝试将它们放入一个容器中。例如,使用
JPanel来包含单选按钮组中的所有单选按钮。 -
每当您有一个描述另一个组件的标签时,请使用
setLabelFor方法,以便辅助技术可以找到与标签关联的组件。当标签显示另一个组件的助记符(例如文本字段)时,这一点尤为重要。 -
如果您创建了自定义组件,请确保它支持可访问性。特别要注意的是,
JComponent的子类不会自动支持可访问性。继承自其他 Swing 组件的自定义组件应根据需要覆盖继承的可访问性信息。有关更多信息,请参见概念:可访问性工作原理和使自定义组件可访问。 -
使用提供的辅助功能实用程序示例来测试您的程序。尽管这些示例的主要目的是向程序员展示如何在实现辅助技术时使用辅助功能 API,但这些示例对于测试可访问性的应用程序程序也非常有用。测试可访问性展示了
ScrollDemo与 Monkey 一起运行的情况,Monkey 是辅助功能实用程序示例之一。Monkey 显示程序中可访问组件的树,并允许您与这些组件进行交互。 -
最后,不要破坏免费获得的内容!如果您的 GUI 有一个不可访问的容器 — 例如,您自己的
Container或JComponent的子类或任何其他不实现Accessible接口的容器 — 那么该容器内的任何组件都将无法访问。
测试可访问性
随附辅助工具的示例可以让您了解您的程序的可访问性如何。有关获取这些实用程序的说明,请参阅Java SE 桌面可访问性主页。按照辅助工具文档中的说明设置 Java 虚拟机(VM)以自动运行一个或多个实用程序。
让我们使用一个辅助工具来比较我们的演示程序的原始版本和已应用支持可访问性规则的版本。这里是一个名为ScrollDemo的程序的图片。

试试这个:
-
点击启动按钮运行
ScrollDemo,使用Java™ Web Start(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。 -
接下来,点击启动按钮运行
AccessibleScrollDemo,使用Java™ Web Start(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。 -
将两个版本并排进行比较。唯一显著的区别是在可访问版本中,cm切换按钮和照片具有工具提示。
-
现在在名为 Monkey 的辅助工具下运行这两个版本。请注意,当辅助工具已被下载并在
accessibility.properties文件中配置时,当您点击运行 ScrollDemo 和 AccessibleScrollDemo 链接(在步骤 1 和 2 中)时,Monkey 窗口会自动弹出。如果 Monkey 窗口在启动时没有出现,问题可能是
accessibility.properties文件不存在于 Java Web Start 使用的 VM 版本中。您可以通过运行 Java Web Start 应用程序管理器并选择文件 > 首选项 > Java来更改您使用的 VM。 -
请注意,当 Monkey 窗口弹出时,您需要选择文件 > 刷新树以查看信息出现在
可访问树下。然后,您可以通过依次单击每个文件夹图标显示的水平图标来展开树。当树已经展开时,您可以查看各个组件的详细信息。在修改后的版本中,原始版本中无法访问的自定义组件(规则和角落)现在可以访问。这对辅助技术可能产生很大的影响。
这是 Monkey 在ScrollDemo上运行的快照:

分割窗格的左侧显示程序的实际组件层次结构。右侧显示层次结构中的可访问组件,这是我们感兴趣的。
首先要注意的是,即使在ScrollDemo中没有明确的支持,Monkey 也能够发现关于程序中各个组件的许多信息。大多数组件及其子级都显示在树中。然而,大多数组件的名称为空(null),这相当没有帮助。描述也为空。
进一步的问题出现在程序的自定义组件上。两个标尺是无法访问的,因此它们不包括在可访问树中。包含标尺的视口显示为叶节点,因为它们没有可访问的子级。自定义角落也不在可访问树中。
现在这里是AccessibleScrollDemo的 Monkey 窗口图片:

现在,规则被列为视口的子级,角落被列为滚动窗格的子级。此外,许多组件现在具有非空名称。
在 Monkey 的上一个快照中,选择了列标题项。Monkey 会在ScrollDemo程序中突出显示相应的组件。

当选择一个项目时,您可以使用 Monkey 的面板菜单来打开四个不同的面板之一,让您与所选组件进行交互。选择面板 > 可访问性 API 面板会打开一个类似下图所示的面板。该面板显示通过AccessibleContext基类和AccessibleComponent接口定义的方法可用的信息。

Monkey 有另外三个面板:
-
AccessibleAction:显示可访问组件支持的操作,并允许您调用该操作。仅适用于上下文实现了
AccessibleAction接口的可访问组件。 -
AccessibleSelection:显示可访问组件的当前选择并允许您操作选择。仅适用于上下文实现了
AccessibleSelection接口的可访问组件。 -
AccessibleHypertext:显示可访问组件中包含的任何超链接,并允许您遍历它们。仅适用于上下文实现了
AccessibleHypertext接口的可访问组件。
辅助功能实用工具示例可作为测试工具,帮助您了解程序中组件的可访问性如何。然而,即使您的组件在 Monkey 或其他示例中表现良好,它们仍然可能不完全可访问,因为 Monkey 和其他示例仅对辅助功能 API 的某些部分进行了测试。
唯一真正的可访问性测试是使用真实世界的辅助技术运行您的程序,但是您可能会发现以下免费开源屏幕阅读器有用:NonVisual Desktop Access (NVDA)。
在组件上设置可访问名称和描述
为程序的组件提供可访问名称和描述是使程序可访问性的最简单和最重要的步骤之一。以下是创建滚动窗格和其使用的自定义组件的AccessibleScrollDemo构造函数的完整列表。粗体语句提供了辅助技术可以使用的组件名称和描述。
public AccessibleScrollDemo() {
// Get the image to use.
ImageIcon bee = createImageIcon("images/flyingBee.jpg",
"Photograph of a flying bee.");
// Create the row and column headers.
columnView = new Rule(Rule.HORIZONTAL, true);
if (bee != null) {
columnView.setPreferredWidth(bee.getIconWidth());
} else {
columnView.setPreferredWidth(320);
}
columnView.getAccessibleContext().setAccessibleName("Column Header");
columnView.getAccessibleContext().
setAccessibleDescription("Displays horizontal ruler for " +
"measuring scroll pane client.");
rowView = new Rule(Rule.VERTICAL, true);
if (bee != null) {
rowView.setPreferredHeight(bee.getIconHeight());
} else {
rowView.setPreferredHeight(480);
}
rowView.getAccessibleContext().setAccessibleName("Row Header");
rowView.getAccessibleContext().
setAccessibleDescription("Displays vertical ruler for " +
"measuring scroll pane client.");
// Create the corners.
JPanel buttonCorner = new JPanel();
isMetric = new JToggleButton("cm", true);
isMetric.setFont(new Font("SansSerif", Font.PLAIN, 11));
isMetric.setMargin(new Insets(2,2,2,2));
isMetric.addItemListener(this);
isMetric.setToolTipText("Toggles rulers' unit of measure " +
"between inches and centimeters.");
buttonCorner.add(isMetric); //Use the default FlowLayout
buttonCorner.getAccessibleContext().
setAccessibleName("Upper Left Corner");
String desc = "Fills the corner of a scroll pane " +
"with color for aesthetic reasons.";
Corner lowerLeft = new Corner();
lowerLeft.getAccessibleContext().
setAccessibleName("Lower Left Corner");
lowerLeft.getAccessibleContext().setAccessibleDescription(desc);
Corner upperRight = new Corner();
upperRight.getAccessibleContext().
setAccessibleName("Upper Right Corner");
upperRight.getAccessibleContext().setAccessibleDescription(desc);
// Set up the scroll pane.
picture = new ScrollablePicture(bee,
columnView.getIncrement());
picture.setToolTipText(bee.getDescription());
picture.getAccessibleContext().setAccessibleName(
"Scroll pane client");
JScrollPane pictureScrollPane = new JScrollPane(picture);
pictureScrollPane.setPreferredSize(new Dimension(300, 250));
pictureScrollPane.setViewportBorder(
BorderFactory.createLineBorder(Color.black));
pictureScrollPane.setColumnHeaderView(columnView);
pictureScrollPane.setRowHeaderView(rowView);
// In theory, to support internationalization you would change
// UPPER_LEFT_CORNER to UPPER_LEADING_CORNER,
// LOWER_LEFT_CORNER to LOWER_LEADING_CORNER, and
// UPPER_RIGHT_CORNER to UPPER_TRAILING_CORNER. In practice,
// bug #4467063 makes that impossible (at least in 1.4.0).
pictureScrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER,
buttonCorner);
pictureScrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER,
lowerLeft);
pictureScrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER,
upperRight);
// Put it in this panel.
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(pictureScrollPane);
setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
}
通常,程序通过组件的可访问上下文直接设置组件的名称和描述。其他时候,程序通过工具提示间接设置可访问描述。在cm切换按钮的情况下,描述会自动设置为按钮上的文本。
概念:可访问性工作原理
如果对象实现了Accessible接口,则该对象是可访问的。Accessible接口仅定义了一个方法getAccessibleContext,该方法返回一个AccessibleContext对象。AccessibleContext对象是一个中介,包含可访问对象的可访问信息。以下图显示了辅助技术如何从可访问对象获取可访问上下文并查询信息:

AccessibleContext是一个抽象类,定义了可访问对象必须提供关于自身的最小信息集。最小信息集包括名称、描述、角色、状态集等。为了标识其可访问对象具有特定功能,可访问上下文可以实现如可访问接口表中所示的一个或多个接口。例如,JButton实现了AccessibleAction、AccessibleValue、AccessibleText和AccessibleExtendedComponent。JButton不需要实现AccessibleIcon,因为这是由按钮附加的ImageIcon实现的。
因为JComponent类本身没有实现Accessible接口,其直接子类的实例是不可访问的。如果你编写一个直接继承自JComponent的自定义组件,你需要显式地让它实现Accessible接口。JComponent确实有一个可访问的上下文,称为AccessibleJComponent,它实现了AccessibleComponent接口并提供了最少量的可访问信息。你可以通过创建AccessibleJComponent的子类并重写重要方法为你的自定义组件提供可访问的上下文。使自定义组件可访问展示了两个示例。
所有其他标准的 Swing 组件都实现了Accessible接口,并具有一个实现一个或多个前述接口的可访问上下文。Swing 组件的可访问上下文被实现为内部类,并具有以下样式的名称:
*Component*.Accessible*Component*
如果你创建一个标准 Swing 组件的子类,并且你的子类与其超类有很大不同,那么你应该为其提供一个自定义的可访问上下文。最简单的方法是创建超类可访问上下文类的子类,并根据需要重写方法。例如,如果你创建一个与JLabel有很大不同的JLabel子类,那么你的JLabel子类应该包含一个扩展AccessibleJLabel的内部类。下一节将展示如何做到这一点,使用JComponent子类扩展AccessibleJComponent的示例。
使自定义组件可访问
滚动演示程序使用了三个自定义组件类。ScrollablePicture是JLabel的子类,而Corner和Rule都是JComponent的子类。
ScrollablePicture类完全依赖于从JLabel通过JLabel.AccessibleJLabel继承的可访问性。创建ScrollablePicture实例的代码为可滚动图片设置了工具提示文本。工具提示文本被上下文用作组件的可访问描述。这种行为由AccessibleJLabel提供。
Corner类的可访问版本仅包含足够的代码使其实例可访问。我们通过向原始版本的Corner添加粗体显示的代码来实现辅助功能支持。
public class Corner extends JComponent implements Accessible {
protected void paintComponent(Graphics g) {
//Fill me with dirty brown/orange.
g.setColor(new Color(230, 163, 4));
g.fillRect(0, 0, getWidth(), getHeight());
}
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleCorner();
}
return accessibleContext;
}
protected class AccessibleCorner extends AccessibleJComponent {
//Inherit everything, override nothing.
}
}
此类提供的所有辅助功能都继承自AccessibleJComponent。这种方法对于Corner来说是可以的,因为AccessibleJComponent提供了合理数量的默认辅助功能信息,并且角落不那么有趣:它们只是为了在屏幕上占据一点空间。其他类,如Rule,需要提供定制信息。
Rule以与Corner相同的方式为自身提供可访问上下文,但上下文覆盖了两个方法以提供有关组件角色和状态的详细信息:
protected class AccessibleRuler extends AccessibleJComponent {
public AccessibleRole getAccessibleRole() {
return AccessibleRuleRole.RULER;
}
public AccessibleStateSet getAccessibleStateSet() {
AccessibleStateSet states =
super.getAccessibleStateSet();
if (orientation == VERTICAL) {
states.add(AccessibleState.VERTICAL);
} else {
states.add(AccessibleState.HORIZONTAL);
}
if (isMetric) {
states.add(AccessibleRulerState.CENTIMETERS);
} else {
states.add(AccessibleRulerState.INCHES);
}
return states;
}
}
AccessibleRole是标识 Swing 组件可以扮演的角色的对象枚举。它包含预定义的角色,如标签、按钮等。我们示例中的标尺不适合任何预定义角色,因此程序在AccessibleRole的子类中发明了一个新角色:
class AccessibleRuleRole extends AccessibleRole {
public static final AccessibleRuleRole RULER
= new AccessibleRuleRole("ruler");
protected AccessibleRuleRole(String key) {
super(key);
}
//Should really provide localizable versions of these names.
public String toDisplayString(String resourceBundleName,
Locale locale) {
return key;
}
}
任何具有状态的组件都可以通过覆盖getAccessibleStateSet方法向辅助技术提供状态信息。规则有两组状态:其方向可以是垂直或水平,其度量单位可以是厘米或英寸。AccessibleState是预定义状态的枚举。此程序使用其预定义的垂直和水平方向状态。因为AccessibleState不包含厘米和英寸的内容,所以程序创建一个子类来提供适当的状态:
class AccessibleRulerState extends AccessibleState {
public static final AccessibleRulerState INCHES
= new AccessibleRulerState("inches");
public static final AccessibleRulerState CENTIMETERS
= new AccessibleRulerState("centimeters");
protected AccessibleRulerState(String key) {
super(key);
}
//Should really provide localizable versions of these names.
public String toDisplayString(String resourceBundleName,
Locale locale) {
return key;
}
}
您已经了解了如何为两个简单的组件实现辅助功能,这些组件仅存在于屏幕上绘制自身。执行更多操作的组件,例如响应鼠标或键盘事件,需要提供更复杂的可访问上下文。您可以通过深入研究 Swing 组件的源代码来找到实现可访问上下文的示例。
辅助功能 API
本节中的表格仅涵盖辅助功能 API 的一部分。有关辅助功能 API 的更多信息,请参阅辅助功能包中类和包的 API 文档。此外,请参考各个 Swing 组件的可访问上下文的 API 文档。
支持辅助功能的 API 分为以下几类:
-
命名和链接组件
-
创建自定义可访问组件
-
可访问接口
命名和链接组件
| 方法 | 目的 |
|---|
| getAccessibleContext().setAccessibleName(String) getAccessibleContext().setAccessibleDescription(String)
(在 JComponent 或 Accessible 对象上) | 为可访问对象提供名称或描述。 |
void setToolTipText(String) (在 JComponent 中) |
设置组件的工具提示。如果您不设置描述,许多可访问上下文将使用工具提示文本作为可访问描述。 |
|---|---|
void setLabelFor(Component) (在 JLabel 中) |
将标签与组件关联起来。这告诉辅助技术,标签描述另一个组件。 |
void setDescription(String) (在 ImageIcon 中) |
为图像图标提供描述。 |
使自定义组件可访问
| 接口或类 | 目的 |
|---|---|
| 可访问性 (一个接口) | 实现此接口的组件是可访问的。JComponent 的子类必须显式实现这一点。 |
| AccessibleContext JComponent.AccessibleJComponent
(一个抽象类及其子类) | AccessibleContext 定义了可访问对象所需的最小信息集。每个 Swing 组件的可访问上下文都是此类的子类,并按照所示命名。例如,JTree 的可访问上下文是 JTree.AccessibleJTree。要提供自定义的可访问上下文,自定义组件应包含一个是 AccessibleContext 子类的内部类。有关更多信息,请参见使自定义组件可访问。 |
| AccessibleRole AccessibleStateSet
(类) | 分别定义由 AccessibleContext 对象的 getAccessibleRole 和 getAccessibleStateSet 方法返回的对象。 |
| AccessibleRelation AccessibleRelationSet
| 定义实现此接口的组件与一个或多个其他对象之间的关系。 |
|---|
可访问接口
| 接口 | 目的 |
|---|---|
| 可访问操作 | 表明对象可以执行操作。通过实现这个接口,可访问上下文可以提供关于可访问对象可以执行的操作的信息,并告诉可访问对象执行这些操作。 |
| 可访问组件 | 表明可访问对象在屏幕上存在。通过这个接口,可访问对象可以提供关于其大小、位置、可见性等信息。所有标准 Swing 组件的可访问上下文都直接或间接实现了这个接口。您的自定义组件的可访问上下文应该也这样做。首选使用AccessibleExtendedComponent方法。 |
| 可访问可编辑文本 | 表明可访问对象显示可编辑文本。除了从其超接口AccessibleText中可获得的信息外,还提供了用于剪切、粘贴、删除、选择和插入文本的方法。 |
| 可访问扩展组件 | 除了从其超接口AccessibleComponent中可获得的信息外,还提供了用于获取键绑定、边框文本和工具提示文本的方法。 |
| 可访问扩展表格 | 除了从其超接口AccessibleTable中可获得的信息外,还提供了在索引和其行或列之间转换的方法。 |
| 可访问超文本 | 表明可访问对象包含超链接。通过这个接口,可访问对象可以提供关于其链接的信息,并允许对其进行遍历。 |
| 可访问图标 | 表明可访问对象有一个关联的图标。提供了返回有关图标的信息,如大小和描述的方法。 |
| 可访问键绑定 | 表明可访问对象支持一个或多个可用于选择对象的键盘快捷键。提供了返回给定对象的键绑定的方法。 |
| 可访问选择 | 表明可访问对象可以包含选择。实现此接口的可访问上下文可以报告有关当前选择的信息,并可以修改选择。 |
| 可访问表格 | 表明可访问对象以二维数据对象呈现数据。通过此接口提供有关表格的信息,如表格标题、行列大小、描述和名称。推荐使用AccessibleExtendedTable方法。 |
| 可访问文本 | 表明可访问对象显示文本。此接口提供返回文本的全部或部分、应用于文本的属性以及文本的其他信息(如长度)的方法。 |
| 可访问数值 | 表明对象具有数值。通过此接口,可访问对象提供有关其当前值及最小和最大值的信息。 |
使用辅助功能 API 的示例
下表列出了一些对辅助技术有良好支持的示例。
| 示例 | 描述位置 | 备注 |
|---|---|---|
AccessibleScrollDemo |
本节 | 包含两个实现Accessible接口的自定义组件。要查看此程序的较不可访问版本,请参见如何使用滚动窗格。 |
ButtonDemo |
如何使用通用按钮 API | 使用三个按钮。通过按钮文本、助记键和工具提示支持可访问性。 |
如何使用焦点子系统
许多组件 - 即使是主要通过鼠标操作的组件,如按钮 - 也可以通过键盘操作。要使按键影响组件,组件必须具有键盘焦点。
从用户的角度来看,具有键盘焦点的组件通常很显眼 - 例如带有虚线或黑色边框。包含该组件的窗口也比屏幕上的其他窗口更显眼。这些视觉提示让用户知道任何键入将与哪个组件相关联。窗口系统中一次只能有一个组件具有键盘焦点。
窗口如何获得焦点取决于窗口系统。在所有平台上,没有绝对可靠的方法来确保窗口获得焦点。在某些操作系统上,例如 Microsoft Windows,前置窗口通常成为焦点窗口。在这些情况下,Window.toFront方法将窗口移至前台,从而使其获得焦点。但是,在其他操作系统上,例如 Solaris™ 操作系统,窗口管理器可能根据光标位置选择焦点窗口,在这种情况下,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方法确定哪个组件将获得焦点。
本节的其余部分涵盖以下主题:
-
焦点子系统简介
-
验证输入
-
使自定义组件可聚焦
-
自定义焦点遍历
-
跟踪多个组件的焦点变化
-
焦点转移时间
-
焦点 API
-
焦点示例
焦点子系统简介
焦点子系统旨在尽可能隐形地执行正确操作。在大多数情况下,它的行为是合理的,如果不是,您可以以各种方式调整其行为。一些常见情况可能包括:
-
排序正确,但焦点未设置在第一个组件上。如前一节中的代码片段所示,您可以使用
requestFocusInWindow方法在窗口可见时将焦点设置在组件上。 -
排序错误。要解决此问题,您可以更改包含层次结构,更改组件添加到其容器的顺序,或者创建自定义焦点遍历策略。有关更多详细信息,请参见自定义焦点遍历。
-
组件必须防止失去焦点,或者在组件失去焦点之前检查一个值。输入验证是解决此问题的方法。
-
自定义组件没有获得焦点。要解决此问题,您需要确保它满足使自定义组件可聚焦中概述的所有要求。
FocusConceptsDemo示例演示了一些概念。

试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行 FocusConceptsDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
如有必要,单击窗口以使其获得焦点。
-
使用 Tab 键从组件到组件移动焦点。
您会注意到当焦点移入文本区域时,它会停留在文本区域中。
-
使用 Control-Tab 将焦点移出文本区域。
-
使用 Shift-Tab 向相反方向移动焦点。
-
使用 Control-Shift-Tab 向相反方向移出文本区域的焦点。
KeyboardFocusManager是焦点子系统的关键元素。它管理状态并启动更改。键盘管理器跟踪焦点所有者 —— 接收键盘输入的组件。焦点窗口是包含焦点所有者的窗口。
JWindow 和焦点: 在 GUI 中使用 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。
以下两个示例展示了抵押贷款计算器。一个使用格式化文本字段,另一个使用标准文本字段进行输入验证。

试试这个:
-
点击“启动”按钮以使用Java™ Web Start运行 FormattedTextFieldDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
单击“启动”按钮以使用Java™ Web Start运行 InputVerificationDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
将两个抵押贷款计算器并排放置。您会发现输入验证演示为每个可编辑文本字段的相关标签指定了有效的输入值。尝试在两个示例中输入格式不正确的值以观察行为。然后尝试输入格式正确但不可接受的值。
您可以在InputVerificationDemo.java中找到输入验证演示的代码。以下是MyVerifier的InputVerifier子类的代码:
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方法仅用于确定输入是否有效,不应弹出对话框或引起其他任何副作用。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) {
//Since the user clicked on us, let us get focus!
requestFocusInWindow();
}
有关 TrackFocusDemo 示例的更多讨论,请参见跟踪多个组件的焦点变化。
自定义焦点遍历
焦点子系统确定了在使用焦点遍历键(如 Tab 键)导航时应用的默认顺序。Swing 应用程序的策略由LayoutFocusTraversalPolicy确定。您可以通过使用setFocusCycleRoot方法在任何Container上设置焦点遍历策略。但是,如果容器不是焦点循环根,则可能没有明显效果。或者,您可以将焦点遍历策略提供者传递给FocusTraversalPolicy方法,而不是焦点循环根。使用isFocusTraversalPolicyProvider()方法来确定Container是否是焦点遍历策略提供者。使用setFocusTraversalPolicyProvider()方法设置一个容器来提供焦点遍历策略。
FocusTraversalDemo示例演示了如何自定义焦点行为。

试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行 FocusTraversalDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
单击窗口,如果需要,使其获得焦点。
-
当您通过组件进行制表时,请注意焦点顺序。焦点顺序是由组件添加到内容窗格的顺序确定的。还要注意,复选框永远不会获得焦点;我们将其从焦点循环中移除。
-
要将焦点移出表格,请使用 Control-Tab 或 Control-Shift-Tab。
-
单击自定义 FocusTraversalPolicy复选框。此框在框架上安装了自定义焦点遍历策略。
-
再次通过组件进行制表。请注意,焦点顺序现在是从左到右,从上到下的顺序。
您可以在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 表以获取完整列表。
以下示例演示通过在键盘焦点管理器上安装属性更改侦听器来跟踪焦点所有者。

试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行 TrackFocusDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
如果需要,点击窗口使其获得焦点。
-
窗口显示六幅图像,每幅图像都由
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");
定时焦点转移
焦点转移是异步的。这种特性可能会导致一些奇怪的与时间相关的问题和假设,尤其是在焦点的自动转移过程中。例如,想象一个包含“开始”按钮、“取消”按钮和文本字段的窗口的应用程序。这些组件按照这个顺序添加:
-
开始按钮
-
文本字段
-
取消按钮
当应用程序启动时,LayoutFocusTraversalPolicy确定焦点遍历策略——在本例中,它是组件添加到其容器的顺序。在这个示例中,期望的行为是“开始”按钮具有初始焦点,当单击“开始”按钮时,它被禁用,然后“取消”按钮接收焦点。实现这种行为的正确方法是按照期望的顺序将组件添加到容器中,或者创建一个自定义的焦点遍历策略。如果由于某种原因无法实现这一点,那么可以使用以下代码片段实现这种行为:
public void actionPerformed(ActionEvent e) {
//This works.
start.setEnabled(false);
cancel.requestFocusInWindow();
}
如期望的那样,焦点从“开始”按钮转移到“取消”按钮,而不是文本字段。但是,如果以相反的顺序调用相同的方法,则会产生不同的结果,如下所示:
public void actionPerformed(ActionEvent e) {
//This does not work.
cancel.requestFocusInWindow();
start.setEnabled(false);
}
在这种情况下,在“开始”按钮离开之前就请求了“取消”按钮的焦点。调用requestFocusInWindow方法会启动焦点转移,但不会立即将焦点移动到“取消”按钮。当“开始”按钮被禁用时,焦点会转移到下一个组件(因此总是有一个具有焦点的组件),在这种情况下,焦点会移动到文本字段,而不是“取消”按钮。
有几种情况下,您需要在可能影响焦点的所有其他更改之后发出焦点请求:
-
隐藏焦点所有者。
-
使焦点所有者不可聚焦。
-
在焦点所有者上调用
removeNotify方法。 -
对焦点所有者的容器执行上述任何操作,或者导致焦点策略发生更改,以使容器不再接受该组件作为焦点所有者。
-
处置包含焦点所有者的顶层窗口。
焦点 API
以下表格列出了与焦点相关的常用构造函数和方法。焦点 API 分为四个类别:
-
组件的有用方法
-
创建和使用自定义焦点遍历策略
-
输入验证 API
-
KeyboardFocusManager 属性
有关焦点架构的更详细信息,请参阅焦点子系统的规范。您可能还会发现如何编写焦点侦听器有用。
组件的有用方法:
方法(在Component中) |
目的 |
|---|---|
| isFocusOwner() | 如果组件是焦点所有者,则返回true。 |
| setRequestFocusEnabled(boolean) isRequestFocusEnabled()
(in JComponent) | 设置或检查此组件是否应获得焦点。将setRequestFocusEnabled设置为false通常会阻止鼠标点击使组件获得焦点,但仍允许键盘导航使组件获得焦点。此方法仅适用于接收鼠标事件的组件。例如,您可以在JButton上使用此方法,但不能在JPanel上使用。如果编写自定义组件,则需要遵守此属性。此方法比setFocusable方法更可取,并将使您的程序更适合使用辅助技术的用户。 |
| setFocusable(boolean) isFocusable() | 设置或获取组件的可聚焦状态。组件必须是可聚焦的才能获得焦点。当使用setFocusable(false)将组件从焦点循环中移除后,无法再使用键盘导航到该组件。建议使用setRequestFocusEnabled方法,以便您的程序可以被使用辅助技术的用户运行。 |
|---|---|
| requestFocusInWindow() | 请求此组件获取焦点。组件的窗口必须是当前焦点窗口。要满足此请求,JComponent的子类必须可见、启用且可聚焦,并且必须具有此请求的输入映射。在触发FOCUS_GAINED事件之前,不应假定组件已获得焦点。此方法优于依赖于平台的requestFocus方法。 |
| setFocusTraversalKeys(int, Set) getFocusTraversalKeys(int)
(在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)。
创建和使用自定义焦点遍历策略
| 类或方法 | 目的 |
|---|---|
| LayoutFocusTraversalPolicy | 默认情况下确定 Swing 组件的焦点遍历策略的类。 |
| getComponentAfter(Container, Component) | 给定作为输入的组件,返回下一个应该获得焦点的组件。 |
| getComponentBefore(Container, Component) | 给定作为输入的组件,返回在此组件之前应该获得焦点的组件。该方法用于向后切换。 |
getDefaultComponent(Container) (在javax.swing.SortingFocusTraversalPolicy中) |
返回应该具有默认焦点的组件。 |
| getFirstComponent(Container) | 返回遍历循环中的第一个组件。 |
| getInitialComponent(Container) | 返回当窗口首次可见时应该接收焦点的组件。 |
| getLastComponent(Container) | 返回遍历循环中的最后一个组件。 |
| setFocusTraversalPolicy(FocusTraversalPolicy) getFocusTraversalPolicy(FocusTraversalPolicy)
(在 java.awt.Container 中) | 设置或获取焦点遍历策略,或确定是否已设置策略。请注意,在不是焦点循环根的容器上设置焦点遍历策略可能没有明显效果。null 值表示未明确设置策略。如果未设置策略,则从父焦点循环根继承策略。 |
| isFocusCycleRoot() setFocusCycleRoot(boolean)
(在 java.awt.Container 中) | 检查或设置容器是否是焦点遍历循环的根。 |
| isFocusTraversalPolicyProvider() setFocusTraversalPolicyProvider(boolean)
(在 java.awt.Container 中) | 检查或设置容器是否用于提供焦点遍历策略。 |
输入验证 API
| 类或方法 | 目的 |
|---|---|
| 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 |
如何编写焦点监听器 | 报告发生在几个组件上的所有焦点事件,以展示焦点事件触发的情况。 |
如何使用键绑定
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
JComponent类支持键绑定作为响应用户键入的单个键的一种方式。以下是键绑定适用的一些示例情况:
-
您正在创建自定义组件并希望支持键盘访问。
例如,您可能希望组件在焦点在其上且用户按下空格键时做出反应。
-
您希望覆盖现有键绑定的行为。
例如,如果您的应用程序通常对 F2 键的按下做出特定反应,您可能希望它执行不同的操作或忽略按键。
-
您希望为现有操作提供新的键绑定。
例如,您可能强烈感觉 Control-Shift-Insert 应该执行粘贴操作。
您通常不需要直接使用键绑定。它们在幕后由助记键(所有按钮和选项卡窗格以及JLabel支持)和加速键(菜单项支持)使用。您可以在启用键盘操作部分找到助记键和加速键的覆盖范围。
键绑定的替代方法是使用键监听器。键监听器作为与键盘输入的低级接口有其用处,但对于响应单个键,键绑定更合适且更易于维护。如果组件没有焦点时要激活键绑定,则键监听器也会变得困难。键绑定的一些优点是它们在一定程度上是自解释的,考虑了包含层次结构,鼓励可重用的代码块(Action对象),并允许轻松删除、自定义或共享操作。此外,它们使更改绑定到操作的键变得容易。另一个Action的优点是它们具有启用状态,这提供了一种轻松禁用操作而无需跟踪其附加到的组件的方法。
本节的其余部分将为您提供使用键绑定所需的详细信息:
-
键绑定的工作原理
-
如何创建和删除键绑定
-
键绑定 API
-
使用键绑定的示例
键绑定的工作原理
JComponent提供的键绑定支持依赖于InputMap和ActionMap类。输入映射将键盘按键绑定到操作名称,而操作映射指定与每个操作名称对应的 action。从技术上讲,您不需要在映射中使用操作名称;您可以使用任何对象作为映射中的“键”。但是,按照惯例,您使用命名操作的字符串作为“键”。
每个InputMap/ActionMap都有一个通常来自 UI 的父级。每当外观和感觉发生变化时,父级都会被重置。通过这种方式,开发人员指定的任何绑定在外观和感觉变化时都不会丢失。
每个JComponent都有一个动作映射和三个输入映射。输入映射对应以下焦点情况:
JComponent.WHEN_FOCUSED
组件具有键盘焦点。WHEN_FOCUSED输入映射通常在组件没有子组件时使用。例如,按钮使用WHEN_FOCUSED映射绑定 Space 键。
这些绑定只在组件获得焦点时生效。
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
该组件包含(或是)具有焦点的组件。这个输入映射通常用于复合组件 一个其实现依赖于子组件的组件。例如,JTable使用WHEN_ANCESTOR_OF_FOCUSED_COMPONENT制作所有绑定,以便如果用户正在编辑,上箭头键(例如)仍然会更改所选单元格。
JComponent.WHEN_IN_FOCUSED_WINDOW
组件的窗口具有焦点或包含具有焦点的组件。这个输入映射通常用于助记键或加速键,无论焦点在窗口的哪个位置,它们都需要处于活动状态。
当用户按下一个键时,JComponent键事件处理代码会在一个或多个输入映射中搜索有效的按键绑定。当找到一个绑定时,它会在动作映射中查找相应的动作。如果动作已启用,则绑定有效且动作被执行。如果它被禁用,则继续搜索有效绑定。
如果一个按键存在多个绑定,只有找到的第一个有效绑定会被使用。输入映射按照以下顺序进行检查:
-
焦点组件的
WHEN_FOCUSED输入映射。 -
焦点组件的
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射。 -
焦点组件的父级的
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射,然后是其父级的父级,依此类推,沿着包含关系层次结构向上继续。注意:跳过禁用组件的输入映射。 -
所有焦点窗口中启用的组件的
WHEN_IN_FOCUSED_WINDOW输入映射都会被搜索。因为搜索组件的顺序是不可预测的,避免重复的WHEN_IN_FOCUSED_WINDOW绑定!
让我们考虑两种典型的按键绑定情况:一个按钮对 Space 键做出反应,一个带有默认按钮对 Enter 键做出反应的框架。
在第一种情况下,假设用户在JButton具有键盘焦点时按下 Space 键。首先,按钮的键监听器会收到事件通知。假设没有键监听器消耗事件(通过在KeyEvent上调用consume方法),则会查阅按钮的WHEN_FOCUSED输入映射。会找到一个绑定,因为JButton使用该输入映射将 Space 绑定到一个动作名称。然后在按钮的动作映射中查找动作名称,并调用该动作的actionPerformed方法。KeyEvent被消耗,处理停止。
在第二种情况下,假设在具有默认按钮的框架内的任何位置按下 Enter 键(使用JRootPane的setDefaultButton方法设置默认按钮)。无论焦点在哪个组件上,首先通知其键监听器。假设它们中没有一个消耗了键事件,则会查阅焦点组件的WHEN_FOCUSED输入映射。如果它没有与该键绑定的内容,或者与该键绑定的操作已禁用,则会查阅焦点组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射,然后(如果未找到绑定或与该键绑定的操作已禁用)查阅组件在包含层次结构中的每个祖先的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射。最终,会搜索根窗格的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射。由于该输入映射对 Enter 有有效绑定,因此会执行该操作,导致默认按钮被点击。
如何创建和删除键绑定
这是一个指定组件应该对 F2 键做出反应的示例:
component.getInputMap().put(KeyStroke.getKeyStroke("F2"),
"doSomething");
component.getActionMap().put("doSomething",
anAction);
*//where anAction is a javax.swing.Action*
如前面的代码所示,要获取组件的动作映射,您可以使用getActionMap方法(从JComponent继承而来)。要获取输入映射,您可以使用getInputMap(int)方法,其中整数是前面列表中显示的JComponent.WHEN_*FOCUSED*常量之一。或者,在通常情况下,常量为JComponent.WHEN_FOCUSED,您可以直接使用不带参数的getInputMap。
要向其中一个映射添加条目,请使用put方法。您可以使用KeyStroke.getKeyStroke(String)方法获取KeyStroke对象来指定键。您可以在如何使用操作中找到创建Action(放入动作映射中)的示例。
这里是一个稍微复杂的示例,指定组件应该对 Space 键做出反应,就像用户点击鼠标一样。
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),
"pressed");
component.getInputMap().put(KeyStroke.getKeyStroke("released SPACE"),
"released");
component.getActionMap().put("pressed",
pressedAction);
component.getActionMap().put("released",
releasedAction);
*//where pressedAction and releasedAction are javax.swing.Action objects*
要使组件忽略其通常会响应的按键,您可以使用特殊的动作名称"none"。例如,以下代码使组件忽略 F2 键。
component.getInputMap().put(KeyStroke.getKeyStroke("F2"),
"none");
注意:
前面的代码不会阻止搜索相关的 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT 和 WHEN_IN_FOCUSED_WINDOW 输入映射以查找 F2 键绑定。要阻止此搜索,必须使用有效的操作而不是 "none"。例如:
Action doNothing = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
//do nothing
}
};
component.getInputMap().put(KeyStroke.getKeyStroke("F2"),
"doNothing");
component.getActionMap().put("doNothing",
doNothing);
键绑定 API
以下表格列出了常用的键绑定 API。还请参阅 API 表格创建和使用操作,在如何使用操作部分。
-
创建和使用 InputMaps
-
创建和使用 ActionMaps
获取和使用 InputMaps
| 方法 | 目的 |
|---|
| 获取输入映射 获取输入映射(int) |
(在 JComponent 中) | 获取组件的一个输入映射。参数可以是这些 JComponent 常量之一:WHEN_FOCUSED、WHEN_IN_FOCUSED_WINDOW 或 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT。无参数方法获取 WHEN_FOCUSED 输入映射。 |
设置(KeyStroke,Object) (在 InputMap 中) |
设置与指定键盘按键关联的操作名称。如果第二个参数为 null,此方法将移除键盘按键的绑定。要忽略键盘按键,请使用 "none" 作为第二个参数。 |
|---|---|
获取指定用户键盘活动的对象 (在 KeyStroke 中) |
获取指定用户键盘活动的对象。典型参数为 "alt shift X"、"INSERT" 和 "typed a"。查看KeyStroke API 文档获取完整详情和getKeyStroke方法的其他形式。 |
获取和使用 ActionMaps
| 方法 | 目的 |
|---|---|
获取操作映射 (在 JComponent 中) |
获取将名称映射到组件操作的对象。 |
设置(Object,Action) (在 ActionMap 中) |
设置与指定名称关联的操作。如果第二个参数为 null,此方法将移除名称的绑定。 |
使用键绑定的示例
以下表格列出了使用键绑定的示例:
| 示例 | 描述位置 | 注释 |
|---|---|---|
| TableFTFEditDemo | 如何使用表格 | IntegerEditor 类在格式化文本字段上注册一个键绑定,以在用户按下 Enter 键时验证输入。 |
| 文本组件演示 | 文本组件特性 | 在文本窗格上注册了按键绑定,用户按下 Control-B、Control-F、Control-P 和 Control-N 键时可以在文本中导航。 |
如何在对话框中使用模态
译文:
docs.oracle.com/javase/tutorial/uiswing/misc/modality.html
Java™ SE 6 已解决了在平台早期版本中出现的模态问题。新的模态模型使开发人员能够限定对话框的模态阻止。
在继续使用新的模态模型之前,请查看以下术语:
-
对话框 — 一个带有标题和边框的顶级弹出窗口,通常从用户那里获取某种形式的输入。对话框可以是模态或非模态。有关对话框的更多信息,请参阅对话框概述页面中的如何创建对话框。
-
模态对话框 — 一个会阻止应用程序中其他顶级窗口输入的对话框,除了以该对话框为所有者创建的窗口。模态对话框会捕获窗口焦点,直到它被关闭,通常是响应按钮按下。
-
非模态对话框 — 一个对话框,允许您在显示此对话框时操作其他窗口。
在 Java SE 6 中,模态和非模态对话框的行为已经更改,使其始终出现在所有被阻止的窗口的顶部,而不仅仅是它们的父窗口的顶部。
Java SE 6 支持以下模态类型:
-
非模态类型 — 非模态对话框在可见时不会阻止任何其他窗口。
-
文档模态类型 — 文档模态对话框会阻止同一文档的所有窗口,除了其子层次结构的窗口。在此上下文中,文档是共享一个称为文档根的最近祖先窗口的窗口层次结构。
-
应用程序模态类型 — 应用程序模态对话框会阻止同一应用程序的所有窗口,除了其子层次结构的窗口。如果在浏览器环境中启动了几个小程序,浏览器可以将它们视为单独的应用程序或单个应用程序。此行为取决于实现。
-
工具包模态类型 — 工具包模态对话框会阻止在同一工具包中运行的所有窗口,除了其子层次结构的窗口。如果启动了几个小程序,它们都将使用相同的工具包。因此,从小程序显示的工具包模态对话框可能会影响其他小程序和嵌入 Java 运行时环境的浏览器实例的所有窗口。
此外,您可以设置模态排除模式:
- 排除模式 — 任何顶级窗口都可以标记为不被模态对话框阻止。此属性使您可以设置模态排除模式。
Dialog.ModalExclusionType枚举指定可能的模态排除类型。
注意: 新的模态模型不实现系统模态,该模态会在模态对话框激活时阻止显示在桌面上的所有应用程序(包括 Java 应用程序)。
ModalityDemo 示例演示了上述四种模态类型中的前三种。

*此图已经缩小以适应页面。
点击图像以查看其自然大小。
试试这个:
-
点击启动按钮以使用Java™ Web Start运行 ModalityDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
将出现以下对话框:
-
Book 1(父框架)
-
Book 2(父框架)
-
反馈(父框架)
-
经典(排除的框架)
-
-
切换到 Book 1 框架并为该书选择传记标题,然后选择确定。
-
传记标题将显示在对话框的标题中。在文本字段中输入名称,例如 - “约翰”。
-
切换到 Book 1 框架并将标题更改为 Funny Tale,然后选择确定。由于输入名称的对话框是无模态的,你可以轻松切换到其父框架。
注意: 无模态对话框标题已更改为 Funny Tale。
-
在无模态对话框中选择确定。
-
有趣的故事文档模态对话框出现。
-
在文本字段中输入一些文本。注意它是由你在无模态对话框中输入的名称签名的。
-
切换到无模态对话框,尝试更改你的名字。你无法这样做,因为文档模态对话框会阻止其父层次结构中的所有窗口。
-
对于 Book 2 的父框架执行相同的操作序列(步骤 3 - 9)。
-
尝试切换到不同的对话框。你会注意到你可以切换到经典框架或反馈框架,以及 Book 1 框架或 Book 2 框架的对话框。
-
切换到反馈父框架。选择评价自己。
-
确认对话框将会出现。尝试切换到不同的对话框。你只能切换到经典对话框,因为标准确认对话框是一个应用程序模态对话框,它会阻止同一应用程序的所有窗口。然而,你会注意到你可以在经典框架中选择你最喜欢的古典作家。这个框架是通过使用
APPLICATION_EXCLUDE模态排除类型创建的,它可以防止所有顶层窗口被任何应用程序模态对话框阻止。
以下代码片段显示了如何创建不同模态类型的对话框:
//The Book 1 parent frame
f1 = new JFrame("Book 1 (parent frame)");
...
//The modeless dialog box
d2 = new JDialog(f1);
...
//The document-modal dialog box
d3 = new JDialog(d2, "", Dialog.ModalityType.DOCUMENT_MODAL);
...
//The Book2 parent frame
f4 = new JFrame("Book 2 (parent frame)");
...
//The modeless dialog box
d5 = new JDialog(f4);
...
//The document-modality dialog box
d6 = new JDialog(d5, "", Dialog.ModalityType.DOCUMENT_MODAL);
...
//The excluded frame
f7 = new JFrame("Classics (excluded frame)");
f7.setModalityExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDED);
...
//The Feedback parent frame and Confirm Dialog box
f8 = new JFrame("Feedback (parent frame)");
...
JButton b8 = new JButton("Rate yourself");
b8.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showConfirmationDialog(null,
"I really like my book",
"Question (application-modal dialog)",
JOptionPane.Yes_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
}
});
在ModalityDemo.java文件中找到演示的完整代码。
在 Java SE 6 中,您可以创建一个没有父级的文档模态对话框。因为Dialog类是Window类的子类,如果它没有所有者,Dialog实例会自动成为文档的根。因此,如果这样的对话框是文档模态的,它的阻止范围为空,它的行为就像是一个非模态对话框。
模态 API
JDialog类的构造函数使您能够创建不同模态类型的对话框。
| 构造函数 | 目的 |
|---|---|
| JDialog(Dialog owner) | 创建一个具有指定Dialog所有者但没有标题的非模态对话框。 |
| JDialog(Dialog owner, boolean modal) | 创建一个具有指定Dialog所有者和模态性的对话框。 |
| JDialog(Dialog owner, String title) | 创建一个具有指定Dialog所有者和标题的非模态对话框。 |
| JDialog(Dialog owner, String title, boolean modal) | 创建一个具有指定Dialog所有者、标题和模态性的对话框。 |
| JDialog(Dialog owner, String title, boolean modal, GraphicsConfiguration gc) | 创建一个具有指定Dialog所有者、标题、模态性和图形配置的对话框。 |
| JDialog(Frame owner) | 创建一个具有指定Frame所有者但没有标题的非模态对话框。如果所有者的值为 null,则会将一个共享的隐藏框架设置为对话框的所有者。 |
| JDialog(Window owner, String title, Dialog.ModalityType modalityType) | 使用指定的Window所有者、标题和模态性创建对话框框。 |
以下表格列出了从java.awt.Dialog类继承的方法。
| 方法 | 目的 |
|---|---|
| 获取模态类型 | 返回此对话框框的模态类型。 |
| 设置模态类型 | 设置此对话框框的模态类型。查看模态类型以获取可能的模态类型。如果给定的模态类型不受支持,则使用MODELESS类型。要确保已设置模态类型,请在调用此方法后调用getModalityType()方法。 |
使用模态 API 的示例
以下表格列出了在对话框中使用模态性的示例。
| 示例 | 描述位置 | 备注 |
|---|---|---|
模态演示 |
本节 | 创建不同模态类型的对话框,演示这些类型的作用域阻塞。 |
如何打印表格
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/printtable.html
JTable类提供了打印表格的支持。JTable打印 API 包括允许你实现基本和高级打印任务的方法。对于常见的打印任务,当你只需要简单打印一个表格时,直接使用print方法。print方法有多种形式,带有不同的参数集。此方法准备你的表格,获取相应的Printable对象,并将其发送到打印机。
如果Printable对象的默认实现不符合你的需求,你可以通过重写getPrintable方法来自定义打印布局,以包装默认的Printable或者完全替换它。
打印表格的最简单方法是无参数调用print方法。请参见下面的代码示例。
try {
boolean complete = table.print();
if (complete) {
/* show a success message */
...
} else {
/*show a message indicating that printing was cancelled */
...
}
} catch (PrinterException pe) {
/* Printing failed, report to the user */
...
}
当你无参数调用print方法时,会显示一个打印对话框,然后以FIT_WIDTH模式交互式打印你的表格,没有页眉或页脚。下面的代码示例显示了带有完整参数集的print方法签名。
boolean complete = table.print(JTable.PrintMode printMode,
MessageFormat headerFormat,
MessageFormat footerFormat,
boolean showPrintDialog,
PrintRequestAttributeSet attr,
boolean interactive,
PrintService service);
当你调用print方法并传入所有参数时,你明确选择了打印特性,如打印模式、页眉和页脚文本、打印属性、目标打印服务,以及是否显示打印对话框,以及是否交互式或非交互式打印。要决定哪些参数最适合你的需求,请参阅下面可用特性的描述。
JTable打印 API 提供以下特性:
-
交互式或非交互式打印
-
显示打印对话框
-
向打印布局添加页眉或页脚(或两者)
-
选择打印模式
-
自动布局和分页
交互式或非交互式打印
在交互模式下,会显示一个带有中止选项的进度对话框,用于打印过程中。这里是一个进度对话框的示例。

此对话框使用户能够跟踪打印进度。进度对话框是模态的,这意味着在屏幕上显示时,用户无法与表格交互。在打印过程中,重要的是你的表格保持不变,否则打印行为将是未定义的。然而,交互式打印不会阻止其他开发人员的代码修改表格。例如,有另一个线程使用SwingUtilities.invokeLater方法发布更新。因此,为确保正确的打印行为,你应该确保你自己的代码在打印过程中不修改表格。
或者,您可以以非交互方式打印表格。在此模式下,打印立即在事件分派线程上开始,并完全阻止任何事件的处理。一方面,此模式安全地保护表格免受任何更改,直到打印完成。另一方面,此模式完全剥夺用户与 GUI 的任何交互。这就是为什么只有在从不可见 GUI 的应用程序打印时才能推荐非交互打印。
打印对话框
您可以显示一个标准的打印对话框,允许用户执行以下操作:
-
选择打印机
-
指定打印份数
-
更改打印属性
-
在开始打印之前取消打印
-
开始打印

您可能注意到打印对话框中没有指定打印输出的总页数。这是因为表打印实现使用了PrintableAPI,而在打印时不知道总页数。
向打印布局添加页眉或页脚(或两者)
头部和页脚由MessageFormat参数提供。这些参数允许对头部和页脚进行本地化。阅读MessageFormat类的文档,因为一些字符,如单引号,是特殊的,需要避免使用。头部和页脚都居中。您可以使用{0}插入页码。
MessageFormat footer = new MessageFormat("第 - {0} 页");
由于在打印时不知道输出的总页数,因此无法指定类似"第 1 页,共 5 页"的编号格式。
打印模式
打印模式负责缩放输出并将其分布在页面上。您可以以以下一种模式之一打印表格:
-
PrintMode.NORMAL -
PrintMode.FIT_WIDTH
在NORMAL模式下,表格以其当前大小打印。如果列不适合一页,它们将根据表的ComponentOrientation跨越额外的页面。在FIT_WIDTH模式下,如果需要,表格的大小会更小,以便在每页上容纳所有列。请注意,宽度和高度都会按比例缩放,以提供相同纵横比的输出。在两种模式下,行会按顺序跨越多个页面,每页尽可能多的行。
自动布局和分页
使用JTable打印 API,您无需关心布局和分页。您只需为print方法指定适当的参数,如打印模式和页脚文本格式(如果您想在页脚插入页码)。如前所示,您可以通过在提供给MessageFormat页脚参数的字符串中包含"{0}"来在页脚中指定页码。在打印输出中,{0}将被当前页码替换。
表打印示例
让我们看一个名为TablePrintDemo1的示例。此程序的完整代码可以在TablePrintDemo1.java中找到。这个演示的丰富 GUI 是由NetBeans IDE GUI 构建器自动生成的。这是TablePrintDemo1应用程序的图片。

试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行 TablePrintDemo1(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
应用程序窗口底部的每个复选框都有工具提示。将光标悬停在复选框上以查找其目的。
-
在“页眉”或“页脚”复选框中编辑文本,或两者都提供不同的页眉或页脚。
-
取消选中“页眉”或“页脚”复选框,或两者都关闭页眉或页脚。
-
取消选中“显示打印对话框”复选框以关闭打印对话框。
-
取消选中“适应打印页面宽度”复选框以选择在
NORMAL模式下打印。 -
取消选中“交互式(显示状态对话框)”复选框以关闭打印对话框。
-
单击“打印”按钮,根据所选选项打印表格。
每当 Web 启动的应用程序尝试打印时,Java Web Start 会弹出一个安全对话框,询问用户是否允许打印。要继续打印,用户必须接受请求。
当您取消交互复选框时,会出现一条消息,警告用户打印非交互式的缺点。您可以在PrintGradesTable方法中找到打印代码。调用此方法时,该方法首先从 GUI 组件中获取选定的选项集,然后调用print方法如下。
boolean complete = gradesTable.print(mode, header, footer,
showPrintDialog, null,
interactive, null);
print方法返回的值然后用于显示成功消息或用户取消打印的消息。
另一个重要特性是表打印 API 使用表渲染器。通过使用表的渲染器,API 提供了一个打印输出,看起来像屏幕上的表格。看一下屏幕上表格的最后一列。它包含自定义图像,表示每个学生的通过或失败状态。现在看打印结果。您会发现勾号和叉号看起来一样。
这是FIT_WIDTH模式下 TablePrintDemo1 打印结果的图片。

*此图已经缩小以适应页面。
点击图片查看其原始大小。
TablePrintDemo2 示例
TablePrintDemo2 示例基于先前的演示,并具有相同的界面。唯一的区别在于打印输出。如果你更仔细地查看 TablePrintDemo1 的打印结果,你可能会注意到勾号和 X 号有些模糊。TablePrintDemo2 示例展示了如何自定义表格以使图像在表格打印中更易辨认。在这个演示中,重写的getTableCellRendererComponent方法会判断表格是否正在打印,并返回更清晰的黑白图像。如果表格没有被打印,它会返回在屏幕上看到的彩色图像。
点击启动按钮以使用Java™ Web Start运行 TablePrintDemo2(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
JComponent类中定义的isPaintingForPrint方法允许我们自定义打印内容与屏幕上看到的内容之间的差异。从TablePrintDemo2.java中提取的自定义单元格渲染器的代码如下。此代码根据isPaintingForPrint方法返回的值选择要使用的图像。
/**
* A custom cell renderer that extends TablePrinteDemo1's renderer, to instead
* use clearer black and white versions of the icons when printing.
*/
protected static class BWPassedColumnRenderer extends PassedColumnRenderer {
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
/* if we're currently printing, use the black and white icons */
if (table.isPaintingForPrint()) {
boolean status = (Boolean)value;
setIcon(status ? passedIconBW : failedIconBW);
} /* otherwise, the superclass (colored) icons are used */
return this;
}
}
这是以FIT_WIDTH模式打印的 TablePrintDemo2 的结果图片。

*此图已经缩小以适应页面。
点击图片查看其原始大小。
TablePrintDemo3 示例
TablePrintDemo3 示例基于前两个演示。此示例展示了如何通过包装默认的Printable对象并添加额外装饰来提供自定义的Printable实现。这个演示具有类似的界面,但是头部和底部的复选框被禁用,因为自定义的可打印对象将提供自己的页眉和页脚。
点击“启动”按钮以使用Java™ Web Start运行 TablePrintDemo3(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
此示例将表格打印在剪贴板图像内部。这里是以FIT_WIDTH模式打印结果的图片。

*此图已经缩小以适应页面。
点击图片查看其原始大小。
此程序的完整代码可以在TablePrintDemo3.java中找到。在此演示中,使用了JTable类的自定义子类FancyPrintingJTable。这个FancyPrintingJTable类重写了getPrintable方法,以返回一个自定义可打印对象,该对象用自己的装饰、页眉和页脚包装默认可打印对象。这里是getPrintable方法的实现。
public Printable getPrintable(PrintMode printMode,
MessageFormat headerFormat,
MessageFormat footerFormat) {
MessageFormat pageNumber = new MessageFormat("- {0} -");
/* Fetch the default printable */
Printable delegate = super.getPrintable(printMode, null, pageNumber);
/* Return a fancy printable that wraps the default */
return new FancyPrintable(delegate);
}
FancyPrintable类负责将默认可打印对象包装成另一个可打印对象,并设置剪贴板图像。当实例化此类的一个实例时,它会加载组装剪贴板图像所需的图像,计算剪贴板图像所需的区域,计算表格的缩小区域,将表格打印到较小区域,并组装并打印剪贴板图像。
注意代码在组装剪贴板图像时对页面大小的灵活性。代码考虑实际页面尺寸,并组装辅助图像,根据需要拉伸其中一些图像,以使最终的剪贴板图像适合实际页面尺寸。下图显示了辅助图像,并指示这些图像如何形成最终输出。

*此图已经缩小以适应页面。
点击图片查看其原始大小。
表打印 API
此部分列出了JTable类中定义的允许您打印表格的方法。
| 方法 | 目的 |
|---|
| boolean print() boolean print(printMode)
boolean print(printMode, MessageFormat, MessageFormat)
boolean print(printMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet, boolean)
boolean print(printMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet, boolean, PrintService) | 在没有参数的情况下调用时,显示打印对话框,然后以FIT_WIDTH模式交互式打印此表格,不包含页眉或页脚文本。如果用户继续打印,则返回true,如果用户取消打印,则返回false。在使用完整参数集调用时,根据指定的参数打印此表格。第一个参数指定打印模式。两个MessageFormat参数指定页眉和页脚文本。第一个布尔参数定义是否显示打印对话框。另一个布尔参数指定是否交互式打印。使用另外两个参数可以指定打印属性和打印服务。
每当省略PrintService参数时,将使用默认打印机。
| Printable getPrintable(PrintMode, MessageFormat, MessageFormat) | 返回用于打印表格的Printable。重写此方法以获取自定义的Printable对象。您可以将一个Printable对象包装到另一个中以获得各种布局。 |
|---|
使用表格打印的示例
此表列出了使用表格打印的示例,并指向这些示例所描述的位置。
| 示例 | 描述位置 | 备注 |
|---|---|---|
TablePrintDemo |
如何使用表格 | 展示了表格打印的基本功能,如显示打印对话框,然后以FIT_WIDTH模式交互式打印,并将页码作为页眉。 |
TablePrintDemo1 |
本页面 | 展示了表格打印的基础知识,并提供了丰富的图形用户界面。允许用户指定页眉或页脚文本,选择打印模式,打开或关闭打印对话框,并选择交互式或非交互式打印。 |
TablePrintDemo2 |
本页面 | 基于 TablePrintDemo1,这个示例具有相同的界面。这个演示展示了如何自定义表格,使打印结果与屏幕上显示的表格看起来不同。 |
TablePrintDemo3 |
本页面 | 这个演示展示了高级表格打印功能,例如将默认的可打印表格包装成另一个可打印表格,以获得不同的布局。 |
如何打印文本
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/printtext.html
JTextComponent 类提供了打印文本文档的支持。JTextComponent API 包括允许您实现基本和高级打印任务的方法。支持的格式包括 HTML、RTF 和纯文本。对于简单打印文本文档等常见打印任务,直接使用 print 方法。print 方法有多种形式,带有不同的参数集。该方法准备您的文本文档,获取相应的 Printable 对象,并将其发送到打印机。
如果默认的 Printable 对象实现不符合您的需求,您可以通过重写 getPrintable 方法来自定义打印布局,以包装默认的 Printable 或完全替换它。
打印文本组件的最简单方法是不带参数调用 print 方法。请参阅下面的代码示例。
try {
boolean complete = textComponent.print();
if (complete) {
/* show a success message */
...
} else {
/*show a message indicating that printing was cancelled */
...
}
} catch (PrinterException pe) {
/* Printing failed, report to the user */
...
}
当您不带参数调用 print 方法时,将显示一个打印对话框,然后您的文本组件将交互式打印,没有页眉或页脚。下面的代码示例显示了带有完整参数集的 print 方法签名。
boolean complete = textComponent.print(MessageFormat headerFormat,
MessageFormat footerFormat,
boolean showPrintDialog,
PrintService service
PrintRequestAttributeSet attributes,
boolean interactive);
当您使用所有参数调用 print 方法时,您可以显式选择打印功能,如页眉和页脚文本、打印属性、目标打印服务,以及是否显示打印对话框,以及是打印交互式还是非交互式。要决定哪些参数最适合您的需求,请参阅下面可用功能的描述。
JTextComponent 打印 API 提供以下功能:
-
交互式或非交互式打印
-
显示打印对话框
-
向打印布局添加页眉或页脚(或两者)
-
自动布局和分页
交互式或非交互式打印
在交互模式下,将显示一个带有中止选项的进度对话框,用于打印过程。这里是一个进度对话框的示例。

该对话框允许用户跟踪打印进度。当在事件分派线程上调用 print 方法时,进度对话框是模态的,否则是非模态的。在打印过程中,确保您的文档保持不变很重要,否则打印行为是未定义的。print 方法确保您的文档不会被更改,并在打印期间禁用组件。
如果您在事件分派线程上以非交互模式调用 print 方法,则所有事件,包括重绘,都将被阻塞。这就是为什么只建议在具有不可见 GUI 的应用程序上在 EDT 上非交互地打印。
打印对话框
您可以显示一个标准的打印对话框,允许用户执行以下操作:
-
选择打印机
-
指定打印份数
-
更改打印属性
-
取消打印之前的启动
-
开始打印

您可能会注意到打印对话框没有指定打印输出中的总页数。这是因为文本打印实现使用了Printable API,并且在打印时不知道总页数。
向打印布局添加页眉或页脚(或两者)
头部和页脚由MessageFormat参数提供。这些参数允许对头部和页脚进行本地化。阅读MessageFormat类的文档,因为像单引号这样的字符是特殊的,需要避免使用。头部和页脚都居中。您可以使用{0}插入页码。
MessageFormat footer = new MessageFormat("第 - {0} 页");
由于在打印时不知道输出中的总页数,因此无法指定类似“第 1 页 / 共 5 页”这样的编号格式。
自动布局和分页
使用JTextComponent打印 API,您无需关心布局和分页。布局和分页都是自动完成的。文档内容会被格式化以适应页面大小,并跨多个页面展开。如果您想在页脚插入页码,只需为print方法指定适当的页脚文本格式。正如之前演示的,您可以通过在提供给MessageFormat页脚参数的字符串中包含"{0}"来指定页脚中的页码。在打印输出中,{0}将被当前页码替换。
文本区域打印示例
让我们看一个名为TextAreaPrintingDemo的示例。此演示的主要特点是根据用户的选择在事件分发线程或后台线程上打印文本文档。此演示显示一个文本区域,允许选择几个打印功能,并根据所选选项打印文本区域的内容。此程序的完整代码可以在TextAreaPrintingDemo.java中找到。此演示的丰富 GUI 是使用NetBeans IDE GUI 构建器构建的。这是TextAreaPrintingDemo应用程序的图片。

尝试这个:
-
点击“启动”按钮,使用Java™ Web Start来运行 TextAreaPrintingDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
在“页眉”或“页脚”复选框中编辑文本,以提供不同的页眉或页脚。
-
清除“显示进度对话框”复选框,如果您想要在不显示进度对话框的情况下打印,这意味着非交互式打印。请注意,一旦开始打印,您将无法取消打印。
-
清除“后台打印”复选框以选择在事件分发线程上打印。请注意,在 EDT 上非交互式打印将使您的应用程序无响应 —— 在打印过程中将阻塞与您的应用程序的交互。
-
点击“打印”按钮,根据所选选项打印文本区域的内容。
每当 Web 启动的应用程序尝试打印时,Java Web Start 会打开一个安全对话框,询问用户是否允许打印,除非在系统设置中已经授予了此权限。要继续打印,用户必须接受请求。
为“打印”按钮注册了一个动作监听器。当用户点击“打印”按钮时,actionPerformed方法调用print方法,启动打印任务。打印任务是一个SwingWorker对象。下面的代码示例显示了PrintingTask类的实现方式。
private class PrintingTask extends SwingWorker<Object, Object> {
private final MessageFormat headerFormat;
private final MessageFormat footerFormat;
private final boolean interactive;
private volatile boolean complete = false;
private volatile String message;
public PrintingTask(MessageFormat header, MessageFormat footer,
boolean interactive) {
this.headerFormat = header;
this.footerFormat = footer;
this.interactive = interactive;
}
@Override
protected Object doInBackground() {
try {
complete = text.print(headerFormat, footerFormat,
true, null, null, interactive);
message = "Printing " + (complete ? "complete" : "canceled");
} catch (PrinterException ex) {
message = "Sorry, a printer error occurred";
} catch (SecurityException ex) {
message =
"Sorry, cannot access the printer due to security reasons";
}
return null;
}
@Override
protected void done() {
message(!complete, message);
}
}
下面的代码示例显示了print方法如何从 GUI 组件中获取所选选项集,然后创建PrintingTask类的实例,并执行打印操作。
private void print(java.awt.event.ActionEvent evt) {
MessageFormat header = createFormat(headerField);
MessageFormat footer = createFormat(footerField);
boolean interactive = interactiveCheck.isSelected();
boolean background = backgroundCheck.isSelected();
PrintingTask task = new PrintingTask(header, footer, interactive);
if (background) {
task.execute();
} else {
task.run()
}
}
粗体代码说明了根据background参数的值调用PrintingTask的方法。每当用户喜欢在后台线程上打印时,将调用execute方法,该方法安排打印任务在后台线程上执行。否则,run方法在 EDT 上执行打印任务。
由于打印大型文档是一项耗时的任务,建议在后台线程上执行打印操作。
文本批量打印示例
TextBatchPrintingDemo示例演示了在后台线程上打印不可见的 HTML 文本文档。启动时,此演示显示一个带有 URL 列表的页面。你可以访问一个 HTML 页面,将显示的页面添加到打印列表中,一旦选择了所有需要的页面,就可以在后台线程上一次性打印它们。此程序的整个代码可以在TextBatchPrintingDemo.java中找到。这是TextBatchPrintingDemo应用程序的图片。

试试这个:
-
点击启动按钮以使用Java™ Web Start运行 TextBatchPrintingDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
点击任何链接查看相应的 HTML 页面。
-
按下 ALT+A 或选择文件 > 添加页面菜单项将显示的页面添加到右侧的打印列表中。
-
按下 ALT+H 或选择文件 > 主页菜单项返回到演示的主页。
-
将需要的页面添加到打印列表中。
-
按下 ALT+C 或选择文件 > 清除所选菜单项,如果需要清除打印列表并重新构建。
-
按下 ALT+P 或选择文件 > 打印所选菜单项以打印所选页面。
-
按下 ALT+Q 或选择文件 > 退出菜单项来退出应用程序。
你可以在printSelectedPages方法中找到打印代码。调用时,此方法首先获取选择打印的页面数量。下面的代码示例显示了printSelectedPages方法如何为每个页面创建一个Runnable对象,然后在单独的线程上打印当前页面。
for (int i = 0; i < n; i++) {
final PageItem item = (PageItem) pages.getElementAt(i);
// This method is called from EDT. Printing is a time-consuming
// task, so it should be done outside EDT, in a separate thread.
Runnable printTask = new Runnable() {
public void run() {
try {
item.print(
// Two "false" args mean "no print dialog" and
// "non-interactive" (ie, batch-mode printing).
null, null, false, printService, null, false);
} catch (PrinterException pe) {
JOptionPane.showMessageDialog(null,
"Error printing " + item.getPage() + "\n" + pe,
"Print Error", JOptionPane.WARNING_MESSAGE);
}
}
};
new Thread(printTask).start();
文本打印 API
此部分列出了JTextComponent类中定义的允许打印文本文档的方法。
| 方法 | 目的 |
|---|
| boolean print() boolean print(MessageFormat, MessageFormat)
boolean print(MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet, boolean, PrintService) | 在没有参数的情况下调用时,显示打印对话框,然后交互式打印此文本组件,没有页眉或页脚文本。如果用户继续打印,则返回true,如果用户取消打印,则返回false。当使用两个MessageFormat参数调用时,显示打印对话框,然后交互式打印此文本组件,并显示指定的页眉和页脚文本。
当使用完整参数集调用时,根据指定的参数打印此文本组件。两个MessageFormat参数指定页眉和页脚文本。第一个布尔参数定义是否显示打印对话框。另一个布尔参数指定是否交互式打印。使用另外两个参数,您可以指定打印属性和打印服务。
每当省略一个PrintService参数时,将使用默认打印机。
| Printable getPrintable(MessageFormat, MessageFormat) | 返回一个Printable对象,用于打印您的文本组件。重写此方法以获得自定义的 Printable 对象。您可以将一个 Printable 对象包装到另一个中,以获得复杂的报告和文档。 |
|---|
使用文本打印的示例
此表列出了使用文本打印的示例,并指向这些示例的描述位置。
| 示例 | 描述位置 | 注释 |
|---|---|---|
TextAreaPrintingDemo |
本页面 | 演示了文本打印的基础知识,并提供了丰富的 GUI。允许用户指定页眉或页脚文本,打开或关闭打印对话框,交互式或非交互式选择打印,然后根据所选选项打印。 |
TextBatchPrintingDemo |
本页面 | 此演示显示了一个带有 URL 列表的文本组件,允许用户查看 HTML 页面,将它们添加到打印列表,并在后台线程上一次打印所有选定的页面。 |
如何创建启动画面
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/splashscreen.html
几乎所有现代应用程序都有启动画面。通常启动画面用于以下目的:
-
广告产品
-
在长时间启动时向用户指示应用程序正在启动
-
提供每次访问仅需一次的信息
Java 基础类(包括 Swing 和抽象窗口工具包(AWT))使开发人员能够在 Java 技术应用程序中创建启动画面。然而,由于启动画面的主要目的是向用户提供关于应用程序启动的反馈,因此应用程序启动和启动画面弹出之间的延迟应尽量小。在启动画面弹出之前,应用程序必须加载和初始化 Java™虚拟机(JVM)、AWT、Swing,有时还包括应用程序相关的库。由此产生的几秒钟的延迟使得使用基于 Java™技术的启动画面变得不太理想。
幸运的是,Java™ SE 6 提供了一种解决方案,允许应用程序在虚拟机启动之前更早地显示启动画面。Java 应用程序启动器能够解码图像并在一个简单的非装饰窗口中显示它。
启动画面可以显示任何gif、png或jpeg图像,具有透明度、半透明度和动画效果。下图展示了作为动画gif文件开发的 Java 应用程序启动画面的示例。

SplashScreen类用于关闭启动画面、更改启动画面图像、获取图像位置或大小以及在启动画面中绘制。应用程序不能创建此类的实例。只能存在一个在此类中创建的单个实例,并且可以使用getSplashScreen()静态方法获取此实例。如果应用程序在启动时没有通过命令行或清单文件选项创建启动画面,则getSplashScreen方法返回 null。
通常,开发人员希望保持启动画面图像在屏幕上并在图像上显示一些内容。启动画面窗口具有一个带有 alpha 通道的覆盖层,可以使用传统的Graphics2D接口访问此覆盖层。
以下代码片段显示了如何获取SplashScreen对象,然后如何使用createGraphics()方法创建图形上下文:
...
final SplashScreen splash = SplashScreen.getSplashScreen();
if (splash == null) {
System.out.println("SplashScreen.getSplashScreen() returned null");
return;
}
Graphics2D g = splash.createGraphics();
if (g == null) {
System.out.println("g is null");
return;
}
...
在SplashDemo.java文件中找到演示的完整代码。
注意:
SplashDemo 应用程序使用固定坐标来显示叠加信息。这些坐标是依赖于图像的,并且针对每个启动画面单独计算。
本机启动画面可以通过以下方式显示:
-
命令行参数
-
使用指定清单选项的 Java™存档(JAR)文件
如何使用命令行参数显示启动画面
要从命令行显示启动画面,请使用-splash:命令行参数。此参数是一个 Java 应用程序启动器选项,用于显示启动画面:
java -splash:*<file name> <class name>*
试试这个:
-
将
SplashDemo.java文件保存在名为misc的目录中。 -
按照以下方式编译文件:
javac misc/SplashDemo.java -
将
splash.gif图像保存在images目录中。 -
通过以下参数从命令行运行应用程序:
java -splash:images/splash.gif misc.SplashDemo -
等待直到启动画面完全显示。
-
应用程序窗口出现。要关闭窗口,请从弹出菜单中选择文件|退出,或单击 X。
-
将启动画面名称更改为一个不存在的图像,例如
nnn.gif。按以下方式运行应用程序:java -splash:images/nnn.gif misc.SplashDemo -
您将看到以下输出字符串:
SplashScreen.getSplashScreen() returned null
如何使用 JAR 文件显示启动画面
如果您的应用程序打包在 JAR 文件中,可以在清单文件中使用SplashScreen-Image选项显示启动画面。将图像放在 JAR 文件中,并在选项中指定路径如下:
Manifest-Version: 1.0
Main-Class: *<class name>*
SplashScreen-Image: *<image name>*
试试这个:
-
将
SplashDemo.java文件保存在名为misc的目录中。 -
按照以下方式编译文件:
javac misc/SplashDemo.java -
将
splash.gif图像保存在images目录中。 -
准备
splashmanifest.mf文件如下:Manifest-Version: 1.0 Main-Class: misc.SplashDemo SplashScreen-Image: images/splash.gif -
使用以下命令创建一个 JAR 文件:
jar cmf splashmanifest.mf splashDemo.jar misc/SplashDemo*.class images/splash.gif有关 JAR 文件的更多信息,请参阅在 JAR 文件中打包程序页面中的使用 JAR 文件。
-
运行应用程序:
java -jar splashDemo.jar -
等待直到启动画面完全显示。
-
应用程序窗口出现。要关闭窗口,请从弹出菜单中选择文件|退出,或单击 X。
启动画面 API
SplashScreen类不能用于创建启动画面。只能存在在此类中创建的单个实例。
| 方法 | 目的 |
|---|---|
| getSplashScreen() | 返回用于 Java 启动画面控制的SplashScreen对象。 |
| createGraphics() | 为启动画面叠加图像创建一个图形上下文(作为Graphics2D对象),允许您在启动画面上绘制。 |
| getBounds() | 返回闪屏窗口的边界作为一个Rectangle。 |
| close() | 关闭闪屏并释放所有相关资源。 |
使用 SplashScreen API 的示例
以下表格列出了使用闪屏的示例。
| 示例 | 描述位置 | 备注 |
|---|---|---|
SplashDemo |
本节 | 在打开应用程序窗口之前显示闪屏。 |
如何使用系统托盘
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/systemtray.html
系统托盘是桌面的一个专门区域,用户可以访问当前运行的程序。在各种操作系统上,这个区域可能有不同的称呼。在 Microsoft Windows 上,系统托盘被称为任务栏状态区,而在 GNU Network Object Model Environment (GNOME) 桌面上被称为通知区域。在 K Desktop Environment (KDE) 中,这个区域被称为系统托盘。然而,在每个系统上,托盘区域被桌面上所有运行的应用程序共享。
java.awt.SystemTray 类是在 Java™ SE 版本 6 中引入的,代表桌面的系统托盘。可以通过调用静态方法 SystemTray.getSystemTray() 来访问系统托盘。在调用此方法之前,使用静态方法 isSupported() 来检查系统托盘是否受支持。如果该平台不支持系统托盘,则 isSupported() 方法返回 false。如果应用程序在这种情况下尝试调用 getSystemTray() 方法,该方法将抛出 java.lang.UnsupportedOperationException。
应用程序无法创建 SystemTray 类的实例。只能存在一个在该类中创建的单个实例,并且可以使用 getSystemTray() 方法获取此实例。
系统托盘包含一个或多个托盘图标,可以使用 add(java.awt.TrayIcon) 方法将它们添加到托盘中。当不再需要时,可以使用 remove(java.awt.TrayIcon) 方法将它们移除。
注意: 如果操作系统或 Java 运行时确定无法将图标添加到系统托盘,则 add() 方法可能会抛出 AWTException。例如,如果桌面上不存在系统托盘,则 X-Window 桌面会抛出 AWTException。
TrayIcon类的功能不仅限于在托盘中显示的图标。它还包括文本工具提示、弹出菜单、气球消息以及与之关联的一组监听器。TrayIcon对象生成各种鼠标事件,并支持添加相应的监听器以接收这些事件的通知。TrayIcon类本身处理某些事件。例如,默认情况下,在托盘图标上执行右键单击时,它会显示指定的弹出菜单。执行双击时,TrayIcon对象生成一个ActionEvent来启动应用程序。当鼠标指针悬停在托盘图标上时,将显示工具提示。图标图像会自动调整大小以适应在托盘上分配给图像的空间。
以下演示使用 AWT 包开发,演示了 SystemTray 和 TrayIcon 类的功能。

不幸的是,TrayIcon类的当前实现对 Swing 弹出菜单(JPopupMenu类)的支持有限,并且不允许应用程序使用javax.swing包的所有功能。针对此问题的解决方案建议在 Bug 数据库中描述,参见 Bug ID 6285881。
试试这个:
-
将
bulb.gif图像文件放置在image目录中。编译并运行示例,参考示例索引。 -
托盘图标将出现在系统托盘中。
![托盘图标图像]()
-
双击托盘图标启动相应的应用程序。对话框将显示。
-
将鼠标指针悬停在托盘图标上,然后单击鼠标右键。弹出菜单将出现。
-
选择设置自动调整大小复选框菜单项。注意图标外观已更改如下。
![调整大小后的托盘图标图像]()
-
选择设置工具提示复选框菜单项。将鼠标指针悬停在托盘图标上。工具提示将出现。
-
选择关于菜单项。对话框将出现。关闭对话框。
-
选择任何显示子菜单项。这些项中的每一个都会显示特定类型的消息对话框:错误、警告、信息或标准。
-
使用退出菜单项退出应用程序。
以下代码片段显示了如何向系统托盘添加托盘图标并应用弹出菜单:
...
//Check the SystemTray is supported
if (!SystemTray.isSupported()) {
System.out.println("SystemTray is not supported");
return;
}
final PopupMenu popup = new PopupMenu();
final TrayIcon trayIcon =
new TrayIcon(createImage("images/bulb.gif", "tray icon"));
final SystemTray tray = SystemTray.getSystemTray();
// Create a pop-up menu components
MenuItem aboutItem = new MenuItem("About");
CheckboxMenuItem cb1 = new CheckboxMenuItem("Set auto size");
CheckboxMenuItem cb2 = new CheckboxMenuItem("Set tooltip");
Menu displayMenu = new Menu("Display");
MenuItem errorItem = new MenuItem("Error");
MenuItem warningItem = new MenuItem("Warning");
MenuItem infoItem = new MenuItem("Info");
MenuItem noneItem = new MenuItem("None");
MenuItem exitItem = new MenuItem("Exit");
//Add components to pop-up menu
popup.add(aboutItem);
popup.addSeparator();
popup.add(cb1);
popup.add(cb2);
popup.addSeparator();
popup.add(displayMenu);
displayMenu.add(errorItem);
displayMenu.add(warningItem);
displayMenu.add(infoItem);
displayMenu.add(noneItem);
popup.add(exitItem);
trayIcon.setPopupMenu(popup);
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.out.println("TrayIcon could not be added.");
}
...
此演示的完整代码可在TrayIconDemo.java文件中找到。此演示还使用bulb.gif图像文件。
解除对应用 Swing 组件的当前限制将使开发人员能够添加诸如JMenuItem(带图像)、JRadioButtonMenuItem和JCheckBoxMenuItem等组件。
SystemTray API
SystemTray类中只能存在一个创建的实例。
| 方法 | 目的 |
|---|---|
| 添加 | 将一个托盘图标添加到系统托盘中。一旦添加,托盘图标将在系统托盘中可见。托盘中图标显示的顺序没有指定 — 这取决于平台和实现。 |
| getSystemTray | 获取代表桌面托盘区域的SystemTray实例。该方法始终为每个应用程序返回相同的实例。在某些平台上,可能不支持系统托盘。使用isSupported()方法检查系统托盘是否受支持。 |
| isSupported | 返回有关当前平台是否支持系统托盘的信息。除了显示托盘图标外,最小的系统托盘支持包括弹出菜单(参见TrayIcon.setPopupMenu(PopupMenu)方法)或操作事件(参见TrayIcon.addActionListener(ActionListener))。 |
TrayIcon API
一个TrayIcon对象代表一个可以添加到系统托盘的托盘图标。TrayIcon对象可以具有工具提示(文本)、图像、弹出菜单和与之关联的一组监听器。
| 方法 | 目的 |
|---|---|
| setImageAutoSize | 设置自动调整大小属性。自动调整大小确定托盘图像是否自动调整大小以适应托盘上为图像分配的空间。默认情况下,自动调整大小属性设置为false。 |
| setPopupMenu | 为此TrayIcon对象设置弹出菜单。如果弹出菜单为null,则不会将弹出菜单与此TrayIcon对象关联。 |
| setToolTip | 为此TrayIcon对象设置工具提示字符串。当鼠标悬停在图标上时,工具提示将自动显示。将工具提示设置为null会移除任何工具提示文本。在某些平台上,工具提示字符串可能会被截断;可以显示的字符数取决于平台。 |
使用 SystemTray API 的示例
以下表列出了使用添加到系统托盘的托盘图标的示例。
| 示例 | 描述位置 | 备注 |
|---|---|---|
TrayIconDemo |
此部分 | 在系统托盘中创建托盘图标,并向托盘图标添加弹出菜单。 |
使用其他 Swing 功能解决常见问题
原文:
docs.oracle.com/javase/tutorial/uiswing/misc/problems.html
问题: 我的应用程序没有显示我通过UIManager.setLookAndFeel请求的外观。
您可能要么将外观设置为无效的外观,要么在 UI 管理器加载默认外观之后设置它。如果您确定您指定的外观是有效的,并且设置外观是程序执行的第一件事情(例如,在其主方法的顶部),请检查是否有一个引用 Swing 类的静态字段。如果没有指定外观,则此引用可能导致加载默认外观。更多信息,包括如何在创建 GUI 后设置外观,请参阅外观部分。
问题: 为什么我的组件没有获得焦点?
-
是否是您创建的自定义组件(例如,
JComponent的直接子类)?如果是,您可能需要为您的组件提供输入映射和鼠标监听器。有关更多信息和演示,请参阅如何使自定义组件可聚焦。 -
组件是否在
JWindow对象内?焦点系统要求JWindow的拥有框架对于JWindow对象中的任何组件都可见才能获得焦点。默认情况下,如果您没有为JWindow对象指定拥有框架,则会为其创建一个不可见的拥有框架。解决方案是在创建JWindow对象时要么指定一个可见且可聚焦的拥有框架,要么改用JDialog或JFrame对象。
问题: 为什么我的对话框无法接收用户按下 Escape 键时生成的事件?
如果您的对话框包含文本字段,则可能正在消耗事件。
-
如果您想无论组件是否消耗事件都获取 Escape 事件,应该使用
KeyEventDispatcher。 -
如果您只想在组件未消耗事件时获取 Escape 事件,则在
JDialog对象中的任何JComponent组件上注册一个键绑定,使用WHEN_IN_FOCUSED_WINDOW输入映射。更多信息,请参阅如何使用键绑定页面。
问题: 为什么我无法将 Swing 组件应用于托盘图标?TrayIcon类的当前实现支持PopupMenu组件,但不支持其 Swing 对应物JPopupMenu。这种限制缩小了使用其他 Swing 功能的能力,例如菜单图标。请参阅 Bug ID 6285881。
- 为了消除这种不便,将创建一个新的
JTrayIcon类。在那之前,请使用 AWT 组件添加菜单项、复选框菜单项或子菜单。
如果在本节中找不到您的问题,请参考解决常见组件问题。
课程:在容器内布置组件
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/index.html
示例索引
本课程告诉您如何使用 Java 平台提供的布局管理器。它还告诉您如何使用绝对定位(无布局管理器)并提供编写自定义布局管理器的示例。对于每个布局管理器(或缺乏布局管理器),本课程指向一个您可以使用 Java™ Web Start 运行的示例。通过调整示例窗口的大小,您可以看到大小变化如何影响布局。
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布置您的 GUI。其中一种构建工具是 NetBeans IDE。否则,如果您想手动编码并且不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局使用。
布局管理器可视化指南
本节展示了标准布局管理器的示例,并指向每个布局管理器的操作指南部分。
使用布局管理器
本节提供了使用标准布局管理器的一般规则。它包括如何设置布局管理器,向容器添加组件,提供大小和对齐提示,放置组件之间的空间,并设置容器布局的方向,使其适合程序运行的区域设置。它还提供了一些选择正确布局管理器的提示。
布局管理工作原理
本节介绍了典型布局序列,然后描述了组件大小更改时会发生什么。
如何使用...
这一系列部分告诉您如何使用 Java 平台提供的每个通用布局管理器。
创建自定义布局管理器
您可以编写自己的布局管理器,而不是使用 Java 平台的布局管理器之一。
布局管理器必须实现LayoutManager接口,该接口规定了每个布局管理器必须定义的五个方法。可选地,布局管理器可以实现LayoutManager2,这是LayoutManager的子接口。
不使用布局管理器(绝对定位)
如果必要,您可以在不使用布局管理器的情况下定位组件。通常,此解决方案用于为组件指定绝对大小和位置。
解决常见布局问题
一些最常见的布局问题涉及显示过小或根本不显示的组件。本节告诉您如何解决这些和其他常见的布局问题。
问题和练习
尝试这些问题和练习,测试您在本课程中学到的知识。
如果您有兴趣使用 JavaFX 创建您的 GUI,请参阅在 JavaFX 中使用布局。
布局管理器的可视化指南
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/visual.html
有几个 AWT 和 Swing 类提供了用于一般用途的布局管理器:
-
BorderLayout -
BoxLayout -
CardLayout -
FlowLayout -
GridBagLayout -
GridLayout -
GroupLayout -
SpringLayout
本节展示了使用这些布局管理器的示例 GUI,并告诉您在哪里找到每个布局管理器的操作说明页面。您可以在操作说明页面和示例索引中找到运行示例的链接。
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您对学习布局管理的所有细节不感兴趣,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编写代码并且不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
BorderLayout

每个内容窗格都初始化为使用BorderLayout。(正如使用顶级容器中解释的,内容窗格是所有框架、小程序和对话框中的主要容器。)BorderLayout 将组件放在最多五个区域:顶部、底部、左侧、右侧和中心。所有额外的空间都放在中心区域。使用 JToolBar 创建的工具栏必须在BorderLayout容器内创建,如果您希望能够将工具栏从其起始位置拖放到其他位置。更多细节,请参阅如何使用 BorderLayout。
BoxLayout

BoxLayout 类将组件放在单行或单列中。它尊重组件的请求的最大尺寸,并且还允许您对齐组件。更多细节,请参阅如何使用 BoxLayout。
CardLayout

CardLayout类允许您在不同时间包含不同组件的区域。CardLayout通常由组合框控制,组合框的状态确定CardLayout显示的哪个面板(一组组件)。使用选项卡窗格的替代方法是使用CardLayout,它提供类似的功能,但具有预定义的 GUI。更多详情,请参阅如何使用 CardLayout。
FlowLayout

FlowLayout是每个JPanel的默认布局管理器。它简单地将组件按照一行排列,如果其容器宽度不够,则开始新行。CardLayoutDemo 中的两个面板,如前面所示,都使用FlowLayout。更多详情,请参阅如何使用 FlowLayout。
GridBagLayout

GridBagLayout是一种复杂、灵活的布局管理器。它通过将组件放置在单元格网格内来对齐组件,允许组件跨越多个单元格。网格中的行可以具有不同的高度,网格列可以具有不同的宽度。更多详情,请参阅如何使用 GridBagLayout。
GridLayout

GridLayout简单地使一组组件大小相等,并以请求的行数和列数显示它们。更多详情,请参阅如何使用 GridLayout。
GroupLayout

GroupLayout是一种为 GUI 构建工具开发的布局管理器,但也可以手动使用。GroupLayout分别处理水平和垂直布局。布局针对每个维度独立定义。因此,每个组件在布局中需要定义两次。上面显示的查找窗口是GroupLayout的一个示例。更多详情,请参阅如何使用 GroupLayout。
SpringLayout


SpringLayout是一种为 GUI 构建工具设计的灵活布局管理器。它允许您指定其控制下组件的边缘之间的精确关系。例如,您可以定义一个组件的左边缘与第二个组件的右边缘之间的一定距离(可以动态计算)。SpringLayout根据一组约束条件布置其关联容器的子组件,如如何使用 SpringLayout 中所示。
使用布局管理器
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/using.html
布局管理器是实现LayoutManager接口*的对象,确定容器内组件的大小和位置。尽管组件可以提供大小和对齐提示,但容器的布局管理器最终决定容器内组件的大小和位置。
注意:本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,可以选择使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编写代码并且不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
本节讨论了与使用布局管理器相关的一些常见任务:
-
设置布局管理器
-
向容器添加组件
-
提供大小和对齐提示
-
在组件之间放置空间
-
设置容器的方向
-
选择布局管理器的提示
-
第三方布局管理器
设置布局管理器
通常情况下,您需要关注的唯一容器的布局管理器是JPanel和内容窗格。每个JPanel对象在初始化时都会使用FlowLayout,除非在创建JPanel时另行指定。内容窗格默认使用BorderLayout。如果您不喜欢面板或内容窗格使用的默认布局管理器,可以自由更改为其他布局管理器。但是,除非您使用JToolBar,否则FlowLayout和BorderLayout管理器仅适用于原型设计。任何真实的应用程序都需要重新设置布局管理器。再次强调,您应该使用适当的工具来执行此操作,而不是手动编写管理器。
您可以使用JPanel构造函数设置面板的布局管理器。例如:
JPanel panel = new JPanel(new BorderLayout());
创建容器后,可以使用setLayout方法设置其布局管理器。例如:
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());
尽管我们强烈建议您使用布局管理器,但您也可以在没有布局管理器的情况下进行布局。通过将容器的布局属性设置为 null,您使容器不使用布局管理器。使用这种策略,称为绝对定位,您必须指定容器内每个组件的大小和位置。绝对定位的一个缺点是,当顶层容器调整大小时,它不会调整得很好。它也无法很好地适应用户和系统之间的差异,例如不同的字体大小和区域设置。
向容器添加组件
当您向面板或内容窗格添加组件时,您为add方法指定的参数取决于面板或内容窗格使用的布局管理器。实际上,一些布局管理器甚至不需要您显式添加组件;例如,GroupLayout。例如,BorderLayout要求您使用类似以下代码指定应将组件添加到的区域(使用BorderLayout中定义的常量之一):
pane.add(aComponent, BorderLayout.PAGE_START);
每个布局管理器的操作指南中都详细说明了您需要为add方法指定哪些参数(如果有的话)。一些布局管理器,如GridBagLayout和SpringLayout,需要复杂的设置过程。然而,许多布局管理器只是根据它们添加到容器中的顺序来放置组件。
除了JPanel和内容窗格之外的 Swing 容器通常提供了 API,您应该使用这些 API 而不是add方法。例如,不要直接向滚动窗格(或者实际上是其视口)添加组件,而是要么在JScrollPane构造函数中指定组件,要么使用setViewportView方法。由于有了这样的专门 API,您不需要知道许多 Swing 容器使用的布局管理器(如果有的话)。(对于好奇的人:滚动窗格实际上使用了一个名为ScrollPaneLayout的布局管理器。)
有关如何向特定容器添加组件的信息,请参阅该容器的操作指南页面。您可以使用各种组件的使用方法找到组件的操作指南页面。
提供大小和对齐提示
有时,您需要自定义组件向其容器的布局管理器提供的大小提示,以便组件能够被很好地布局。您可以通过指定组件的最小、首选和最大大小来实现这一点。您可以调用组件的设置大小提示方法——setMinimumSize、setPreferredSize和setMaximumSize。或者您可以创建一个子类,重写适当的获取器方法——getMinimumSize、getPreferredSize和getMaximumSize。以下是使组件的最大大小无限制的示例:
component.setMaximumSize(new Dimension(Integer.MAX_VALUE,
Integer.MAX_VALUE));
许多布局管理器不关注组件的请求的最大尺寸。然而,BoxLayout和SpringLayout会关注。此外,GroupLayout提供了明确设置最小、首选或最大尺寸的能力,而无需触及组件。
除了提供大小提示外,您还可以提供对齐提示。例如,您可以指定两个组件的顶部边缘应该对齐。您可以通过调用组件的setAlignmentX和setAlignmentY方法,或者通过覆盖组件的getAlignmentX和getAlignmentY方法来设置对齐提示。尽管大多数布局管理器会忽略对齐提示,但BoxLayout会遵守它们。您可以在如何使用 BoxLayout 中找到设置对齐的示例。
在组件之间放置空间
影响容器中可见组件之间空间量的三个因素:
布局管理器
一些布局管理器会自动在组件之间放置空间;其他则不会。有些允许您指定组件之间的空间量。有关间距支持的信息,请参见每个布局管理器的操作页面。
不可见组件
你可以创建轻量级组件,不进行绘制,但可以在 GUI 中占据空间。通常情况下,您可以在由BoxLayout控制的容器中使用不可见组件。参见如何使用 BoxLayout 以查看使用不可见组件的示例。
空边框
无论使用何种布局管理器,您都可以通过向组件添加空边框来影响组件之间的视觉空间量。最适合空边框的候选组件是通常没有默认边框的组件,例如面板和标签。一些其他组件可能在某些外观和感觉实现中无法很好地与边框配合工作,因为它们的绘制代码的实现方式不同。有关边框的信息,请参见如何使用边框。
设置容器的方向
本网站使用英语编写,文本从左到右,然后从上到下。然而,许多其他语言具有不同的方向。componentOrientation属性提供了一种指示特定组件应该使用与默认的从左到右,从上到下方向不同的方式的方法。在诸如单选按钮之类的组件中,方向可能被用作提示,指示外观和感觉应该切换按钮中图标和文本的位置。在容器中,方向被用作布局管理器的提示。
要设置容器的方向,您可以使用Component定义的方法setComponentOrientation或者,为了同时设置容器的子组件的方向,使用applyComponentOrientation。任一方法的参数可以是常量,如ComponentOrientation.RIGHT_TO_LEFT,或者可以是调用ComponentOrientation方法getOrientation(Locale)。例如,以下代码使所有JComponent都使用阿拉伯语区域设置进行初始化,然后相应地设置内容窗格和其中所有组件的方向:
JComponent.setDefaultLocale(new Locale("ar"));
JFrame frame = new JFrame();
...
Container contentPane = frame.getContentPane();
contentPane.applyComponentOrientation(
ComponentOrientation.getOrientation(
contentPane.getLocale()));
这里有两张图片展示了FlowLayout在完全相同的容器中以不同方向布局组件的方式。

默认方向(从左到右)

从右到左方向
支持组件方向的标准布局管理器包括FlowLayout、BorderLayout、BoxLayout、GridBagLayout和GridLayout。
注意: 必须注意将组件方向应用于渲染器、编辑器和通过容器层次结构的正常遍历无法访问的任何其他组件。
选择布局管理器的提示
布局管理器有不同的优势和劣势。本节讨论了一些常见的布局场景以及哪些布局管理器可能适用于每种场景。然而,再次强烈建议您使用构建工具来创建您的布局管理器,比如NetBeans IDE Matisse GUI builder,而不是手动编码管理器。下面列出的场景仅供信息参考,以防您想知道不同情况下使用哪种类型的管理器,或者您绝对必须手动编码您的管理器。
如果我们讨论的布局管理器都不适合您的情况,并且您不能使用构建工具,请随意使用您可能编写或找到的其他布局管理器。还要记住,灵活的布局管理器如GridBagLayout和SpringLayout可以满足许多布局需求。
场景: 您需要在尽可能多的空间中显示一个组件。
如果它是容器中唯一的组件,请使用GridLayout或BorderLayout。否则,BorderLayout或GridBagLayout可能是一个很好的选择。
如果使用BorderLayout,您需要将占用大量空间的组件放在中心位置。使用GridBagLayout,您需要设置组件的约束条件,使fill=GridBagConstraints.BOTH。另一种可能性是使用BoxLayout,使占用大量空间的组件指定非常大的首选大小和最大大小。
场景: 您需要以它们的自然大小在一个紧凑的行中显示几个组件。
考虑使用JPanel来组合组件,并使用JPanel的默认FlowLayout管理器或BoxLayout管理器。SpringLayout也适用于这种情况。
场景: 您需要以行和列的方式显示几个相同大小的组件。
GridLayout非常适合这种情况。
场景: 您需要以行或列的方式显示几个组件,可能在它们之间有不同数量的空间,自定义对齐方式或自定义组件大小。
BoxLayout非常适合这种情况。
场景: 您需要显示对齐的列,就像在一个类似表单的界面中,一个标签列用于描述相邻列中的文本字段。
SpringLayout是这种情况的自然选择。几个教程示例中使用的SpringUtilities类定义了一个makeCompactGrid方法,让您可以轻松地对齐多行和多列的组件。
场景: 您有一个包含许多组件的复杂布局。
考虑使用非常灵活的布局管理器,如GridBagLayout或SpringLayout,或将组件分组到一个或多个JPanel中以简化布局。如果采用后一种方法,每个JPanel可能会使用不同的布局管理器。
第三方布局管理器
Swing 社区创建了其他第三方布局管理器,以补充 Java 平台提供的布局管理器。以下列表并非绝对,但下面列出的布局管理器是最受欢迎的:
*早在 JDK 1.1 时引入了第二个接口LayoutManager2。LayoutManager2扩展了LayoutManager,提供了最大尺寸和对齐支持。LayoutManager2还添加了addLayoutComponent方法,接受一个Object,以及invalidateLayout方法。布局管理器还需要LayoutManager2提供的通知,因此任何现代布局管理器都需要实现它。
布局管理的工作原理
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/howLayoutWorks.html
注意: 本课程涵盖手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
这里是一个使用LayoutManager2 对容器进行布局管理的示例。
-
布局管理器基本上执行两项任务:
-
计算容器的最小/首选/最大大小。
-
布局容器的子元素。
布局管理器根据提供的约束、容器的属性(如插入)以及子元素的最小/首选/最大大小来执行此操作。如果子元素本身是一个容器,则使用其自身的布局管理器来获取其最小/首选/最大大小并进行布局。
-
-
容器可以是有效的(即,
isValid()返回 true),也可以是无效的。要使容器有效,所有容器的子元素必须已经布局,并且也必须全部有效。Container.validate方法可用于验证无效容器。此方法触发容器及其子容器沿组件层次结构向下的布局,并将此容器标记为有效。 -
组件创建后,默认处于无效状态。
Window.pack方法验证窗口,并首次布局窗口的组件层次结构。
最终结果是为了确定容器的最佳大小,系统确定了包含层次结构底部容器的大小。然后这些大小向上层次结构传播,最终确定容器的总大小。
如果组件的大小发生变化,例如更改字体后,必须调用该组件的revalidate和repaint方法来重新调整大小并重绘。revalidate和repaint都是线程安全的 您无需从事件分派线程调用它们。
当你在组件上调用revalidate时,请求会沿着包含层次结构传递,直到遇到一个容器,比如滚动窗格或顶层容器,这些容器不应受到组件调整大小的影响。(这是通过调用容器的isValidateRoot方法来确定的。)然后对容器进行布局,这会调整重新验证的组件的大小以及所有受影响组件的大小。
如何使用各种布局管理器
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/layoutlist.html
以下每个页面描述了如何使用特定类型的布局管理器。另一种访问这些页面的方法是通过布局管理器可视化指南。
注意: 这节课涵盖了手动编写布局代码,这可能是具有挑战性的。如果你对学习布局管理的所有细节不感兴趣,你可能更喜欢使用GroupLayout布局管理器结合构建工具来布局你的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果你想手动编写代码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果你有兴趣使用 JavaFX 创建你的 GUI,请参阅JavaFX 中的布局教程。
-
如何使用 BorderLayout
-
如何使用 BoxLayout
-
如何使用 CardLayout
-
如何使用 FlowLayout
-
如何使用 GridBagLayout
-
如何使用 GridLayout
-
如何使用 GroupLayout
-
如何使用 SpringLayout
如何使用 BorderLayout
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/border.html
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么GridBagLayout被推荐为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
以下图表示了一个使用BorderLayout类的应用程序的快照。

点击启动按钮以使用Java™ Web Start运行 BorderLayoutDemo(下载 JDK 7 或更高版本)。或者,要自己编译和运行示例,请参考示例索引。
这个演示的完整代码在BorderLayoutDemo.java文件中。
如前面的图片所示,BorderLayout对象有五个区域。这些区域由BorderLayout常量指定:
-
PAGE_START -
PAGE_END -
LINE_START -
LINE_END -
CENTER
版本说明:
在 JDK 1.4 版本发布之前,各个区域的首选名称不同,从罗盘点(例如,BorderLayout.NORTH表示顶部区域)到我们示例中使用的常量的较长版本。我们示例中使用的常量是首选的,因为它们是标准的,并且使程序能够适应具有不同方向的语言。
如果窗口被放大,中心区域会尽可能占用所有可用空间。其他区域只会扩展到填满所有可用空间为止。通常,一个容器只使用BorderLayout对象的一个或两个区域 只有中心,或中心和底部。
以下代码向框架的内容窗格添加组件。因为内容窗格默认使用 BorderLayout 类,所以代码不需要设置布局管理器。完整程序在 BorderLayoutDemo.java 文件中。
...*//Container pane = aFrame.getContentPane()*...
JButton button = new JButton("Button 1 (PAGE_START)");
pane.add(button, BorderLayout.PAGE_START);
//Make the center component big, since that's the
//typical usage of BorderLayout.
button = new JButton("Button 2 (CENTER)");
button.setPreferredSize(new Dimension(200, 100));
pane.add(button, BorderLayout.CENTER);
button = new JButton("Button 3 (LINE_START)");
pane.add(button, BorderLayout.LINE_START);
button = new JButton("Long-Named Button 4 (PAGE_END)");
pane.add(button, BorderLayout.PAGE_END);
button = new JButton("5 (LINE_END)");
pane.add(button, BorderLayout.LINE_END);
指定组件的位置(例如,BorderLayout.LINE_END)作为 add 方法的参数之一。如果此组件在由 BorderLayout 对象控制的容器中缺失,请确保已指定组件的位置,并且没有其他组件放置在相同位置。
所有使用 BorderLayout 类的教程示例将组件指定为 add 方法的第一个参数。例如:
add(component, BorderLayout.CENTER) //preferred
然而,在其他程序中的代码将组件指定为第二个参数。例如,以下是编写前述代码的替代方式:
add(BorderLayout.CENTER, component) //valid but old fashioned
*or*
add("Center", component) //valid but error prone
BorderLayout API
以下表列出了指定间隙(以像素为单位)的构造函数和方法。
指定间隙
| 构造函数或方法 | 目的 |
|---|---|
BorderLayout(int *horizontalGap*, int *verticalGap*) |
定义具有指定组件间隙的边界布局。 |
setHgap(int) |
设置组件之间的水平间距。 |
setVgap(int) |
设置组件之间的垂直间距。 |
使用 BorderLayout 的示例
以下表列出了使用 BorderLayout 类的代码示例,并提供到相关部分的链接。
| 示例 | 描述位置 | 注释 |
|---|---|---|
BorderLayoutDemo |
本页 | 将一个组件放在五个可能的位置中的每一个。 |
TabbedPaneDemo |
如何使用选项卡窗格 | 其中一个示例,将一个组件放在内容窗格的中心,使组件尽可能大。 |
CheckBoxDemo |
如何使用复选框 | 创建一个使用 BorderLayout 类的 JPanel 对象。将组件放入左侧(实际上是 LINE_START)和中心位置。 |
如何使用 BoxLayout
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果你不想学习布局管理的所有细节,可以选择使用GroupLayout布局管理器结合构建工具来布局你的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果你想手动编码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果你有兴趣使用 JavaFX 来创建你的 GUI,请查看JavaFX 布局教程。
Swing 包含一个名为BoxLayout的通用布局管理器。BoxLayout可以将其组件堆叠在一起或者按行排列 - 由你选择。你可以将其视为FlowLayout的一个版本,但功能更强大。这里是一个应用程序的图片,演示了如何使用BoxLayout来显示一个居中的组件列:

点击“启动”按钮以使用Java™ Web Start运行 BoxLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
你可以在BoxLayoutDemo.java中查看代码。
以下图片展示了一个使用两个BoxLayout实例的 GUI。在 GUI 的顶部,一个从上到下的盒式布局将一个标签放在滚动窗格上方。在 GUI 的底部,一个从左到右的盒式布局将两个按钮并排放置。BorderLayout结合了 GUI 的两部分,并确保任何多余的空间都给了滚动窗格。

你可以在示例索引中找到运行 ListDialog 以及其源文件的链接,用于使用 Swing 组件。
以下代码取自ListDialog.java,布局了 GUI。这段代码位于对话框的构造函数中,该对话框是作为JDialog子类实现的。粗体代码设置了 Box 布局并向其中添加了组件。
JScrollPane listScroller = new JScrollPane(list);
listScroller.setPreferredSize(new Dimension(250, 80));
listScroller.setAlignmentX(LEFT_ALIGNMENT);
...
//Lay out the label and scroll pane from top to bottom.
JPanel listPane = new JPanel();
listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
JLabel label = new JLabel(labelText);
...
listPane.add(label);
listPane.add(Box.createRigidArea(new Dimension(0,5)));
listPane.add(listScroller);
listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
//Lay out the buttons from left to right.
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(cancelButton);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(setButton);
//Put everything together, using the content pane's BorderLayout.
Container contentPane = getContentPane();
contentPane.add(listPane, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END);
第一行粗体代码创建了一个从上到下的 Box 布局,并将其设置为listPane的布局管理器。BoxLayout 构造函数的两个参数是它管理的容器和组件将被布局的轴。PAGE_AXIS 常量指定组件应该按照页面上行流动的方向进行布局,由目标容器的ComponentOrientation属性确定。LINE_AXIS 常量指定组件应该按照文本行的方向进行布局,由目标容器的ComponentOrientation属性确定。这些常量允许通过以正确的从左到右、从右到左或从上到下的方向为所用语言的容器中的组件进行布局来进行国际化。
接下来的三行粗体代码将标签和滚动窗格添加到容器中,并使用一个刚性区域分隔它们 一个用于在组件之间添加空间的不可见组件。在这种情况下,刚性区域没有宽度,并在标签和滚动窗格之间放置了确切的5 像素。稍后在使用不可见组件作为填充中讨论了刚性区域。
下一块粗体代码创建了一个从左到右的 Box 布局,并为buttonPane容器设置了布局。然后代码向容器添加了两个按钮,使用一个刚性区域在按钮之间放置了10 像素。为了将按钮放置在其容器的右侧,添加到容器的第一个组件是glue。这个 glue 是一个不可见组件,根据需要增长以吸收其容器中的任何额外空间。在使用不可见组件作为填充中讨论了 glue。
作为使用不可见组件的替代方案,有时可以使用空边框来在组件周围创建空间,尤其是面板。例如,前面的代码片段使用空边框在对话框及其内容的所有边缘之间以及内容的两个部分之间放置了10 像素。边框完全独立于布局管理器。它们只是 Swing 组件如何绘制其边缘并在组件内容和边缘之间提供填充的方式。更多信息请参见如何使用边框。
以下部分更详细地讨论了BoxLayout:
-
Box 布局特性
-
使用不可见组件作为填充
-
修复对齐问题
-
指定组件大小
-
Box 布局 API
-
使用 Box 布局的示例
不要让BoxLayout讨论的长度吓到你!你可能已经可以使用BoxLayout了。如果遇到问题或想利用BoxLayout的功能,继续阅读。
Box Layout 特点
如前所述,BoxLayout将组件排列在彼此上方或一行中。在安排组件时,BoxLayout 会考虑组件的对齐方式以及最小、首选和最大尺寸。在本节中,我们将讨论从上到下的布局。相同的概念适用于从左到右或从右到左的布局。只需用 Y 替换 X,宽度替换高度等。
版本说明:在 JDK 版本 1.4 之前,没有为以本地化方式指定框布局轴的常量。相反,在创建BoxLayout时,您指定X_AXIS(从左到右)或Y_AXIS(从上到下)。我们的示例现在使用常量LINE_AXIS和PAGE_AXIS,因为它们可以使程序适应具有不同方向的语言。在默认的从左到右方向中,LINE_AXIS指定从左到右的布局,PAGE_AXIS指定从上到下的布局。
当BoxLayout从上到下布局组件时,它会尝试将每个组件的大小设置为组件的首选高度。如果布局的垂直空间与首选高度之和不匹配,那么BoxLayout会尝试调整组件的大小以填充空间。组件会增长或缩小以填充空间,BoxLayout会遵守每个组件的最小和最大尺寸。任何额外的空间都会出现在容器的底部。
对于从上到下的框布局,容器的首选宽度是子组件的最大首选宽度。如果容器被强制变宽,BoxLayout会尝试将每个组件的宽度调整为容器的宽度(减去插入)。如果组件的最大尺寸小于容器的宽度,则 X 对齐会起作用。
X 对齐不仅影响组件相对位置,还影响组件(作为一组)在容器中的位置。以下图示说明了具有受限制最大宽度的组件的对齐方式。

在第一个图中,所有三个组件的 X 对齐为 0.0(Component.LEFT_ALIGNMENT)。这意味着组件的左侧应对齐。此外,这意味着所有三个组件在容器中尽可能靠左位置。
在第二个图中,所有三个组件的 X 对齐为 0.5(Component.CENTER_ALIGNMENT)。这意味着组件的中心应该对齐,并且组件应该位于其容器的水平中心。
在第三个图中,组件的 X 对齐为 1.0(Component.RIGHT_ALIGNMENT)。您可以猜测这对于组件的对齐和位置相对于其容器意味着什么。
您可能想知道当组件具有受限制的最大尺寸和不同的 X 对齐方式时会发生什么。下一个图显示了一个示例:

如您所见,具有 X 对齐为 0.0(Component.LEFT_ALIGNMENT)的组件的左侧与具有 0.5 X 对齐(Component.CENTER_ALIGNMENT)的组件的中心对齐,后者与具有 X 对齐为 1.0(Component.RIGHT_ALIGNMENT)的组件的右侧对齐。这种混合对齐方式在修复对齐问题中进一步讨论。
如果没有任何组件具有最大宽度会怎么样?在这种情况下,如果所有组件具有相同的 X 对齐方式,则所有组件将与其容器一样宽。如果 X 对齐方式不同,则具有 X 对齐为 0.0(左)或 1.0(右)的任何组件将较小。所有具有中间 X 对齐方式(例如中心)的组件将与其容器一样宽。以下是两个示例:

要更好地了解BoxLayout,您可以运行自己的 BoxLayoutDemo2 实验。
试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行 BoxLayoutDemo2(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
您可以在
BoxLayoutDemo2.java中查看代码。您将看到一个类似上面的窗口,其中包含三个矩形。每个矩形都是
BLDComponent的一个实例,它是JComponent的子类。 -
单击其中一个矩形内部。
这是如何更改矩形的 X 对齐方式。
-
单击窗口底部的复选框。
这将关闭所有矩形的限制尺寸。
-
使窗口变得更高。
这使得矩形的容器比矩形的首选大小之和更大。容器是一个带有红色轮廓的
JPanel,这样您就可以知道容器的边缘在哪里。
使用不可见组件作为填充器
由框布局控制的每个组件都与其相邻组件紧密相连。如果您希望在组件之间有空间,可以向一个或两个组件添加空的边框,或插入不可见组件来提供空间。您可以借助Box类创建不可见组件。
Box类定义了一个嵌套类Box.Filler,这是一个透明的组件,不绘制任何内容,用于在其他组件之间提供空间。然而,Filler实际上并不是不可见的,因为没有调用setVisible(false)。Box类提供了便利方法来帮助您创建常见类型的填充器。以下表格详细介绍了如何使用Box和Box.Filler创建不可见组件。
| 类型 | 大小约束 | 创建方式 |
|---|---|---|
| 刚性区域 | ![]() |
Box.createRigidArea(size) |
| 粘合剂,水平 | ![]() |
Box.createHorizontalGlue() |
| 粘合剂,垂直 | ![]() |
Box.createVerticalGlue() |
自定义Box.Filler |
(如指定) | new Box.Filler(minSize, prefSize, maxSize) |
这是您通常如何使用每种类型的填充器:
刚性区域
当您希望在两个组件之间有固定大小的空间时,请使用此选项。例如,在从左到右的框中在两个组件之间放置 5 像素,您可以使用以下代码:
container.add(firstComponent);
container.add(Box.createRigidArea(new Dimension(5,0)));
container.add(secondComponent);


注意: Box类提供了另一种用于在组件之间放置固定空间的填充器:垂直或水平支柱。不幸的是,支柱具有无限的最大高度或宽度(分别用于水平和垂直支柱)。这意味着,如果您在垂直框内使用水平框,水平框有时可能会变得太高。因此,我们建议您使用刚性区域而不是支柱。
粘合剂
使用这个方法可以指定布局中多余的空间应该去哪里。将其视为一种弹性胶水 有弹性和可伸缩性,但除非你拉开它粘附的组件,否则不占用空间。例如,在一个从左到右的盒式布局中,在两个组件之间放置水平粘合剂,可以使任何额外的空间都在这些组件之间,而不是在所有组件的右侧。以下是一个示例,使得从左到右的盒式布局中的空间在两个组件之间,而不是在组件的右侧:
container.add(firstComponent);
container.add(Box.createHorizontalGlue());
container.add(secondComponent);


自定义Box.Filler
使用这个方法可以指定组件的最小、首选和最大尺寸。例如,要在一个从左到右的布局中创建一些填充物,使得两个组件之间至少有 5 像素的间距,并确保容器的最小高度为 100 像素,你可以使用以下代码:
container.add(firstComponent);
Dimension minSize = new Dimension(5, 100);
Dimension prefSize = new Dimension(5, 100);
Dimension maxSize = new Dimension(Short.MAX_VALUE, 100);
container.add(new Box.Filler(minSize, prefSize, maxSize));
container.add(secondComponent);

修复对齐问题
有时候BoxLayout会出现两种类型的对齐问题:
-
一组组件都具有相同的对齐方式,但你想要改变它们的对齐方式以使它们看起来更好。例如,你可能希望一组从左到右的按钮的底部对齐,而不是它们的中心对齐。以下是一个示例:
![自定义对齐]()
-
由
BoxLayout控制的两个或更多组件具有不同的默认对齐方式,导致它们对齐不正确。例如,如下所示,如果一个标签和一个面板在一个从上到下的盒式布局中,标签的左边缘默认与面板的中心对齐。![X 对齐不匹配]()
一般来说,由从上到下的BoxLayout对象控制的所有组件应该具有相同的 X 对齐方式。同样,由从左到右的BoxLayout控制的所有组件通常应该具有相同的 Y 对齐方式。你可以通过调用其setAlignmentX方法来设置JComponent的 X 对齐方式。所有组件都可以选择的另一种方法是在组件类的自定义子类中覆盖getAlignmentX方法。类似地,你可以通过调用setAlignmentY方法或覆盖getAlignmentY来设置组件的 Y 对齐方式。
这里有一个示例,取自一个名为BoxAlignmentDemo的应用程序,将两个按钮的 Y 对齐方式更改为底部对齐:
button1.setAlignmentY(Component.BOTTOM_ALIGNMENT);
button2.setAlignmentY(Component.BOTTOM_ALIGNMENT);
点击“启动”按钮以使用Java™ Web Start运行 BoxAlignmentDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
默认情况下,大多数组件都具有中心 X 和 Y 对齐。然而,按钮、组合框、标签和菜单项具有不同的默认 X 对齐值:LEFT_ALIGNMENT。前面的图片展示了如果将一个左对齐的组件(如标签)与一个在由上至下控制的容器中的中心对齐组件放在一起会发生什么。
BoxAlignmentDemo程序提供了修复不匹配对齐问题的示例。通常,只需将有问题的按钮或标签设置为中心对齐即可。例如:
label.setAlignmentX(Component.CENTER_ALIGNMENT);
指定组件尺寸
正如之前提到的,BoxLayout会关注组件的请求的最小、首选和最大尺寸。在微调布局时,你可能需要调整这些尺寸。
有时需要调整尺寸是显而易见的。例如,按钮的最大尺寸通常与其首选尺寸相同。如果希望在有额外空间时将按钮绘制得更宽,那么需要更改其最大尺寸。
然而,有时需要调整尺寸并不那么明显。你可能会在框布局中得到意想不到的结果,而不知道原因。在这种情况下,通常最好首先将问题视为对齐问题。如果调整对齐方式没有帮助,那么可能存在尺寸问题。我们稍后会进一步讨论这个问题。
注意: 虽然BoxLayout会关注组件的最大尺寸,但许多布局管理器不会。例如,如果你将一个按钮放在BorderLayout的底部,那么按钮的宽度可能会超过其首选宽度,无论按钮的最大尺寸是多少。另一方面,BoxLayout永远不会使按钮的宽度超过其最大尺寸。
你可以通过两种方式更改最小、首选和最大尺寸:
-
通过调用适当的
set*Xxx*Size方法(由JComponent类定义)。例如:comp.setMinimumSize(new Dimension(50, 25)); comp.setPreferredSize(new Dimension(50, 25)); comp.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); -
通过覆盖适当的
get*Xxx*Size方法。例如:*...//in a subclass of a component class:* public Dimension getMaximumSize() { size = getPreferredSize(); size.width = Short.MAX_VALUE; return size; }
如果你在使用框布局时遇到问题,并且已经排除了对齐问题,那么问题很可能与尺寸有关。例如,如果由框布局控制的容器占用了太多空间,那么容器中的一个或多个组件可能需要限制其最大尺寸。
您可以使用两种技术来追踪箱式布局中的尺寸问题:
-
在问题的 Swing 组件外部添加花哨的线条边框。这样你就可以看到它们的真实大小。例如:
comp.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.red), comp.getBorder())); -
使用
System.out.println来打印组件的最小、首选和最大尺寸,也许还有它们的边界。
Box 布局 API
以下表格列出了常用的BoxLayout和Box构造函数和方法。使用箱式布局的 API 分为以下几类:
-
创建
BoxLayout对象 -
创建空间填充器
-
其他有用的方法
创建BoxLayout对象
| 构造函数或方法 | 目的 |
|---|---|
BoxLayout(Container, int) |
创建一个控制指定Container的BoxLayout实例。整数参数指定容器的组件应该沿着哪个轴布局。当容器具有默认组件方向时,BoxLayout.LINE_AXIS指定组件从左到右布局,BoxLayout.PAGE_AXIS指定组件从上到下布局。 |
Box(int) |
创建一个Box - 使用指定轴的BoxLayout的容器。 |
static Box createHorizontalBox() (在Box中) |
创建一个从左到右布局其组件的Box。 |
static Box createVerticalBox() (在Box中) |
创建一个从上到下布局其组件的Box。 |
创建空间填充器
这些方法在Box类中定义。
| 构造函数或方法 | 目的 |
|---|---|
Component createRigidArea(Dimension) |
创建一个刚性组件。 |
| Component createHorizontalGlue() Component createVerticalGlue()
| Component createGlue() | 创建一个粘合剂组件。水平粘合剂和垂直粘合剂非常有用。 |
Component createHorizontalStrut() Component createVerticalStrut() |
创建一个“支柱”组件。我们建议使用刚性区域而不是支柱。 |
|---|---|
Box.Filler(Dimension, Dimension, Dimension) |
创建一个具有指定最小、首选和最大尺寸的组件(参数按照指定顺序提供)。有关详细信息,请参阅本节前面的自定义Box.Filler讨论。 |
其他有用的方法
| 方法 | 目的 |
|---|---|
void changeShape(Dimension, Dimension, Dimension) (在Box.Filler中) |
更改接收者Box.Filler对象的最小、首选和最大尺寸。布局会相应更改。 |
使用 Box 布局的示例
以下表格列出了使用箱式布局的许多示例。
| 示例 | 描述位置 | 注释 |
|---|---|---|
| BoxLayoutDemo2 | 本页 | 使用箱式布局创建一个居中的列组件。 |
| BoxAlignmentDemo | 本页 | 演示如何解决常见的对齐问题。 |
| BoxLayoutDemo | 本页面 | 让您尝试对齐和最大尺寸。 |
| ListDialog | 本页面 | 一个简单但逼真的例子,展示了如何同时使用自顶向下箱式布局和左右箱式布局。使用水平粘合剂、刚性区域和空边框。还设置了组件的 X 对齐方式。 |
| InternalFrameEventDemo | 如何编写内部框架监听器 | 使用自顶向下布局来将按钮和滚动窗格居中放置在内部框架中。 |
| MenuGlueDemo | 自定义菜单布局 | 展示如何使用粘合组件将菜单右对齐在菜单栏中。 |
| MenuLayoutDemo | 自定义菜单布局 | 展示如何通过将菜单栏更改为使用自顶向下的箱式布局,以及上下文菜单更改为使用左右的箱式布局来自定义菜单布局。 |
ConversionPanel.java 在 Converter 演示中 |
如何使用面板 | 通过将组件的宽度设置为相同,以及它们容器的宽度设置为相同,来在不同的箱式布局控制容器中对齐两个组件。 |
如何使用 CardLayout
注意: 本课程涵盖手动编写布局代码,这可能具有挑战性。如果您对学习布局管理的所有细节不感兴趣,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
以下图表示一个应用程序的快照,该应用程序使用CardLayout类在两个面板之间切换。

点击“启动”按钮以使用Java™ Web Start运行 CardLayoutDemo(下载 Java SE)。或者,要自行编译和运行示例,请参考示例索引。
这个演示的完整代码在CardLayoutDemo.java文件中。
CardLayout类管理两个或多个组件(通常是JPanel实例),它们共享相同的显示空间。使用CardLayout类时,让用户通过使用组合框在组件之间进行选择。CardLayoutDemo应用程序是一个示例,用于说明此功能。
另一种完成相同任务的方法是使用选项卡窗格。下图显示了前面示例的选项卡窗格版本:

因为选项卡窗格提供了自己的 GUI,所以使用选项卡窗格比使用CardLayout类更简单。例如,使用选项卡窗格实现前面的示例会导致代码行数更少的程序。
单击“启动”按钮以使用Java™ Web Start运行 TabDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
此演示的完整代码位于TabDemo.java文件中。
从概念上讲,CardLayout管理的每个组件都像一叠扑克牌或交换卡片,任何时候只有顶部卡片可见。您可以通过以下任何方式选择显示的卡片:
-
通过请求第一张或最后一张卡片,按照添加到容器的顺序
-
通过向前或向后翻转卡片堆栈来选择卡片。
-
通过指定具有特定名称的卡片
CardLayoutDemo类使用最后一种方案。
来自CardLayoutDemo.java应用程序的以下代码片段创建了CardLayout对象及其管理的组件。
*//Where instance variables are declared:*
JPanel cards;
final static String BUTTONPANEL = "Card with JButtons";
final static String TEXTPANEL = "Card with JTextField";
*//Where the components controlled by the CardLayout are initialized:*
//Create the "cards".
JPanel card1 = new JPanel();
...
JPanel card2 = new JPanel();
...
//Create the panel that contains the "cards".
cards = new JPanel(new CardLayout());
cards.add(card1, BUTTONPANEL);
cards.add(card2, TEXTPANEL);
要将组件添加到CardLayout对象管理的容器中,请指定一个标识要添加的组件的字符串。例如,在此演示中,第一个面板的字符串是"Card with JButtons",第二个面板的字符串是"Card with JTextField"。在此演示中,这些字符串也在组合框中使用。
要选择CardLayout对象显示的组件,请在您的代码示例中添加额外的代码:
*//Where the GUI is assembled:*
//Put the JComboBox in a JPanel to get a nicer look.
JPanel comboBoxPane = new JPanel(); //use FlowLayout
String comboBoxItems[] = { BUTTONPANEL, TEXTPANEL };
JComboBox cb = new JComboBox(comboBoxItems);
cb.setEditable(false);
cb.addItemListener(this);
comboBoxPane.add(cb);
...
pane.add(comboBoxPane, BorderLayout.PAGE_START);
pane.add(cards, BorderLayout.CENTER);
...
//Method came from the ItemListener class implementation,
//contains functionality to process the combo box item selecting
public void itemStateChanged(ItemEvent evt) {
CardLayout cl = (CardLayout)(cards.getLayout());
cl.show(cards, (String)evt.getItem());
}
此示例显示,要使用CardLayout类的show方法,必须设置当前可见的组件。show方法中的第一个参数是CardLayout控制的容器,即CardLayout管理的组件的容器。第二个参数是标识要显示的组件的字符串。这个字符串与将组件添加到容器时使用的字符串相同。
CardLayout API
以下表格列出了用于选择组件的CardLayout类方法。对于每个方法,第一个参数是CardLayout是布局管理器的容器(CardLayout控制的卡片的容器)。
| 方法 | 目的 |
|---|---|
first (Container *parent*) |
翻转到容器的第一张卡片。 |
next (Container *parent*) |
翻转到容器的下一张卡片。如果当前可见的卡片是最后一张,则此方法将翻转到布局中的第一张卡片。 |
previous (Container *parent*) |
翻转到容器的上一张卡片。如果当前可见的卡片是第一张,则此方法将翻转到布局中的最后一张卡片。 |
last (Container *parent*) |
翻转到容器的最后一张卡片。 |
show (Container *parent*, String *name*) |
使用指定的 name 使用 addLayoutComponent 方法添加到此布局中的组件进行翻转。 |
使用 CardLayout 的示例
在这个教程中,只有一个示例使用了 CardLayout,即 CardLayoutDemo。一般来说,我们的示例使用 选项卡面板 而不是 CardLayout,因为选项卡面板提供了自己的 GUI。
如何使用流式布局
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
FlowLayout类提供了一个非常简单的布局管理器,默认情况下由JPanel对象使用。以下图表示使用流式布局的应用程序的快照:

单击“启动”按钮以使用Java™ Web Start运行FlowLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
此演示的完整代码位于FlowLayoutDemo.java文件中。
FlowLayout 类将组件放在一行中,大小为它们的首选大小。如果容器中的水平空间太小,无法将所有组件放在一行中,FlowLayout 类将使用多行。如果容器比一行组件所需的宽度更宽,则默认情况下,该行在容器内水平居中。要指定行是左对齐还是右对齐,请使用带有对齐参数的FlowLayout构造函数。FlowLayout类的另一个构造函数指定在组件周围放置多少垂直或水平填充。
下面的代码片段创建了一个FlowLayout对象和它管理的组件。
FlowLayout experimentLayout = new FlowLayout();
...
compsToExperiment.setLayout(experimentLayout);
compsToExperiment.add(new JButton("Button 1"));
compsToExperiment.add(new JButton("Button 2"));
compsToExperiment.add(new JButton("Button 3"));
compsToExperiment.add(new JButton("Long-Named Button 4"));
compsToExperiment.add(new JButton("5"));
选择“从左到右”或“从右到左”选项,并单击“应用方向”按钮以设置组件的方向。以下代码片段将“从左到右”组件方向应用于experimentLayout。
compsToExperiment.setComponentOrientation(
ComponentOrientation.LEFT_TO_RIGHT);
流式布局 API
以下表列出了FlowLayout类的构造函数。
| 构造函数 | 目的 |
|---|---|
FlowLayout() |
构造一个具有居中对齐和水平垂直间隙的默认大小为5 像素的新FlowLayout对象。 |
FlowLayout(int *align*) |
创建一个新的流式布局管理器,具有指定的对齐方式和水平垂直间隙,默认大小为5 像素。对齐参数可以是FlowLayout.LEADING,FlowLayout.CENTER或FlowLayout.TRAILING。当FlowLayout对象控制具有从左到右组件方向(默认)的容器时,LEADING值指定左对齐的组件,TRAILING值指定右对齐的组件。 |
FlowLayout (int *align*, int *hgap*, int *vgap*) |
创建一个新的流式布局管理器,具有指定的对齐方式和指定的水平和垂直间隙。hgap和vgap参数指定组件之间要放置的像素数。 |
使用 FlowLayout 的示例
以下表格列出了使用FlowLayout类的代码示例,并提供到相关部分的链接。
| 示例 | 描述位置 | 备注 |
|---|---|---|
FlowLayoutDemo |
本页 | 设置内容窗格使用FlowLayout。如果将RIGHT_TO_LEFT常量设置为true并重新编译,您可以看到FlowLayout如何处理具有从右到左组件方向的容器。 |
CardLayoutDemo |
如何使用 CardLayout | 在BorderLayout的顶部部分很好地居中一个组件,并将组件放在使用FlowLayout的JPanel中。 |
ButtonDemo |
如何使用按钮、复选框和单选按钮 | 使用JPanel的默认FlowLayout。 |
TextInputDemo |
如何使用格式化文本字段 | 使用一个右对齐的FlowLayout呈现两个按钮的面板。 |
如何使用 GridBagLayout
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您对学习布局管理的所有细节不感兴趣,您可能更喜欢使用 GroupLayout 布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编写代码而不想使用 GroupLayout,那么推荐使用 GridBagLayout 作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建您的 GUI,请参阅 JavaFX 中的布局。
这是一个使用 GridBagLayout 的示例图片。

点击“启动”按钮以使用 Java™ Web Start 运行 GridBagLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考 示例索引。
GridBagDemo 的代码位于 GridBagLayoutDemo.java 中。
GridBagLayout 是 Java 平台提供的最灵活 也是最复杂 布局管理器之一。GridBagLayout 将组件放置在行和列的网格中,允许指定的组件跨越多行或多列。并非所有行都必须具有相同的高度。同样,并非所有列都必须具有相同的宽度。基本上,GridBagLayout 将组件放置在网格中的矩形(单元格)中,然后使用组件的首选大小来确定单元格的大小。
以下图显示了前面小程序的网格。您可以看到,网格有三行三列。第二行的按钮跨越所有列;第三行的按钮跨越了右侧两列。

如果您按照下图所示放大窗口,您会注意到包含按钮 5 的底部行获得了所有新的垂直空间。新的水平空间均匀分配给所有列。此调整大小行为基于程序为GridBagLayout中的各个组件分配的权重。您还会注意到每个组件占用所有可用的水平空间 但不占用所有可用的垂直空间(正如您可以看到的按钮 5)。此行为也由程序指定。

程序指定其组件的大小和位置特征的方式是为每个组件指定约束条件。在组件上设置约束的首选方法是使用Container.add变体,传递一个GridBagConstraints对象,如下一节所示。
以下各节解释了您可以设置的约束条件并提供了示例。
指定约束条件
以下代码是典型的使用GridBagLayout的容器中的内容。您将在下一节看到更详细的示例。
JPanel pane = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
*//For each component to be added to this container:*
*//...Create the component...*
*//...Set instance variables in the GridBagConstraints instance...*
pane.add(theComponent, c);
正如您可能从上面的示例中猜到的那样,即使组件具有不同的约束条件,也可以重用相同的GridBagConstraints实例。但是,建议您不要重用GridBagConstraints,因为如果您忘记为每个新实例重置字段,这很容易导致引入细微的错误。
注意:以下讨论假定GridBagLayout控制具有从左到右组件方向的容器。
您可以设置以下GridBagConstraints实例变量:
gridx,gridy
指定组件左上角的行和列。最左侧的列地址为gridx=0,最顶部的行地址为gridy=0。使用GridBagConstraints.RELATIVE(默认值)指定组件放置在刚刚在此组件之前添加到容器的组件的右侧(对于gridx)或下方(对于gridy)。我们建议为每个组件指定gridx和gridy值,而不仅仅使用GridBagConstraints.RELATIVE;这样往往会产生更可预测的布局。
gridwidth,gridheight
指定组件显示区域中的列数(对于gridwidth)或行数(对于gridheight)。这些约束指定组件使用的单元格数,而不是它使用的像素数。默认值为 1。使用GridBagConstraints.REMAINDER指定组件为其行(对于gridwidth)或列(对于gridheight)中的最后一个。使用GridBagConstraints.RELATIVE指定组件为其行(对于gridwidth)或列(对于gridheight)中的倒数第二个。我们建议为每个组件指定gridwidth和gridheight值,而不仅仅使用GridBagConstraints.RELATIVE和GridBagConstraints.REMAINDER;这样往往会产生更可预测的布局。
注意: GridBagLayout不允许组件跨越多行,除非组件位于最左侧列,或者您已为组件指定了正值的gridx和gridy值。
fill
当组件的显示区域大于组件请求的大小时使用,以确定是否以及如何调整组件的大小。有效值(定义为GridBagConstraints常量)包括NONE(默认值),HORIZONTAL(使组件足够宽以水平填充其显示区域,但不更改其高度),VERTICAL(使组件足够高以垂直填充其显示区域,但不更改其宽度)和BOTH(使组件完全填充其显示区域)。
ipadx,ipady
指定内部填充:需要添加到组件大小的量。默认值为零。组件的宽度至少为其最小宽度加上ipadx*2像素,因为填充应用于组件的两侧。同样,组件的高度至少为其最小高度加上ipady*2像素。
insets
指定组件的外部填充--组件与其显示区域边缘之间的最小空间量。该值被指定为一个Insets对象。默认情况下,每个组件都没有外部填充。
anchor
当组件比其显示区域小时使用,以确定在区域内的哪个位置放置组件。有效值(定义为GridBagConstraints常量)为CENTER(默认值),PAGE_START,PAGE_END,LINE_START,LINE_END,FIRST_LINE_START,FIRST_LINE_END,LAST_LINE_END和LAST_LINE_START。
这里是一个容器中这些值在默认的从左到右的组件方向中如何解释的图片。
| FIRST_LINE_START | PAGE_START | FIRST_LINE_END |
|---|---|---|
| LINE_START | CENTER | LINE_END |
| LAST_LINE_START | PAGE_END | LAST_LINE_END |
版本说明: PAGE_* 和 *LINE_* 常量是在 1.4 版本中引入的。之前的版本需要使用指向罗盘方向的常量。例如,NORTHEAST 表示显示区域的右上部分。我们建议您使用新的常量,因为它们更容易本地化。
weightx,weighty
指定权重是一门艺术,可以对 GridBagLayout 控制的组件的外观产生重大影响。权重用于确定如何在列之间(weightx)和行之间(weighty)分配空间;这对于指定调整大小行为很重要。
除非您至少为 weightx 或 weighty 指定一个非零值,否则所有组件都会聚集在容器的中心。这是因为当权重为 0.0(默认值)时,GridBagLayout 将任何额外的空间放在其单元格网格和容器边缘之间。
通常,权重用 0.0 和 1.0 来指定极端值:中间的数字根据需要使用。较大的数字表示组件的行或列应该获得更多的空间。对于每一列,权重与该列中指定的最高 weightx 相关,每个多列组件的权重在组件所在的列之间以某种方式分配。同样,每一行的权重与该行中指定的最高 weighty 相关。额外的空间倾向于分配给最右边的列和底部的行。
下一节将深入讨论约束条件,以解释示例程序的工作原理。
示例解释
这里,再次展示了 GridBagLayoutDemo 应用程序的图片。

点击“启动”按钮以使用 Java™ Web Start 运行 GridBagLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
以下代码创建了 GridBagLayout 和它管理的组件。您可以在 GridBagLayoutDemo.java 中找到整个源文件。
JButton button;
pane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
if (shouldFill) {
//natural height, maximum width
c.fill = GridBagConstraints.HORIZONTAL;
}
button = new JButton("Button 1");
if (shouldWeightX) {
c.weightx = 0.5;
}
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
pane.add(button, c);
button = new JButton("Button 2");
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 0.5;
c.gridx = 1;
c.gridy = 0;
pane.add(button, c);
button = new JButton("Button 3");
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 0.5;
c.gridx = 2;
c.gridy = 0;
pane.add(button, c);
button = new JButton("Long-Named Button 4");
c.fill = GridBagConstraints.HORIZONTAL;
c.ipady = 40; //make this component tall
c.weightx = 0.0;
c.gridwidth = 3;
c.gridx = 0;
c.gridy = 1;
pane.add(button, c);
button = new JButton("5");
c.fill = GridBagConstraints.HORIZONTAL;
c.ipady = 0; //reset to default
c.weighty = 1.0; //request any extra vertical space
c.anchor = GridBagConstraints.PAGE_END; //bottom of space
c.insets = new Insets(10,0,0,0); //top padding
c.gridx = 1; //aligned with button 2
c.gridwidth = 2; //2 columns wide
c.gridy = 2; //third row
pane.add(button, c);
此示例对GridBagLayout管理的所有组件使用一个GridBagConstraints实例,但在实际情况中,建议不要重用GridBagConstraints,因为如果忘记为每个新实例重置字段,很容易引入细微错误。在将每个组件添加到容器之前,代码设置(或重置为默认值)GridBagConstraints对象中的适当实例变量。然后将组件添加到其容器,将GridBagConstraints对象指定为add方法的第二个参数。
例如,要使按钮 4 变得特别高,示例中有这段代码:
c.ipady = 40;
在设置下一个组件的约束之前,代码将ipady的值重置为默认值:
c.ipady = 0;
如果组件的显示区域大于组件本身,则可以使用GridBagConstraints.anchor约束指定组件在显示区域中的显示位置。anchor约束的值可以是绝对的(北、南、东、西等),也可以是相对于方向的(在页面的开始、在行的末尾、在第一行的开始等),或者相对于组件的基线。有关anchor约束的可能值的完整列表,包括基线相关值,请参阅GridBagConstraints.anchor的 API 文档。您可以在上面的代码片段中看到,按钮 5 指定应在显示区域的末尾显示,通过设置GridBagConstraints.PAGE_END作为锚点。
注意: 教程的示例以不同的方式指定约束对象,您可能在其他程序中也会看到。我们的示例以前是通过在GridBagLayout对象上调用setConstraints方法来指定约束。例如:
GridBagLayout gridbag = new GridBagLayout();
pane.setLayout(gridbag);
...
gridbag.setConstraints(button, c);
pane.add(button);
但是,我们建议您使用Container.add方法,因为这比使用setConstraints更清晰。
这里是一个表格,显示了GridBagLayoutDemo内容窗格中每个组件的所有约束条件。非默认值用粗体标记。与前一表项不同的值用斜体标记。
| 组件 | 约束条件 |
|---|---|
| 所有组件 |
ipadx = 0
fill = GridBagConstraints.HORIZONTAL
|
| 按钮 1 |
|---|
ipady = 0 weightx = 0.5 weighty = 0.0 gridwidth = 1 anchor = GridBagConstraints.CENTER insets = new Insets(0,0,0,0) gridx = 0 gridy = 0
|
| 按钮 2 |
|---|
weightx = 0.5
***gridx = 1***
gridy = 0
|
| 按钮 3 |
|---|
weightx = 0.5
***gridx = 2***
gridy = 0
|
| 按钮 4 |
|---|
***ipady = 40***
*weightx = 0.0*
***gridwidth = 3***
***gridx = 0***
***gridy = 1***
|
| 按钮 5 |
|---|
*ipady = 0*
weightx = 0.0
***weighty = 1.0***
***anchor = GridBagConstraints.PAGE_END***
***insets = new Insets(10,0,0,0)***
***gridwidth = 2***
***gridx = 1***
***gridy = 2***
|
GridBagLayoutDemo有两个跨越多列的组件(按钮 4 和按钮 5)。为了使按钮 4 变高,我们向其添加了内部填充(ipady)。为了在按钮 4 和按钮 5 之间留出空间,我们使用插入来在按钮 5 上方添加至少 10 像素,并使按钮 5 贴近其单元格的底边。
pane容器中的所有组件都尽可能宽,给定它们所占用的单元格。程序通过将GridBagConstraints的fill实例变量设置为GridBagConstraints.HORIZONTAL来实现这一点,并为所有组件保持该设置。如果程序没有指定填充,按钮将保持其自然宽度,如下所示:

当您放大 GridBagLayoutDemo 的窗口时,列会成比例增长。这是因为第一行中的每个组件,每个组件都是一列宽,weightx = 0.5。这些组件的weightx的实际值并不重要。重要的是所有组件,因此所有列,具有大于 0 的相等权重。如果由GridBagLayout管理的任何组件未设置weightx,则当组件的容器变宽时,组件将保持在容器中心聚集在一起,如下所示:

如果容器的大小比首选大小小或大,则任何空间都根据GridBagContainer的权重分配。
请注意,如果您放大窗口,只有最后一行会变高。这是因为只有按钮 5 的weighty大于零。
GridBagLayout API
GridBagLayout和GridBagConstraints类各自只有一个构造函数,没有参数。您不是在GridBagConstraints对象上调用方法,而是操作其实例变量,如指定约束中所述。通常,您在GridBagLayout对象上调用的唯一方法是setConstraints,如示例解释中所示。
使用 GridBagLayout 的示例
您可以在本教程中找到使用GridBagLayout的示例。以下表格列出了一些示例。
| 示例 | 描述位置 | 注释 |
|---|---|---|
GridBagLayoutDemo |
本节 | 使用许多功能——权重、插入、内部填充、水平填充、精确单元格定位、多列单元格和锚定(组件在单元格内的定位)。 |
TextSamplerDemo |
使用文本组件 | 对齐两对标签和文本字段,并在容器的整个宽度上添加一个标签。 |
ContainerEventDemo |
如何编写容器监听器 | 使用权重、填充和相对定位在容器中定位五个组件。 |
如何使用 GridLayout
注意: 本课程涵盖手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码且不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
以下图表示使用GridLayout类的应用程序的快照。

点击“启动”按钮以使用Java™ Web Start运行GridLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
此演示的完整代码位于GridLayoutDemo.java文件中。
GridLayout 对象将组件放置在单元格网格中。每个组件占据其单元格中的所有可用空间,每个单元格的大小完全相同。如果调整GridLayoutDemo窗口的大小,GridLayout 对象会更改单元格大小,以使单元格尽可能大,给定容器可用的空间。
下面的代码片段创建了GridLayout对象和其管理的组件。
GridLayout experimentLayout = new GridLayout(0,2);
...
compsToExperiment.setLayout(experimentLayout);
compsToExperiment.add(new JButton("Button 1"));
compsToExperiment.add(new JButton("Button 2"));
compsToExperiment.add(new JButton("Button 3"));
compsToExperiment.add(new JButton("Long-Named Button 4"));
compsToExperiment.add(new JButton("5"));
GridLayout 类的构造函数创建一个具有两列并且必要行数的实例。
使用组合框设置组件周围的垂直或水平填充量,然后单击“应用间隙”按钮。以下代码片段显示了如何使用GridLayout类的setVgap和setHgap方法处理您的选择:
applyButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
//Get the horizontal gap value
String horGap = (String)horGapComboBox.getSelectedItem();
//Get the vertical gap value
String verGap = (String)verGapComboBox.getSelectedItem();
//Set up the horizontal gap value
experimentLayout.setHgap(Integer.parseInt(horGap));
//Set up the vertical gap value
experimentLayout.setVgap(Integer.parseInt(verGap));
//Set up the layout of the buttons
experimentLayout.layoutContainer(compsToExperiment);
}
});
GridLayout API
以下表格列出了指定行数和列数的GridLayout类的构造函数。
GridLayout 类的构造函数
| 构造函数 | 目的 |
|---|---|
GridLayout(int *rows*, int *cols*) |
创建具有指定行数和列数的网格布局。布局中的所有组件都具有相同的大小。rows和cols中的一个,但不是两者都可以为零,这意味着可以在一行或一列中放置任意数量的对象。 |
GridLayout(int *rows*, int *cols*, int *hgap*, int *vgap*) |
创建具有指定行数和列数的网格布局。此外,水平和垂直间隙设置为指定值。水平间隙放置在每列之间。垂直间隙放置在每行之间。 |
GridLayout类有两个构造函数:
使用GridLayout的示例
下表列出了使用GridLayout类的代码示例,并提供到相关部分的链接。
| 示例 | 描述位置 | 注释 |
|---|---|---|
GridLayoutDemo |
本页面 | 使用了 2 列网格。 |
ComboBoxDemo2 |
如何使用组合框 | 其中一个示例,使用 1x1 网格使组件尽可能大。 |
LabelDemo |
如何使用标签 | 使用了 3 行网格。 |
如何使用 GroupLayout
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/group.html
GroupLayout 是为 GUI 构建器(如 NetBeans IDE 提供的 GUI 构建器 Matisse)开发的布局管理器。尽管布局管理器最初是为适应 GUI 构建器的需求而设计的,但它也适用于手动编码。本讨论将教你GroupLayout的工作原理,并向你展示如何使用GroupLayout构建 GUI,无论你选择使用像 Matisse 这样的 GUI 构建器还是编写自己的代码。
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您对学习布局管理的所有细节不感兴趣,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编写代码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
设计原则:独立维度
GroupLayout 分别处理水平和垂直布局。每个维度的布局都是独立定义的。在定义水平布局时,你不需要担心垂直维度,反之亦然,因为沿着每个轴的布局完全独立于沿着另一个轴的布局。
当只关注一个维度时,你只需一次解决一半的问题。这比同时处理两个维度要容易。这意味着,当然,每个组件在布局中需要定义两次。如果忘记这样做,GroupLayout 将生成异常。
布局组织:分层组
GroupLayout 使用两种类型的布局——顺序和并行,结合了分层组合。
-
采用顺序排列,组件就像
BoxLayout或FlowLayout沿一个轴一样简单地一个接一个地放置。每个组件的位置都是相对于前一个组件定义的。 -
第二种方式将组件并排放置——在同一空间中叠加。它们可以沿垂直轴基线对齐、顶部对齐或底部对齐。沿水平轴,如果组件大小不同,它们可以左对齐、右对齐或居中对齐。
通常,沿一个维度并排放置的组件在另一个维度上是顺序排列的,以避免重叠。
这两种布局之所以强大,是因为它们可以被嵌套层次化。为此,GroupLayout 定义了布局组。一个组可以是顺序的或并行的,并且可以包含组件、其他组和间隙(下面讨论)。
顺序组的大小是包含元素的大小之和,而并行组的大小对应于最大元素的大小(尽管,根据元素和基线的位置,基线对齐组的大小可能比最大元素稍大一些)。
定义布局意味着定义组件如何通过组合顺序和并行排列进行分组。
让我们用一个简单的例子来看看它在实践中是如何工作的。
一个例子
让我们从一些简单的东西开始,只有一排中的三个组件:

我们将使用组来表示此布局。从水平轴开始,很容易看到有一个从左到右排列的顺序组,包含 3 个组件。沿着垂直轴,有一个相同位置、大小和基线的并行组,包含相同的 3 个组件:

在伪代码中,布局规范可能看起来像这样(真正的代码在下面的编写代码部分):
horizontal layout = sequential group { c1, c2, c3 }
vertical layout = parallel group (BASELINE) { c1, c2, c3 }
这说明了前面提到的一个原则:在一个维度上顺序组合的组件通常在另一个维度上形成并行组。
现在让我们再添加一个组件 C4,与 C3 左对齐:

沿着水平轴,新组件占据与 C3 相同的水平空间,以便与 C3 形成并行组。沿着垂直轴,C4 与最初的三个组件的并行组形成顺序组。

在伪代码中,布局规范现在看起来像这样:
horizontal layout = sequential group { c1, c2, parallel group (LEFT) { c3, c4 } }
vertical layout = sequential group { parallel group (BASELINE) { c1, c2, c3 }, c4 }
现在您了解了使用GroupLayout设计布局的最重要方面。还有一些细节需要解释:如何添加间隙,如何定义大小和调整大小行为,如何定义对齐布局,以及如何编写真实代码。
间隙
间隙可以被视为具有特定大小的不可见组件。可以像组件或其他组件一样向组中添加任意大小的间隙。使用间隙,您可以精确控制组件之间的距离或与容器边框的距离。
GroupLayout还定义了自动间隙,这些间隙对应于相邻组件之间(或组件与容器边框之间)的首选距离。这样的间隙的大小是动态计算的,基于应用程序使用的外观和感觉(LayoutStyle类用于此)。使用自动(首选)间隙有两个优点:您不必指定间隙的像素大小,它们会自动调整到 UI 运行的外观和感觉,反映实际的外观和感觉指南。
GroupLayout区分两个组件之间的首选间隙和组件与容器边框之间的首选间隙。在GroupLayout API 中有相应的方法用于添加这些间隙(addPreferredGap和setContainerGap)。有三种类型的组件间隙:相关,不相关和缩进。LayoutStyle.ComponentPlacement枚举定义了用作addPreferredGap方法参数的相应常量:RELATED,UNRELATED和INDENT。相关和不相关间隙之间的区别仅在于大小 - 不相关组件之间的距离稍大一些。缩进表示当一个组件位于另一个组件下方并带有缩进时,两个组件之间的首选水平距离。

如上所述,GroupLayout可以自动插入间隙 - 如果您没有显式添加自己的间隙,它会为您添加相关的首选间隙。但这不是默认行为。您必须通过在GroupLayout上调用setAutoCreateGaps(true)和setAutoCreateContainerGaps(true)来打开此功能。然后您将自动获得正确的间距。
编写代码
现在,让我们看一下创建上述布局的实际代码。
假设我们有一个名为panel的容器和已经呈现的相同四个组件(c1,c2,c3和c4)。首先,我们创建一个新的GroupLayout对象并将其与面板关联:
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
我们指定自动插入间隙:
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
然后,我们定义组并添加组件。我们使用setHorizontalGroup和setVerticalGroup方法为每个维度建立根组。通过createSequentialGroup和createParallelGroup方法创建组。使用addComponent方法将组件添加到组中。
layout.setHorizontalGroup(
layout.createSequentialGroup()
.addComponent(c1)
.addComponent(c2)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(c3)
.addComponent(c4))
);
layout.setVerticalGroup(
layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(c1)
.addComponent(c2)
.addComponent(c3))
.addComponent(c4)
);
您可以为平行组指定对齐方式。它可以是GroupLayout.Alignment枚举中定义的以下常量之一:LEADING,TRAILING,CENTER和BASELINE。这些常量用于两个维度,并取决于组件方向是从左到右还是从右到左(从上到下还是从下到上)。例如,如果水平(垂直)组件方向是从左到右(从上到下),LEADING表示左(上),而TRAILING表示右(下)。CENTER表示在两个维度上“居中”。如果不指定对齐方式,将使用LEADING。BASELINE对齐方式仅在垂直维度上有效。
注意:
组布局中的对齐仅对不同大小的组件有意义。相同大小的组件将自动对齐到每个GroupLayout.Alignment常量。
有关代码的一些注释:
-
您不需要直接将组件添加到容器中 - 当使用其中一个 addComponent 方法时,这将隐式完成。
-
注意
addComponent方法的链式调用用于填充组。addComponent方法总是返回调用它的组。由于这一点,你不需要使用局部变量来保存组。 -
缩进代码是个好主意,这样可以更容易看到组的层次结构。给每个组件一个新行,在层次结构中的每个新组添加一个缩进级别。一个好的源代码编辑器会帮助你匹配括号来关闭
createXXXGroup方法。遵循这些简单规则,更容易添加新组件或移除现有组件。
组件大小和可调整性
在布局中可调整大小的组件数量没有限制。
在GroupLayout中,每个组件的大小受到三个值的限制;最小大小、首选大小和最大大小。这些大小控制组件在布局中的调整大小。GroupLayout.addComponent(...)方法允许指定大小约束。
如果没有明确指定,布局会通过使用组件的getMinimumSize()、getPreferredSize()和getMaximumSize()方法来询问组件的默认大小。对于大多数组件,比如使JTextField可调整大小或JButton固定大小,你不需要指定任何内容,因为这些组件本身具有默认的调整大小行为。另一方面,你可以覆盖默认行为。例如,你可以使JTextField固定大小或JButton可调整大小。
GroupLayout定义了提供对调整大小行为精确控制的常量。它们可以作为addComponent(Component comp, int min, int pref, int max)方法的参数使用。以下是两个示例:
-
强制组件可调整大小(允许缩小和增长):
*group*.addComponent(component, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ...这允许组件在零大小(最小)到任意大小(
Short.MAX_VALUE作为最大大小表示“无限”)之间调整大小。如果我们不希望组件在其默认最小大小以下缩小,我们会在第二个参数中使用GroupLayout.DEFAULT_SIZE而不是0。 -
要使组件固定大小(禁止调整大小):
group.addComponent(component, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) ...
在这些示例中,组件的初始大小不会改变,其默认大小是组件的首选大小。如果我们想要为组件指定特定大小,我们会在第二个参数中指定,而不是使用GroupLayout.DEFAULT_SIZE。
可调整大小的间隙
指定大小和可调整性也适用于间隙,包括首选间隙。例如,你可以指定两个组件之间的首选间隙,它就像一个弹簧,将组件推开(到容器的相反侧)。两个组件的首选距离仅用作间隙的最小大小。请看下面的代码片段:
layout.createSequentialGroup()
.addComponent(c1)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(c2);
并排组大小
放置在平行组中的可调整大小的元素被拉伸以填充由组中最大元素确定的空间,因此它们最终以相同大小对齐。GroupLayout还提供了控制是否应调整封闭平行组本身的功能。如果组调整大小被抑制,它会阻止包含的元素超过组的首选大小。这样,您可以使一组组件在两侧对齐,或者限制单个组件具有相同的大小。
让我们尝试使我们的例子中的两个组件(水平维度上的c3和c4)大小相同:
layout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
.addComponent(c3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(c4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE);
底层机制的工作方式如下:
-
平行组的大小设置为最大元素的首选大小;因此在我们的例子中是
c4的首选大小。 -
可调整大小的元素被拉伸到组的大小。在我们的例子中,只有
c3被有效地拉伸,c4的大小已经对应于组的大小。
结果,c3和c4将具有相同的宽度。组件不会进一步调整大小,因为平行组本身不可调整大小(上面createParallelGroup方法的第二个参数为false)。

给细心读者的问题:为什么在这个例子中我们将平行组中的两个组件都定义为可调整大小?似乎只有c3可调整大小就足够了,因为c4无论如何都不会被拉伸...
答案是:由于平台和本地化的独立性。否则,我们将不得不依赖于c4组件始终比c3大。但是当应用程序在不同平台上运行或被翻译成另一种语言时,这可能会发生变化。通过使两个组件都可调整大小,它们会相互调整,无论哪一个在特定时刻更大。
使组件大小相同
前面的情况是特殊的,因为组件在同一个平行组中。但是如果我们希望不相关的组件具有相同的大小怎么办?显然,通过分组不能始终保证相同的大小。对话框底部一行中的“确定”和“取消”按钮就是一个很好的例子。为此,GroupLayout提供了一个linkSize方法。该方法允许将任意组件的大小链接在一起,而不管它们放置在何处。链接组件的结果大小根据最大组件设置。例如:
layout.linkSize(SwingConstants.HORIZONTAL, c3, c4);
在这个例子中,尺寸在水平维度上是有选择性地链接的。
运行时更改您的 GUI
有两个重要的方法可以在运行时对 GUI 进行更改,replace() 和 setHonorsVisibility()。使用这两种方法,您可以在运行时交换组件或更改组件的可见性,并使 GUI 相应地重新排列。
replace(Component existingComponent, Component newComponent) 用新组件替换现有组件。动态布局所需的常见操作之一是能够像这样替换组件。例如,也许复选框在显示图形或树的组件之间切换。 GroupLayout 通过 replace() 方法使这种情况变得简单。您可以在不重新创建所有组的情况下交换组件。
用户界面中的另一个常见操作是动态更改组件的可见性。也许组件只有在用户完成表单的早期部分时才显示。为了避免在这种情况下组件移动,应该占用空间,无论组件的可见性如何。 GroupLayout 提供了两种配置不可见组件处理方式的方法。 setHonorsVisibility(boolean) 方法全局设置了不可见组件的处理方式。默认值为 true,表示不可见组件被视为不存在。另一方面,值为 false 为不可见组件提供空间,将其视为可见。 setHonorsVisibility(Component,Boolean) 方法可用于在特定组件级别配置行为。为了确定如何处理可见性,GroupLayout 首先检查组件是否已指定值,如果没有,则检查全局属性的设置。
一些历史:
Java 标准版 6 中的 GroupLayout 由三个不同的工作部分组成:获取组件基线的能力,获取组件之间首选间隔的能力(LayoutStyle),以及 GroupLayout。这项工作最初是作为一个开源项目在 java.net/projects/swing-layout/ 上完成的。
NetBeans 5.0 通过 swing-layout 项目支持 GroupLayout。由于这项工作的成功,所有三个部分都已合并到了 Java 标准版 6 中的 GroupLayout 中。Java SE 6 中的 GroupLayout 与 swing-layout 中的主要区别在于包名称和方法名称。NetBeans 5.5 提供了针对 Java SE 6 中的 GroupLayout 或 swing-layout 中的 GroupLayout 的定位能力。NetBeans 定位的版本取决于项目定位的 Java 平台版本。定位 Java SE 6 的项目使用 Java SE 中的 GroupLayout,否则使用 swing-layout 中的 GroupLayout。
一个 GroupLayout 示例
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/groupExample.html
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您对学习布局管理的所有细节不感兴趣,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅JavaFX 中的布局。
作为使用GroupLayout创建 GUI 的示例,让我们为这个“查找”对话框创建一个布局:

横向布局
从左到右检查水平维度,我们可以看到有 3 个组成序列。第一个实际上不是一个组,只是一个组件--标签。第二个是包含文本字段和复选框的组(我们稍后将对其进行分解)。第三个是两个按钮的组。如下图所示:

让我们用代码勾画出顺序组。请注意,GroupLayout.Alignment.LEADING对应于水平维度中的左对齐。还请注意,我们不指定间隙,假设自动插入间隙功能已打开。
layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(label)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING))
);
现在让我们分解中间的组。这是最难的部分。有一个文本字段与两个包含两个复选框的并行组序列。请参考以下插图:

让我们添加相应的代码:
layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(label)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(textField)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(caseCheckBox)
.addComponent(wholeCheckBox))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(wrapCheckBox)
.addComponent(backCheckBox))))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING))
);
我们希望文本字段可以调整大小,但由于JTextField默认返回正确的最大大小,这是自动发生的。
右侧剩余的组很简单:只包含两个按钮。以下是代码:
layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(label)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(textField)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(caseCheckBox)
.addComponent(wholeCheckBox))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(wrapCheckBox)
.addComponent(backCheckBox))))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(findButton)
.addComponent(cancelButton))
);
最后,我们希望按钮始终保持相同大小,因此让我们将它们链接起来:
layout.linkSize(SwingConstants.HORIZONTAL, findButton, cancelButton);
现在我们完成了水平维度。让我们切换到垂直维度。从现在开始,我们只需要考虑 y 轴。
纵向布局
在纵向维度上,我们从上到下检查布局。我们绝对希望第一行上的所有组件都对齐在基线上。因此,在垂直轴上,有一个基线组的序列,然后是剩余组件的一组。请参考以下图片。

让我们勾画出代码。首先,我们需要定义两个并行组。请注意,GroupLayout.Alignment.LEADING对应于垂直维度中的顶部对齐。
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING))
);
我们可以立即填充基线组:
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(label)
.addComponent(textField)
.addComponent(findButton))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING))
);
现在让我们看看底部组。请注意,取消按钮与复选框不在共享基线上;它与顶部对齐。因此,第二个平行组包括按钮和两个基线组的顺序组,其中包含复选框:

相应的代码如下所示:
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(label)
.addComponent(textField)
.addComponent(findButton))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(caseCheckBox)
.addComponent(wrapCheckBox))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(wholeCheckBox)
.addComponent(backCheckBox)))
.addComponent(cancelButton))
);
因此,我们创建了一个完整的布局,包括调整大小行为,而不需要指定一个像素的数字——一个真正的跨平台布局。请注意,我们不需要指定组件之间的间隙,我们会自动获得正确的间距,并根据外观和感觉指南。这是查找对话框布局的完整代码:
GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(label)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(textField)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(caseCheckBox)
.addComponent(wholeCheckBox))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(wrapCheckBox)
.addComponent(backCheckBox))))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(findButton)
.addComponent(cancelButton))
);
layout.linkSize(SwingConstants.HORIZONTAL, findButton, cancelButton);
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(label)
.addComponent(textField)
.addComponent(findButton))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(caseCheckBox)
.addComponent(wrapCheckBox))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(wholeCheckBox)
.addComponent(backCheckBox)))
.addComponent(cancelButton))
);
这是完整的Find.java文件。您可以编译并运行它。尝试水平调整对话框的大小,看看布局如何自动调整到新的大小。
如何使用 SpringLayout
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/spring.html
注意:本课程涵盖手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布置您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么建议使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅使用 JavaFX 中的布局。
SpringLayout类是在 JDK 版本 1.4 中添加的,用于支持 GUI 构建器中的布局。SpringLayout是一个非常灵活的布局管理器,可以模拟其他布局管理器的许多功能。但是,SpringLayout是非常低级的,因此您真的应该只在 GUI 构建器中使用它,而不是尝试手动编写弹簧布局管理器。
本节以一个简单的示例开始,展示创建第一个弹簧布局所需记住的所有事项,以及当您忘记它们时会发生什么!后来,它介绍了几种不同类型网格中布置组件的实用方法。
这里是我们将要涵盖的一些布局的图片:



弹簧布局的工作原理
Spring 布局通过在组件边缘之间定义方向关系或约束来完成其工作。例如,您可以定义一个组件的左边缘与另一个组件的右边缘之间的固定距离(比如 5 像素)。
在SpringLayout中,每个边缘的位置取决于另一个边缘的位置。如果随后添加约束以为边缘创建新的绑定,则将丢弃先前的绑定,边缘仍然依赖于单个边缘。
与许多布局管理器不同,SpringLayout不会自动设置其管理的组件的位置。如果您手动编写使用SpringLayout的 GUI,请记但通过约束西/东和北/南位置来初始化组件位置。根据您使用的约束,您可能还需要显式设置容器的大小。
组件定义了边缘属性,这些属性由Spring实例连接。每个弹簧有四个属性 其最小、首选和最大值,以及其实际(当前)值。与每个组件相关联的弹簧被收集到一个SpringLayout.Constraints对象中。
Spring类的一个实例包含三个特征其行为的属性:最小值、首选值和最大值。这些属性中的每一个可能参与根据一系列规则定义其第四个值属性。
Spring类的一个实例可以被视为一个机械弹簧,当弹簧被压缩或拉伸远离其首选值时,它提供一个校正力。这个力被建模为距离首选值的线性函数,但具有两个不同的常数 -- 一个用于压缩力,一个用于张力力。这些常数由弹簧的最小值和最大值指定,以便当弹簧处于其最小值时产生与其处于最大值时产生的相等且相反的力。因此,首选值和最小值之间的差值代表了弹簧可以被压缩的容易程度。其最大值和首选值之间的差值表示了Spring可以被拉伸的容易程度。
基于此,SpringLayout可以被视为一组通过边缘上的一组弹簧连接的对象。
示例:SpringDemo
这一部分将带您了解为使用SpringLayout指定容器约束条件的典型步骤。第一个示例,SpringDemo1.java,是一个非常简单的应用程序,其中包含一个由弹簧布局控制的内容窗格中的标签和文本字段。以下是相关代码:
public class SpringDemo1 {
public static void main(String[] args) {
...
Container contentPane = frame.getContentPane();
SpringLayout layout = new SpringLayout();
contentPane.setLayout(layout);
contentPane.add(new JLabel("Label: "));
contentPane.add(new JTextField("Text field", 15));
...
frame.pack();
frame.setVisible(true);
}
}
单击“启动”按钮以使用Java™ Web Start运行 SpringDemo1(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
当 GUI 首次出现时,它看起来是这样的:

当调整大小使其变大时,它看起来是这样的:

显然,我们有一些问题。窗口不仅显示得太小,而且即使调整大小,组件仍然位于(0,0)处。这是因为我们没有设置任何弹簧来指定组件的位置和容器的宽度。一个小小的安慰是,至少组件都处于其首选大小 我们从SpringLayout为每个组件创建的默认弹簧中免费获得了这一点。
我们的下一个示例,SpringDemo2.java,通过为每个组件指定位置来改善情况。点击“启动”按钮以使用Java™ Web Start运行 SpringDemo2(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
在这个示例中,我们将指定组件应该在一行中显示,并且它们之间有 5 个像素的间距。以下代码指定了标签的位置:
//Adjust constraints for the label so it's at (5,5).
layout.putConstraint(SpringLayout.WEST, label,
5,
SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.NORTH, label,
5,
SpringLayout.NORTH, contentPane);
第一个putConstraint调用指定标签的左(西)边缘应距其容器的左边缘 5 像素。这对应于 x 坐标为 5。第二个putConstraint调用设置了标签的顶部(北)边缘与其容器之间的类似关系,导致 y 坐标为 5。
这是设置文本字段位置的代码:
//Adjust constraints for the text field so it's at
//(*<label's right edge>* + 5, 5).
layout.putConstraint(SpringLayout.WEST, textField,
5,
SpringLayout.EAST, label);
layout.putConstraint(SpringLayout.NORTH, textField,
5,
SpringLayout.NORTH, contentPane);
第一个putConstraint调用使文本字段的左(西)边缘与标签的右(东)边缘相隔 5 像素。第二个putConstraint调用与第一个片段中的第二个调用类似,并具有将组件的 y 坐标设置为 5 的相同效果。
前面的示例仍然存在容器显示得太小的问题。但是当我们调整窗口大小时,组件就位于正确位置了:

为了使容器最初以正确的大小显示,我们需要设置定义容器右(东)边和底部(南)边的弹簧。默认情况下,没有设置右边和底部容器边缘的约束。通过设置这些约束来定义容器的大小。SpringDemo3.java展示了如何做到这一点。点击启动按钮使用Java™ Web Start来运行 SpringDemo3(下载 JDK 7 或更高版本)。或者,要自己编译和运行示例,请参考示例索引。
这里是设置容器弹簧的代码:
layout.putConstraint(SpringLayout.EAST, contentPane,
5,
SpringLayout.EAST, textField);
layout.putConstraint(SpringLayout.SOUTH, contentPane,
5,
SpringLayout.SOUTH, textField);
第一个putConstraint调用使容器的右边距离文本字段的右边缘向右移动 5 像素。第二个调用使其底边距离最高组件的底边超出 5 像素(为简单起见,我们假设是文本字段)。
最终,窗口以正确的大小显示:

当我们使窗口变大时,我们可以看到弹簧布局在起作用,将额外空间分配给可用的组件。

在这种情况下,弹簧布局选择将所有额外空间都分配给文本字段。虽然弹簧布局似乎对待标签和文本字段有所不同,但弹簧布局对任何 Swing 或 AWT 组件都没有特殊知识。它依赖于组件的最小、首选和最大大小属性的值。下一节将讨论弹簧布局如何使用这些属性,以及为什么它们可能导致空间分配不均匀。
弹簧和组件大小
SpringLayout对象会自动为SpringLayout控制的每个组件的高度和宽度安装Spring。这些弹簧本质上是组件的getMinimumSize、getPreferredSize和getMaximumSize方法的封装。通过“封装”我们的意思是这些弹簧不仅从这些方法中初始化适当的值,而且这些弹簧会跟踪这些值。例如,代表组件宽度的Spring对象是一种特殊类型的弹簧,它简单地将其实现委托给组件的相关大小方法。这样,随着组件特性的变化,弹簧会与大小方法保持同步。
当组件的getMaximumSize和getPreferredSize方法返回相同的值时,SpringLayout将其解释为组件不应该被拉伸。JLabel和JButton是以这种方式实现的组件的示例。因此,SpringDemo3 示例中的标签不会被拉伸。
一些组件的getMaximumSize方法,如JTextField,返回其最大尺寸的宽度和高度值为Integer.MAX_VALUE,表示组件可以增长到任意大小。因此,当 SpringDemo3 窗口被放大时,SpringLayout将所有额外空间分配给唯一可以增长的弹簧 决定文本字段大小的弹簧。
关于 SpringLayout API 的更多信息
SpringDemo 示例使用SpringLayout方法putConstraint来设置与每个组件关联的弹簧。putConstraint方法是一个方便的方法,让您修改组件的约束而无需使用完整的弹簧布局 API。这里再次是从SpringDemo3设置标签位置的代码:
layout.putConstraint(SpringLayout.WEST, label,
5,
SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.NORTH, label,
5,
SpringLayout.NORTH, contentPane);
这里是直接使用SpringLayout.Constraints和Spring类的等效代码:
SpringLayout.Constraints contentPaneCons =
layout.getConstraints(contentPane);
contentPaneCons.setX(Spring.sum(Spring.constant(5),
contentPaneCons
.getConstraint(SpringLayout.WEST)));
contentPaneCons.setY(Spring.sum(Spring.constant(5),
contentPaneCons
.getConstraint(SpringLayout.NORTH)));
要查看整个演示如何转换为使用此 API,请查看SpringDemo4.java。该文件还包括一个更加精心制作(并且更长)的代码版本,用于设置容器的大小。点击“启动”按钮以使用Java™ Web Start运行 SpringDemo4(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
正如前面的片段所暗示的,SpringLayout和SpringLayout.Constraints倾向于使用不同的约定来描述弹簧。SpringLayout API 使用边缘来定义其约束。弹簧连接边缘以建立它们之间的线性关系。边缘由组件使用以下常量定义:
-
SpringLayout.NORTH指定组件边界矩形的顶边。 -
SpringLayout.SOUTH指定组件边界矩形的底边。 -
SpringLayout.EAST指定组件边界矩形的右边缘。 -
SpringLayout.WEST指定组件边界矩形的左边缘。 -
SpringLayout.BASELINE指定组件的基线。 -
SpringLayout.HORIZONTAL_CENTER指定组件边界矩形的水平中心。 -
SpringLayout.VERTICAL_CENTER指定组件边界矩形的垂直中心。
边与Spring对象不同,SpringLayout.Constraints类了解边,但仅对以下属性有Spring对象:
-
x
-
y
-
width
-
height
每个Constraints对象维护其弹簧与其代表的边之间的以下关系:
west = x
north = y
east = x + width
south = y + height
如果您感到困惑,不要担心。下一节介绍了一些实用方法,您可以使用这些方法来完成一些常见的布局任务,而无需了解弹簧布局 API 的任何内容。
网格的实用方法
因为SpringLayout类是为 GUI 构建器创建的,为布局设置单独的弹簧可能会很麻烦。本节介绍了一些方法,您可以使用这些方法来安装布局一组组件所需的所有弹簧。这些方法模拟了GridLayout、GridBagLayout和BoxLayout类的一些特性。
两个方法,称为makeGrid和makeCompactGrid,在SpringUtilities.java中定义。这两种方法都通过将组件分组到行和列中,并使用Spring.max方法来创建宽度或高度弹簧,使得行或列足够大以容纳其中的所有组件。在makeCompactGrid方法中,相同的宽度或高度弹簧用于特定列或行中的所有组件。相比之下,在makeGrid方法中,宽度和高度弹簧由容器中的每个组件共享,强制它们的大小都相同。此外,Spring提供了用于创建不同类型弹簧的工厂方法,包括依赖于其他弹簧的弹簧。
让我们看看这些方法是如何运作的。我们的第一个示例,在源文件SpringGrid.java中实现,显示了一堆数字在文本字段中。中心文本字段比其他字段宽得多。就像GridLayout一样,有一个大单元格会强制所有单元格大小相等。点击启动按钮以使用Java™ Web Start运行 SpringGrid(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。

这是在 SpringGrid 中创建和布局文本字段的代码:
JPanel panel = new JPanel(new SpringLayout());
for (int i = 0; i < 9; i++) {
JTextField textField = new JTextField(Integer.toString(i));
...*//when i==4, put long text in the text field*...
panel.add(textField);
}
...
SpringUtilities.makeGrid(panel,
3, 3, //rows, cols
5, 5, //initialX, initialY
5, 5);//xPad, yPad
现在让我们看一个示例,在源文件SpringCompactGrid.java中,该示例使用makeCompactGrid方法而不是makeGrid。此示例显示了大量数字,以展示弹簧布局最小化所需空间的能力。点击“启动”按钮以使用Java™ Web Start运行 SpringCompactGrid(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
这是 SpringCompactGrid GUI 的外观:

这是创建并布局 SpringCompactGrid 中文本字段的代码:
JPanel panel = new JPanel(new SpringLayout());
int rows = 10;
int cols = 10;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
int anInt = (int) Math.pow(r, c);
JTextField textField =
new JTextField(Integer.toString(anInt));
panel.add(textField);
}
}
//Lay out the panel.
SpringUtilities.makeCompactGrid(panel, //parent
rows, cols,
3, 3, //initX, initY
3, 3); //xPad, yPad
makeCompactGrid方法最方便的用途之一是将标签与组件关联,其中标签位于一列,组件位于另一列。文件SpringForm.java以这种方式使用makeCompactGrid,如下图所示。

点击“启动”按钮以使用Java™ Web Start运行 SpringForm(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
这是创建并布局 SpringForm 中标签-文本字段对的代码:
String[] labels = {"Name: ", "Fax: ", "Email: ", "Address: "};
int numPairs = labels.length;
//Create and populate the panel.
JPanel p = new JPanel(new SpringLayout());
for (int i = 0; i < numPairs; i++) {
JLabel l = new JLabel(labels[i], JLabel.TRAILING);
p.add(l);
JTextField textField = new JTextField(10);
l.setLabelFor(textField);
p.add(textField);
}
//Lay out the panel.
SpringUtilities.makeCompactGrid(p,
numPairs, 2, //rows, cols
6, 6, //initX, initY
6, 6); //xPad, yPad
因为我们使用了真正的布局管理器而不是绝对定位,布局管理器会动态响应涉及组件的更改。例如,如果标签的名称是本地化的,弹簧布局会生成一个根据需要给第一列更多或更少空间的配置。正如下图所示,当窗口调整大小时,灵活大小的组件——文本字段——会占用所有多余空间,而标签则保持所需空间。

我们在SpringBox.java中的makeCompactGrid方法的最后一个示例显示了一些配置为在单行中布局的按钮。单击“启动”按钮以使用Java™ Web Start运行 SpringBox(下载 JDK 7 或更高版本)。或者,要自己编译和运行示例,请参考示例索引。

请注意,在单行的情况下,行为几乎与BoxLayout的行为相同。不仅组件的布局方式与BoxLayout安排它们的方式相同,而且使用SpringLayout的容器的最小、首选和最大尺寸返回与BoxLayout相同的结果。这是产生此布局的makeCompactGrid调用:
//Lay out the buttons in one row and as many columns
//as necessary, with 6 pixels of padding all around.
SpringUtilities.makeCompactGrid(contentPane, 1,
contentPane.getComponentCount(),
6, 6, 6, 6);
让我们看看当我们调整窗口大小时会发生什么。这是一个奇特的特殊情况,值得注意,因为在你的第一个布局中可能会意外遇到它。

什么都没动!这是因为没有定义任何组件(按钮)或它们之间的间距是可伸缩的。在这种情况下,弹簧布局计算出父容器的最大尺寸等于其首选尺寸,这意味着父容器本身不可伸缩。如果 AWT 拒绝调整大小不可伸缩的窗口可能会更少混淆,但它没有。布局管理器在这里无法做出任何明智的操作,因为没有组件会占用所需的空间。它不会崩溃,而是什么都不做,保持所有组件不变。
SpringLayout API
使用SpringLayout的 API 分布在三个类中:
-
SpringLayout -
SpringLayout.Constraints -
Spring
SpringLayout
| 构造函数或方法 | 目的 |
|---|---|
| SpringLayout() | 创建一个SpringLayout实例。 |
| SpringLayout.Constraints getConstraints(Component) | 获取与指定组件关联的约束(弹簧集合)。 |
| Spring getConstraint(String, Component) | 获取组件边缘的弹簧。第一个参数指定边缘,必须是以下SpringLayout常量之一:NORTH、SOUTH、EAST或WEST。 |
| void putConstraint(String, Component, int, String, Component) void putConstraint(String, Component, Spring, String, Component) | 定义两个组件边缘之间关系的便捷方法。前两个参数指定第一个组件及其受影响的边缘。后两个参数指定第二个组件及其受影响的边缘。第三个参数指定确定两者之间距离的 spring。当第三个参数为整数时,创建一个常量 spring 以提供组件边缘之间的固定距离。 |
SpringLayout.Constraints
| 构造函数或方法 | 目的 |
|---|
| SpringLayout.Constraints() SpringLayout.Constraints(Spring, Spring) | 创建一个SpringLayout.Constraints实例。 |
SpringLayout.Constraints(Spring, Spring, Spring, Spring) | 创建一个SpringLayout.Constraints实例。前两个参数分别指定 X 和 Y springs,后两个参数分别指定高度和宽度 springs。省略参数会导致相应的 spring 为null,SpringLayout通常会用适当的默认值替换。 |
| Spring getConstraint(String) Spring getHeight() | 获取约束Spring。获取高度Spring。 |
Spring getWidth() | 获取宽度Spring。 |
Spring getX() | 获取 X 坐标Spring。 |
Spring getY() | 获取 Y 坐标Spring。 |
void setConstraint(String, Spring) | 设置SpringLayout.Constraints的约束。第一个参数指定 X spring,第二个参数指定 Y spring。 |
void setHeight(Spring) | 设置高度Spring。 |
void setWidth(Spring) | 设置宽度Spring。 |
void setY(Spring) | 获取或设置指定的弹簧。getConstraint 和 setConstraint 方法的字符串参数指定一个边缘名称,并且必须是 SpringLayout 常量 NORTH、SOUTH、EAST 或 WEST 中的一个。 |
Spring
| 方法 | 目的 |
|---|---|
| 静态 Spring constant(int) 静态 Spring constant(int, int, int) | 创建一个不跟踪组件大小的弹簧。三参数版本创建一个弹簧,其最小值、首选值和最大值按指定顺序设置。一参数版本创建一个弹簧,其最小值、首选值和最大值都设置为指定整数。尽管名称是常量,但由 constant 返回的弹簧是可变的。为使布局正常工作,SpringLayout 可能被迫调整“常量”弹簧。因此,除非(1)您真正希望弹簧始终完全相同,并且(2)其他弹簧在布局中提供了一些灵活性,否则应避免重用常量弹簧。 |
| 静态 Spring sum(Spring, Spring) 静态 Spring max(Spring, Spring)
静态 Spring minus(Spring) | 创建一个经过某种数学操作的弹簧。sum 方法将两个弹簧相加。max 方法返回一个值始终大于或等于两个参数值的弹簧。minus 方法返回一个与参数方向相反的弹簧。minus 方法可用于创建 sum 方法的参数,从而获得两个弹簧之间的差异。 |
| int getMinimumValue() int getPreferredValue()
int getMaximumValue() | 从弹簧获取相应的值。对于由 SpringLayout 创建的自动跟踪组件的弹簧,这些方法会调用组件相应的 get*Xxx*Size 方法。 |
| 获取值() 设置值(int) | 获取或设置弹簧的当前值。 |
|---|
使用 SpringLayout 的示例
以下表格列出了一些使用弹簧布局的示例。
| 示例 | 描述位置 | 注释 |
|---|---|---|
SpringDemo3 |
本页 | 使用SpringLayout创建一排均匀间隔、自然大小的组件。 |
SpringDemo4 |
本页 | 重新实现 SpringDemo3 以直接使用SpringLayout.Constraints和Spring。 |
SpringGrid |
本页 | 使用SpringLayout和makeGrid实用方法创建所有组件大小相同的布局。 |
SpringCompactGrid |
本页 | 使用SpringLayout和makeCompactGrid实用方法创建一种布局,其中一行中的所有组件具有相同的高度,一列中的所有组件具有相同的宽度。 |
SpringForm |
本页 | 使用SpringLayout和makeCompactGrid对齐标签-文本字段对。 |
SpringBox |
本页 | 使用SpringLayout和makeCompactGrid演示布局单行组件时,当没有弹簧可以增长时会发生什么。 |
SpinnerDemo |
如何使用微调器 | 使用SpringLayout和makeCompactGrid布局标签-微调器对的行。 |
TextInputDemo |
如何使用格式化文本字段 | 使用SpringLayout和makeCompactGrid布局标记组件的行。这些组件是文本字段、格式化文本字段和微调器的混合。 |
创建自定义布局管理器
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/custom.html
在开始创建自定义布局管理器之前,请确保没有现有的布局管理器符合您的要求。特别是像GridBagLayout、SpringLayout和BoxLayout这样的布局管理器在许多情况下都足够灵活。您还可以从其他来源找到布局管理器,比如从互联网上。最后,您可以通过将组件分组到诸如面板之类的容器中来简化布局。
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 来创建您的 GUI,请参阅在 JavaFX 中使用布局。
要创建自定义布局管理器,您必须创建一个实现LayoutManager接口的类。您可以直接实现它,或者实现其子接口LayoutManager2。
每个布局管理器必须至少实现LayoutManager接口所需的以下五个方法:
void addLayoutComponent(String, Component)
被Container类的add方法调用。通常不将字符串与其组件关联的布局管理器在此方法中不执行任何操作。
void removeLayoutComponent(Component)
被Container方法remove和removeAll调用。布局管理器重写此方法以清除与Component关联的内部状态。
Dimension preferredLayoutSize(Container)
被Container类的getPreferredSize方法调用,该方法本身在各种情况下被调用。此方法应计算并返回容器的理想大小,假设其中的组件将达到或超过其首选大小。此方法必须考虑容器的内部边框,这些边框由getInsets方法返回。
Dimension minimumLayoutSize(Container)
被Container的getMinimumSize方法调用,该方法本身在各种情况下都会被调用。该方法应计算并返回容器的最小尺寸,假设其中的组件将在其最小尺寸或以上。该方法必须考虑容器的内部边框,这些边框由getInsets方法返回。
void layoutContainer(Container)
用于定位和调整容器中每个组件的大小。布局管理器的layoutContainer方法实际上不会绘制组件。它只是调用每个组件的setSize、setLocation和setBounds方法中的一个或多个来设置组件的大小和位置。
该方法必须考虑容器的内部边框,这些边框由getInsets方法返回。如果适用,它还应考虑容器的方向(由getComponentOrientation方法返回)。您不能假设在调用layoutContainer之前将调用preferredLayoutSize或minimumLayoutSize方法。
除了实现前述的五个方法外,布局管理器通常会实现至少一个公共构造函数和toString方法。
如果您希望支持组件约束、最大尺寸或对齐方式,则您的布局管理器应实现LayoutManager2接口。事实上,出于这些原因以及许多其他原因,几乎所有现代布局管理器都需要实现LayoutManager2。该接口在LayoutManager所需的五个方法基础上添加了五个方法:
-
addLayoutComponent(Component, Object) -
getLayoutAlignmentX(Container) -
getLayoutAlignmentY(Container) -
invalidateLayout(Container) -
maximumLayoutSize(Container)
这些方法中,最重要的是addLayoutComponent(Component, Object)和invalidateLayout(Container)。addLayoutComponent方法用于使用指定的约束对象向布局中添加组件。invalidateLayout方法用于使布局失效,因此如果布局管理器缓存了信息,则应将其丢弃。有关LayoutManager2的更多信息,请参阅LayoutManager2 API 文档。
最后,每当您创建自定义布局管理器时,都应注意保持对不再是Container子级的Component实例的引用。换句话说,布局管理器应覆盖removeLayoutComponent以清除与Component相关的任何缓存状态。
自定义布局的示例
示例CustomLayoutDemo使用了一个名为DiagonalLayout的自定义布局管理器。您可以在DiagonalLayout.java中找到布局管理器的源代码。DialogLayout以对角线方式从左到右布置组件,每行一个组件。这里是使用DialogLayout布置五个按钮的 CustomLayoutDemo 的图片。

点击启动按钮以使用Java™ Web Start运行CustomLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
不使用布局管理器(绝对定位)
尽管可以不使用布局管理器,但尽可能使用布局管理器。布局管理器使得更容易调整外观和感觉依赖的组件外观,不同的字体大小,容器的大小变化以及不同的语言环境。布局管理器也可以轻松地被其他容器以及其他程序重复使用。
注意:本课程涵盖了手动编写布局代码,这可能具有挑战性。如果您不想学习布局管理的所有细节,您可能更喜欢使用GroupLayout布局管理器结合构建工具来布局您的 GUI。其中一个构建工具是 NetBeans IDE。否则,如果您想手动编码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅在 JavaFX 中使用布局。
如果一个容器包含的组件的大小不受容器的大小、字体、外观和语言更改的影响,那么绝对定位可能是合理的。包含内部框架的桌面窗格属于这一类别。内部框架的大小和位置不直接取决于桌面窗格的大小。程序员确定内部框架在桌面窗格内的初始大小和位置,然后用户可以移动或调整框架的大小。在这种情况下,布局管理器是不必要的。
另一种适合使用绝对定位的情况是自定义容器,该容器执行特定于容器的大小和位置计算,并且可能需要了解容器的专门状态。这是分割窗格的情况。
创建一个没有布局管理器的容器涉及以下步骤。
-
将容器的布局管理器设置为 null,调用
setLayout(null)。 -
对容器的每个子元素调用
Component类的setbounds方法。 -
调用
Component类的repaint方法。
然而,使用绝对定位创建容器可能会在包含容器的窗口调整大小时出现问题。
这是一个内容窗格使用绝对定位的框架的快照。

点击“启动”按钮以使用Java™ Web Start运行 AbsoluteLayoutDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
其代码位于AbsoluteLayoutDemo.java中。以下代码片段显示了如何在内容窗格中创建和布局组件。
pane.setLayout(null);
JButton b1 = new JButton("one");
JButton b2 = new JButton("two");
JButton b3 = new JButton("three");
pane.add(b1);
pane.add(b2);
pane.add(b3);
Insets insets = pane.getInsets();
Dimension size = b1.getPreferredSize();
b1.setBounds(25 + insets.left, 5 + insets.top,
size.width, size.height);
size = b2.getPreferredSize();
b2.setBounds(55 + insets.left, 40 + insets.top,
size.width, size.height);
size = b3.getPreferredSize();
b3.setBounds(150 + insets.left, 15 + insets.top,
size.width + 50, size.height + 20);
...*//In the main method:*
Insets insets = frame.getInsets();
frame.setSize(300 + insets.left + insets.right,
125 + insets.top + insets.bottom);
解决常见布局问题
原文:
docs.oracle.com/javase/tutorial/uiswing/layout/problems.html
注意: 本课程涵盖了手动编写布局代码,这可能具有挑战性。如果你不想学习布局管理的所有细节,可以选择使用GroupLayout布局管理器结合构建工具来布局你的 GUI。其中一种构建工具是 NetBeans IDE。否则,如果你想手动编码而不想使用GroupLayout,那么推荐使用GridBagLayout作为下一个最灵活和强大的布局管理器。
如果你有兴趣使用 JavaFX 来创建 GUI,请查看使用 JavaFX 中的布局。
问题: 如何指定组件的确切尺寸?
-
一些较现代的布局管理器提供了覆盖组件设置的尺寸的方法。检查你正在使用的布局管理器是否允许你指定组件大小。
-
确保你真的需要设置组件的确切尺寸。每个 Swing 组件的首选尺寸都不同,取决于其使用的字体和外观。因此,通常没有必要指定 Swing 组件的确切尺寸。
-
如果组件不受布局管理器控制,可以通过调用
setSize或setBounds方法来设置其大小。否则,你需要提供尺寸提示,然后确保你使用的布局管理器尊重尺寸提示。 -
如果你扩展了 Swing 组件类,可以通过重写组件的
getMinimumSize、getPreferredSize和getMaximumSize方法来给出尺寸提示。这种方法的好处在于,每个get*Xxxx*Size方法可以通过调用super.get*Xxxx*Size()来获取组件的默认尺寸提示。然后在返回之前可以调整尺寸,如果有必要的话。这对于文本组件特别方便,其中你可能希望固定宽度,但高度由内容确定。然而,有时会在GridBagLayout和文本字段中遇到问题,如果容器的尺寸小于首选尺寸,则使用最小尺寸,这可能会导致文本字段大幅缩小。 -
另一种给出尺寸提示的方法是调用组件的
setMinimumSize、setPreferredSize和setMaximumSize方法。 -
如果为已经可见的组件指定了新的尺寸提示,则需要在其上调用
revalidate方法,以确保其包含层次结构再次布局。然后调用repaint方法。
注意: 无论您如何指定组件的大小,请确保组件的容器使用一个能够尊重组件请求大小的布局管理器。FlowLayout 和 GridBagLayout 布局管理器使用组件的首选大小(后者取决于您设置的约束条件),但 BorderLayout 和 GridLayout 通常不会。BoxLayout 布局管理器通常使用组件的首选大小(尽管组件可以更大),并且是少数几个尊重组件最大大小的布局管理器之一。
问题: 我添加了组件到容器后,组件没有显示出来。
- 在添加组件后,您需要调用
revalidate和repaint才能使组件显示在容器中。
问题: 我的自定义组件尺寸太小了。
-
组件是否实现了
getPreferredSize和getMinimumSize方法?如果是,它们是否返回了正确的值? -
您是否使用了一个可以利用所有可用空间的布局管理器?请参阅选择布局管理器的提示以获取一些关于选择布局管理器并指定其使用特定组件的最大可用空间的提示。
如果您在此列表中找不到您的问题,请参阅解决常见组件问题。
问题和练习:在容器内布置组件
原文:
docs.oracle.com/javase/tutorial/uiswing/QandE/questions-ch4.html
问题
在以下每个问题中,选择最适合描述布局的布局管理器。假设由布局管理器控制的容器是JPanel。
1. 该容器有一个组件应尽可能占据空间
![]() |
![]() |
|---|
a. BorderLayout
b. GridLayout
c. GridBagLayout
d. a 和 b
e. b 和 c
2. 该容器有一行组件,应该以相同大小显示,填充容器的整个区域。

a. FlowLayout
b. GridLayout
c. BoxLayout
d. a 和 b
3. 该容器以列形式显示多个组件,额外的空间位于前两个组件之间。
![]() |
![]() |
|---|
a. FlowLayout
b. BoxLayout
c. GridLayout
d. BorderLayout
4. 该容器可以在不同时间显示三个完全不同的组件,可能取决于用户输入或程序状态。即使组件的大小不同,从一个组件切换到下一个也不应更改为组件分配的空间量。

a. SpringLayout
b. BoxLayout
c. CardLayout
d. GridBagLayout
练习
1. 实现问题 1 中描述和显示的布局。
2. 实现问题 2 中描述和显示的布局。
3. 实现问题 3 中描述和显示的布局。
4. 实现问题 4 中描述和显示的布局。
5. 通过添加一行代码,使您为练习 2 编写的程序显示组件从右到左,而不是从左到右。
检查您的答案。
课程:修改外观和感觉
原文:
docs.oracle.com/javase/tutorial/uiswing/lookandfeel/index.html
示例索引
这节课告诉你如何改变你的 Swing 应用程序的外观和感觉。应用程序的“外观”指的是它的外观。而“感觉”指的是小部件的行为。你可以选择使用默认的 Swing 外观和感觉(比如 Metal 外观和感觉的 Ocean 主题),或者使用本地平台的外观和感觉(比如 Windows、GTK+),或者你可以自定义自己的外观和感觉。
如何设置外观和感觉
设置外观和感觉为可用外观和感觉之一的基本信息。提供了通过编程和命令行设置外观和感觉的信息。
Synth 外观和感觉
提供了如何使用 Synth 包自定义外观和感觉的信息。
Nimbus 外观和感觉
提供了如何使用 JDK 7 发布的 Nimbus 包自定义外观和感觉的信息。
如果你有兴趣使用 JavaFX 创建你的 GUI,请参阅使用 CSS 为 JavaFX 应用程序设置外观和感觉和使用 JavaFX 图表。
如何设置外观和感觉
原文:
docs.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
Swing 的架构设计使您可以更改应用程序 GUI 的“外观和感觉”(L&F)(请参阅A Swing Architecture Overview)。“外观”指的是 GUI 小部件的外观(更正式地说,JComponents),而“感觉”指的是小部件的行为方式。
Swing 的架构通过将每个组件分为两个不同的类来实现多个 L&Fs 的功能:一个JComponent子类和一个对应的ComponentUI子类。例如,每个JList实例都有一个ListUI的具体实现(ListUI扩展自ComponentUI)。在 Swing 的文档中,ComponentUI子类被称为各种名称——“UI”,“组件 UI”,“UI 代理”和“外观和感觉代理”都用来标识ComponentUI子类。
大多数开发人员几乎不需要直接与 UI 代理交互。在大多数情况下,UI 代理由JComponent子类内部用于关键功能,JComponent子类提供了所有对 UI 代理的访问的覆盖方法。例如,JComponent子类中的所有绘图都委托给 UI 代理。通过委托绘图,外观可以根据 L&F 的不同而变化。
每个 L&F 都有责任为 Swing 定义的每个ComponentUI子类提供具体实现。例如,Java 外观和感觉创建一个MetalTabbedPaneUI实例来为JTabbedPane提供 L&F。UI 代理的实际创建由 Swing 为您处理——在大多数情况下,您几乎不需要直接与 UI 代理交互。
本节的其余部分讨论以下主题:
-
可用的外观和感觉
-
以编程方式设置外观和感觉
-
指定外观和感觉:命令行
-
指定外观和感觉:swing.properties
-
UI 管理器如何选择外观和感觉
-
启动后更改外观和感觉
-
一个示例
-
主题
-
SwingSet2 演示程序
可用的外观和感觉
Sun 的 JRE 提供以下 L&Fs:
-
CrossPlatformLookAndFeel—这是在所有平台上看起来相同的“Java L&F”(也称为“Metal”)。它是 Java API 的一部分(javax.swing.plaf.metal),如果您在代码中不做任何设置,则将使用默认值。 -
SystemLookAndFeel—在这里,应用程序使用与其运行的系统相对应的 L&F。系统 L&F 在运行时确定,应用程序会询问系统返回适当 L&F 的名称。 -
Synth——用 XML 文件创建自己的外观和感觉的基础。
-
多路复用—一种让 UI 方法同时委托给多个不同外观实现的方式。
对于 Linux 和 Solaris,如果安装了 GTK+ 2.2 或更高版本,则系统 L&Fs 是“GTK+”,否则是“Motif”。对于 Windows,系统 L&F 是“Windows”,模仿正在运行的特定 Windows OS 的 L&F—经典 Windows、XP 或 Vista。GTK+、Motif 和 Windows L&Fs 由 Sun 提供,并随 Java SDK 和 JRE 一起提供,尽管它们不是 Java API 的一部分。
苹果提供了自己的 JVM,其中包含他们专有的 L&F。
总之,当您使用 SystemLookAndFeel 时,您将看到以下内容:
| 平台 | 外观和感觉 |
|---|---|
| Solaris、安装了 GTK+ 2.2 或更高版本的 Linux | GTK+ |
| 其他 Solaris、Linux | Motif |
| IBM UNIX | IBM* |
| HP UX | HP* |
| 经典 Windows | Windows |
| Windows XP | Windows XP |
| Windows Vista | Windows Vista |
| Macintosh | Macintosh* |
- 由系统供应商提供。
在 API 中看不到系统 L&F。它需要的 GTK+、Motif 和 Windows 包已随 Java SDK 一起提供:
com.sun.java.swing.plaf.gtk.GTKLookAndFeel
com.sun.java.swing.plaf.motif.MotifLookAndFeel
com.sun.java.swing.plaf.windows.WindowsLookAndFeel
请注意路径包括 java,而不是 javax。
注意: GTK+ L&F 只能在安装了 GTK+ 2.2 或更高版本的 UNIX 或 Linux 系统上运行,而 Windows L&F 只能在 Windows 系统上运行。与 Java(Metal)L&F 一样,Motif L&F 可在任何平台上运行。
所有 Sun 的 L&Fs 都有很多共同点。这种共同点在 API 中的 Basic 外观和感觉中定义(javax.swing.plaf.basic)。Motif 和 Windows L&Fs 分别通过扩展 javax.swing.plaf.basic 中的 UI 代理类构建(可以通过相同的方式构建自定义 L&F)。"Basic" L&F 不会在不被扩展的情况下使用。
在 API 中,您将看到四个 L&F 包:
-
javax.swing.plaf.basic—在创建自定义 L&F 时要扩展的基本 UI 代理 -
javax.swing.plaf.metal—Java L&F,也被称为跨平台 L&F("Metal" 是这个 L&F 的 Sun 项目名称)。这个 L&F 的当前默认“主题”(下面讨论)是“Ocean”,因此通常被称为 Ocean L&F。 -
javax.swing.plaf.multi—一个多路复用外观,允许 UI 方法同时委托给多个外观实现。它可以用于增强特定外观的行为,例如在 Windows 外观之上提供音频提示的外观。这是创建残障人士可访问的外观的一种方式。 -
javax.swing.plaf.synth—使用 XML 文件轻松配置的 L&F(在本课程的下一部分讨论)
您不限于使用 Java 平台提供的 L&Fs。您可以使用程序类路径中的任何 L&F。外部 L&Fs 通常以一个或多个 JAR 文件的形式提供,您可以在运行时将其添加到程序的类路径中。例如:
java -classpath .;C:\java\lafdir\customlaf.jar YourSwingApplication
一旦外部 L&F 在程序的类路径中,程序就可以像使用 Java 平台提供的任何 L&Fs 一样使用它。
以编程方式设置外观和感觉
注意: 如果要设置外观和感觉,应该在应用程序中的第一步就这样做。否则,你可能会在请求的外观和感觉之外初始化 Java 外观和感觉。当静态字段引用 Swing 类时,可能会无意中发生这种情况,导致加载外观和感觉。如果尚未指定外观和感觉,则加载 JRE 的默认外观和感觉。对于 Sun 的 JRE,默认是 Java 外观和感觉,对于 Apple 的 JRE 是 Apple 外观和感觉,依此类推。
Swing 组件使用的外观和感觉是通过javax.swing包中的UIManager类指定的。每当创建一个 Swing 组件时,该组件都会向 UI 管理器请求实现组件外观和感觉的 UI 委托。例如,每个JLabel构造函数都会查询 UI 管理器以获取适用于标签的 UI 委托对象。然后使用该 UI 委托对象来实现所有的绘制和事件处理。
要以编程方式指定外观和感觉,可以使用UIManager.setLookAndFeel()方法,并将适当的LookAndFeel子类的完全限定名称作为参数。例如,以下代码中的粗体代码使程序使用跨平台的 Java 外观和感觉:
public static void main(String[] args) {
try {
// Set cross-platform Java L&F (also called "Metal")
UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName());
}
catch (UnsupportedLookAndFeelException e) {
// handle exception
}
catch (ClassNotFoundException e) {
// handle exception
}
catch (InstantiationException e) {
// handle exception
}
catch (IllegalAccessException e) {
// handle exception
}
new SwingApplication(); //Create and show the GUI.
}
或者,这段代码使程序使用系统外观和感觉:
public static void main(String[] args) {
try {
// Set System L&F
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
}
catch (UnsupportedLookAndFeelException e) {
// handle exception
}
catch (ClassNotFoundException e) {
// handle exception
}
catch (InstantiationException e) {
// handle exception
}
catch (IllegalAccessException e) {
// handle exception
}
new SwingApplication(); //Create and show the GUI.
}
你也可以将外观和感觉的实际类名作为UIManager.setLookAndFeel()的参数。例如,
// Set cross-platform Java L&F (also called "Metal")
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
或
// Set Motif L&F on any platform
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
你不限于前面的参数。你可以为程序类路径中的任何外观和感觉指定名称。
指定外观和感觉:命令行
你可以通过使用-D标志设置swing.defaultlaf属性在命令行中指定外观和感觉。例如:
java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel MyApp
java -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel MyApp
指定外观和感觉:swing.properties 文件
另一种指定当前外观和感觉的方法是使用swing.properties文件来设置swing.defaultlaf属性。这个文件可能需要你自己创建,在 Sun 的 Java 发布版本的lib目录中(其他 Java 供应商可能使用不同的位置)。例如,如果你在*javaHomeDirectory*\bin中使用 Java 解释器,那么swing.properties文件(如果存在)就在*javaHomeDirectory*\lib中。以下是swing.properties文件的示例内容:
# Swing properties
swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel
UI 管理器如何选择外观和感觉
当 UI 管理器需要设置外观和感觉时,以下是外观和感觉确定步骤:
-
如果程序在需要外观和感觉之前设置了外观和感觉,UI 管理器会尝试创建指定外观和感觉类的实例。如果成功,所有组件都将使用该外观和感觉。
-
如果程序没有成功指定外观和感觉,那么 UI 管理器将使用
swing.defaultlaf属性指定的外观和感觉。如果swing.properties文件和命令行中都指定了该属性,则以命令行定义为准。 -
如果这些步骤都没有导致有效的外观和感觉,Sun 的 JRE 将使用 Java 外观和感觉。其他供应商,如苹果,将使用其默认外观和感觉。
启动后更改外观和感觉
即使程序的 GUI 可见,您也可以使用setLookAndFeel更改外观和感觉。为了使现有组件反映新的外观和感觉,每个顶级容器调用一次SwingUtilities的updateComponentTreeUI方法。然后,您可能希望调整每个顶级容器的大小以反映其包含组件的新大小。例如:
UIManager.setLookAndFeel(lnfName);
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();
一个示例
在以下示例中,LookAndFeelDemo.java,您可以尝试不同的外观和感觉。该程序创建了一个简单的 GUI,带有一个按钮和一个标签。每次单击按钮时,标签会递增。
您可以通过更改第 18 行的LOOKANDFEEL常量来更改外观和感觉。前几行的注释告诉您哪些值是可接受的:
// Specify the look and feel to use by defining the LOOKANDFEEL constant
// Valid values are: null (use the default), "Metal", "System", "Motif",
// and "GTK"
final static String LOOKANDFEEL = "Motif";
这里将常量设置为"Motif",这是一个可以在任何平台上运行的外观和感觉(默认为"Metal")。"GTK+"不会在 Windows 上运行,而"Windows"只会在 Windows 上运行。如果选择无法运行的外观和感觉,您将获得 Java 或 Metal 外观和感觉。
在实际设置外观和感觉的代码部分,您将看到几种不同的设置方式,如上所述:
if (LOOKANDFEEL.equals("Metal")) {
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
// an alternative way to set the Metal L&F is to replace the
// previous line with:
// lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
您可以通过注释/取消注释两个替代方案来验证这两个参数是否有效。
这是LookAndFeelDemo源文件的列表:
package lookandfeel;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.plaf.metal.*;
public class LookAndFeelDemo implements ActionListener {
private static String labelPrefix = "Number of button clicks: ";
private int numClicks = 0;
final JLabel label = new JLabel(labelPrefix + "0 ");
// Specify the look and feel to use by defining the LOOKANDFEEL constant
// Valid values are: null (use the default), "Metal", "System", "Motif",
// and "GTK"
final static String LOOKANDFEEL = "Metal";
// If you choose the Metal L&F, you can also choose a theme.
// Specify the theme to use by defining the THEME constant
// Valid values are: "DefaultMetal", "Ocean", and "Test"
final static String THEME = "Test";
public Component createComponents() {
JButton button = new JButton("I'm a Swing button!");
button.setMnemonic(KeyEvent.VK_I);
button.addActionListener(this);
label.setLabelFor(button);
JPanel pane = new JPanel(new GridLayout(0, 1));
pane.add(button);
pane.add(label);
pane.setBorder(BorderFactory.createEmptyBorder(
30, //top
30, //left
10, //bottom
30) //right
);
return pane;
}
public void actionPerformed(ActionEvent e) {
numClicks++;
label.setText(labelPrefix + numClicks);
}
private static void initLookAndFeel() {
String lookAndFeel = null;
if (LOOKANDFEEL != null) {
if (LOOKANDFEEL.equals("Metal")) {
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
// an alternative way to set the Metal L&F is to replace the
// previous line with:
// lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
}
else if (LOOKANDFEEL.equals("System")) {
lookAndFeel = UIManager.getSystemLookAndFeelClassName();
}
else if (LOOKANDFEEL.equals("Motif")) {
lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
}
else if (LOOKANDFEEL.equals("GTK")) {
lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
}
else {
System.err.println("Unexpected value of LOOKANDFEEL specified: "
+ LOOKANDFEEL);
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
}
try {
UIManager.setLookAndFeel(lookAndFeel);
// If L&F = "Metal", set the theme
if (LOOKANDFEEL.equals("Metal")) {
if (THEME.equals("DefaultMetal"))
MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
else if (THEME.equals("Ocean"))
MetalLookAndFeel.setCurrentTheme(new OceanTheme());
else
MetalLookAndFeel.setCurrentTheme(new TestTheme());
UIManager.setLookAndFeel(new MetalLookAndFeel());
}
}
catch (ClassNotFoundException e) {
System.err.println("Couldn't find class for specified look and feel:"
+ lookAndFeel);
System.err.println("Did you include the L&F library in the class path?");
System.err.println("Using the default look and feel.");
}
catch (UnsupportedLookAndFeelException e) {
System.err.println("Can't use the specified look and feel ("
+ lookAndFeel
+ ") on this platform.");
System.err.println("Using the default look and feel.");
}
catch (Exception e) {
System.err.println("Couldn't get specified look and feel ("
+ lookAndFeel
+ "), for some reason.");
System.err.println("Using the default look and feel.");
e.printStackTrace();
}
}
}
private static void createAndShowGUI() {
//Set the look and feel.
initLookAndFeel();
//Make sure we have nice window decorations.
JFrame.setDefaultLookAndFeelDecorated(true);
//Create and set up the window.
JFrame frame = new JFrame("SwingApplication");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
LookAndFeelDemo app = new LookAndFeelDemo();
Component contents = app.createComponents();
frame.getContentPane().add(contents, BorderLayout.CENTER);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event dispatch thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
主题
主题被引入作为轻松更改跨平台 Java(Metal)外观和感觉的颜色和字体的一种方式。在上面列出的示例程序LookAndfeelDemo.java中,您可以通过将第 23 行的THEME常量设置为三个值之一来更改金属外观和感觉的主题。
-
DefaultMetal -
Ocean -
Test
Ocean,比纯金属外观稍柔和,自 Java SE 5 以来一直是金属(Java)外观的默认主题。尽管它的名字是DefaultMetal,但它不是金属的默认主题(在 Java SE 5 之前是,这解释了它的名字)。Test主题是在TestTheme.java中定义的主题,您必须使用LookAndfeelDemo.java编译。正如所写,TestTheme.java设置了三种主要颜色(结果有些奇怪)。您可以修改TestTheme.java以测试任何您喜欢的颜色。
设置主题的代码部分从LookAndfeelDemo.java的第 92 行开始。请注意,您必须使用金属外观和感觉来设置主题。
if (LOOKANDFEEL.equals("Metal")) {
if (THEME.equals("DefaultMetal"))
MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
else if (THEME.equals("Ocean"))
MetalLookAndFeel.setCurrentTheme(new OceanTheme());
else
MetalLookAndFeel.setCurrentTheme(new TestTheme());
UIManager.setLookAndFeel(new MetalLookAndFeel());
}
SwingSet2 演示程序
当您下载JDK 和 JavaFX 演示和示例捆绑包并打开它时,会看到一个包含名为SwingSet2的演示程序的demo\jfc文件夹。该程序具有丰富的图形用户界面,并允许您从菜单中更改外观。此外,如果您正在使用 Java(Metal)外观,您可以选择各种不同的主题。各种主题的文件(例如,RubyTheme.java)位于SwingSet2\src文件夹中。
这是“Ocean”主题,是跨平台 Java(Metal)外观的默认主题:

这是“Steel”主题,是跨平台 Java(Metal)外观的原始主题:

要在已安装 JDK 的系统上运行SwingSet2演示程序,请使用以下命令:
java -jar SwingSet2.jar
这将为您提供 Ocean 的默认主题。
要获得金属 L&F,请运行以下命令:
java -Dswing.metalTheme=steel -jar SwingSet2.jar
合成外观
原文:
docs.oracle.com/javase/tutorial/uiswing/lookandfeel/synth.html
创建自定义外观或修改现有外观可能是一项艰巨的任务。javax.swing.plaf.synth包可用于以更少的工作量创建自定义外观。您可以通过编程方式或使用外部 XML 文件创建合成外观。以下讨论专注于使用外部 XML 文件创建合成外观。有关通过编程方式创建合成外观的讨论,请参阅 API 文档。
使用合成外观,您提供“外观”。合成本身提供“感觉”。因此,您可以将合成 L&F 视为“皮肤”。
合成架构
请回顾前一个主题,每个 L&F 都有责任为 Swing 定义的许多ComponentUI子类提供具体实现。合成 L&F 为您处理了这一切。要使用合成,您无需创建任何ComponentUI—您只需指定每个组件如何绘制,以及影响布局和大小的各种属性。
合成在比组件更细粒度的级别上运行—这个细粒度级别称为“区域”。每个组件都有一个或多个区域。许多组件只有一个区域,例如JButton。其他组件有多个区域,例如JScrollBar。由合成提供的每个ComponentUI将SynthStyle与ComponentUI定义的每个区域关联起来。例如,合成为JScrollBar定义了三个区域:轨道、拇指和滚动条本身。合成为JScrollBar定义的ScrollBarUI(为JScrollBar定义的ComponentUI子类)实现将SynthStyle与每个区域关联起来。

SynthStyle提供了合成ComponentUI实现所使用的样式信息。例如,SynthStyle定义了前景色、背景色、字体信息等。此外,每个SynthStyle都有一个用于绘制区域的SynthPainter。例如,SynthPainter定义了paintScrollBarThumbBackground和paintScrollBarThumbBorder两种方法,用于绘制滚动条拇指区域。
在合成中,每个ComponentUI都使用SynthStyleFactory获取SynthStyles。有两种定义SynthStyleFactory的方法:通过 Synth XML 文件或以编程方式。以下代码展示了如何加载指定合成外观的 XML 文件——在幕后,这将创建一个从 XML 文件中填充SynthStyles的SynthStyleFactory实现:
SynthLookAndFeel laf = new SynthLookAndFeel();
laf.load(MyClass.class.getResourceAsStream("laf.xml"), MyClass.class);
UIManager.setLookAndFeel(laf);
编程路线涉及创建一个返回 SynthStyles 的 SynthStyleFactory 实现。以下代码创建了一个为按钮和树返回不同 SynthStyles 的自定义 SynthStyleFactory:
class MyStyleFactory extends SynthStyleFactory {
public SynthStyle getStyle(JComponent c, Region id) {
if (id == Region.BUTTON) {
return buttonStyle;
}
else if (id == Region.TREE) {
return treeStyle;
}
return defaultStyle;
}
}
SynthLookAndFeel laf = new SynthLookAndFeel();
UIManager.setLookAndFeel(laf);
SynthLookAndFeel.setStyleFactory(new MyStyleFactory());
XML 文件
Synth XML 文件的 DTD 解释可以在javax.swing.plaf.synth/doc-files/synthFileFormat.html找到。
当加载 Synth 外观时,只有那些有定义(绑定到区域的“style”,如下所述)的 GUI 组件(或区域)才会被呈现。对于任何组件都没有默认行为——没有在 Synth XML 文件中定义样式,GUI 就是一块空白画布。
要指定组件(或区域)的呈现方式,您的 XML 文件必须包含一个




















浙公网安备 33010602011771号