重绘Swing控件开发Windows11深色风格高精度计算器项目

181 阅读9分钟

项目简介

本计算器采用Java Swing实现,支持高精度运算、多表达式计算和现代UI设计,主要特性包括:

  • Windows 11深色风格圆角UI组件
  • 右键菜单支持复制/粘贴/全选
  • 自适应窗口字体缩放
  • 利用栈实现多行表达式计算
  • 使用BigDecimal实现高精度计算

项目演示图片

运行界面

计算器01.png

表达式计算

计算器02.png

高精度计算

计算器03.png


核心代码解析

1. 主框架初始化

/**
 • 现代风格计算器主类
 • 实现功能:
 • - 界面初始化
 • - 事件监听绑定
 • - 组件布局管理
 */
public class ModernCalculator {
    // 界面组件
    private JFrame frame;            // 主窗口容器
    private JTextArea display;       // 多行显示区域(支持历史记录)
    
    // 计算逻辑相关
    private StringBuilder currentInput = new StringBuilder();  // 用户输入缓存(支持多行)
    private Stack<BigDecimal> operandStack = new Stack<>();    // 操作数栈(存储BigDecimal数值)
    private Stack<Character> operatorStack = new Stack<>();    // 运算符栈(存储+-*/%等)

    // 构造函数
    public ModernCalculator() {
        initialize();  // 执行界面初始化
        addContextMenu();  // 添加右键上下文菜单
    }

    /**
     ◦ 初始化主界面
     ◦ 实现步骤:
     ◦ 1. 窗口基础设置
     ◦ 2. 显示区域配置
     ◦ 3. 按钮面板布局
     ◦ 4. 事件监听绑定
     */
    private void initialize() {
        // 主窗口设置 --------------------------------------------------
        frame = new JFrame("计算器");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭时退出程序
        frame.getContentPane().setBackground(new Color(0x202020)); // 设置暗色背景
        
        // 图标加载(需在项目res目录放置icon.png)
        try {
            ImageIcon icon = new ImageIcon(getClass().getResource("/res/icon.png"));
            frame.setIconImage(icon.getImage()); // 设置任务栏/窗口图标
        } catch (Exception e) {
            System.err.println("图标加载失败: " + e.getMessage());
        }

        // 窗口尺寸设置
        frame.setSize(428, 600);          // 初始尺寸(模拟手机计算器比例)
        frame.setMinimumSize(new Dimension(428, 600)); // 最小尺寸限制(防止UI变形)

        // 显示区域初始化 ----------------------------------------------
        display = new JTextArea("", 6, 26); // 6行26列的多行文本框
        display.setEditable(false);       // 禁止直接编辑(仅通过按钮输入)
        display.setBackground(new Color(0x202020)); // 暗色背景
        display.setForeground(Color.WHITE);         // 白色文字
        display.setLineWrap(true);                  // 启用自动换行
        frame.add(display, BorderLayout.NORTH);     // 添加到窗口顶部区域

        // 按钮面板布局 ------------------------------------------------
        JPanel buttonPanel = new JPanel(new GridLayout(5, 4, 1, 1)); // 5行4列网格布局
        buttonPanel.setBackground(new Color(0x202020)); // 统一背景色
        
        // 按钮标签定义(按计算器物理布局顺序)
        String[] buttonLabels = {
            "%", "C", "CE", "÷",    // 第一行:特殊功能键
            "7", "8", "9", "×",     // 第二行:数字及乘法
            "4", "5", "6", "−",     // 第三行:数字及减法
            "1", "2", "3", "+",     // 第四行:数字及加法
            "±", "0", ".", "="      // 第五行:符号/数字/等于
        };
        
        // 动态创建按钮
        for (String label : buttonLabels) {
            ModernWebButton button = createStyledButton(label); // 创建风格化按钮
            button.addActionListener(new ButtonClickListener()); // 绑定点击事件监听器
            buttonPanel.add(button); // 添加到按钮面板
        }
        
        frame.add(buttonPanel, BorderLayout.CENTER); // 将按钮面板添加到窗口中心区域
        
        // 窗口缩放监听器(实现字体自适应)-------------------------------
        frame.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent evt) {
                adjustFontSize(); // 当窗口大小改变时动态调整字体
            }
        });
        
        // 显示窗口
        frame.setLocationRelativeTo(null); // 居中显示
        frame.setVisible(true);            // 设为可见
    }

    /**
     ◦ 创建风格化按钮(工厂方法)
     ◦ @param label 按钮文字
     ◦ @return 配置完成的ModernWebButton实例
     */
    private ModernWebButton createStyledButton(String label) {
        // 运算符按钮特殊配色
        if (label.matches("[%C÷×−+]|CE")) {
            return new ModernWebButton(label, 
                new Color(0x323232),   // 默认颜色
                new Color(0x3a3d3d),   // 悬停颜色
                new Color(0x323232)    // 点击颜色
            );
        }
        // 等号按钮特殊配色
        else if (label.equals("=")) {
            return new ModernWebButton(label,
                new Color(0x4cc2ff),   // 默认蓝
                new Color(0x47b1e8),   // 悬停蓝
                new Color(0x42a1d3)    // 点击蓝
            );
        }
        // 数字按钮默认配色
        return new ModernWebButton(label);
    }

