响应式 Swing 应用程序
在本节中,我们将创建一个应用程序,该应用程序响应用户操作,即按下按钮。
在上一节中,我们编写了一个程序,该程序显示了一个文本标签和一个按钮。在本节中,我们将向应用程序添加功能,以便当用户按下按钮时文本发生变化。为了实现这一点,我们将编写一个 JLabel
的子类,它可以更改其文本,并实现 ActionListener
接口。因为 JButton 在按下时会生成动作事件,所以我们可以通过将我们的专用 JLabel 注册为监听器来将其连接到 JButton。以下是代码
新的监听器
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingConstants;
import javax.swing.JLabel;
// ActionEvent and ActionListener
// live in the java.awt.event
// package
class ChangingLabel extends JLabel implements ActionListener {
// String constants which define the text that's displayed.
private static final String TEXT1 = "Hello, World!";
private static final String TEXT2 = "Hello again!";
// Instance field to keep track of the text currently
// displayed
private boolean usingText1;
// Constructor. Sets alignment and initial text, and
// sets the usingText1 field appropriately.
public ChangingLabel() {
this.setHorizontalAlignment(SwingConstants.CENTER);
this.setText(TEXT1);
usingText1 = true;
}
// A method to change the label text. If the
// current text is TEXT1, changes it to TEXT2,
// and vice-versa. The method is private here,
// because it is never called directly.
private void changeLabel() {
if(usingText1)
this.setText(TEXT2);
else
this.setText(TEXT1);
usingText1 = !usingText1;
}
// This method implements the ActionListener interface.
// Any time that an action is performed by a component
// that this object is registered with, this method
// is called. We can analyze the event object received
// here for more information if we want to, but it's not
// necessary in this application.
public void actionPerformed(ActionEvent e) {
this.changeLabel();
}
}
|
具有活动按钮的 Swing 应用程序
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
// Application with a changing text field
public class HelloWithButton2 {
public static void main(String[] args) {
// Constructs the ChangingLabel. All the
// intialization is done be the default
// constructor, defined in the ChangingLabel
// class below.
ChangingLabel changingLabel = new ChangingLabel();
// Create the button
JButton button = new JButton("Button");
// Register the ChangingLabel as an action
// listener to the button. Whenever the
// button is pressed, its ActionEvent will
// be sent the ChangingLabel's
// actionPerformed() method, and the code
// there will be executed.
button.addActionListener(changingLabel);
// Create the frame
JFrame frame = new JFrame("Hello");
// Add the label and the button to the
// frame, using layout constants.
frame.add(changingLabel, BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.toFront();
}
}
|
在此应用程序中,类 ChangingLabel
定义了一种特殊类型的标签,该标签通过实现 ActionListener
接口成为动作监听器(即可以接收动作事件)。它还可以通过私有方法 changeLabel()
更改其文本。actionPerformed()
方法在收到动作事件时只是调用 changeLabel()
。
HelloWithButton2
中的程序主代码与上一节中的代码类似,但使用的是 ChangeLabel
而不是普通的 JLabel
对象。它还使用按钮的 addActionListener()
方法将 ChangeLabel 注册为按钮的 ActionListener
。我们可以这样做,因为 JButton 会创建动作事件,而 ChangeLabel 是一个 ActionListener(即实现了 ActionListener
接口)。
在上面的示例中要注意一件事。定义了两个类,一个用于在静态 main() 方法中包含程序代码,另一个用于定义程序中使用的类型。但是,没有理由不能将 main()
方法直接放在我们新类型的类中。事实上,将程序的 main()
方法放在组件的自定义子类中是 Swing 应用程序编程中常见的习惯用法。以这种方式重新组织后,程序如下所示
合并后的类
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingConstants;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
// ActionEvent and ActionListener
// live in the java.awt.event
// package
// Application with a changing text field
public class ChangingLabel extends JLabel implements ActionListener {
// String constants which define the text that's displayed.
private static final String TEXT1 = "Hello, World!";
private static final String TEXT2 = "Hello again!";
// Instance field to keep track of the text currently
// displayed
private boolean usingText1;
// Constructor. Sets alignment and initial text, and
// sets the usingText1 field appropriately.
public ChangingLabel() {
this.setHorizontalAlignment(SwingConstants.CENTER);
this.setText(TEXT1);
usingText1 = true;
}
// A method to change the label text. If the
// current text is TEXT1, changes it to TEXT2,
// and vice-versa. The method is private here,
// because it is never called directly.
private void changeLabel() {
if(usingText1) {
this.setText(TEXT2);
usingText1 = false;
} else {
this.setText(TEXT1);
usingText1 = true;
}
}
// This method implements the ActionListener interface.
// Any time that an action is performed by a component
// that this object is registered with, this method
// is called. We can analyze the event object received
// here for more information if we want to, but it's not
// necessary in this application.
public void actionPerformed(ActionEvent e) {
this.changeLabel();
}
// Main method. This is the code that is executed when the
// program is run
public static void main(String[] args) {
// Constructs the ChangingLabel. All the
// intialization is done be the default
// constructor, defined in the ChangingLabel
// class below.
ChangingLabel changingLabel = new ChangingLabel();
// Create the button
JButton button = new JButton("Button");
// Register the ChangingLabel as an action
// listener to the button. Whenever the
// button is pressed, its ActionEvent will
// be sent the ChangingLabel's
// actionPerformed() method, and the code
// there will be executed.
button.addActionListener(changingLabel);
// Create the frame
JFrame frame = new JFrame("Hello");
// Add the label and the button to the
// frame, using layout constants.
frame.add(changingLabel, BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.toFront();
}
}
|
此程序执行的操作与第一个程序完全相同,但将所有代码组织到一个类中。
在动作、更改、鼠标和其他监听器中执行操作的线程称为Swing 线程。这些方法必须快速执行并返回,因为 Swing 线程必须可用于 Swing 框架以保持应用程序的响应性。换句话说,如果您执行一些耗时的计算或其他活动,您必须在单独的线程中执行,而不是在 Swing 线程中。但是,如果您从自己启动的非 Swing 线程访问 Swing 组件,它们可能会由于竞争条件而挂起,如果 Swing 线程同时对其进行操作。因此,必须通过中间 Runnable 使用 SwingUtilities.invokeAndWait()
方法从非 Swing 线程访问 Swing 组件
多线程
String myMessage = "abc";
final String message = myMessage; // Making final allows to access from inner class here.
SwingUtilities.invokeAndWait(new Runnable {
public void run() {
myLabel.setText(message);
}
})
|
此调用将在适当的时候触发setText(message),此时 Swing 线程将准备好响应。当实现仅在缓慢过程本身在 Swing 线程之外实现时才能正常工作的移动进度条时,这种方法也很常见。