C++ Qt開發:Charts與數據庫組件聯動

Qt 是一個跨平臺C++圖形界面開發庫,利用Qt可以快速開發跨平臺窗體應用程序,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹Charts組件與QSql數據庫組件的常用方法及靈活運用。

在之前的文章中詳細介紹了關於QCharts繪圖組件的使用方式,本章將繼續延續這個知識點,通過使用QSql數據庫模塊動態的讀取某一個時間節點上的數據,當用戶點擊查詢數據時則動態的輸出該事件節點的所有數據,並將數據繪製到圖形組件內,實現動態查詢圖形的功能。

首先我們需要生成一些測試數據,在文章課件中有一個InitDatabase案例,該案例中通過QSql組件動態創建一個Times表,該表中有三個字段分別記錄了主機IP地址、時間、以及數據,並動態的想表中插入一些隨機測試數據,讀者可運行這段程序並等待十分鐘以上,此時數據庫database.sqlite3中將會出現如下所示的數據集;

再來看下主窗體是如何設計的,左側使用一個ComboBox下拉選擇框,右側使用兩個可自由調節的Date/TimeEdit組件,最底部則是一個graphicsView繪圖組件,如下圖;

由於涉及到IP地址的選擇,所以在MainWindow主構造函數中我們需要對ComboBox組件進行初始化,在初始化時我們需要打開數據庫並將數據庫中的Times表,並查詢到address字段,這裏在查詢語句中使用DISTINCT語句,該語句是用於在SQL查詢中選擇唯一值的關鍵字,它能夠確保查詢的結果集中每個列的值都是唯一的。

SELECT DISTINCT address FROM Times;

在代碼中,上述查詢的目的是從 "Times" 表中選擇唯一的 "address" 列的值。如果 "Times" 表中有多個行具有相同的 "address" 值,DISTINCT 會確保在結果中只返回一個該值,以避免重複。

當具備了這條語句那麼查詢唯一值將變得非常容易,當查詢到對應值只有只需要通過comboBox->addItem即可將唯一的IP地址追加到組件中,如下代碼所示;

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 初始化繪圖
    InitLineChart();

    // 初始化時間組件
    QDateTime curDateTime = QDateTime::currentDateTime();

    // 設置當前時間
    ui->dateTimeEdit_Start->setDateTime(curDateTime);
    ui->dateTimeEdit_End->setDateTime(curDateTime);

    // 設置時間格式
    ui->dateTimeEdit_Start->setDisplayFormat("yyyy-MM-dd hh:mm:ss");
    ui->dateTimeEdit_End->setDisplayFormat("yyyy-MM-dd hh:mm:ss");

    // 初始化數據庫
    db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("database.sqlite3");

    if (!db.open())
    {
        std::cout << db.lastError().text().toStdString() << std::endl;
        return;
    }

    // 查詢數據庫中的IP地址信息
    QSqlQuery query;
    if (query.exec("SELECT DISTINCT address FROM Times;"))
    {
        QSet<QString> uniqueAddresses;

        while (query.next())
        {
            // Assuming 'address' is the name of the column
            QString data_name = query.value(0).toString();
            uniqueAddresses.insert(data_name);
        }

        // 清空現有的項
        ui->comboBox->clear();

        // 將唯一地址添加到 QComboBox 中
        foreach (const QString &uniqueAddress, uniqueAddresses)
        {
            ui->comboBox->addItem(uniqueAddress);
        }
    }
    else
    {
        std::cout << query.lastError().text().toStdString() << std::endl;
    }
}

接着來看下如何實現InitLineChart()繪圖函數,繪圖部分由於我們不需要直接繪製所以這裏可以先初始化折線圖表,等待後期添加數據繪製即可,這段代碼的實現如下所示;

首先,創建一個QChart對象,代表整個圖表,並將其添加到QGraphicsView中。隨後,通過隱藏圖例提高圖表的美觀度。接着,創建一個QLineSeries對象,表示折線圖中的數據序列,並將其添加到圖表中。爲確保正確顯示,創建了X軸和Y軸的座標軸對象,並設置了範圍、格式和刻度。最後,將X軸和Y軸與折線序列關聯,以便在圖表中顯示數據。這段代碼實現了一個簡單的折線圖的初始化,爲進一步添加和展示數據提供了基礎。

