警告
本部分包含从 C++ 自动翻译到 Python 的摘录,可能包含错误。
计算器示例#
该示例展示了如何使用信号和槽来实现计算器小部件的功能,以及如何使用 QGridLayout
将子小部件放置在网格中。
计算器示例截图
示例包含两个类
Calculator
是带有所有计算器功能的计算器小部件。
Button
是用于每个计算器按钮的小部件。它继承自QToolButton
。
我们将首先回顾 Calculator
,然后我们将看看 Button
。
计算器类定义#
class Calculator(QWidget): Q_OBJECT # public Calculator(QWidget parent = None) # private slots def digitClicked(): def unaryOperatorClicked(): def additiveOperatorClicked(): def multiplicativeOperatorClicked(): def equalClicked(): def pointClicked(): def changeSignClicked(): def backspaceClicked(): def clear(): def clearAll(): def clearMemory(): def readMemory(): def setMemory(): def addToMemory():
Calculator
类提供了一个简单的计算器小部件。它继承自 QDialog
并具有与计算器按钮相关联的几个私有槽。重写了 QObject::eventFilter() 来处理计算器显示上的鼠标事件。
按钮根据其行为分组。例如,所有的数字按钮(标注为 0 到 9)将数字附加到当前操作数。对于这些,我们将多个按钮连接到相同的槽(例如,digitClicked()
)。这些类别是数字、一元运算符(根号、x²、1/x)、加法运算符(+、-)和乘法运算符(×、÷)。其他按钮有自己的槽。
# private template<typename PointerToMemberFunction> def createButton(text,member): def abortOperation(): calculate = bool(float rightOperand, QString pendingOperator)
私有函数 createButton()
用于小部件构造的一部分。在发生除以零、平方根操作应用于负数时调用 abortOperation()
。调用 calculate()
应用二元运算符(+、-、×、或 ÷)。
sumInMemory = float() sumSoFar = float() factorSoFar = float() pendingAdditiveOperator = QString() pendingMultiplicativeOperator = QString() waitingForOperand = bool()
这些变量以及计算器显示的内容(一个 QLineEdit
)代表了计算器的状态
sumInMemory
包含计算器内存中的值(使用 MS、M+ 或 MC)。
sumSoFar
存储到目前为止积累的值。当用户点击等号时,重新计算sumSoFar
并且显示在显示中。清除所有操作将sumSoFar
重置为零。
factorSoFar
存储乘除法过程中的暂时值。
pendingAdditiveOperator
存储用户最后点击的加法运算符。
pendingMultiplicativeOperator
存储用户最后点击的乘法运算符。
waitingForOperand
当计算器等待用户开始输入操作数时为true
。
由于加法和乘法运算符有不同的优先级,因此它们被处理方式不同。例如,1 + 2 ÷ 3 被解释为 1 + (2 ÷ 3),因为 ÷ 的优先级高于 +。
下表显示了用户输入数学表达式时计算器状态的变化。
用户输入
显示
迄今为止的总和
加法运算符
迄今为止的因子
乘法运算符
是否等待操作数?
0
0
true
1
1
0
false
1 +
1
1
true
1 + 2
2
1
false
1 + 2 ÷
2
1
2
÷
true
1 + 2 ÷ 3
3
1
2
÷
false
1 + 2 ÷ 3 -
1.66667
1.66667
true
1 + 2 ÷ 3 - 4
4
1.66667
false
1 + 2 ÷ 3 - 4 =
-2.33333
0
true
一元运算符,如开平方根,不需要特别处理;它们可以立即应用,因为当运营商按钮被点击时,操作数已经已知。
display = QLineEdit() enum { NumDigitButtons = 10 } digitButtons[NumDigitButtons] = Button()
最后,我们声明与显示和显示数字的按钮相关的变量。
计算器类实现#
def __init__(self, parent): super().__init__(parent) self.sumInMemory = 0.0 self.sumSoFar = 0.0 , factorSoFar(0.0), waitingForOperand(True)
在构造函数中,我们初始化计算器的状态。变量 pendingAdditiveOperator
和 pendingMultiplicativeOperator
不需要显式初始化,因为 QString 构造函数会将它们初始化为空字符串。也可以直接在头文件中初始化这些变量。这称为 成员初始化
,可以避免长的初始化列表。
display = QLineEdit("0") display.setReadOnly(True) display.setAlignment(Qt.AlignRight) display.setMaxLength(15) font = display.font() font.setPointSize(font.pointSize() + 8) display.setFont(font)
我们创建了代表计算器显示屏的 QLineEdit
并设置了一些属性。特别地,我们将它设置为只读。
我们还把 display
的字体放大了 8 点。
for i in range(0, NumDigitButtons): digitButtons[i] = createButton(QString.number(i), Calculator.digitClicked) pointButton = createButton(tr("."), Calculator::pointClicked) changeSignButton = createButton(tr("\302\261"), Calculator::changeSignClicked) backspaceButton = createButton(tr("Backspace"), Calculator::backspaceClicked) clearButton = createButton(tr("Clear"), Calculator::clear) clearAllButton = createButton(tr("Clear All"), Calculator::clearAll) clearMemoryButton = createButton(tr("MC"), Calculator::clearMemory) readMemoryButton = createButton(tr("MR"), Calculator::readMemory) setMemoryButton = createButton(tr("MS"), Calculator::setMemory) addToMemoryButton = createButton(tr("M+"), Calculator::addToMemory) divisionButton = createButton(tr("\303\267"), Calculator::multiplicativeOperatorClicked) timesButton = createButton(tr("\303\227"), Calculator::multiplicativeOperatorClicked) minusButton = createButton(tr("-"), Calculator::additiveOperatorClicked) plusButton = createButton(tr("+"), Calculator::additiveOperatorClicked) squareRootButton = createButton(tr("Sqrt"), Calculator::unaryOperatorClicked) powerButton = createButton(tr("x\302\262"), Calculator::unaryOperatorClicked) reciprocalButton = createButton(tr("1/x"), Calculator::unaryOperatorClicked) equalButton = createButton(tr("="), Calculator::equalClicked)
对于每个按钮,我们使用适当的文本标签和按钮的插槽调用私有函数 createButton()
。
mainLayout = QGridLayout() mainLayout.setSizeConstraint(QLayout.SetFixedSize) mainLayout.addWidget(display, 0, 0, 1, 6) mainLayout.addWidget(backspaceButton, 1, 0, 1, 2) mainLayout.addWidget(clearButton, 1, 2, 1, 2) mainLayout.addWidget(clearAllButton, 1, 4, 1, 2) mainLayout.addWidget(clearMemoryButton, 2, 0) mainLayout.addWidget(readMemoryButton, 3, 0) mainLayout.addWidget(setMemoryButton, 4, 0) mainLayout.addWidget(addToMemoryButton, 5, 0) for i in range(1, NumDigitButtons): row = ((9 - i) / 3) + 2 column = ((i - 1) % 3) + 1 mainLayout.addWidget(digitButtons[i], row, column) mainLayout.addWidget(digitButtons[0], 5, 1) mainLayout.addWidget(pointButton, 5, 2) mainLayout.addWidget(changeSignButton, 5, 3) mainLayout.addWidget(divisionButton, 2, 4) mainLayout.addWidget(timesButton, 3, 4) mainLayout.addWidget(minusButton, 4, 4) mainLayout.addWidget(plusButton, 5, 4) mainLayout.addWidget(squareRootButton, 2, 5) mainLayout.addWidget(powerButton, 3, 5) mainLayout.addWidget(reciprocalButton, 4, 5) mainLayout.addWidget(equalButton, 5, 5) setLayout(mainLayout) setWindowTitle(tr("Calculator"))
布局由单个 QGridLayout
处理。setSizeConstraint() 调用确保 Calculator
小部件始终以其最佳大小显示(其 size hint),防止用户调整计算器的大小。大小提示由子小部件的大小和 size policy 决定。
大多数子小部件只占用网格布局中的一个单元格。对于这些,我们只需要向 addWidget()
方法传递行号和列号。对于 display
、backspaceButton
、clearButton
和 clearAllButton
小部件,它们占用的列数超过一个,因此我们还需要传递行跨度和列跨度。
def digitClicked(self): clickedButton = Button(sender()) digitValue = clickedButton.text().toInt() if display.text() == "0" and digitValue == 0.0: return if waitingForOperand: display.clear() waitingForOperand = False display.setText(display.text() + QString.number(digitValue))
按下计算器的一个数字按钮将发射按钮的 clicked()
信号,该信号将触发 digitClicked()
接口。
首先,我们通过使用 QObject::sender() 函数找到发出信号的按钮。这个函数将发送者作为一个 QObject 指针返回。既然我们知道发送者是一个 Button
对象,我们可以安全地进行类型转换。我们可以使用 C 风格的转换或 C++ 的 static_cast<>()
,但是作为一种防御性编程技术,我们使用 qobject_cast()。优点是如果对象类型不正确,则返回一个空指针。由于空指针造成的崩溃比不安全转换造成的崩溃更容易诊断。一旦我们获得按钮,我们就使用 text()
提取操作符。
插槽需要考虑两种特殊情况。如果 display
包含“0”,并且用户点击了 0 按钮,显示“00”将是愚蠢的。并且如果计算器处于等待新操作数的状态,新数字将是新操作数的第一位数字;在这种情况下,必须首先清除之前的计算结果。
最后,将新数字追加到显示屏中的值。
def unaryOperatorClicked(self): clickedButton = Button(sender()) clickedOperator = clickedButton.text() operand = display.text().toDouble() result = 0.0 if clickedOperator == tr("Sqrt"): if operand < 0.0: abortOperation() return result = std.sqrt(operand) elif clickedOperator == tr("x\302\262"): result = std.pow(operand, 2.0) elif clickedOperator == tr("1/x"): if operand == 0.0: abortOperation() return result = 1.0 / operand display.setText(QString.number(result)) waitingForOperand = True
当单击任何一个一元运算符按钮时,将调用 unaryOperatorClicked()
接口。再次使用 QObject::sender() 获取被点击按钮的指针。从按钮的文本中提取运算符并存储在 clickedOperator
中。操作数从 display
获取。
然后我们执行操作。如果将平方根应用于负数或在 1/x 中为零,我们调用 abortOperation()
。如果一切顺利,我们将操作的结果显示在行编辑器中,并将 waitingForOperand
设置为 true
。这样确保如果用户输入新的数字,该数字将被视为新的操作数,而不是附加到当前值。
def additiveOperatorClicked(self): clickedButton = Button(sender()) if not clickedButton: return clickedOperator = clickedButton.text() operand = display.text().toDouble()
当用户单击 + 或 - 按钮时,将调用 additiveOperatorClicked()
接口。
在我们可以真正对所点击的运算符做出处理后,我们必须处理任何挂起的操作。我们首先从乘法运算符开始,因为它们的优先级高于加法运算符
if not pendingMultiplicativeOperator.isEmpty(): if not calculate(operand, pendingMultiplicativeOperator): abortOperation() return display.setText(QString.number(factorSoFar)) operand = factorSoFar factorSoFar = 0.0 pendingMultiplicativeOperator.clear()
如果之前单击了 × 或 ÷,而没有随后单击 =,则显示屏中的当前值是 × 或 ÷ 的右操作数,我们最终可以执行操作并更新显示屏。
if not pendingAdditiveOperator.isEmpty(): if not calculate(operand, pendingAdditiveOperator): abortOperation() return display.setText(QString.number(sumSoFar)) else: sumSoFar = operand
如果之前单击了 + 或 -,则 sumSoFar
是左操作数,而显示屏中的当前值是运算符的右操作数。如果没有挂起的加法运算符,则 sumSoFar
将简单地设置为显示屏中的文本。
pendingAdditiveOperator = clickedOperator waitingForOperand = True
最后,我们可以处理刚刚点击的运算符。由于我们还没有右操作数,我们将点击的运算符存储在变量pendingAdditiveOperator
中。当有了右操作数后,我们将使用sumSoFar
作为左操作数应用这个运算。
def multiplicativeOperatorClicked(self): clickedButton = Button(sender()) if not clickedButton: return clickedOperator = clickedButton.text() operand = display.text().toDouble() if not pendingMultiplicativeOperator.isEmpty(): if not calculate(operand, pendingMultiplicativeOperator): abortOperation() return display.setText(QString.number(factorSoFar)) else: factorSoFar = operand pendingMultiplicativeOperator = clickedOperator waitingForOperand = True
slot multiplicativeOperatorClicked()
与additiveOperatorClicked()
类似。我们在这里不需要担心待处理的加法运算符,因为乘法运算符比加法运算符有优先级。
def equalClicked(self): operand = display.text().toDouble() if not pendingMultiplicativeOperator.isEmpty(): if not calculate(operand, pendingMultiplicativeOperator): abortOperation() return operand = factorSoFar factorSoFar = 0.0 pendingMultiplicativeOperator.clear() if not pendingAdditiveOperator.isEmpty(): if not calculate(operand, pendingAdditiveOperator): abortOperation() return pendingAdditiveOperator.clear() else: sumSoFar = operand display.setText(QString.number(sumSoFar)) sumSoFar = 0.0 waitingForOperand = True
与在additiveOperatorClicked()
中的做法一样,我们首先处理任何待处理的乘法和加法运算符。然后显示sumSoFar
并将变量重置为0。将变量重置为0是必要的,以避免重复计数。
def pointClicked(self): if waitingForOperand: display.setText("0") if not display.text().contains('.'): display.setText(display.text() + tr(".")) waitingForOperand = False
slot pointClicked()
将小数点添加到display
中的内容。
def changeSignClicked(self): text = display.text() value = text.toDouble() if value > 0.0: text.prepend(tr("-")) elif value < 0.0: text.remove(0, 1) display.setText(text)
slot changeSignClicked()
变更display
中数值的符号。如果当前数值为正,我们添加负号;如果当前数值为负,我们从数值中删除第一个字符(负号)。
def backspaceClicked(self): if waitingForOperand: return text = display.text() text.chop(1) if text.isEmpty(): text = "0" waitingForOperand = True display.setText(text)
slot backspaceClicked()
移除显示中的最右边字符。如果得到空字符串,我们显示“0”并将变量waitingForOperand
设置为true
。
def clear(self): if waitingForOperand: return display.setText("0") waitingForOperand = True
slot clear()
将当前操作数重置为0。这相当于连续点击Backspace按钮直到整个操作数被删除。
def clearAll(self): sumSoFar = 0.0 factorSoFar = 0.0 pendingAdditiveOperator.clear() pendingMultiplicativeOperator.clear() display.setText("0") waitingForOperand = True
slot clearAll()
将计算器重置到初始状态。
def clearMemory(self): sumInMemory = 0.0 def readMemory(self): display.setText(QString.number(sumInMemory)) waitingForOperand = True def setMemory(self): equalClicked() sumInMemory = display.text().toDouble() def addToMemory(self): equalClicked() sumInMemory += display.text().toDouble()
slot clearMemory()
从存储中删除总和。函数readMemory()
显示总和作为操作数,函数setMemory()
用当前总和替换内存中的总和,函数addToMemory()
将当前值添加到内存中的值。在setMemory()
和addToMemory()
中,我们首先调用函数equalClicked()
来更新sumSoFar
和显示中的数值。
template<typename PointerToMemberFunction> Button Calculator.createButton(QString text, PointerToMemberFunction member) button = Button(text) button.clicked.connect(this, member) return button
私有函数createButton()
在构造函数中被调用来创建计算器按钮。
def abortOperation(self): clearAll() display.setText(tr("####"))
私有函数abortOperation()
在计算失败时被调用。它重置计算器状态并显示“####”。
def calculate(self, float rightOperand, QString pendingOperator): if pendingOperator == tr("+"): sumSoFar += rightOperand elif pendingOperator == tr("-"): sumSoFar -= rightOperand elif pendingOperator == tr("\303\227"): = rightOperand elif pendingOperator == tr("\303\267"): if rightOperand == 0.0: return False factorSoFar /= rightOperand return True
私有函数calculate()
执行二进制运算。右操作数由rightOperand
给出。对于加法运算符,左操作数是sumSoFar
;对于乘法运算符,左操作数是factorSoFar
。如果发生除以零,函数返回false
。