Swing 大型示例
下面的示例使用多个嵌套的布局管理器构建用户界面,将监听器和客户端属性应用于组件,并演示如何从事件调度线程 (EDT) 创建组件。
主方法非常简单。
主方法
public static void main(String[] args) {
// Remember, all swing components must be accessed from
// the event dispatch thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Calculator calc = new Calculator();
calc.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
calc.setVisible(true);
}
});
}
|
它创建一个新的计算器,将其设置为在关闭时退出程序,然后使其可见。这段代码的有趣之处在于它不是在主线程中发生的,而是在事件调度线程中发生的。虽然在像这样的简单应用程序中,只需将整个组件创建包装在 invokeLater()
中以将其推入 EDT 就足够了,但大型应用程序需要小心,确保它们只从 EDT 访问 UI 组件。不这样做会导致应用程序出现神秘的死锁。
构造函数是计算器进行布局的地方。
构造函数
public Calculator() {
super("Calculator");
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel numberPanel = buildNumberPanel();
JPanel operatorPanel = buildOperatorPanel();
JPanel clearPanel = buildClearPanel();
lcdDisplay = new JTextArea();
mainPanel.add(clearPanel, BorderLayout.SOUTH);
mainPanel.add(numberPanel, BorderLayout.CENTER);
mainPanel.add(operatorPanel, BorderLayout.EAST);
mainPanel.add(lcdDisplay, BorderLayout.NORTH);
errorDisplay = new JLabel(" ");
errorDisplay.setFont(new Font("Dialog", Font.BOLD, 12));
getContentPane().setLayout(new BorderLayout());
getContentPane().add(mainPanel, BorderLayout.CENTER);
getContentPane().add(errorDisplay, BorderLayout.SOUTH);
pack();
resetState();
}
|
这种方法使用继承来定义计算器。另一种方法是使用 getComponent()
方法,该方法将返回一个按预期布局的面板。然后,计算器将成为一个控制器,指示面板的行为,并拥有所有组件所在的 面板。虽然更复杂,但这种方法也可以更容易地扩展到更大、更复杂的 UI。继承的问题在于子类能够覆盖当前在父类构造函数中调用的方法。如果覆盖的方法需要子类中的一个字段已经初始化,那么就会遇到麻烦。在父类中调用该方法时,子类还没有完全初始化。
构造函数使用两个 BorderLayout 来进行布局。第一个用于主面板,第二个用于将主面板与错误消息面板组合在一起。此外,主面板中的每个子面板都是使用它们自己的布局管理器构建的。这是一个将更复杂的 UI 构建为多个更简单的子面板组合的良好示例。
在这里,我们开始进入有趣的部分。对于每个按钮,我们希望有一些东西监听按下的按钮。ActionListener
类就是为此而生的。
监听器
private final ActionListener numberListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent source = (JComponent)e.getSource();
Integer number = (Integer) source.getClientProperty(NUMBER_PROPERTY);
if (number == null){
throw new IllegalStateException("No NUMBER_PROPERTY on component");
}
numberButtonPressed(number.intValue());
}
};
private final ActionListener decimalListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
decimalButtonPressed();
}
};
private final ActionListener operatorListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent source = (JComponent) e.getSource();
Integer opCode = (Integer) source.getClientProperty(OPERATOR_PROPERTY);
if (opCode == null) {
throw new IllegalStateException("No OPERATOR_PROPERTY on component");
}
operatorButtonPressed(opCode);
}
};
private final ActionListener clearListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
resetState();
}
};
|
我们为每种类型的按钮设置一个单独的监听器。数字、小数点、运算符和清除按钮。每个按钮都处理与该按钮类相关联的消息,并且仅处理与该按钮类相关联的消息。虽然这很好,但您如何确定该类中的哪个按钮被按下呢?嗯,Swing 提供了一种在对象上设置任意属性的方法。这是通过 getClientProperty putClientProperty
对来完成的。在下面,我们通过与按钮的 clientProperty
关联的数字来区分数字按钮。类似地,operatorListener
也执行相同的操作,但提取它自己的属性。
在这里我们可以看到监听器是如何分配给按钮的。
按钮
private JButton buildNumberButton(int number) {
JButton button = new JButton(Integer.toString(number));
button.putClientProperty(NUMBER_PROPERTY, Integer.valueOf(number));
button.addActionListener(numberListener);
return button;
}
private JButton buildOperatorButton(String symbol, int opType) {
JButton plus = new JButton(symbol);
plus.putClientProperty(OPERATOR_PROPERTY, Integer.valueOf(opType));
plus.addActionListener(operatorListener);
return plus;
}
|
首先,我们通过将 String
传递给 JButton 的构造函数来设置按钮的标签,然后我们使用 putClientProperty
设置我们想要的属性值,然后我们为按钮添加适当的动作监听器。动作监听器在组件执行其预期操作时被激活。对于 JButton
,这意味着被按下。
在这里我们展示了如何构建数字面板。
面板
public JPanel buildNumberPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(4, 3));
panel.add(buildNumberButton(1));
panel.add(buildNumberButton(2));
panel.add(buildNumberButton(3));
panel.add(buildNumberButton(4));
panel.add(buildNumberButton(5));
panel.add(buildNumberButton(6));
panel.add(buildNumberButton(7));
panel.add(buildNumberButton(8));
panel.add(buildNumberButton(9));
JButton buttonDec = new JButton(".");
buttonDec.addActionListener(decimalListener);
panel.add(buttonDec);
panel.add(buildNumberButton(0));
// Exit button is to close the calculator and terminate the program.
JButton buttonExit = new JButton("EXIT");
buttonExit.setMnemonic(KeyEvent.VK_C);
buttonExit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
panel.add(buttonExit);
return panel;
}
|
我们希望为数字键创建一个均匀的网格,并且希望它们的大小都相同,因此我们使用 GridLayout
作为我们的 LayoutManager。此网格深度为 4 行,宽度为 3 列。运算符面板和清除面板的构建方式都类似。
计算器
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
public class Calculator extends JFrame {
private static final String NUMBER_PROPERTY = "NUMBER_PROPERTY";
private static final String OPERATOR_PROPERTY = "OPERATOR_PROPERTY";
private static final String FIRST = "FIRST";
private static final String VALID = "VALID";
// These would be much better if placed in an enum,
// but enums are only available starting in Java 5.
// Code using them isn't back portable.
private static interface Operator{
static final int EQUALS = 0;
static final int PLUS = 1;
static final int MINUS = 2;
static final int MULTIPLY = 3;
static final int DIVIDE = 4;
}
private String status;
private int previousOperation;
private double lastValue;
private JTextArea lcdDisplay;
private JLabel errorDisplay;
public static void main(String[] args) {
// Remember, all swing components must be accessed from
// the event dispatch thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Calculator calc = new Calculator();
calc.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
calc.setVisible(true);
}
});
}
public Calculator() {
super("Calculator");
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel numberPanel = buildNumberPanel();
JPanel operatorPanel = buildOperatorPanel();
JPanel clearPanel = buildClearPanel();
lcdDisplay = new JTextArea();
lcdDisplay.setFont(new Font("Dialog", Font.BOLD, 18));
mainPanel.add(clearPanel, BorderLayout.SOUTH);
mainPanel.add(numberPanel, BorderLayout.CENTER);
mainPanel.add(operatorPanel, BorderLayout.EAST);
mainPanel.add(lcdDisplay, BorderLayout.NORTH);
errorDisplay = new JLabel(" ");
errorDisplay.setFont(new Font("Dialog", Font.BOLD, 12));
getContentPane().setLayout(new BorderLayout());
getContentPane().add(mainPanel, BorderLayout.CENTER);
getContentPane().add(errorDisplay, BorderLayout.SOUTH);
pack();
resetState();
}
private final ActionListener numberListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent source = (JComponent)e.getSource();
Integer number = (Integer) source.getClientProperty(NUMBER_PROPERTY);
if(number == null){
throw new IllegalStateException("No NUMBER_PROPERTY on component");
}
numberButtonPressed(number.intValue());
}
};
private final ActionListener decimalListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
decimalButtonPressed();
}
};
private final ActionListener operatorListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent source = (JComponent) e.getSource();
Integer opCode = (Integer) source.getClientProperty(OPERATOR_PROPERTY);
if (opCode == null) {
throw new IllegalStateException("No OPERATOR_PROPERTY on component");
}
operatorButtonPressed(opCode);
}
};
private final ActionListener clearListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
resetState();
}
};
private JButton buildNumberButton(int number) {
JButton button = new JButton(Integer.toString(number));
button.putClientProperty(NUMBER_PROPERTY, Integer.valueOf(number));
button.addActionListener(numberListener);
return button;
}
private JButton buildOperatorButton(String symbol, int opType) {
JButton plus = new JButton(symbol);
plus.putClientProperty(OPERATOR_PROPERTY, Integer.valueOf(opType));
plus.addActionListener(operatorListener);
return plus;
}
public JPanel buildNumberPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(4, 3));
panel.add(buildNumberButton(7));
panel.add(buildNumberButton(8));
panel.add(buildNumberButton(9));
panel.add(buildNumberButton(4));
panel.add(buildNumberButton(5));
panel.add(buildNumberButton(6));
panel.add(buildNumberButton(1));
panel.add(buildNumberButton(2));
panel.add(buildNumberButton(3));
JButton buttonDec = new JButton(".");
buttonDec.addActionListener(decimalListener);
panel.add(buttonDec);
panel.add(buildNumberButton(0));
// Exit button is to close the calculator and terminate the program.
JButton buttonExit = new JButton("EXIT");
buttonExit.setMnemonic(KeyEvent.VK_C);
buttonExit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
panel.add(buttonExit);
return panel;
}
public JPanel buildOperatorPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(4, 1));
panel.add(buildOperatorButton("+", Operator.PLUS));
panel.add(buildOperatorButton("-", Operator.MINUS));
panel.add(buildOperatorButton("*", Operator.MULTIPLY));
panel.add(buildOperatorButton("/", Operator.DIVIDE));
return panel;
}
public JPanel buildClearPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(1, 3));
JButton clear = new JButton("C");
clear.addActionListener(clearListener);
panel.add(clear);
JButton CEntry = new JButton("CE");
CEntry.addActionListener(clearListener);
panel.add(CEntry);
panel.add(buildOperatorButton("=", Operator.EQUALS));
return panel;
}
public void numberButtonPressed(int i) {
String displayText = lcdDisplay.getText();
String valueString = Integer.toString(i);
if (("0".equals(displayText)) || (FIRST.equals(status))) {
displayText = "";
}
int maxLength = (displayText.indexOf(".") >= 0) ? 21 : 20;
if(displayText.length() + valueString.length() <= maxLength){
displayText += valueString;
clearError();
} else {
setError("Reached the 20 digit max");
}
lcdDisplay.setText(displayText);
status = VALID;
}
public void operatorButtonPressed(int newOperation) {
Double displayValue = Double.valueOf(lcdDisplay.getText());
// if(newOperation == Operator.EQUALS && previousOperation != //Operator.EQUALS){
// operatorButtonPressed(previousOperation);
// } else {
switch (previousOperation) {
case Operator.PLUS:
displayValue = lastValue + displayValue;
commitOperation(newOperation, displayValue);
break;
case Operator.MINUS:
displayValue = lastValue - displayValue;
commitOperation(newOperation, displayValue);
break;
case Operator.MULTIPLY:
displayValue = lastValue * displayValue;
commitOperation(newOperation, displayValue);
break;
case Operator.DIVIDE:
if (displayValue == 0) {
setError("ERROR: Division by Zero");
} else {
displayValue = lastValue / displayValue;
commitOperation(newOperation, displayValue);
}
break;
case Operator.EQUALS:
commitOperation(newOperation, displayValue);
// }
}
}
public void decimalButtonPressed() {
String displayText = lcdDisplay.getText();
if (FIRST.equals(status)) {
displayText = "0";
}
if(!displayText.contains(".")){
displayText = displayText + ".";
}
lcdDisplay.setText(displayText);
status = VALID;
}
private void setError(String errorMessage) {
if(errorMessage.trim().equals("")){
errorMessage = " ";
}
errorDisplay.setText(errorMessage);
}
private void clearError(){
status = FIRST;
errorDisplay.setText(" ");
}
private void commitOperation(int operation, double result) {
status = FIRST;
lastValue = result;
previousOperation = operation;
lcdDisplay.setText(String.valueOf(result));
}
/**
* Resets the program state.
*/
void resetState() {
clearError();
lastValue = 0;
previousOperation = Operator.EQUALS;
lcdDisplay.setText("0");
}
}
|
在这个“计算器”中:4 * 6 = 36,4 * 9 = 81,4 * 3 = 9。字符串中的 Error1
case Operator.MULTIPLY:
displayValue *= displayValue;
// This is Error1, must be: displayValue *= lastValue;
在纠正了这个“计算器”中的 Error1 之后:1 / 3 = 3,5 / 2 = 0.4,8 / 4 = 0.5。字符串中的 Error2
case Operator.DIVIDE:
if (displayValue == 0) {
setError("ERROR: Division by Zero");
} else {
displayValue /= lastValue;
// This is Error2, must be: displayValue = lastValue / displayValue;
在纠正了这个“计算器”中的 Error2 之后:9 - 6 = -3,6 - 3 = -3,8 - 4 = -4。字符串中的 Error3
case Operator.MINUS:
displayValue -= lastValue;
// This is Error3, must be: displayValue = lastValue - displayValue;
字符串中的 Error4
if(newOperation == Operator.EQUALS && previousOperation !=
Operator.EQUALS){
operatorButtonPressed(previousOperation);
} else {
在删除了这些字符串后,计算器可以正常工作。
这里您可以找到类似的计算器,它在类似于 Java applet 的 Web 上运行,并提供源代码和代码审查。