【Java】 GUI实战案例——使用swing编写计算器

要求

模仿windows自带的计算器功能,设计一个简单计算器

  1. 至少要求实现整数和小数的加、减、乘、除、清零功能。
  2. 其他计算功能,请参考windows自带的计算器,尽可能多的实现。

代码实现

package sy3;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/**自定义一种继承自异常类Exception的异常MyException */
class MyException extends Exception {
    /**一般构建方法与父类相同 */
    public MyException()     {
        super();
    }
    /**带异常提示参数Message的构建方法也与父类相同 */
    public MyException(String message) {
        super(message);
    }
}

public class Sy3_2 extends JFrame implements ActionListener {
    /**screenPanel用于组织显示屏,screenPanel用于组织按钮 */
    private JPanel screenPanel = new JPanel();
    private JPanel buttonPanel = new JPanel();
    /**
     * 变量input和output分别用于存储输入的内容和输出的内容,
     * 因为输入内容频繁变化,因此使用了更自由的StingBuffer类 
     */
    private StringBuffer input = new StringBuffer();
    private String output = "";
    /**把两个文本区及其对应的滚动条绑为数组,方便批量管理 */
    private JTextArea textArea[] = new JTextArea[2];
    private JScrollPane scrollPane[] = new JScrollPane[2];
    /**12个数字按钮0~9、"."、"(-)"及其显示的名称 */
    private JButton numberButtons[] = new JButton[12];
    private String NB_Names[] = {"7", "8", "9", "4", "5", "6", "1", "2", "3", ".", "0", "(-)"};
    /**6个运算按钮"+"、"-"、"*"、"/"、"Power"、"="及其显示的名称 */
    private JButton calculateButtons[] = new JButton[6];
    private String CB_Names[] = {"+", "-", "*", "/", "^", "="};
    /**2个编辑按钮"DEL"、"CE"及其显示的名称 */
    private JButton ediButtons[] = new JButton[2];
    private String EB_Names[] = {"DEL", "CE"};

   /**主方法仅用于生成主类 */
    public static void main(String[] args) {
        new Sy3_2();
    }
    /**主类的构建方法 */
    public Sy3_2() {
        // 窗口名称
        super("简易计算器");
        // 创建主面板
        Container c = getContentPane();

        /**
         * 开始设置显示屏
         */
        // 把屏幕面板布局设为2行1列的网格布局
        screenPanel.setLayout(new GridLayout(2,1));
        /**批量生成并设置显示屏 */
        for (int i = 0; i < 2; i++) {
            // 生成文本区对象,第二行留的空位用于显示滚动条
            textArea[i] = new JTextArea(2, 17);
            // 文本区不可编辑
            textArea[i].setEditable(false);
            // 设置字体为Aria,加粗,字号24磅
            textArea[i].setFont(new Font("Arial", 1, 24));
            // 实例化滚动面板
            scrollPane[i] = new JScrollPane(textArea[i]);
            // 设置滚动面板的垂直方向滚动条为不可见
            scrollPane[i].setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
            // 把文本区加入滚动面板,当输入内容超过显示屏长度时可以通过滑动水平滚动条查看后续内容
            screenPanel.add(scrollPane[i]);
        }

        /**
         * 开始设置按钮
         */
        // 按钮面板采用4行5列的网格布局,单元格之间
        buttonPanel.setLayout(new GridLayout(4,5,5,5));
        /**使用自定义的createButton方法批量生成三种按钮 */
        createButton(numberButtons, NB_Names, Color.black);
        createButton(calculateButtons, CB_Names, Color.red);
        createButton(ediButtons, EB_Names, Color.blue);
        /** 按某种顺序将4行×5列,共20个按钮添加到按钮面板 */
        for (int i = 0; i < 20; i++) {
            // 每行的前3个按钮是数字按钮
            if (i%5 < 3) {
                // (i/5)*3分出是哪一行,i%3分出是哪一列
                buttonPanel.add(numberButtons[(i/5)*3+i%3]);
            }
            // 每行的第4列是运算按钮
            else if (i%5 == 3) {
                buttonPanel.add(calculateButtons[i/5]);
            }
            // 如果是第4列,并且是前10个按钮就放操作按钮
            else if (i%5 == 4 && i <= 9) {
                buttonPanel.add(ediButtons[i/5]);
            }
            // 如果是第4列,并且是后10个按钮就放运算按钮
            else if (i%5 == 4 && i > 9) {
                buttonPanel.add(calculateButtons[i/5+2]);
            }
        }

        /**
         * 开始设置主窗口
         */
        /**将所有面板添加到主容器 */
        // 主容器采用边界布局,
        c.setLayout(new BorderLayout());
        // 显示屏面板放到上面
        c.add("North",screenPanel);
        // 按钮面板发给到下面
        c.add("Center",buttonPanel);
        // 窗口关闭动作时停止程序运行
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 窗口可见
        setVisible(true);
        // 窗口宽度500,高度400
        setSize(500,400);
        // 禁止调整主窗口大小
        setResizable(false);
        // 窗口居中显示
        setLocationRelativeTo(null); 
    }