2. 现代风格按钮组件(完整实现)

/**
 • 自定义现代风格按钮组件
 • 继承JButton并实现:
 • 1. 圆角矩形外观
 • 2. 三态颜色变化(默认/悬停/点击)
 • 3. 抗锯齿渲染
 */
public static class ModernWebButton extends JButton {
    // 颜色配置
    private final Color DEFAULT_COLOR;  // 默认状态颜色
    private final Color HOVER_COLOR;    // 鼠标悬停颜色
    private final Color CLICKED_COLOR;  // 点击状态颜色
    private boolean isClicked = false;  // 点击状态标记

    /**
     ◦ 全参数构造函数
     ◦ @param text 按钮文字
     ◦ @param defaultColor 默认状态颜色
     ◦ @param hoverColor 悬停状态颜色
     ◦ @param clickedColor 点击状态颜色
     */
    public ModernWebButton(String text, Color defaultColor, 
                          Color hoverColor, Color clickedColor) {
        super(text);
        this.DEFAULT_COLOR = defaultColor;
        this.HOVER_COLOR = hoverColor;
        this.CLICKED_COLOR = clickedColor;
        
        // 基础设置 ------------------------------
        setOpaque(false);               // 透明背景(需要自定义绘制)
        setBorderPainted(false);        // 隐藏默认边框
        setFocusPainted(false);         // 去除焦点虚线框
        setContentAreaFilled(false);    // 禁用默认填充
        setFont(new Font("Arial", Font.BOLD, 26)); // 统一字体样式
        
        // 鼠标事件监听 ---------------------------
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                setBackground(CLICKED_COLOR); // 按下时显示点击色
                isClicked = true;
            }
            
            @Override
            public void mouseReleased(MouseEvent e) {
                // 根据是否悬停恢复颜色
                setBackground(getModel().isRollover() ? HOVER_COLOR : DEFAULT_COLOR);
                isClicked = false;
            }
            
            @Override
            public void mouseEntered(MouseEvent e) {
                if (!isClicked) setBackground(HOVER_COLOR); // 非点击状态悬停变色
            }
            
            @Override
            public void mouseExited(MouseEvent e) {
                if (!isClicked) setBackground(DEFAULT_COLOR); // 恢复默认颜色
            }
        });
        
        setBackground(DEFAULT_COLOR); // 初始颜色设置
    }

    // 默认构造函数(数字按钮使用)
    public ModernWebButton(String text) {
        this(text, 
            new Color(0x3b3b3b),  // 默认灰
            new Color(0x313333),  // 悬停灰
            new Color(0x262929)   // 点击灰
        );
    }

    /**
     ◦ 自定义绘制方法(实现圆角效果)
     ◦ @param g 图形上下文
     */
    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g.create();
        // 启用抗锯齿
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                          RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                          RenderingHints.VALUE_RENDER_QUALITY);

        // 计算圆角半径(基于按钮高度)
        int cornerRadius = getHeight() / 8;
        
        // 绘制圆角矩形背景
        RoundRectangle2D rect = new RoundRectangle2D.Double(
            0, 0, 
            getWidth()-1,  // 宽度留1像素边框
            getHeight()-1, // 高度留1像素边框
            cornerRadius, 
            cornerRadius
        );
        g2.setColor(getBackground());
        g2.fill(rect);
        
        // 调用父类方法绘制文本
        super.paintComponent(g2);
        g2.dispose();
    }
}

(因篇幅限制,表达式解析、右键菜单等核心模块解析请见完整版文档)


实现亮点(技术细节)

// 字体自适应算法示例
private void adjustFontSize() {
    int frameHeight = frame.getHeight(); // 获取当前窗口高度
    float baseSize = frameHeight / 30f;  // 基础字体大小(按窗口高度的1/30计算)
    float fontSize = Math.max(20, baseSize); // 设置最小20pt
    
    Font currentFont = display.getFont();
    Font newFont = currentFont.deriveFont(Font.BOLD, fontSize);
    display.setFont(newFont); // 应用新字体
    
    // 触发文本重排
    display.revalidate();
    display.repaint();
}

