PyQt5 - QWidgets部件进阶教程之计算器
来源:互联网 发布:闪电邮mac版 编辑:程序博客网 时间:2024/05/22 11:36
- 废话
- 定义计算器类
- 实现计算器部件类
- 实现按钮类
- 整体代码
- 最终效果
废话
该案例包含两个类:
- 计算器是一个Calculator部件,包含所有计算器功能
- Button部件用于每个计算机按钮,它由QToolButton派生
我们先校验计算器,然后再看按钮
定义计算器类
from PyQt5.QtWidgets import *from PyQt5.QtCore import *import sysclass Calculator(QWidget): def __init__(self): super(Calculator, self).__init__()app = QApplication(sys.argv)cal = Calculatorcal.show()app.exec_()
- 相关槽(cpp)
private slots: void digitClicked(); void unaryOperatorClicked(); void additiveOperatorClicked(); void multiplicativeOperatorClicked(); void equalClicked(); void pointClicked(); void changeSignClicked(); void backspaceClicked(); void clear(); void clearAll(); void clearMemory(); void readMemory(); void setMemory(); void addToMemory();
按钮根据他们的行为分类组合,例如所有数字按钮在当前的操作数追加一个数字。为此我们将各类按钮连接到相同的槽(例如digitClicked()),类目有:数字,一元运算符、加减法运算符、乘除法运算符,其他的按钮有他们自己的槽。
def createButton(self, text, member): pass def abortOperation(self): pass def calculate(self, rightOperand, pendingOperator): pass
- createButton()函数作为部件构造器的一部分
- abortOperation() 当出现被零除或负数平方根时被调用
- calculate()提供二进制操作(+, -, ×, ÷)
self.sumInMemory = 0 self.sumSoFar = 0 self.factorSoFar = 0 self.pendingAdditiveOperator = '' self.pendingMultiplicativeOperator = '' self.waitingForOperand = True
- sumInMemory包含计算器内存中存储的值
- sumSoFar存储目前累积值,当用户点击‘=’,它被重计算并显示出来,清除所有并重置sumSoFar为零
- 当做乘除法时,factorSoFar用来存储临时值
- pendingAdditiveOperator存储用户点击的最终加法操作符
- pendingMultiplicativeOperator存储用户点击的最终乘法操作符
- 当计算器希望用户输入一个操作符时,waitingForOperand为真
加法和乘法操作符对待不同,是因为他们有不同的优先级,例如1 + 2 ÷ 3可理解为1 + (2 ÷ 3),因为‘÷’比‘+’优先级高。
一元运算符(像平发根)不需要特别的操作,当已知运算数并点击运算符按钮时,他们可以立即应用。
实现计算器部件类
构造器内,我们初始化计算器状态
self.sumInMemory = 0.0 self.sumSoFar = 0.0 self.factorSoFar = 0.0 self.pendingAdditiveOperator = '' self.pendingMultiplicativeOperator = '' self.waitingForOperand = True self.display = QLineEdit('0') self.display.setReadOnly(True) self.display.setAlignment(Qt.AlignRight) self.display.setMaxLength(15)
QLineEdit代表计算器的显示,并设置一些它的属性,我们特别设置它为只读。
self.NumDigitButton = range(10) self.digitButton = list(self.NumDigitButton) for i in self.NumDigitButton: self.digitButton[i] = self.creatButton(str(i), self.digitClicked) self.pointButton = self.creatButton('.', self.pointClicked) self.changeSignButton = self.creatButton('\302\261', self.changeSignClicked) self.backspaceButoon = self.creatButton('BackSpace', self.backspaceClicked) self.clearButton = self.creatButton('Clear', self.clear) self.clearAllButton = self.creatButton('Clear All', self.clearAll) self.clearMemoryButton = self.creatButton('MC', self.clearMemory) self.readMemoryButton = self.creatButton('MR', self.readMemory) self.setMemoryButton = self.creatButton('MS', self.setMemory) self.addToMemoryButton = self.creatButton('M+', self.addToMemory) self.divisionButton = self.creatButton('\303\267', self.multiplicativeOperatorClicked) self.timesButton = self.creatButton('\303\227', self.multiplicativeOperatorClicked) self.minusButton = self.creatButton('-', self.additiveOperatorClicked) self.plusButton = self.creatButton('+', self.additiveOperatorClicked) self.squrareRootButton = self.creatButton('Sqrt', self.unaryOperatorClicked) self.powerButton = self.creatButton('x\302\262', self.unaryOperatorClicked) self.reciprocalButton = self.creatButton('1/x', self.unaryOperatorClicked) self.equalButton = self.creatButton('=', self.equalClicked)
对每个按钮调用createButton()函数,设置相应的文本标签和按钮对应的槽。
self.mainLayout = QGridLayout() self.mainLayout.setSizeConstraint(QLayout.SetFixedSize) self.mainLayout.addWidget(self.display, 0, 0, 1, 6) self.mainLayout.addWidget(self.backspaceButoon, 1, 0, 1, 2) self.mainLayout.addWidget(self.clearButton, 1, 2, 1, 2) self.mainLayout.addWidget(self.clearAllButton, 1, 4, 1, 2) self.mainLayout.addWidget(self.clearMemoryButton, 2, 0) self.mainLayout.addWidget(self.readMemoryButton, 3, 0) self.mainLayout.addWidget(self.setMemoryButton, 4, 0) self.mainLayout.addWidget(self.addToMemoryButton, 5, 0) for i in self.NumDigitButton: if i > 0: row = ((9 - i) / 3) + 2 column = ((i - 1) % 3) + 1 self.mainLayout.addWidget(self.digitButton[i], row, column) self.mainLayout.addWidget(self.digitButton[0], 5, 1) self.mainLayout.addWidget(self.pointButton, 5, 2) self.mainLayout.addWidget(self.changeSignButton, 5, 3) self.mainLayout.addWidget(self.divisionButton, 2, 4) self.mainLayout.addWidget(self.timesButton, 3, 4) self.mainLayout.addWidget(self.minusButton, 4, 4) self.mainLayout.addWidget(self.plusButton, 5, 4) self.mainLayout.addWidget(self.squrareRootButton, 2, 5) self.mainLayout.addWidget(self.powerButton, 3, 5) self.mainLayout.addWidget(self.reciprocalButton, 4, 5) self.mainLayout.addWidget(self.equalButton, 5, 5) self.setLayout(self.mainLayout) self.setWindowTitle('Calculator')
- 通过单独的QGridLayout操作布局,调用setSizeConstraint()来确保计算器部件以最优尺寸显示,阻止用户改变尺寸,尺寸提示由尺寸和子部件尺寸策略决定。
- 大部分子部件只占用网格布局的一个单元,这样我们只需向addWidget()传递row和column。display、backspaceButton、clearButton和clearAllButton部件占用超过一列,这样我们必须传递行跨度和列跨度。
def digitClicked(self): clickedButton = self.sender() digitValue = int(clickedButton.text()) if self.waitingForOperand: self.display.clear() self.waitingForOperand = False self.display.setText(self.display.text() + str(digitValue))
- 点击计算器的一个数字按钮将会发射按钮的clicked()信号,这会出发digitClicked()槽。
- 首先,我们需要使用sender()找出哪个按钮发射信号,这个函数将发射者作为对象返回。一旦我们得知那个按钮,就可以使用text()提取运算符。
- 槽需要考虑两个特定情况
- 如果显示中包含‘0’,用户在点击‘0’按钮时,它会显示‘00’
- 如果计算器进入等待新的操作符状态时,新的数字是新操作数的第一个数字,这样的情况下,前面任何计算结果必须首先清零
def unaryOperatorClicked(self): clickedButton = self.sender() clickedOperator = clickedButton.text() operand = float(self.display.text()) result = 0.0 if clickedOperator == 'Sqrt': if operand < 0.0: self.abortOperation() return result = math.sqrt(operand) elif clickedOperator == 'x\302\262': result = math.pow(operand, 2.0) elif clickedOperator == '1/x': if operand == 0.0: self.abortOperation() return result = 1.0 / operand self.display.setText(str(result)) self.waitingForOperand = True
- unaryOperatorClicked()槽当一元操作符按钮点击时被调用,同样适用sender()获取对象,操作符通过按钮文本提取并存储在clickedOperator,运算数通过display获得。
- 当提供给Sqrt负数或1/x到0时,调用abortOperation(),如果一切顺利,会在行编辑中显示运算结果,并将waitingForOperand设置为True,这样确保如果用户输入新数字时,数字会被认为是新运算数,而不是插入到当前值后
def additiveOperatorClicked(self): clickedButton = self.sender() clickedOperator = clickedButton.text() operand = float(self.display.text()) if len(self.pendingMultiplicativeOperator) != 0: if not self.calculate(operand, self.pendingMultiplicativeOperator): self.abortOperation() return self.display.setText(str(self.factorSoFar)) operand = self.factorSoFar self.factorSoFar = 0.0 self.pendingMultiplicativeOperator = '' if len(self.pendingAdditiveOperator) != 0: if not self.calculate(operand, self.pendingAdditiveOperator): self.abortOperation() return self.display.setText(str(self.sumSoFar)) else: self.sumSoFar = operand self.pendingAdditiveOperator = clickedOperator self.waitingForOperand = True
- 当用户点击‘+’或‘-’时,additiveOperatorClicked()槽被调用,在我们可以实际对点击的操作符做什么之前,我们必须处理所有挂起操作,先从乘法运算开始,自高优先级到加法运算。
- 如果‘×’或‘÷’先点击,之后并未点击‘=’,display中的当前值是右运算数,最后我们可以执行运算并更新display
- 如果‘+’或‘-’先点击,sumSoFar是左运算数,display中的当前值是右运算数,如果这里没有待加运算,sumSoFar会直接设置到display的文本中
- 当没有右运算数时,我们将点击的运算符存储在pendingAdditiveOperator变量,这会在之后出现右运算数时,与作为左运算数的sumSoFar进行计算。
def multiplicativeOperatorClicked(self): clickedButton = self.sender() clickedOperator = clickedButton.text() operand = float(self.display.text()) if len(self.pendingMultiplicativeOperator) != 0: if not self.calculate(operand, self.pendingMultiplicativeOperator): self.abortOperation() return self.display.setText(self.factorSoFar) else: self.factorSoFar = operand self.pendingMultiplicativeOperator = clickedOperator self.waitingForOperand = True
multiplicativeOperatorClicked()槽与additiveOperatorClicked()类似,这里我们不需要担心待加运算数,因为乘法运算符比加法运算符优先级高。
def equalClicked(self): operand = float(self.display.text()) if len(self.pendingMultiplicativeOperator) != 0: if not self.calculate(operand, self.pendingMultiplicativeOperator): self.abortOperation() return operand = self.factorSoFar self.factorSoFar = 0.0 self.pendingMultiplicativeOperator = '' if len(self.pendingAdditiveOperator) != 0: if not self.calculate(operand, self.pendingAdditiveOperator): self.abortOperation() return self.pendingAdditiveOperator = '' else: self.sumSoFar = operand self.display.setText(str(self.sumSoFar)) self.sumSoFar = 0.0 self.waitingForOperand = True
像additiveOperatorClicked(),仍从处理挂起乘法和加法运算符开始,然后显示sumSoFar,并将变量归零,重置变量是为避免二次计算。
def pointClicked(self): if self.waitingForOperand: self.display.setText('0') if '.' not in self.display.text(): self.display.setText(self.display.text() + '.') self.waitingForOperand = False
pointClicked()槽在当前显示内容加小数点
def changeSignClicked(self): text = self.display.text() value = float(text) if value > 0.0: text = '-' + text elif value < 0.0: text = text[1:] self.display.setText(str(text))
changeSignClicked()槽改变当前显示值的符号,如果当前值为正,我们加个符号;如果当前值为负,我们移除值的第一个字符,这里面使用了python字符串切片的方法。
def backspaceClicked(self): if self.waitingForOperand: return text = self.display.text() text = text[0:-1] if len(text) == 0: text = '0' self.waitingForOperand = True self.display.setText(str(text))
backspaceClicked()移除显示中最右边的字符,如果得到空字符串,将显示‘0’,并将waitingForOperand设置为True
def clear(self): if self.waitingForOperand: return self.display.setText('0') self.waitingForOperand = True
clear()槽重置当前运算数为零,它相当于点很多次退格来清除目前的运算数。
def clearAll(self): self.sumSoFar = 0.0 self.factorSoFar = 0.0 self.pendingAdditiveOperator = '' self.pendingMultiplicativeOperator = '' self.display.setText('0') self.waitingForOperand = True
clearAll()槽将计算器重置为初始化状态
def clearMemory(self): self.sumInMemory = 0.0 def readMemory(self): self.display.setText(str(self.sumInMemory)) self.waitingForOperand = True def setMemory(self): self.equalClicked() self.sumInMemory = float(self.display.text()) def addToMemory(self): self.equalClicked() self.sumInMemory += float(self.display.text())
- clearMemory()清除内存中的总数
- readMemory()显示总数作为运算数
- setMemory()用当前总数替换内存中的总数
- addToMemory()将当前总数加到内存中的值
def creatButton(self, text, member): button = Button(text) button.clicked.connect(member) return button
createButton()用来构建计算器按钮
def abortOperation(self): self.clearAll() self.display.setText('####')
当计算失败时调用abortOperation(),它重置计算器状态并显示’####’
def calculate(self, rightOperand, pendingOperator): if pendingOperator == '+': self.sumSoFar += rightOperand elif pendingOperator == '-': self.sumSoFar -= rightOperand elif pendingOperator == '\303\227': self.factorSoFar *= rightOperand elif pendingOperator == '\303\267': if rightOperand == 0.0: return False self.factorSoFar /= rightOperand return True
- calculate()函数执行二元运算,右运算数通过rightOperand赋值
- 加法运算中,左运算数是sumSoFar
- 乘法运算中,左运算数是factorSoFar
- 如果除零,函数返回错误
实现按钮类
按钮类构造器包括文本标签和父部件,并重写sizeHint()来给提供文本周围更多的空间。
class Button(QToolButton): def __init__(self, text): super(Button, self).__init__() self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.setText(text) def sizeHint(self): size = QSize(QToolButton().sizeHint()) size.setHeight(size.height() + 20) size.setWidth(max(size.width(), size.height())) return size
- 按钮的外观定义,由计算器部件的布局通过设置尺寸和子部件布局的尺寸策略决定
- setSizePolicy()的调用是确保按钮可以横向扩展,填满所有可用空间,默认下,QToolButtons不填充可用空间。若不调用,同列的不同按钮的宽度会不一
- 在sizeHint()中,我们尝试返回一个为所有按钮看起来还不错的尺寸,这里复用了sizeHint()的基础类(QToolButton),但从下面的方式进行修改:
- 将sizeHint()高度增加20
- 将sizeHint()宽度设成至少与高度相当
这样来确保大多数字体下,数字和运算符按钮是方的,不会在Backspace、Clear和Clear All上截断文本。
整体代码
from PyQt5.QtWidgets import *from PyQt5.QtCore import *from PyQt5.QtGui import *import sys, mathclass Calculator(QWidget): def __init__(self): super(Calculator, self).__init__() self.sumInMemory = 0.0 self.sumSoFar = 0.0 self.factorSoFar = 0.0 self.pendingAdditiveOperator = '' self.pendingMultiplicativeOperator = '' self.waitingForOperand = True self.display = QLineEdit('0') self.display.setReadOnly(True) self.display.setAlignment(Qt.AlignRight) self.display.setMaxLength(15) self.font = QFont() self.font.setPointSize(self.font.pointSize() + 8) self.display.setFont(self.font) self.NumDigitButton = range(10) self.digitButton = list(self.NumDigitButton) for i in self.NumDigitButton: self.digitButton[i] = self.creatButton(str(i), self.digitClicked) self.pointButton = self.creatButton('.', self.pointClicked) self.changeSignButton = self.creatButton('\302\261', self.changeSignClicked) self.backspaceButoon = self.creatButton('BackSpace', self.backspaceClicked) self.clearButton = self.creatButton('Clear', self.clear) self.clearAllButton = self.creatButton('Clear All', self.clearAll) self.clearMemoryButton = self.creatButton('MC', self.clearMemory) self.readMemoryButton = self.creatButton('MR', self.readMemory) self.setMemoryButton = self.creatButton('MS', self.setMemory) self.addToMemoryButton = self.creatButton('M+', self.addToMemory) self.divisionButton = self.creatButton('\303\267', self.multiplicativeOperatorClicked) self.timesButton = self.creatButton('\303\227', self.multiplicativeOperatorClicked) self.minusButton = self.creatButton('-', self.additiveOperatorClicked) self.plusButton = self.creatButton('+', self.additiveOperatorClicked) self.squrareRootButton = self.creatButton('Sqrt', self.unaryOperatorClicked) self.powerButton = self.creatButton('x\302\262', self.unaryOperatorClicked) self.reciprocalButton = self.creatButton('1/x', self.unaryOperatorClicked) self.equalButton = self.creatButton('=', self.equalClicked) self.mainLayout = QGridLayout() self.mainLayout.setSizeConstraint(QLayout.SetFixedSize) self.mainLayout.addWidget(self.display, 0, 0, 1, 6) self.mainLayout.addWidget(self.backspaceButoon, 1, 0, 1, 2) self.mainLayout.addWidget(self.clearButton, 1, 2, 1, 2) self.mainLayout.addWidget(self.clearAllButton, 1, 4, 1, 2) self.mainLayout.addWidget(self.clearMemoryButton, 2, 0) self.mainLayout.addWidget(self.readMemoryButton, 3, 0) self.mainLayout.addWidget(self.setMemoryButton, 4, 0) self.mainLayout.addWidget(self.addToMemoryButton, 5, 0) for i in self.NumDigitButton: if i > 0: row = ((9 - i) / 3) + 2 column = ((i - 1) % 3) + 1 self.mainLayout.addWidget(self.digitButton[i], row, column) self.mainLayout.addWidget(self.digitButton[0], 5, 1) self.mainLayout.addWidget(self.pointButton, 5, 2) self.mainLayout.addWidget(self.changeSignButton, 5, 3) self.mainLayout.addWidget(self.divisionButton, 2, 4) self.mainLayout.addWidget(self.timesButton, 3, 4) self.mainLayout.addWidget(self.minusButton, 4, 4) self.mainLayout.addWidget(self.plusButton, 5, 4) self.mainLayout.addWidget(self.squrareRootButton, 2, 5) self.mainLayout.addWidget(self.powerButton, 3, 5) self.mainLayout.addWidget(self.reciprocalButton, 4, 5) self.mainLayout.addWidget(self.equalButton, 5, 5) self.setLayout(self.mainLayout) self.setWindowTitle('Calculator') def digitClicked(self): clickedButton = self.sender() digitValue = int(clickedButton.text()) if self.waitingForOperand: self.display.clear() self.waitingForOperand = False self.display.setText(self.display.text() + str(digitValue)) def unaryOperatorClicked(self): clickedButton = self.sender() clickedOperator = clickedButton.text() operand = float(self.display.text()) result = 0.0 if clickedOperator == 'Sqrt': if operand < 0.0: self.abortOperation() return result = math.sqrt(operand) elif clickedOperator == 'x\302\262': result = math.pow(operand, 2.0) elif clickedOperator == '1/x': if operand == 0.0: self.abortOperation() return result = 1.0 / operand self.display.setText(str(result)) self.waitingForOperand = True def additiveOperatorClicked(self): clickedButton = self.sender() clickedOperator = clickedButton.text() operand = float(self.display.text()) if len(self.pendingMultiplicativeOperator) != 0: if not self.calculate(operand, self.pendingMultiplicativeOperator): self.abortOperation() return self.display.setText(str(self.factorSoFar)) operand = self.factorSoFar self.factorSoFar = 0.0 self.pendingMultiplicativeOperator = '' if len(self.pendingAdditiveOperator) != 0: if not self.calculate(operand, self.pendingAdditiveOperator): self.abortOperation() return self.display.setText(str(self.sumSoFar)) else: self.sumSoFar = operand self.pendingAdditiveOperator = clickedOperator self.waitingForOperand = True def multiplicativeOperatorClicked(self): clickedButton = self.sender() clickedOperator = clickedButton.text() operand = float(self.display.text()) if len(self.pendingMultiplicativeOperator) != 0: if not self.calculate(operand, self.pendingMultiplicativeOperator): self.abortOperation() return self.display.setText(self.factorSoFar) else: self.factorSoFar = operand self.pendingMultiplicativeOperator = clickedOperator self.waitingForOperand = True def equalClicked(self): operand = float(self.display.text()) if len(self.pendingMultiplicativeOperator) != 0: if not self.calculate(operand, self.pendingMultiplicativeOperator): self.abortOperation() return operand = self.factorSoFar self.factorSoFar = 0.0 self.pendingMultiplicativeOperator = '' if len(self.pendingAdditiveOperator) != 0: if not self.calculate(operand, self.pendingAdditiveOperator): self.abortOperation() return self.pendingAdditiveOperator = '' else: self.sumSoFar = operand self.display.setText(str(self.sumSoFar)) self.sumSoFar = 0.0 self.waitingForOperand = True def pointClicked(self): if self.waitingForOperand: self.display.setText('0') if '.' not in self.display.text(): self.display.setText(self.display.text() + '.') self.waitingForOperand = False def changeSignClicked(self): text = self.display.text() value = float(text) if value > 0.0: text = '-' + text elif value < 0.0: text = text[1:] self.display.setText(str(text)) def backspaceClicked(self): if self.waitingForOperand: return text = self.display.text() text = text[0:-1] if len(text) == 0: text = '0' self.waitingForOperand = True self.display.setText(str(text)) def clear(self): if self.waitingForOperand: return self.display.setText('0') self.waitingForOperand = True def clearAll(self): self.sumSoFar = 0.0 self.factorSoFar = 0.0 self.pendingAdditiveOperator = '' self.pendingMultiplicativeOperator = '' self.display.setText('0') self.waitingForOperand = True def clearMemory(self): self.sumInMemory = 0.0 def readMemory(self): self.display.setText(str(self.sumInMemory)) self.waitingForOperand = True def setMemory(self): self.equalClicked() self.sumInMemory = float(self.display.text()) def addToMemory(self): self.equalClicked() self.sumInMemory += float(self.display.text()) def creatButton(self, text, member): button = Button(text) button.clicked.connect(member) return button def abortOperation(self): self.clearAll() self.display.setText('####') def calculate(self, rightOperand, pendingOperator): if pendingOperator == '+': self.sumSoFar += rightOperand elif pendingOperator == '-': self.sumSoFar -= rightOperand elif pendingOperator == '\303\227': self.factorSoFar *= rightOperand elif pendingOperator == '\303\267': if rightOperand == 0.0: return False self.factorSoFar /= rightOperand return Trueclass Button(QToolButton): def __init__(self, text): super(Button, self).__init__() self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.setText(text) def sizeHint(self): size = QSize(QToolButton().sizeHint()) size.setHeight(size.height() + 20) size.setWidth(max(size.width(), size.height())) return sizeapp = QApplication(sys.argv)cal = Calculator()cal.show()app.exec_()
最终效果
- PyQt5 - QWidgets部件进阶教程之计算器
- PyQt5 - QWidgets部件进阶教程
- PyQt5 - QWidgets部件进阶教程之日历窗口部件
- PyQt5 - QWidgets部件进阶教程之模拟时钟
- PyQt5 - QWidgets部件进阶教程之数字时钟
- PyQt5 - QWidgets部件进阶教程之字符映射表
- PyQt5 - QWidgets部件进阶教程之塑形时钟
- PyQt5 - QWidgets部件进阶教程之分组框
- PyQt5 - QWidgets部件进阶教程之行编辑
- PyQt5 - QWidgets部件入门教程
- PyQt5学习教程9:使用Grid Layout布局计算器界面
- PyQt5:QCalendarWidget日历部件(27)
- PyQt5:计算器UI(1)
- Vim教程之进阶
- Android之人品计算器教程
- PyQt5 写一个计算器框架
- PyQt5初级教程--PyQt5中的部件II[9/13]
- python3+PyQt5 自定义窗口部件--使用窗口部件样式表
- 特殊的数字
- Myeclipse+Tomcat运行网站
- Android环境搭建
- 杨辉三角
- 字母图形
- PyQt5 - QWidgets部件进阶教程之计算器
- 线段树之HDU 1754 I hate it
- 指示器(菊花效果)代码演示--iOS开发
- rrdtool的完整例子
- 阶乘计算
- Emacs录制宏
- 高精度计算
- HDU 1166 敌兵布阵(线段树&树状数组)
- Android BaseAnimation近200种动画效果源码