跳到內容

Swing 大型示例

50% developed
來自華夏公益教科書,開放的世界,開放的書籍


計算器

[編輯 | 編輯原始碼]

下面的示例使用多個巢狀的佈局管理器構建使用者介面,將監聽器和客戶端屬性應用於元件,並演示如何從事件排程執行緒 (EDT) 建立元件。

主方法

[編輯 | 編輯原始碼]

主方法非常簡單。

Example 主方法
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 元件。不這樣做會導致應用程式出現神秘的死鎖。

建構函式

[編輯 | 編輯原始碼]

建構函式是計算器進行佈局的地方。

Example 建構函式
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 類就是為此而生的。

Example 監聽器
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 也執行相同的操作,但提取它自己的屬性。

構建按鈕

[編輯 | 編輯原始碼]

在這裡我們可以看到監聽器是如何分配給按鈕的。

Example 按鈕
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,這意味著被按下。

構建面板

[編輯 | 編輯原始碼]

在這裡我們展示瞭如何構建數字面板。

Example 面板
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 列。運算子面板和清除面板的構建方式都類似。

計算器的完整程式碼

[編輯 | 編輯原始碼]
Computer code 計算器
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 上執行,並提供原始碼和程式碼審查。


Clipboard

待辦事項
新增螢幕截圖。


華夏公益教科書