C++ Qt開發:SqlRelationalTable關聯表元件

lyshark發表於2023-12-29

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

在上一篇文章中詳細介紹了SqlTableModle元件是如何使用的,本篇文章將介紹SqlRelationalTable關聯表元件,該該元件其實是SqlTableModle元件的擴充套件類,其提供了一個帶關係的資料模型,用於處理資料庫中的表與表之間的關係。透過這個類,你可以在一個表中使用外來鍵關聯到另一個表的資料上。例如將主表中的某個欄位與附加表中的特定欄位相關聯起來,QSqlRelation(關聯表名,關聯ID,名稱)就是用來實現多表之間快速關聯的。

1.1 ComboBox

首先我們來實現一個簡單的聯動效果,資料庫元件可以與ComboBox元件形成多級聯動效果,在日常開發中多級聯動效果應用非常廣泛,例如當我們選擇指定使用者時,讓其在另一個ComboBox元件中列舉出該使用者所維護的主機列表,又或者當使用者選擇省份時,自動列舉出該省份下面的城市列表等。

在進行聯動之前需要建立兩張表,表結構內容介紹如下:

  • User(id,name)表:儲存指定使用者的ID號與使用者名稱
  • UserAddressList(id,name,address)表:與User表中的使用者名稱相關聯,儲存該使用者所管理的主機列表資訊

透過資料庫元件實現的聯動非常簡單,初始化表結構得到了兩張表,當程式執行時預設在MainWindow建構函式處填充第一個ComboBox元件,也就是執行一次資料庫查詢,並將結果透過addItem()放入到第一個元件內。

QSqlDatabase db;

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

    InitMultipleSQL();

    db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("./database.db");
     if (!db.open())
     {
            std::cout << db.lastError().text().toStdString()<< std::endl;
            return;
     }

     QSqlQuery query;
     query.exec("select * from User;");
     QSqlRecord rec = query.record();

     while(query.next())
     {
         int index_name = rec.indexOf("name");
         QString data_name = query.value(index_name).toString();
         ui->comboBox_user->addItem(data_name);
     }
}

而當使用者選中了第一個ComboBox元件時,則讓其轉到槽函式on_comboBox_activated(const QString &arg1)上面,如下圖所示;

該槽函式需要一個傳入引數,此引數代表元件選中的文字內容,透過利用該文字內容在資料庫內執行二次查詢並將查詢結果填充之對應的第二個ComboBox元件內即可實現元件的聯動選擇效果,其槽函式程式碼如下所示;

void MainWindow::on_comboBox_user_activated(const QString &arg1)
{
    if(db.open())
    {
        QSqlQuery query;
        query.prepare("select * from UserAddressList where name = :x");
        query.bindValue(":x",arg1);
        query.exec();

        QSqlRecord rec = query.record();

        ui->comboBox_address->clear();
        while(query.next())
        {
            int index = rec.indexOf("address");
            QString data_ = query.value(index).toString();
            ui->comboBox_address->addItem(data_);
        }
    }
}

讀者可自行執行案例中的SqlComboBox案例,執行後可自行選擇不同的使用者名稱,則此時會輸出該使用者名稱所對應的地址表,如下圖所示;

1.2 TableView

接著,我們繼續以TableView元件為例,簡單介紹一下如何實現元件與資料的繫結,首先我們需要建立一個表並插入幾條測試記錄,執行如下程式碼實現建庫建表.

建立一張新表,表結構內容介紹如下:

  • LyShark(name,age)表:儲存指定使用者名稱與使用者年齡

在主建構函式中我們可以直接透過QSqlQueryModel來得到特定表中的記錄,並透過setHeaderData將表中的資料關聯到對應的資料模型內,最後透過setModel方法即可將對應的表資料關聯到前端顯示,其核心程式碼如下所示;

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

    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("./database.db");
     if (!db.open())
     {
            std::cout << db.lastError().text().toStdString()<< std::endl;
            return;
     }

     // 查詢資料表中記錄
     qryModel=new QSqlQueryModel(this);
     qryModel->setQuery("SELECT * FROM LyShark ORDER BY id");
     if (qryModel->lastError().isValid())
     {
         return;
     }

     // 設定TableView表頭資料
     qryModel->setHeaderData(0,Qt::Horizontal,"ID");
     qryModel->setHeaderData(1,Qt::Horizontal,"Name");
     qryModel->setHeaderData(2,Qt::Horizontal,"Age");

     // 將資料繫結到模型上
     theSelection=new QItemSelectionModel(qryModel);
     ui->tableView->setModel(qryModel);
     ui->tableView->setSelectionModel(theSelection);
     ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
}

執行程式碼後,程式會從資料庫內取出結果並輸出到tableView元件上,如下圖所示;

1.3 SqlRelationalTable

在最開始我們也說過,SqlRelationalTable 並不是Qt中標準的類或方法。它僅僅只是QSqlTableModel的一個子類,其支援在關聯式資料庫表之間建立關係,建立關聯時我們只需要使用setRelation方法即可。

