跳到内容

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

待办事项
添加屏幕截图。


华夏公益教科书