天天看點

PyQt5做電腦(詳細講解, 絕對讓你學會)整體分析項目難點(對我)效果圖開始寫電腦 總結:

整體分析

     電腦的整體設計時參考了ios電腦的簡單計算來做的, 不涉及複雜的計算,是以主要的問題是處理計算的邏輯。

其次是界面的設計, 考慮的恐怖的輸入(1+-1=0, 2* = 4, 1+2-3*4等, 後面講計算邏輯的時候在分析), 對于函數的不好了解的地方,在函數中都加了注釋。

項目難點(對我)

  1.  解決計算的問題(使用list做棧)
  2. 對于何時清理螢幕的顯示,不明這句,在接下來講的時候,參考下手機裡的計算
  3. 小數點隻能有一個

效果圖

PyQt5做電腦(詳細講解, 絕對讓你學會)整體分析項目難點(對我)效果圖開始寫電腦 總結:
PyQt5做電腦(詳細講解, 絕對讓你學會)整體分析項目難點(對我)效果圖開始寫電腦 總結:

開始寫電腦

安裝PyQt5

pip install PyQt5
           

引入的檔案, 後面的代碼就不加了, 接下來寫的函數都是在同一個類裡Calculaor

import sys
from PyQt5.QtWidgets import (QMainWindow, QWidget, QApplication,
                             QLineEdit, QPushButton, QGridLayout)
from PyQt5.QtGui import QRegExpValidator                       
from PyQt5.QtCore import Qt, QRegExp
           

界面設計

class Calculator(QWidget):
    """
    電腦的基本頁面的基本界面, 完成基本的計算
    """

    def __init__(self):
        super(Calculator, self).__init__()
        self.ui()
        self.char_stack = []       # 操作符号的棧
        self.num_stack = []        # 操作數的棧
        self.nums = [chr(i) for i in range(48, 58)]   # 用于判斷按鈕的值是不是數字
        self.operators = ['+', '-', '*', '/']         # 用于判斷按鈕的值是不是操作符

        self.empty_flag = True      # 這個flag的含義是來判斷電腦是不是第一次啟動,在顯示螢幕中無資料
        self.after_operator = False # 看了電腦的計算,比如1+2在輸入+後,1海顯示在螢幕上,輸入了2之後,1就被替換了, 這個flag的作用就是這樣的  

        self.char_top = ''          # 保留棧頂的操作符号
        self.num_top = 0            # 保留棧頂的數值
        self.res = 0                # 保留計算結果,看電腦計算一次後,在繼續按等号,還會重複最近一次的計算1+2,得到3之後,在按等号就是3+2, 以此類推.

        # >先計算, 為什麼同樣的符号改成了後計算, 是為了友善做一項操作,
        # 就是在你計算一個表達式之後,在繼續按住等号, 以及會執行最後一次的符号運算
        self.priority_map = {
            '++': '>', '+-': '>', '-+': '>', '--': '>',
            '+*': '<', '+/': '<', '-*': '<', '-/': '<',
            '**': '>', '//': '>', '*+': '>', '/+': '>',
            '*-': '>', '/-': '>', '*/': '>', '/*': '>'
        }

    def ui(self):
        # 這個函數主要适用于初始化界面
        reg = QRegExp("^$")      # 把鍵盤禁用了, 僅可以按鈕的輸入
        validator = QRegExpValidator(reg, self)
        
        # 這個line_edit就是顯示屏....
        self.line_edit = QLineEdit('0', self)
        self.line_edit.setAlignment(Qt.AlignRight)
        self.line_edit.setValidator(validator)
        self.line_edit.setReadOnly(True)
        
        #  使用girdlayout進行界面布局
        grid = QGridLayout()
        self.setLayout(grid)

        btn_names = [
            'C', '草', '%', '/',
            '7', '8', '9', '*',
            '4', '5', '6', '-',
            '1', '2', '3', '+',
            '0', '', '.', '='
        ]

        grid.addWidget(self.line_edit, 0, 0, 1, 4)
        positions = [(i, j) for i in range(1, 6) for j in range(4)]
        for pos, name in zip(positions, btn_names):
            if name == '':
                continue
            btn = QPushButton(name)
            # 在布局的時候,直接把每個按鈕連接配接到點選事件上
            btn.clicked.connect(self.show_msg)
            if name == '0':
                tmp_pos = (pos[0], pos[1]+1) 
                grid.addWidget(btn, *pos, 1, 2)
            else:
                grid.addWidget(btn, *pos)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setWindowTitle('Calculator')
        self.move(300, 150)
        self.show()
           

