C++ Qt學習筆記 (1) 簡易計算器設計

最近開始學習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;
    }
}
*/

後續還會對簡易計算器進行改進。。。(未完待續)

       

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