「Qt Widget中文示例指南」如何創建一個計算器?(二)

Qt 是目前最先進、最完整的跨平臺C++開發工具。它不僅完全實現了一次編寫,所有平臺無差別運行,更提供了幾乎所有開發過程中需要用到的工具。如今,Qt已被運用於超過70個行業、數千家企業,支持數百萬設備及應用。

本文將展示如何使用信號和槽來實現計算器小部件的功能,以及如何使用QGridLayout在網格中放置子小部件。在上文中(點擊這裏回顧>>)爲大家介紹了實現計算器的Calculator類定義,本文將主要介紹Calculator類是如何實現的,持續關注我們哦~

Qt Widget中文示例指南圖集
計算器示例的屏幕截圖

這個例子由兩個類組成:

  • Calculator是計算器小部件,具有計算器的所有功能。
  • Button是用於每個計算器按鈕的小部件,它派生自QToolButton。

我們將從回顧計算器開始,然後再看看按鈕。

Calculator類實現

Calculator::Calculator(QWidget *parent)
: QWidget(parent), sumInMemory(0.0), sumSoFar(0.0)
, factorSoFar(0.0), waitingForOperand(true)
{

在構造函數中,初始化計算器的狀態。pendingAdditiveOperator和pendingMultiplicativeOperator變量不需要顯式初始化,因爲QString構造函數將它們初始化爲空字符串。也可以直接在header文件中初始化這些變量,這稱爲成員初始化,避免了長初始化列表。

display = new QLineEdit("0");
display->setReadOnly(true);
display->setAlignment(Qt::AlignRight);
display->setMaxLength(15);

QFont font = display->font();
font.setPointSize(font.pointSize() + 8);
display->setFont(font);

我們創建了表示計算器顯示的QLineEdit ,並設置了它的一些屬性,特別地我們將其設置爲只讀。

我們還將顯示器的字體放大了8個點。

for (int i = 0; i < NumDigitButtons; ++i)
digitButtons[i] = createButton(QString::number(i), &Calculator::digitClicked);

Button *pointButton = createButton(tr("."), &Calculator::pointClicked);
Button *changeSignButton = createButton(tr("\302\261"), &Calculator::changeSignClicked);

Button *backspaceButton = createButton(tr("Backspace"), &Calculator::backspaceClicked);
Button *clearButton = createButton(tr("Clear"), &Calculator::clear);
Button *clearAllButton = createButton(tr("Clear All"), &Calculator::clearAll);

Button *clearMemoryButton = createButton(tr("MC"), &Calculator::clearMemory);
Button *readMemoryButton = createButton(tr("MR"), &Calculator::readMemory);
Button *setMemoryButton = createButton(tr("MS"), &Calculator::setMemory);
Button *addToMemoryButton = createButton(tr("M+"), &Calculator::addToMemory);

Button *divisionButton = createButton(tr("\303\267"), &Calculator::multiplicativeOperatorClicked);
Button *timesButton = createButton(tr("\303\227"), &Calculator::multiplicativeOperatorClicked);
Button *minusButton = createButton(tr("-"), &Calculator::additiveOperatorClicked);
Button *plusButton = createButton(tr("+"), &Calculator::additiveOperatorClicked);

Button *squareRootButton = createButton(tr("Sqrt"), &Calculator::unaryOperatorClicked);
Button *powerButton = createButton(tr("x\302\262"), &Calculator::unaryOperatorClicked);
Button *reciprocalButton = createButton(tr("1/x"), &Calculator::unaryOperatorClicked);
Button *equalButton = createButton(tr("="), &Calculator::equalClicked);

對於每個按鈕,我們使用適當的文本標籤和連接到按鈕的插槽調用私有createButton()函數。

QGridLayout *mainLayout = new 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 (int i = 1; i < NumDigitButtons; ++i) {
int row = ((9 - i) / 3) + 2;
int 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處理,QLayout::setSizeConstraint()調用確保Calculator小部件始終顯示爲其最佳大小(其大小提示),從而防止用戶調整計算器的大小,大小提示由子小部件的大小和大小策略決定。

大多數子部件只佔用網格佈局中的一個單元格,對於這些我們只需要將一行和一列傳遞給QGridLayout::addWidget()。display、backspaceButton、clearButton和clearAllButton小部件佔用多於一列,對於這些我們還必須船體一個行空間和一個列空間。

void Calculator::digitClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
int digitValue = clickedButton->text().toInt();
if (display->text() == "0" && digitValue == 0.0)
return;

if (waitingForOperand) {
display->clear();
waitingForOperand = false;
}
display->setText(display->text() + QString::number(digitValue));
}

按下計算器的數字按鈕時將發出按鈕的clicked()信號,該信號將觸發digitClicked()插槽。

首先我們先使用QObject::sender()找出哪個按鈕發送了信號,這個函數以QObject指針的形式返回發送方。因爲我們知道發送方是一個Button對象,所以可以安全地強制轉換QObject。本來可以使用C風格的強制轉換或c++ static_cast<>(),但作爲一種防禦性編程技術,我們使用qobject_cast()。這樣做的好處是,如果對象的類型錯誤,則返回空指針。空指針導致的崩潰比不安全強制轉換導致的崩潰更容易診斷。有了按鈕後,我們使用QToolButton::text()提取操作符。

插槽需要特別考慮兩種情況,如果顯示包含“0”,而用戶點擊了“0”按鈕,那麼顯示“00”將是愚蠢的。如果計算器處於等待新操作數的狀態,新數字就是新操作數的第一位;在這種情況下,必須首先清除先前計算的任何結果。

最後,我們將新數字附加到顯示的值後面。

void Calculator::unaryOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();
double result = 0.0;

if (clickedOperator == tr("Sqrt")) {
if (operand < 0.0) {
abortOperation();
return;
}
result = std::sqrt(operand);
} else if (clickedOperator == tr("x\302\262")) {
result = std::pow(operand, 2.0);
} else if (clickedOperator == tr("1/x")) {
if (operand == 0.0) {
abortOperation();
return;
}
result = 1.0 / operand;
}
display->setText(QString::number(result));
waitingForOperand = true;
}