    /**快速创建按钮的方法:根据按钮名称表Names,创建按钮数组buttons中的元素,并将其颜色设为color */
    public void createButton(JButton buttons[], String Names[], Color color) {
        for (int i = 0; i < Names.length; i++) {
            // 实力化数组中的按钮
            buttons[i] = new JButton(Names[i]);
            // 添加动作监听器
            buttons[i].addActionListener(this);
            // 设置颜色为color
            buttons[i].setForeground(color);
            // 设置字体为"MSYH"(微软雅黑),加粗,字号20磅
            buttons[i].setFont(new Font("MSYH", 1, 20));
        }
    }

    /**实现ActionListener接口的actionPerformed方法:各个按钮的响应动作 */
    public void actionPerformed(ActionEvent e) {
        /**actionCommand存储本次点击事件e的按钮的名称 */
        String actionCommand = e.getActionCommand();
        /**是否为数字按钮的条件变量 */
        Boolean isNumber = actionCommand.equals(NB_Names[0]);
        // isNumber只循环到NB_Names.length-1,不包括负号(因为它的响应动作有点特殊
        for (int i = 1; i < NB_Names.length-1; i++) {
            isNumber = isNumber || actionCommand.equals(NB_Names[i]);
        }
        /**是否为运算按钮的条件变量 */
        Boolean isCalculate = actionCommand.equals(CB_Names[0]);
        //isCalculate只循环到CB_Names.length-1,不包括等于号(因为它的相应动作比较特殊)
        for (int i = 1; i < CB_Names.length-1; i++) {
            isCalculate = isCalculate || actionCommand.equals(CB_Names[i]);
        }
        /**是否为编辑按钮的条件变量 */
        Boolean isEdit = actionCommand.equals(EB_Names[0]) || actionCommand.equals(EB_Names[1]);

        /**如果是数字按钮,则依次将字符连接作为输入变量 */
        if (isNumber) {
            input.append(actionCommand); 
        }
        /**单独写负号按钮的响应,因为它要输入的符号是"-",而不是按钮上的"(-)" */
        else if (actionCommand.equals(NB_Names[11])) {
            input.append("-");
        }
        /**如果是编辑按钮,根据相应操作修改字符串 */
        else if (isEdit) {
            if (actionCommand.equals("DEL")) {
                // 删除倒数第一个字符
                if (String.valueOf(input.charAt(input.length()-1)).equals(" ")) {
                    input.replace(input.length()-3, input.length(), "");
                } else {
                    input.deleteCharAt(input.length()-1);
                }
            }
            else if (actionCommand.equals("CE")) {
                // 直接清空
                input.setLength(0);
                output = "";
            }
        }
        /**如果是运算按钮,则以空格为分隔符,将运算符连接到输入变量中 */
        else if (isCalculate) {
            input.append(" " + actionCommand + " ");
        }
        /**如果是等于号,则进行复杂的响应 */
        else if (actionCommand.equals("=")) {
            /**计算过程中极有可能出错,因此要捕捉异常 */
            try {
                // 使用自定义的getResult获取得数并传递给output用于显示结果
                output = "=" + getResult(input);
                // 使用自定义的checkInput检查输入是否有误
                checkInput(input);
            } 
            catch (MyException myException) {
                /**得数为无穷的的异常 */
                if(myException.getMessage().equals("Infinity"))
                    output = "=" + myException.getMessage();
                /**其他异常 */
                else {
                    // 显示屏上输出错误类型
                    output = myException.getMessage();
                    // 控制面板打印错误原因
                    myException.printStackTrace();
                }
            }
        }
        /**无论按下什么按键,都要刷新显示屏的内容 */
        // 输入量input转为字符串,批量去除运算符左右的空格之后再显示到输入区
        textArea[0].setText(input.toString().replace(" ", ""));
        // 输出量本身是字符串,直接显示即可
        textArea[1].setText(output);
    }

    /**获取的方法:根据输入的input,返回String类型的结果result */
    private String getResult(StringBuffer input) throws MyException {
        /**toCalculate获取input内容,后续使用toCalculate进行计算,以免计算过程中改动input而影响显示屏的内容 */
        StringBuffer toCalculate = new StringBuffer(input);
        /**数学计算的优先顺序 */
        String calculateOder[] = {"^", "*", "/", "+", "-"};
        /**结果 */
        String result;
        /**按照计算优先顺序,使用自定义的calculate方法不断化简toCalculate */
        for (int i = 0; i < calculateOder.length; i++) {
            // 注意:每次只执行calculateOder[i]所对应的那一种运算
            toCalculate = calculate(toCalculate, calculateOder[i]);
        }
        // 当toCalculate经过calculateOder的顺序依次化简后得到最终结果
        result = toCalculate.toString();
        // 返回计算结果
        return result;
    }

