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 上執行,並提供原始碼和程式碼審查。
