如何使用焦点子系统
许多组件 - 即使是主要通过鼠标操作的组件,如按钮 - 也可以通过键盘操作。要使按键影响组件,组件必须具有键盘焦点。
从用户的角度来看,具有键盘焦点的组件通常很显眼 - 例如带有虚线或黑色边框。包含该组件的窗口也比屏幕上的其他窗口更显眼。这些视觉提示让用户知道任何键入将与哪个组件相关联。窗口系统中一次只能有一个组件具有键盘焦点。
窗口如何获得焦点取决于窗口系统。在所有平台上,没有绝对可靠的方法来确保窗口获得焦点。在某些操作系统上,例如 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 的应用程序打印时才能推荐非交互打印。
打印对话框
您可以显示一个标准的打印对话框,允许用户执行以下操作:
-
选择打印机
-
指定打印份数
-
更改打印属性
-
在开始打印之前取消打印
-
开始打印
您可能注意到打印对话框中没有指定打印输出的总页数。这是因为表打印实现使用了Printable
API,而在打印时不知道总页数。
向打印布局添加页眉或页脚(或两者)
头部和页脚由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 中所示。