每當單擊一個一元操作符按鈕時,就調用unaryOperatorClicked()插槽,再次使用QObject::sender()獲取指向被單擊按鈕的指針。操作符從按鈕的文本中提取並存儲在clickoperator中,操作數從display中獲得。

然後我們執行這個操作,如果Sqrt應用於負數或1/x到零,調用abortOperation()。如果一切順利,我們將在行編輯中顯示操作的結果,並將waitingForOperand設置爲true。這確保瞭如果用戶鍵入一個新數字,該數字將被視爲一個新的操作數,而不是附加到當前值。

void Calculator::additiveOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
if (!clickedButton)
return;
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();

當用戶單擊+或-按鈕時調用additiveOperatorClicked()槽。

在對單擊的操作符進行實際操作之前,我們必須處理所有掛起的操作。從乘法運算符開始,因爲它們的優先級高於加法運算符:

if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
display->setText(QString::number(factorSoFar));
operand = factorSoFar;
factorSoFar = 0.0;
pendingMultiplicativeOperator.clear();
}

如果之前已經單擊了x或÷,而沒有隨後單擊=,則顯示中的當前值是x或÷操作符的右操作數,我們最終可以執行該操作並更新顯示。

if (!pendingAdditiveOperator.isEmpty()) {
if (!calculate(operand, pendingAdditiveOperator)) {
abortOperation();
return;
}
display->setText(QString::number(sumSoFar));
} else {
sumSoFar = operand;
}

如果前面已經單擊了+或-,則sumSoFar是左操作數,而顯示的當前值是操作符的右操作數。如果沒有掛起的加法運算符,則簡單地將sumSoFar設置爲顯示中的文本。

pendingAdditiveOperator = clickedOperator;
waitingForOperand = true;
}

最後,我們可以處理剛剛點擊的操作符。由於還沒有右操作數,所以將單擊的操作符存儲在pendingAdditiveOperator變量中。稍後當有一個右操作數,而左操作數爲sumSoFar時,將應用該操作。

void Calculator::multiplicativeOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
if (!clickedButton)
return;
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();

if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
display->setText(QString::number(factorSoFar));
} else {
factorSoFar = operand;
}

pendingMultiplicativeOperator = clickedOperator;
waitingForOperand = true;
}

multiplicativeOperatorClicked()插槽類似於additiveOperatorClicked(),不需要擔心掛起的加法運算符,因爲乘法運算符優先於加法運算符。