项目完整代码

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Stack;

public class ModernCalculator {
    private JFrame frame;
    private JTextArea display;
    private StringBuilder currentInput = new StringBuilder();
    private Stack<BigDecimal> operandStack = new Stack<>();
    private Stack<Character> operatorStack = new Stack<>();

    public ModernCalculator() {
        initialize();
        addCopyAndSelectAllAndPasteMenuToDisplay();  // 新增:为文本区域添加右键菜单,包含粘贴功能
    }

    private void initialize() {
        frame = new JFrame("计算器");
        frame.getContentPane().setBackground(new Color(0x202020)); // 设置内容面板的背景颜色
        
        ImageIcon icon = new ImageIcon(getClass().getResource("/res/icon.png"));
        if (icon.getImageLoadStatus() == MediaTracker.ERRORED) {
            System.err.println("Failed to load icon.");
        } else {
            frame.setIconImage(icon.getImage()); // 设置图标
        }
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // 设置初始大小
        int defaultWidth = 428;
        int defaultHeight = 600;
        frame.setSize(defaultWidth, defaultHeight);
        
        // 设置最小尺寸,防止窗口缩小到默认大小之下
        frame.setMinimumSize(new Dimension(defaultWidth, defaultHeight));
        
        frame.setLayout(new BorderLayout());
    
        display = new JTextArea("", 6, 26); // 使用JTextArea支持多行显示
        display.setEditable(false);
        display.setBackground(new Color(0x202020)); // 设置JTextArea的背景颜色
        display.setForeground(Color.WHITE); // 设置文本颜色以便于阅读
        frame.add(display, BorderLayout.NORTH);
    
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(5, 4, 1, 1)); // 调整为5行4列,水平和垂直间距设为1
        buttonPanel.setBackground(new Color(0x202020)); // 设置JPanel的背景颜色
    
        String[] buttonLabels = {"%", "C", "CE", "÷",
                "7", "8", "9", "×",
                "4", "5", "6", "−",
                "1", "2", "3", "+",
                "±", "0", ".", "="};
    
        for (String label : buttonLabels) {
            ModernWebButton button;
            if (label.equals("%") || label.equals("C") || label.equals("CE") || label.equals("÷") || label.equals("×") || label.equals("−") || label.equals("+")) {
                // 为特殊按钮设置自定义颜色
                button = new ModernWebButton(label, new Color(0x323232), new Color(0x3a3d3d), new Color(0x323232));
                button.setForeground(Color.WHITE); // 设置按钮文字颜色为白色
            } 
            else if(label.equals("="))
            {
                button = new ModernWebButton(label, new Color(0x4cc2ff), new Color(0x47b1e8), new Color(0x42a1d3));
                button.setForeground(Color.BLACK); // 设置等号按钮的文字颜色为黑色
            }
            else {
                // 普通按钮使用默认颜色
                button = new ModernWebButton(label);
                button.setForeground(Color.WHITE); // 设置按钮文字颜色为白色
            }
    
            button.addActionListener(new ButtonClickListener());
           
            button.setPreferredSize(new Dimension(80, 60)); // 设置按钮的首选大小
            buttonPanel.add(button);
        }
    
