最近開始學習c++ qt, 按照教材上的例程設計一個簡易的桌面計算器:
Qt是一個基於C++語言的跨平臺應用程序和UI開發框架,主要包含一個類庫,和跨平臺開發及國際化的工具,最初由挪威的Trolltech公司開發,後來被諾基亞收購,現在屬於Digia公司。qt最大的特點是其跨平臺的屬性,同一份代碼可以在不同平臺上編譯運行。qt中使用信號/槽機制來代替傳統UI設計中的回調函數。
信號和槽是Qt中對象通信的一種方式,它代替了傳統的GUI編程中利用回調函數傳遞消息的機制。回調函數的原理是:將處理函數傳遞到按鈕控件中去,在用戶點擊按鈕對象之後,按鈕控件對象調用傳進去的處理函數,這種將一個函數傳進去,在將來某個時刻調用的方式稱爲回調。信號與槽機制:同樣需要編寫處理函數(稱爲槽函數),但是不需要將處理函數傳遞給控件,只需要將按鈕的單擊信號(如clicked(),這是Qt種已經比那些好的函數)和槽函數連接起來即可。使用更加方便,但是也帶來了效率上的損耗。
QWidget是所有圖像用戶將界面的基類,QWidget包含所有界面共有的特徵:1. 接受鼠標點擊事件 2. 接受鍵盤事件 3. 區域渲染
同時,QWidget也可以包含其他的界面控件。
(由於目前沒有找到好的Qt教材,所以只對Qt有一個簡單的瞭解,後續會進行更加全面的學習)
1. 計算器界面設計:
這裏界面設計採用了Qt designer, 在Qt Creator種新建項目,進入.ui文件。進行界面設計,如下圖所示:
對界面中的控件進行命名,以及屬性設置:
將計算器的按鈕與相應的槽函數進行連接:
這裏使用Qt中的connect方法,將按鈕和相應的槽函數連接在一起:
connect(btn, SIGNAL(clicked()), this, SLOT(onDigitClicked()));
connect函數的形式如上所示:
在計算器設計中,採用的方式爲,將數字按鈕0~9連接到同一個槽函數中,將加減乘除連接到同一個槽函數中,將其它的按鈕連接到對應的槽函數中,首先定義在widget.h文件中定義相應的槽函數:
private slots:
void onDigitClicked(); // 數字鍵按下對應的槽函數
void onOperatorClicked(); // 運算符鍵按下對應的槽函數
void onEqualBtnClicked(); // 按下運算鍵對應的槽函數
void onDotBtnClicked(); // 按下小數點對應的槽函數
void onClearBtnClicked(); // 清除按鈕對應的槽函數
void onClearAllBtnClicked(); // 清除所有按鈕對應的槽函數
void onSignBtnClicked(); // 正負號按鍵所對應的槽函數
定義函數connectSlots(),將連接槽函數的過程進行封裝:
private:
void connectSolts(); // 將數字鍵以及符號鍵連接到槽函數
實現connectSlots()函數:
void Widget::connectSolts()
{
// 鏈接槽函數
// 將十個數字鍵連接到槽函數onDigitClicked();
QPushButton *digit_btns[10] =
{
ui->btn_0,
ui->btn_1,
ui->btn_2,
ui->btn_3,
ui->btn_4,
ui->btn_5,
ui->btn_6,
ui->btn_7,
ui->btn_8,
ui->btn_9
};
for(auto btn : digit_btns)
//for(int i=0; i<10; i++)
{
// 將按鍵連接到槽函數
connect(btn, SIGNAL(clicked()), this, SLOT(onDigitClicked()));
}
// 對應的按鈕爲指針
QPushButton *operatorBtn[4] =
{
ui->btn_add,
ui->btn_subtract,
ui->btn_multiply,
ui->btn_divide
};
for(auto btn : operatorBtn)
// for(int i=0; i<4; i++)
{
connect(btn, SIGNAL(clicked()), this, SLOT(onOperatorClicked()));
}
connect(ui->btn_equal, SIGNAL(clicked()), this, SLOT(onEqualBtnClicked())); // 等號鍵按下
connect(ui->btn_dot, SIGNAL(clicked()), this, SLOT(onDotBtnClicked())); // 小數點鍵安按下
connect(ui->btn_clear, SIGNAL(clicked()), this, SLOT(onClearBtnClicked())); // 清除鍵按下
connect(ui->btn_clear_all, SIGNAL(clicked()), this, SLOT(onClearAllBtnClicked())); // 清除所有 按鈕
connect(ui->btn_neg_pos, SIGNAL(clicked()),this, SLOT(onSignBtnClicked())); // 符號按鍵
}
上面通過循環的方式,將相應的按鈕與槽函數連接。相應槽函數的實現如下:
1. 數字鍵按下對應的槽函數:
void Widget::onDigitClicked()
{
// std::cout << "數字鍵按下" << std::endl;
qDebug() << "digit key pressed" << endl;
QPushButton *digitBtn = static_cast<QPushButton*>(sender()); // sender()表示信號的發送者
QString value = digitBtn->text(); // 獲取按鈕的text屬性
// 判斷按鍵
if(ui->result->text() == "0" && value == "0") // 按鍵爲0
return;
if(waitForOperator) // 等在操作數 狀態爲真
{
ui->result->setText(value);
waitForOperator = false; // 此時不再需要等待操作數
}
else
{
ui->result->setText(ui->result->text() + value);
}
}
2. 運算符鍵按下對應的槽函數實現:
void Widget::onOperatorClicked()
{
// qDebug() << "operator key pressed" << endl;
// 判斷按下的運算符鍵
QPushButton *clickedBtn = static_cast<QPushButton*>(sender()); // 將信號源轉化爲QpusuBytton指針
QString value = clickedBtn->text(); // 獲取運算符
// 此時的狀態是按下了運算符,所以需要獲取第一個運算數
double operand = ui->result->text().toDouble(); // 獲取運算數
if(pendingOperator.isEmpty()) // 運算符爲空
{
result = operand;
}
else
{
if(!calculate(operand, value))
{
abortOperation();
return;
}
ui->result->setText(QString::number(result));
}
// 更新運算符
pendingOperator = value;
waitForOperator = true; // 等待新的輸入數字
}
函數calculate(operand, value)用於獲取運算符和操作數之後進行計算,具體的實現如下所示:
bool Widget::calculate(double operand, QString pendingOperator)
{
if(pendingOperator == "+")
{
result += operand; // 加法運算
}
else if(pendingOperator == "-")
{
result -= operand;
}
else if(pendingOperator == "*")
{
result *= operand;
}
else
{
if(operand == 0)
{
return false;
}
result /= operand;
}
return true;
}
Qt中的鍵盤事件:
Qt中使用QKeyEvent來描述鍵盤事件,當鍵盤按下胡哦這鬆開的時候,鍵盤事件便會傳遞給擁有鍵盤輸入焦點的控件,key()函數可以用來獲取具體的按鍵值。在簡易計算器設計中中加入鍵盤事件,使得在鍵盤上輸入數據和在計算器上輸入具有相同的效果,給按鈕添加快捷鍵的方式主要有兩種方法:
1. 重寫鍵盤事件函數:
首先,在widget.h文件中,鍵盤事件函數必須定義爲protected類型:
#include <QKeyEvent>
protected:
void keyPressEvent(QKeyEvent* event);
鍵盤事件函數的定義:
(未解決的bug, emit ui->btn_0->clicked報錯問題)
void Widget::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_0:
emit ui->btn_0->clicked();
break;
case Qt::Key_1:
emit ui->btn_1->clicked();
break;
case Qt::Key_2:
emit ui->btn_2->clicked();
break;
case Qt::Key_3:
emit ui->btn_3->clicked();
break;
case Qt::Key_4:
emit ui->btn_4->clicked();
break;
case Qt::Key_5:
emit ui->btn_5->clicked();
break;
case Qt::Key_6:
emit ui->btn_6->clicked();
break;
case Qt::Key_7:
emit ui->btn_7->clicked();
break;
case Qt::Key_8:
emit ui->btn_8->clicked();
break;
case Qt::Key_9:
emit ui->btn_9->clicked();
break;
case Qt::Key_Plus:
emit ui->btn_add->clicked();
break;
case Qt::Key_Minus:
emit ui->btn_subtract->clicked();
break;
case Qt::Key_Asterisk:
emit ui->btn_multiply->clicked();
break;
case Qt::Key_Slash:
emit ui->btn_divide->clicked();
break;
case Qt::Key_Enter:
case Qt::Key_Equal:
emit ui->btn_equal->clicked();
break;
case Qt::Key_Period:
emit ui->btn_dot->clicked();
break;
case Qt::Key_M:
emit ui->btn_neg_pos->clicked();
break;
case Qt::Key_Backspace:
emit ui->btn_clear->clicked();
break;
default:
break;
}
}
2. 通過按鈕控件的setShortCut()函數
在widget.h中定義快捷鍵設置的函數setShortCut()
public:
void setShortCut(); // 設計快捷鍵
void Widget::setShortCut()
{
Qt::Key key[18] = {
Qt::Key_0, Qt::Key_1, Qt::Key_2,
Qt::Key_3, Qt::Key_4, Qt::Key_5,
Qt::Key_6, Qt::Key_7, Qt::Key_8,
Qt::Key_9,
Qt::Key_Plus, Qt::Key_Minus, Qt::Key_Asterisk, Qt::Key_Slash,
Qt::Key_Enter, Qt::Key_Period, Qt::Key_Backspace, Qt::Key_M
};
QPushButton *btn[18] = {
ui->btn_0, ui->btn_1, ui->btn_2,
ui->btn_3, ui->btn_4, ui->btn_5,
ui->btn_6, ui->btn_7, ui->btn_8,
ui->btn_9,
ui->btn_add, ui->btn_subtract, ui->btn_multiply, ui->btn_divide,
ui->btn_equal, ui->btn_dot, ui->btn_clear, ui->btn_neg_pos
};
for(int i=0; i<18; i++)
{
btn[i]->setShortcut(QKeySequence(key[i]));
}
ui->btn_clear_all->setShortcut(QKeySequence("Ctrl+Backspace"));
}
Qt中的鼠標事件:
Qt中用一個對象表示一個事件(event), 繼承自QEvent。QMouseEvent事件用來表示鼠標事件,它可以檢測到當前哪個鍵被按下了,或者鼠標的當前位置。QWheelEvent用來表示鼠標滾輪事件,用來獲取滾輪的滑動方向和距離,共有5個鼠標事件處理函數,在.h文件中這些事件處理函數必須爲protected類型,同時需要包含頭文件<QMouseEvent><QwheelEvent>
mousePressEvent()
mouseReleaseEvent()
mouseDoubleClickedEvent()
mouseMoveEvent()
wheelEvent()
---------------------------------------------------------------------------------------------------------------
簡易計算器完整代碼:
widget.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QString>
#include <QKeyEvent>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
private:
bool calculate(double operand, QString pendingOperator); // 做算數運算
void abortOperation(); // 結束運算
void connectSolts(); // 將數字鍵以及符號鍵連接到槽函數
QString pendingOperator; // 存儲運算符
double result; // 存儲運算結果
bool waitForOperator; // 標誌位,是否等待操作數
private slots:
void onDigitClicked(); // 數字鍵按下對應的槽函數
void onOperatorClicked(); // 運算符鍵按下對應的槽函數
void onEqualBtnClicked(); // 按下運算鍵對應的槽函數
void onDotBtnClicked(); // 按下小數點對應的槽函數
void onClearBtnClicked(); // 清除按鈕對應的槽函數
void onClearAllBtnClicked(); // 清除所有按鈕對應的槽函數
void onSignBtnClicked(); // 正負號按鍵所對應的槽函數
public:
void setShortCut(); // 設計快捷鍵
//protected:
// void keyPressEvent(QKeyEvent* event);
};
#endif // WIDGET_H
widget.cpp文件:
#include "widget.h"
#include "ui_widget.h"
#include <QString>
#include <QLayout>
#include <QMessageBox>
#include <iostream>
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
waitForOperator = true; // 初始狀態等待操作數
result = 0.0; // 初始狀態結果爲0
ui->result->setText("0");
connectSolts(); // 連接所有的按鍵與對應的槽函數
setShortCut();
}
Widget::~Widget()
{
delete ui;
}
bool Widget::calculate(double operand, QString pendingOperator)
{
if(pendingOperator == "+")
{
result += operand; // 加法運算
}
else if(pendingOperator == "-")
{
result -= operand;
}
else if(pendingOperator == "*")
{
result *= operand;
}
else
{
if(operand == 0)
{
return false;
}
result /= operand;
}
return true;
}
void Widget::abortOperation()
{
// 終止運算,清除數據,輸出錯誤信息
result = 0;
pendingOperator.clear(); // 清除運算符
ui->result->setText("0"); // 清除顯示結果
waitForOperator = true; // 等待一個操作數輸入
QMessageBox::warning(this, "運算錯誤", "除數不能爲零");
}
void Widget::connectSolts()
{
// 鏈接槽函數
// 將十個數字鍵連接到槽函數onDigitClicked();
QPushButton *digit_btns[10] =
{
ui->btn_0,
ui->btn_1,
ui->btn_2,
ui->btn_3,
ui->btn_4,
ui->btn_5,
ui->btn_6,
ui->btn_7,
ui->btn_8,
ui->btn_9
};
for(auto btn : digit_btns)
//for(int i=0; i<10; i++)
{
// 將按鍵連接到槽函數
connect(btn, SIGNAL(clicked()), this, SLOT(onDigitClicked()));
}
QPushButton *operatorBtn[4] =
{
ui->btn_add,
ui->btn_subtract,
ui->btn_multiply,
ui->btn_divide
};
for(auto btn : operatorBtn)
// for(int i=0; i<4; i++)
{
connect(btn, SIGNAL(clicked()), this, SLOT(onOperatorClicked()));
}
connect(ui->btn_equal, SIGNAL(clicked()), this, SLOT(onEqualBtnClicked())); // 等號鍵按下
connect(ui->btn_dot, SIGNAL(clicked()), this, SLOT(onDotBtnClicked())); // 小數點鍵安按下
connect(ui->btn_clear, SIGNAL(clicked()), this, SLOT(onClearBtnClicked())); // 清除鍵按下
connect(ui->btn_clear_all, SIGNAL(clicked()), this, SLOT(onClearAllBtnClicked())); // 清除所有 按鈕
connect(ui->btn_neg_pos, SIGNAL(clicked()),this, SLOT(onSignBtnClicked())); // 符號按鍵
}
void Widget::onDigitClicked()
{
// std::cout << "數字鍵按下" << std::endl;
qDebug() << "digit key pressed" << endl;
QPushButton *digitBtn = static_cast<QPushButton*>(sender()); // sender()表示信號的發送者
QString value = digitBtn->text(); // 獲取按鈕的text屬性
// 判斷按鍵
if(ui->result->text() == "0" && value == "0") // 按鍵爲0
return;
if(waitForOperator) // 等在操作數 狀態爲真
{
ui->result->setText(value);
waitForOperator = false; // 此時不再需要等待操作數
}
else
{
ui->result->setText(ui->result->text() + value);
}
}
void Widget::onOperatorClicked()
{
// qDebug() << "operator key pressed" << endl;
// 判斷按下的運算符鍵
QPushButton *clickedBtn = static_cast<QPushButton*>(sender()); // 將信號源轉化爲QpusuBytton指針
QString value = clickedBtn->text(); // 獲取運算符
// 此時的狀態是按下了運算符,所以需要獲取第一個運算數
double operand = ui->result->text().toDouble(); // 獲取運算數
if(pendingOperator.isEmpty()) // 運算符爲空
{
result = operand;
}
else
{
if(!calculate(operand, value))
{
abortOperation();
return;
}
ui->result->setText(QString::number(result));
}
// 更新運算符
pendingOperator = value;
waitForOperator = true; // 等待新的輸入數字
}
void Widget::onEqualBtnClicked()
{
// 等號鍵按下,需要計算最終的結果
double operand = ui->result->text().toDouble(); // 獲取運算數
if(pendingOperator.isEmpty()) // 沒有輸入加減乘除運算符,直接按了等號
{
return;
}
if(!calculate(operand, pendingOperator))
{
abortOperation();
return;
}
ui->result->setText(QString::number(result));
waitForOperator = true;
result = 0.0;
pendingOperator.clear();
}
void Widget::onDotBtnClicked()
{
if(waitForOperator)
{
ui->result->setText("0");
}
if(ui->result->text().contains("."))
{
// no operation
}
else
{
ui->result->setText(ui->result->text() + ".");
}
waitForOperator = false; // 當前數字的輸入還未結束
}
void Widget::onClearBtnClicked()
{
ui->result->setText("0"); // 輸入的數字清零
waitForOperator = true; // 重新輸入
}
void Widget::onClearAllBtnClicked()
{
ui->result->setText("0");
waitForOperator = true;
result = 0.0;
pendingOperator.clear();
}
void Widget::onSignBtnClicked()
{
QString text = ui->result->text();
double value = text.toDouble();
if(value > 0)
{
text.prepend("-");
}
else
{
text.remove(0, 1);
}
ui->result->setText(text);
}
void Widget::setShortCut()
{
Qt::Key key[18] = {
Qt::Key_0, Qt::Key_1, Qt::Key_2,
Qt::Key_3, Qt::Key_4, Qt::Key_5,
Qt::Key_6, Qt::Key_7, Qt::Key_8,
Qt::Key_9,
Qt::Key_Plus, Qt::Key_Minus, Qt::Key_Asterisk, Qt::Key_Slash,
Qt::Key_Enter, Qt::Key_Period, Qt::Key_Backspace, Qt::Key_M
};
QPushButton *btn[18] = {
ui->btn_0, ui->btn_1, ui->btn_2,
ui->btn_3, ui->btn_4, ui->btn_5,
ui->btn_6, ui->btn_7, ui->btn_8,
ui->btn_9,
ui->btn_add, ui->btn_subtract, ui->btn_multiply, ui->btn_divide,
ui->btn_equal, ui->btn_dot, ui->btn_clear, ui->btn_neg_pos
};
for(int i=0; i<18; i++)
{
btn[i]->setShortcut(QKeySequence(key[i]));
}
ui->btn_clear_all->setShortcut(QKeySequence("Ctrl+Backspace"));
}
/*
void Widget::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_0:
emit ui->btn_0->clicked();
break;
case Qt::Key_1:
emit ui->btn_1->clicked();
break;
case Qt::Key_2:
emit ui->btn_2->clicked();
break;
case Qt::Key_3:
emit ui->btn_3->clicked();
break;
case Qt::Key_4:
emit ui->btn_4->clicked();
break;
case Qt::Key_5:
emit ui->btn_5->clicked();
break;
case Qt::Key_6:
emit ui->btn_6->clicked();
break;
case Qt::Key_7:
emit ui->btn_7->clicked();
break;
case Qt::Key_8:
emit ui->btn_8->clicked();
break;
case Qt::Key_9:
emit ui->btn_9->clicked();
break;
case Qt::Key_Plus:
emit ui->btn_add->clicked();
break;
case Qt::Key_Minus:
emit ui->btn_subtract->clicked();
break;
case Qt::Key_Asterisk:
emit ui->btn_multiply->clicked();
break;
case Qt::Key_Slash:
emit ui->btn_divide->clicked();
break;
case Qt::Key_Enter:
case Qt::Key_Equal:
emit ui->btn_equal->clicked();
break;
case Qt::Key_Period:
emit ui->btn_dot->clicked();
break;
case Qt::Key_M:
emit ui->btn_neg_pos->clicked();
break;
case Qt::Key_Backspace:
emit ui->btn_clear->clicked();
break;
default:
break;
}
}
*/
後續還會對簡易計算器進行改進。。。(未完待續)