    /**化简算式的方法:根据输入的算式toCalculate,以及计算操作符operation,返回经operation化简后的toCalculate */
    private StringBuffer calculate(StringBuffer toCalculate, String operation) throws MyException {
        /**以空格作为分界符,将toCalculate转换为以数字或操作符为元素的数组inputList */
        String inputList[] = toCalculate.toString().split(" ");
        /**每次输入的运算数 */
        Double x, y;
        /**每次输出的运算结果 */
        Double z = 0.0;
        /**item用于存储该次运算的片段 */
        String item = "";
        /**
         * 1.只要输入的算式toCalculate还有运算符号,则运算符号周围一定有空格,
         * 因此inputList根据空格分割toCalculate后也必然超过1个元素,
         * 此时才有必要进行后续的化简计算;
         * 2.反之,当算式toCalculate没有运算符号,则inputList必然只有1个元素,
         * 此时toCalculate已经化到最简,无需进行后续的化简计算。
         */
        if (inputList.length > 1) {
            // 序号为1,3,5…等奇数的位置是运算符,只有这些位置有必要遍历
            for (int i = 1; i <= inputList.length-2; i+=2) {
                // 下面这行用来在控制台追踪程序运行形况
                System.out.println("in the" + operation + i + "step, boolean="+inputList[i].equals(operation));
                /**如果遍历到的运算符是本次运算支持的运算符,就执行运算 */
                if (inputList[i].equals(operation)) {
                    // 下面这行用来在控制台追踪程序运行形况
                    System.out.println("inputList[" +i+"] = "+ inputList[i]);
                    // 运算符的上一个元素是输入数x
                    x = Double.parseDouble(inputList[i-1]);
                    // 运算符的下一个元素是输入数y
                    y = Double.parseDouble(inputList[i+1]);
                    /**根据不同的操作符,选择不同的计算方法 */
                    if (operation.equals("^")) {
                        z = Math.pow(x, y);
                    }
                    else if (operation.equals("*")) {
                        z = x * y;
                    }
                    else if (operation.equals("/")) {
                        if (y == 0) {
                            // 除0错误
                            throw new MyException("Infinity");
                        }
                        z = x / y;
                    }
                    else if (operation.equals("+")) {
                        z = x + y;
                    }
                    else if (operation.equals("-")) {
                        z = x - y;
                    }
                    // 记录一下刚才运算的式子是哪一个片段
                    item = inputList[i-1] + " " + inputList[i] + " " +  inputList[i+1];
                    /**
                     * 把toCalculate中参与过运算的片段item等价替换为其计算结果z。
                     * .indexOf(item)返回片段item在toCalculate中第一次出现时,item第一个字符开始的位置,
                     * .indexOf(item)+item.length()即片段item在toCalculate中第一次出现时,item最后一个字符的位置,
                     * .replace(int begin, int end, String replacement)将toCalculate中从begin到end的片段替换为replacement。
                     */
                    toCalculate.replace(toCalculate.indexOf(item), toCalculate.indexOf(item)+item.length(), String.valueOf(z));
                    // 下面这行用来在控制台追踪程序运行形况
                    System.out.println("in the operation" + operation +", step"+i+", toCalculate="+ toCalculate +", boolean="+inputList[i].equals(operation));
                }
            }
        }
        return toCalculate;
    }
    /**自定义输入检查方法:检查input的结构是否有误,有误则抛出异常 */
    private void checkInput(StringBuffer input) throws MyException{
        /**先把输入的StingBuffer转为Sting,再用String的split方法将其按空格切分为存储着运算符和数字的数组 */
        String checker[] = input.toString().split(" ");
        /**
         * 如果式子中有n个数字,则有n-1个运算符,相应地checker[]总共有n+(n-1)个元素,
         * 如果checker中的元素数量是偶数,那一定是输入有误。
         */
        if (checker.length%2 == 0) {
            throw new MyException("数字或运算符输入有误");
        }
        /**遍历checker的每一个元素,检查其合理性 */
        for (int i = 0; i < checker.length; i++) {
            /**奇数位置应为运算符 */
            if (i%2 == 1) {
                /**
                 * isCalculate代表该位置是否为运算符,初始值是false,
                 * 只要奇数位置的字符等价于CB_Names中的任何一个元素,都会使isCalculate变成true
                 */
                Boolean isCalculate = false;
                for (int j = 0; j < CB_Names.length; j++) {
                    isCalculate = isCalculate || (checker[i].equals(CB_Names[j]));
                }
                /**如果奇数位置不是任何一个运算符,肯定是运算符输入有误 */
                if (isCalculate == false) {
                    throw new MyException("运算符输入有误");
                }
            }
            /**偶数位置应为数字 */
            else {
                /**尝试将该元素转换为Double对象,如果出错,肯定是数字输入有误 */
                try {
                    Double.parseDouble(checker[i]);
                } catch (Exception e) {
                    throw new MyException("数据输入有误");
                }
            }
        }
    }
}

主要参考

Java程序设计 图形用户界面 小巫版简易计算器_iteye_17686的博客-CSDN博客

小结

优点:

  1. 封装较强,代码重复量少。
  2. 进行了异常捕捉,程序结构较为完善。

缺点:

  1. 未使用JTextPane的丰富样式,故而未能完成拟物质感的右对齐显示,显示屏字体也不够化还原。
  2. 依赖数组类型重复造轮子,未使用栈的良好特性。

你可能感兴趣的:(java)