處理C按鈕, 清空line_edit

def clear_line_edit(self):
        self.line_edit.clear()
        self.line_edit.setText('0')
        self.res = 0
        # 清空,就相當于剛打開電腦一樣
        self.empty_flag = True
           

處理數字按鈕1, 2, ....

def deal_num_btn(self, sender_text):
        # 這個after_operatpr就是判斷是不是在輸入了操作符号後,有輸入數字
        # 比如 1+ 這時候在輸入2, 這種情況, 這時候,應該把1清理掉,去顯示2
        if self.after_operator:
            self.line_edit.clear()
            self.after_operator = False
        _str = self.line_edit.text()
        # 對line_edit是否有資料,有資料就繼續往裡面追加,沒有就是重新開始
        if _str == '0' or _str == 'Error' or self.empty_flag:
            _str = ''
        self.line_edit.setText(_str+sender_text)
        # 加入了資料,empty_falg就改變了
        self.empty_flag = False
           

處理操作符+, -, *, /

def deal_operator_btn(self, sender_text):
        # 操作符号 +, -, *, /
        self.empty_flag = False
        _str = self.line_edit.text()
        
        # 比如剛打開電腦 你直接輸入了一個 +
        if _str == '0' or _str == 'Error':
            # 就是需要上一次的計算結果, 需要把上一次的計算結果送入數字棧,操作符送入符号棧
            self.num_stack.append(self.res)
            self.char_stack.append(sender_text)
        else:
            # 在你輸入操作符号之前,可能輸入了數字或者一個操作符
            # 如果輸入的是一個操作符那麼,num_stack和char_stack的長度是一樣的,可以來判斷
            # 1++ 第二個加号并沒有進入數字棧,是以可以看出, 他倆的長度是一樣的
            self.num_top = float(_str) if _str.count('.') != 0 \
                                            else int(_str)
            self.num_stack.append(self.num_top)
            self.char_top = sender_text
            num_stack_len, char_stack_len = len(self.num_stack), len(self.char_stack)
            if (num_stack_len == char_stack_len) and num_stack_len != 0:
                #在這裡處理類似 輸入 1+- 這種情況就是 1-後一個字元替換前面的
                self.char_stack[-1] = sender_text
            else:
                # 是正常的輸入,1+2+此時,2入數字棧
                # 1+2*..... 類似輸入
                if len(self.char_stack) == 0:
                    self.char_stack.append(self.char_top)
                else:
                    # 考慮符号的優先級, 1+2*這個時候隻需要*入棧即可, 1*2+就要去計算1*2了
                    operator_cmp_key = self.char_stack[-1] + sender_text
                    if self.priority_map[operator_cmp_key] == '>':
                        print(self.num_stack, self.char_stack)
                        self.calculate(sender_text)
                    self.char_stack.append(sender_text)
                # 你輸入一個操作符号, 那麼接下裡輸入的時候,需要把前的line_edit 内容清空
                self.after_operator = True
           

處理小數點

def deal_point_btn(self):
        _str = self.line_edit.text()
        self.empty_flag = False
        # 計算line_edit中有多少小數點
        point_count = self.line_edit.text().count('.')
        if point_count == 0:
            _str += '.'
        self.line_edit.setText(_str)
           

處理=操作