void Calculator::equalClicked()
{
double operand = display->text().toDouble();

if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
operand = factorSoFar;
factorSoFar = 0.0;
pendingMultiplicativeOperator.clear();
}
if (!pendingAdditiveOperator.isEmpty()) {
if (!calculate(operand, pendingAdditiveOperator)) {
abortOperation();
return;
}
pendingAdditiveOperator.clear();
} else {
sumSoFar = operand;
}

display->setText(QString::number(sumSoFar));
sumSoFar = 0.0;
waitingForOperand = true;
}

與additiveOperatorClicked()一樣,我們首先處理任何掛起的乘法和加法操作符,然後顯示sumSoFar並將變量重置爲零。必須將變量重置爲零,以避免對值進行兩次計數。

void Calculator::pointClicked()
{
if (waitingForOperand)
display->setText("0");
if (!display->text().contains('.'))
display->setText(display->text() + tr("."));
waitingForOperand = false;
}

pointClicked()槽向顯示的內容添加一個小數點。

void Calculator::changeSignClicked()
{
QString text = display->text();
double value = text.toDouble();

if (value > 0.0) {
text.prepend(tr("-"));
} else if (value < 0.0) {
text.remove(0, 1);
}
display->setText(text);
}

changeSignClicked()槽改變顯示值的符號,如果當前值爲正,則在前面加一個負號;如果當前值爲負,則從值中刪除第一個字符(負號)。

void Calculator::backspaceClicked()
{
if (waitingForOperand)
return;

QString text = display->text();
text.chop(1);
if (text.isEmpty()) {
text = "0";
waitingForOperand = true;
}
display->setText(text);
}

backspaceclick()將刪除顯示中最右邊的字符,如果得到一個空字符串,則顯示“0”並將waitingForOperand設置爲true。

void Calculator::clear()
{
if (waitingForOperand)
return;

display->setText("0");
waitingForOperand = true;
}

clear()槽將當前操作數重置爲零,這相當於按退格鍵多次擦除整個操作數。

void Calculator::clearAll()
{
sumSoFar = 0.0;
factorSoFar = 0.0;
pendingAdditiveOperator.clear();
pendingMultiplicativeOperator.clear();
display->setText("0");
waitingForOperand = true;
}

clearAll()槽將計算器重置爲初始狀態。

void Calculator::clearMemory()
{
sumInMemory = 0.0;
}

void Calculator::readMemory()
{
display->setText(QString::number(sumInMemory));
waitingForOperand = true;
}

void Calculator::setMemory()
{
equalClicked();
sumInMemory = display->text().toDouble();
}

void Calculator::addToMemory()
{
equalClicked();
sumInMemory += display->text().toDouble();
}

clearMemory()插槽擦除保存在內存中的總和,readMemory()將總和顯示爲操作數,setMemory()將內存中的總和替換爲當前的總和,addtommemory()將當前值添加到內存中的值。對於setMemory()和addtommemory(),我們首先調用equalClicked()來更新sumSoFar和顯示中的值。

template<typename PointerToMemberFunction>
Button *Calculator::createButton(const QString &text, const PointerToMemberFunction &member)
{
Button *button = new Button(text);
connect(button, &Button::clicked, this, member);
return button;
}

私有的createButton()函數從構造函數中被調用來創建計算器按鈕。

void Calculator::abortOperation()
{
clearAll();
display->setText(tr("####"));
}

私有的abortOperation()函數在計算失敗時被調用,重置計算器狀態,顯示“####”。

bool Calculator::calculate(double rightOperand, const QString &pendingOperator)
{
if (pendingOperator == tr("+")) {
sumSoFar += rightOperand;
} else if (pendingOperator == tr("-")) {
sumSoFar -= rightOperand;
} else if (pendingOperator == tr("\303\227")) {
factorSoFar *= rightOperand;
} else if (pendingOperator == tr("\303\267")) {
if (rightOperand == 0.0)
return false;
factorSoFar /= rightOperand;
}
return true;
}

私有的calculate()函數執行一個二進制操作,右操作數由rightOperand給出。對於加法操作符,左操作數爲sumSoFar;對於乘法運算符,左操作數是factorSoFar。如果發生除零,函數返回false。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章