// 初始化Chart圖表
void MainWindow::InitLineChart()
{
    // 創建圖表的各個部件
    QChart *chart = new QChart();

    // 將Chart添加到ChartView
    ui->graphicsView_line->setChart(chart);
    ui->graphicsView_line->setRenderHint(QPainter::Antialiasing);

    // 隱藏圖例
    chart->legend()->hide();

    // 創建曲線序列
    QLineSeries *series0 = new QLineSeries();

    // 序列添加到圖表
    chart->addSeries(series0);

    // 創建座標軸
    QValueAxis *axisX = new QValueAxis;    // X軸
    axisX->setRange(1, 100);               // 設置座標軸範圍
    axisX->setLabelFormat("%d %");         // 設置X軸格式
    axisX->setMinorTickCount(5);           // 設置X軸刻度

    QValueAxis *axisY = new QValueAxis;    // Y軸
    axisY->setRange(0, 100);               // Y軸範圍
    axisY->setMinorTickCount(4);           // s設置Y軸刻度

    // 設置X於Y軸數據集
    chart->setAxisX(axisX, series0);       // 爲序列設置座標軸
    chart->setAxisY(axisY, series0);
}

當界面中的按鈕被點擊後,事件觸發時執行,其主要功能是從數據庫中查詢記錄並根據用戶在界面上選擇的設備地址、起始時間和結束時間條件,篩選符合條件的數據,並將其顯示在折線圖中。

首先,獲取折線圖對象和數據庫查詢結果的指針,然後清空折線序列準備接收新的數據。通過遍歷數據庫查詢結果,獲取每條記錄的字段值,同時獲取用戶輸入的查詢條件。計算時間差並限制查詢範圍在3600秒內,然後判斷記錄是否在指定的時間範圍內,並將符合條件的數據點添加到折線序列中。如果查詢範圍超出定義,輸出錯誤消息。

void MainWindow::on_pushButton_clicked()
{
    // 獲取指針
    QLineSeries *series0=(QLineSeries *)ui->graphicsView_line->chart()->series().at(0);

    // 清空圖例
    series0->clear();

    // 查詢數據
    QSqlQuery query("SELECT * FROM Times;",db);
    QSqlRecord rec = query.record();

    // 賦予數據
    qreal t=0,intv=1;

    // 循環所有記錄
    while(query.next())
    {
        // 判斷當前記錄是否有效
        if(query.isValid())
        {
            QString address_value = query.value(rec.indexOf("address")).toString();
            QString date_time = query.value(rec.indexOf("datetime")).toString();
            int this_value = query.value(rec.indexOf("value")).toInt();

            // 獲取組件字符串
            QString address_user = ui->comboBox->currentText();
            QString start_user_time = ui->dateTimeEdit_Start->text();
            QString end_user_time = ui->dateTimeEdit_End->text();

            // 將時間字符串轉爲秒,並計算差值 (秒爲單位)
            QDateTime start_timet = QDateTime::fromString(start_user_time, "yyyy-MM-dd hh:mm:ss");
            QDateTime end_timet = QDateTime::fromString(end_user_time, "yyyy-MM-dd hh:mm:ss");

            uint stime = start_timet.toTime_t();
            uint etime = end_timet.toTime_t();

            // 只允許查詢小於3600秒的記錄
            uint sub_time = etime - stime;
            if(sub_time <= 3600)
            {
                // 查詢指定區間內的數據
                if(date_time.toStdString() >= start_user_time.toStdString() &&
                        date_time.toStdString() <= end_user_time.toStdString() &&
                        address_value == address_user
                        )
                {
                    // std::cout << "區間內的數據: " << this_value << std::endl;
                    series0->append(t,this_value);
                    t+=intv;
                }
            }
            else
            {
                std::cout << "查詢範圍超出定義." << std::endl;
                return;
            }
        }
    }
}

這段代碼實現了通過用戶輸入條件查詢數據庫,並動態更新折線圖的功能,用於在界面上顯示符合條件的數據趨勢。

至此數據庫與繪圖組件的聯動效果就實現了,其實很容易理解,因爲是一個案例並沒有包含任何複雜的功能這也是爲了方便功能的展示,讀者可自行運行並查詢一個區間內的折線圖,如下所示;

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