def deal_equal_btn(self):
        _str = self.line_edit.text()
        self.empty_flag = True
        try:
            # 在等号前 處理的數字 可能是上一次的計算結果,也可能是輸入的資料的最後一個指,是以不能直接儲存在num_top中, 考慮如果是上一次的計算結果是,直接儲存在num_top會是什麼結果
            tmp_num = float(_str) if _str.count('.') != 0 \
                                            else int(_str)
            self.num_stack.append(tmp_num)
            if len(self.num_stack) == 1:
                # 你剛做完一個計算, 結果還保留在螢幕上,這時候再按=, 
                # 例如 1+2, 此時螢幕顯示3,你再按=就是計算3+2, 再按就是5+2
                # 需要上一次的結果, 是以要在數字棧中加入num_top, 符号棧中加入char_top
                self.char_stack.append(self.char_top)
                self.num_stack.append(self.num_top)
            else:
                # line_edit的值不是上一次的計算結果,我們就把他儲存在num_top中。
                self.num_top = tmp_num
        except Exception as e:
            # 可能出現異常的原因是 我忘了,可能抓不到異常,如果發現請告訴我
            self.num_stack.append(self.num_top)
            print('Error: {}'.format(e.args))
        self.calculate()
        self.num_stack.clear()
        self.char_stack.clear()
           

按鈕點選的事件

def show_msg(self):
        # 看ui函數,每個按鈕都連接配接了show_msg的點選事件
        sender = self.sender()
        sender_text = sender.text()

        if sender_text == 'C':
            self.clear_line_edit()
        elif sender_text in self.nums:
            self.deal_num_btn(sender_text)
        elif sender_text == '.':
            self.deal_point_btn()
        elif sender_text in self.operators:
            self.deal_operator_btn(sender_text)
        elif sender_text == '=':
            self.deal_equal_btn()
           

對計算的處理

def auxiliary_calculate(self, first_num, second_num, operator: str):
        # 輔助計算
        if operator == '/':
            if second_num == 0:
                _str = 'Error'
                self.res = 0
                self.line_edit.setText(_str)
                return None
            else:
                return first_num / second_num
        elif operator == '*':
            return first_num * second_num
        elif operator == '+':
            return first_num + second_num
        else:
            return first_num - second_num

    def calculate(self, operator='='):
        # 這裡就很好了解了
        if operator == '=':
            # 要最後的結果
            print(self.num_stack)
            print(self.char_stack)
            error_falg = False
            while len(self.char_stack) >= 1:
                n1 = self.num_stack.pop()
                n2 = self.num_stack.pop()
                op = self.char_stack.pop()
                result = self.auxiliary_calculate(n2, n1, op)
                if result is None:
                    self.num_stack.clear()
                    self.char_stack.clear()
                    error_falg = True
                    break
                else:
                    self.num_stack.append(result)
            if not error_falg:
                self.res = self.num_stack.pop()
                if self.res == int(self.res):
                    self.res = int(self.res)
                self.line_edit.setText(str(self.res))
            else:
                self.line_edit.setText('Error')
        else:
            op = self.char_stack.pop()
            while len(self.char_stack) >= 0 and (self.priority_map[op+operator] == '>'):
                n1 = self.num_stack.pop()
                n2 = self.num_stack.pop()
                result = self.auxiliary_calculate(n2, n1, op)
                if result is None:
                    self.num_stack.clear()
                    self.char_stack.clear()
                    break
                self.num_stack.append(self.auxiliary_calculate(n2, n1, op))
                try:
                    op = self.char_stack.pop()
                except Exception as e:
                    break
            self.res = self.num_stack[-1]
            if self.res == int(self.res):
                self.res = int(self.res)
            self.line_edit.setText(str(self.res))
           

開啟電腦

if __name__ == '__main__':
    app = QApplication(sys.argv)
    cal = Calculator()
    sys.exit(app.exec_())
           

總結:

       本片文章一步一步的分析了如何用pyqt做一個電腦, 主要可能有問題的地方都在文章中做了注釋,并且舉了簡單的例子。

在做電腦的時候,多看看手機上的電腦在結合注釋,應該更好了解.

       完整的代碼在Github