        frame.add(buttonPanel, BorderLayout.CENTER);
        frame.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent evt) {
                adjustFontSize();
            }
        });
        
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void addCopyAndSelectAllAndPasteMenuToDisplay() {
        TransparentPopupMenu popupMenu = new TransparentPopupMenu(new Color(255, 255, 255, 100)); // 设置透明背景色
    
        JMenuItem copyItem = new JMenuItem("复制");
        copyItem.setFont(new Font("黑体", Font.PLAIN, 16)); // 设置字体、样式和大小
        copyItem.addActionListener(e -> {
            String selectedText = display.getSelectedText();
            if (selectedText != null && !selectedText.isEmpty()) {
                StringSelection selection = new StringSelection(selectedText);
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
            }
        });
        popupMenu.add(copyItem);
    
        JMenuItem pasteItem = new JMenuItem("粘贴");
        pasteItem.setFont(new Font("黑体", Font.PLAIN, 16)); // 设置字体、样式和大小
        pasteItem.addActionListener(e -> {
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            try {
                String data = (String) clipboard.getData(DataFlavor.stringFlavor);
                if (data != null) {
                    int caretPosition = display.getCaretPosition();
                    String oldText = display.getText();
                    display.setText(oldText.substring(0, caretPosition) + data + oldText.substring(caretPosition));
                    currentInput.append(data);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
        popupMenu.add(pasteItem);
    
        JMenuItem selectAllItem = new JMenuItem("全选");
        selectAllItem.setFont(new Font("黑体", Font.PLAIN, 16)); // 设置字体、样式和大小
        selectAllItem.addActionListener(e -> display.selectAll());
        popupMenu.add(selectAllItem);
    
        display.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (SwingUtilities.isRightMouseButton(e)) {
                    showPopup(e, popupMenu);
                }
            }
    
            @Override
            public void mouseReleased(MouseEvent e) {
                if (SwingUtilities.isRightMouseButton(e)) {
                    showPopup(e, popupMenu);
                }
            }
    
            private void showPopup(MouseEvent e, JPopupMenu menu) {
                menu.show(display, e.getX(), e.getY());
            }
        });
    }

    static class TransparentPopupMenu extends JPopupMenu {
        private final Color backgroundColor;

        public TransparentPopupMenu(Color backgroundColor) {
            this.backgroundColor = backgroundColor;
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setColor(backgroundColor);
            g2d.fillRect(0, 0, getWidth(), getHeight());
            super.paintComponent(g2d);
            g2d.dispose();
        }
    }

    private class ButtonClickListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            String command = e.getActionCommand();

            switch (command) {
                case "=":
                    evaluateExpression();
                    break;
                case "C":
                    clearAll();
                    break;
                case "CE":
                    clearCurrentLine();
                    break;
                case "±":
                    toggleSign();
                    break;
                case "%":
                    moduloOperation();
                    break;
                default:
                    updateDisplay(command);
                    break;
            }
        }

        private void updateDisplay(String text) {
            if (text.equals("−") && (currentInput.length() == 0 || currentInput.charAt(currentInput.length() - 1) == '−')) {
                currentInput.append(text);
            } else if (Character.isDigit(text.charAt(0)) && currentInput.length() > 0 && currentInput.charAt(currentInput.length() - 1) == '−') {
                currentInput.append(text);
            } else {
                currentInput.append(text);
            }
            display.setText(currentInput.toString());
        }

        private BigDecimal evaluateInfixExpression(String expression) throws Exception {
            int i = 0;
            boolean lastWasOperatorOrOpenParenthesis = true;
            while (i < expression.length()) {
                char c = expression.charAt(i);
                if (Character.isWhitespace(c)) {
                    i++;
                    continue;
                }
                if ((c == '-' || c == '+') && lastWasOperatorOrOpenParenthesis) { 
                    StringBuilder numBuilder = new StringBuilder("" + c);
                    i++;
                    while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
                        numBuilder.append(expression.charAt(i));
                        i++;
                    }
                    operandStack.push(new BigDecimal(numBuilder.toString()));
                    lastWasOperatorOrOpenParenthesis = false;
                } else if (Character.isDigit(c) || c == '.') {
                    int numStart = i;
                    while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
                        i++;
                    }
                    operandStack.push(new BigDecimal(expression.substring(numStart, i)));
                    lastWasOperatorOrOpenParenthesis = false;
                } else if (isOperator(c)) {
                    while (!operatorStack.isEmpty() && precedence(operatorStack.peek()) >= precedence(c)) {
                        processAnOperator();
                    }
                    operatorStack.push(c);
                    i++;
                    lastWasOperatorOrOpenParenthesis = true;
                } else if (c == '(') {
                    operatorStack.push(c);
                    i++;
                    lastWasOperatorOrOpenParenthesis = true;
                } else if (c == ')') {
                    while (operatorStack.peek() != '(') {
                        processAnOperator();
                    }
                    operatorStack.pop(); 
                    i++;
                    lastWasOperatorOrOpenParenthesis = false;
                } else {
                    throw new Exception("Unexpected character encountered: " + c);
                }
            }
            while (!operatorStack.isEmpty()) {
                processAnOperator();
            }
            return operandStack.pop();
        }

        private boolean isOperator(char ch) {
            return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%';
        }

        private int precedence(char op) {
            if (op == '+' || op == '-') {
                return 1;
            } else if (op == '*' || op == '/' || op == '%') {
                return 2;
            }
            return -1;
        }

        private void processAnOperator() throws Exception {
            if (operandStack.size() < 2) {
                throw new Exception("Invalid expression");
            }
            BigDecimal b = operandStack.pop();
            BigDecimal a = operandStack.pop();
            char op = operatorStack.pop();
            operandStack.push(applyOperation(a, b, op));
        }

        private BigDecimal applyOperation(BigDecimal a, BigDecimal b, char op) {
            switch (op) {
                case '+':
                    return a.add(b);
                case '-':
                    return a.subtract(b);
                case '*':
                    return a.multiply(b);
                case '/':
                    if (b.compareTo(BigDecimal.ZERO) == 0) throw new ArithmeticException("Cannot divide by zero.");
                    return a.divide(b, MathContext.DECIMAL128); // 使用 MathContext.DECIMAL128
                case '%':
                    return a.remainder(b);
                default:
                    throw new IllegalArgumentException("Unknown operator: " + op);
            }
        }

        private void evaluateExpression() {
            try {
                String standardExpression = currentInput.toString()
                        .replace("÷", "/")
                        .replace("×", "*")
                        .replace("−", "-");

                BigDecimal result = evaluateInfixExpression(standardExpression.replaceAll("\\s+", ""));
                currentInput.append("\n\n=").append(result.toPlainString());
                display.setText(currentInput.toString());
                adjustFontSize();
            } catch (Exception ex) {
                display.setText("Error");
                currentInput.setLength(0);
            }
        }

        private void clearAll() {
            currentInput.setLength(0);
            display.setText("");
        }

        private void clearCurrentLine() {
            currentInput.setLength(Math.max(0, currentInput.lastIndexOf("\n") + 1));
            display.setText(currentInput.toString());
        }

        private void toggleSign() {
            String text = currentInput.toString().trim();
            if (!text.isEmpty()) {
                int len = currentInput.length();
                if (currentInput.charAt(len - 1) == '−') {
                    currentInput.deleteCharAt(len - 1);
                } else {
                    currentInput.insert(0, '−');
                }
                display.setText(currentInput.toString());
            }
        }

        private void moduloOperation() {
            if (currentInput.length() > 0 && Character.isDigit(currentInput.charAt(currentInput.length() - 1))) {
                currentInput.append('%');
                display.setText(currentInput.toString());
            }
        }
    }

    private void adjustFontSize() {
        int height = frame.getHeight();
        float fontSize = Math.max(20, height / 30f);
        Font font = new Font("Arial", Font.BOLD, (int) fontSize);
        display.setFont(font);
    }

    public static class ModernWebButton extends JButton {
        // 颜色常量,默认颜色可以用于普通按钮
        private static final Color DEFAULT_COLOR = new Color(0X3b3b3b);  // 默认背景颜色
        private static final Color HOVER_COLOR = new Color(0x313333);     // 鼠标悬停时的背景颜色
        private static final Color CLICKED_COLOR = new Color(0x262929);   // 鼠标点击时的背景颜色
    
        private boolean isClicked = false;
        private Color defaultColor;  
    
        // 默认构造函数
        public ModernWebButton(String text) {
            this(text, DEFAULT_COLOR, HOVER_COLOR, CLICKED_COLOR);
        }
    
        // 带有自定义颜色的构造函数,适用于特殊按钮
        public ModernWebButton(String text, Color defaultColor, Color hoverColor, Color clickedColor) {
            super(text);
    
            this.defaultColor = defaultColor;
    
            setOpaque(false);
            setContentAreaFilled(false);
            setBorderPainted(false);
            setFocusPainted(false);
            setFont(new Font("Arial", Font.BOLD, 26));
    
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    setBackground(clickedColor);
                    isClicked = true;
                }
    
                @Override
                public void mouseReleased(MouseEvent e) {
                    if (getModel().isRollover()) {
                        setBackground(hoverColor);
                    } else {
                        setBackground(defaultColor);
                    }
                    isClicked = false;
                }
    
                @Override
                public void mouseEntered(MouseEvent e) {
                    if (!isClicked) {
                        setBackground(hoverColor);
                    }
                }
    
                @Override
                public void mouseExited(MouseEvent e) {
                    if (!isClicked) {
                        setBackground(defaultColor);
                    }
                }
            });
    
            setBackground(defaultColor);
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    
            int w = getWidth();
            int h = getHeight();
    
            RoundRectangle2D rect = new RoundRectangle2D.Double(0, 0, w - 1, h - 1, h / 8, h / 8);
            g2.setColor(getBackground());
            g2.fill(rect);
    
            super.paintComponent(g2);
            g2.dispose();
        }
    
        @Override
        public void setBackground(Color bg) {
            if (bg == null) {
                super.setBackground(defaultColor);
            } else {
                super.setBackground(bg);
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(ModernCalculator::new);
    }
}

使用说明(操作示例)

示例输入:
15×3+20% 
=45.2

功能说明:
1. 连续运算:支持多行表达式自动保留历史记录
2. 特殊符号:
   • ±:切换当前数值正负号
   • %:百分比计算(20% = 0.2)
3. 错误处理:
   • 除以零时会显示"Error"并清空输入