setRelationQSqlRelationalTableModel 類中的一個方法,用於設定模型中某一列的關聯關係。這個方法的目的是告訴模型某一列的值在另一個表中有關聯,並提供相關的資訊,以便在檢視中顯示更有意義的資料而不是外來鍵的原始值。

以下是 setRelation 方法的簡單說明:

void QSqlRelationalTableModel::setRelation(int column, const QSqlRelation &relation);
  • column: 要設定關聯關係的列的索引。
  • relation: 包含關聯資訊的 QSqlRelation 物件。

QSqlRelation 的建構函式如下:

QSqlRelation::QSqlRelation(const QString &tableName, const QString &indexColumn, const QString &displayColumn);
  • tableName: 關聯的表的名稱。
  • indexColumn: 關聯表中與當前表關聯的列的名稱,通常是外來鍵列。
  • displayColumn: 關聯表中要顯示的列的名稱,通常是與外來鍵列相關的實際資料。

示例:

QSqlRelationalTableModel model;
model.setTable("orders");
model.setRelation(2, QSqlRelation("customers", "customer_id", "customer_name"));
model.select();

在這個例子中,第二列(索引為2的列)的資料將從名為 "customers" 的表中獲取,該表的外來鍵列為 "customer_id",並且在檢視中顯示的是該關聯表的 "customer_name" 列的值。使用 setRelation 方法可以使得在表格中更容易地顯示和編輯關聯資料,而不是直接顯示外來鍵的值。

在關聯表之前,我們需要設定初始化資料,此處我們提供兩個表結構,表Student用於儲存學生名字以及學生課程號,另一張Departments則用於儲存每個編號所對應的系名稱,執行程式碼完成建立。

// 初始化資料表
void MainWindow::InitSQL()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("./database.db");
    if (!db.open())
           return;

    // 執行SQL建立表
    db.exec("DROP TABLE Student");
    db.exec("CREATE TABLE Student ("
                   "id INTEGER PRIMARY KEY AUTOINCREMENT, "
                   "name VARCHAR(40) NOT NULL, "
                   "departID INTEGER NOT NULL)"
            );

    // 逐條插入資料
    db.exec("INSERT INTO Student(name,departID) VALUES('zhangsan',10)");
    db.exec("INSERT INTO Student(name,departID) VALUES('lisi',20)");
    db.exec("INSERT INTO Student(name,departID) VALUES('wangwu',30)");
    db.exec("INSERT INTO Student(name,departID) VALUES('wangmazi',40)");

    db.exec("DROP TABLE Departments");
    db.exec("CREATE TABLE Departments("
            "departID INTEGER NOT NULL,"
            "department VARCHAR(40) NOT NULL)"
            );

    db.exec("INSERT INTO Departments(departID,department) VALUES (10,'數學學院')");
    db.exec("INSERT INTO Departments(departID,department) VALUES (20,'物理學院')");
    db.exec("INSERT INTO Departments(departID,department) VALUES (30,'計算機學院')");
}

接著我們來看下在MainWindow建構函式中是如何進行初始化和表關聯的,以下是對程式碼的簡要說明:

開啟資料庫連線

建立一個 SQLite 資料庫連線,並指定了資料庫檔案的路徑。如果資料庫連線成功開啟,就繼續執行後面的程式碼。

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("./database.db");
if (!db.open())
    return;

設定主視窗的佈局和屬性

將主視窗的中央部件設定為一個 QTableView,同時對錶格的選擇行為和外觀進行了設定。

this->setCentralWidget(ui->tableView);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableView->setAlternatingRowColors(true);

開啟資料表並設定模型

建立一個 QSqlRelationalTableModel 並設定了一些表格的屬性,包括表名、編輯策略、排序等。

tabModel = new QSqlRelationalTableModel(this, db);
tabModel->setTable("Student");
tabModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
tabModel->setSort(0, Qt::AscendingOrder);

tabModel->setHeaderData(0, Qt::Horizontal, "學號");
tabModel->setHeaderData(1, Qt::Horizontal, "姓名");
tabModel->setHeaderData(2, Qt::Horizontal, "學院");

設定查詢關係資料表

設定關係型欄位,將 "學院" 列與 "Departments" 表中的 "departID" 列關聯起來,並在表格中顯示 "department" 列的資料。

tabModel->setRelation(2, QSqlRelation("Departments", "departID", "department"));

設定表格的選擇模型和代理

程式碼設定了表格的選擇模型,併為表格設定了一個關係型代理(QSqlRelationalDelegate),以便在表格中顯示關聯表的資料而不是外來鍵的值。

theSelection = new QItemSelectionModel(tabModel);
ui->tableView->setModel(tabModel);
ui->tableView->setSelectionModel(theSelection);
ui->tableView->setItemDelegate(new QSqlRelationalDelegate(ui->tableView));

選擇並顯示資料表

最後,透過呼叫 select 方法來選擇和顯示資料表的內容。

tabModel->select();

其實程式碼中最重要的部分就是setRelation,我們只要確保資料庫檔案正確,並且 Student 表和 Departments 表存在,並且在 Student 表中的 "學院" 列與 Departments 表中的 "departID" 列正確關聯即可,其他的就交給元件來處理,如下圖所示;

相關文章