十六、Qt數據庫 之 QSqlQueryModel & QSqlTableModel

 

聲明:本文原創於yafeilinux的百度博客,http://hi.baidu.com/yafeilinux 轉載請註明出處。


(1)QSqlQueryModel

在上一篇的最後我們說到,Qt中使用了自己的機制來避免使用SQL語句,它爲我們提供了更簡單的數據庫操作和數據顯示模型。它們分別是隻讀的QSqlQueryModel,操作單表的QSqlTableModel和以及可以支持外鍵的QSqlRelationalTableModel。這次我們先講解QSqlQueryModel。

QSqlQueryModel類爲SQL的結果集提供了一個只讀的數據模型,下面我們先利用這個類進行一個最簡單的操作。

我們新建Qt4 Gui Application工程,我這裏工程名爲queryModel ,然後選中QtSql模塊,Base class選QWidget。工程建好後,添加C++ Header File ,命名爲database.h,更改其內容如下:

#ifndef DATABASE_H
#define DATABASE_H

#include <QSqlDatabase>
#include <QSqlQuery>

static bool createConnection()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("database.db");
    if(!db.open()) return false;
    QSqlQuery query;
    query.exec("create table student (id int primary key, name vchar)");
    query.exec("insert into student values (0,'yafei0')");
    query.exec("insert into student values (1,'yafei1')");
    query.exec("insert into student values (2,'yafei2')");
    return true;
}

#endif // DATABASE_H

這裏我們使用了db.setDatabaseName("database.db"); ,我們沒有再使用以前的內存數據庫,而是使用了真實的文件,這樣後面對數據庫進行的操作就能保存下來了。

然後進入main.cpp,將其內容更改如下:

#include <QtGui/QApplication>
#include "widget.h"
#include "database.h"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    if(!createConnection())
        return 1;

    Widget w;
    w.show();
    return a.exec();
}

下面我們在widget.ui中添加一個顯示爲“查詢”的Push Button,並進入其單擊事件槽函數,更改如下:

void Widget::on_pushButton_clicked()
{
    QSqlQueryModel *model = new QSqlQueryModel;
    model->setQuery("select * from student");
    model->setHeaderData(0, Qt::Horizontal, tr("id"));
    model->setHeaderData(1, Qt::Horizontal, tr("name"));
    QTableView *view = new QTableView;
    view->setModel(model);
    view->show();

}

我們新建了QSqlQueryModel類對象model,並用setQuery()函數執行了SQL語句“("select * from student");”用來查詢整個student表的內容,可以看到,該類並沒有完全避免SQL語句。然後我們設置了表中屬性顯示時的名字。最後我們建立了一個視圖view,並將這個model模型關聯到視圖中,這樣數據庫中的數據就能在窗口上的表中顯示出來了。

我們在widget.cpp中添加頭文件:

#include <QSqlQueryModel>
#include <QTableView>

我們運行程序,並按下“查詢”按鍵,效果如下:

我們在工程文件夾下查看數據庫文件:



下面我們利用這個模型來操作數據庫。

1.我們在void Widget::on_pushButton_clicked()函數中添加如下代碼:

int column = model->columnCount(); //獲得列數
int row = model->rowCount();    // 獲得行數
QSqlRecord record = model->record(1); //獲得一條記錄
QModelIndex index = model->index(1,1);   //獲得一條記錄的一個屬性的值
qDebug() << "column num is:" << column << endl
            << "row num is:" << row << endl
            <<"the second record is:" << record << endl
            << "the data of index(1,1) is:"<< index.data();

我們在widget.cpp中添加頭文件:

#include <QSqlRecord>
#include <QModelIndex>
#include <QDebug>

此時運行程序,效果如下:


 

 

2.當然我們在這裏也可以使用前面介紹過的query執行SQL語句。

例如我們在void Widget::on_pushButton_clicked()函數中添加如下代碼:

QSqlQuery query = model->query();
query.exec("select name from student where id = 2 ");
query.next();
qDebug() << query.value(0).toString();

這樣就可以輸出表中的值了,你可以運行程序測試一下。

3.當我們將函數改爲如下。

void Widget::on_pushButton_clicked()
{
    QSqlQueryModel *model = new QSqlQueryModel;
    model->setQuery("select * from student");
    model->setHeaderData(0, Qt::Horizontal, tr("id"));
    model->setHeaderData(1, Qt::Horizontal, tr("name"));
    QTableView *view = new QTableView;
    view->setModel(model);
    view->show();

    QSqlQuery query = model->query();
    query.exec("insert into student values (10,'yafei10')");
 
    //插入一條記錄
}

這時我們運行程序,效果如下:

我們發現表格中並沒有增加記錄,怎麼回事呢?

我們關閉程序,再次運行,效果如下:

發現這次新的記錄已經添加了。在上面我們執行了添加記錄的SQL語句,但是在添加記錄之前,就已經進行顯示了,所以我們的更新沒能動態的顯示出來。爲了能讓其動態地顯示我們的更新,我們可以將函數更改如下:

void Widget::on_pushButton_clicked()
{
    QSqlQueryModel *model = new QSqlQueryModel;
    model->setQuery("select * from student");
    model->setHeaderData(0, Qt::Horizontal, tr("id"));
    model->setHeaderData(1, Qt::Horizontal, tr("name"));
    QTableView *view = new QTableView;
    view->setModel(model);
    view->show();

    QSqlQuery query = model->query();
    query.exec("insert into student values (20,'yafei20')");
    //插入一條記錄
   model->setQuery("select * from student"); //再次查詢整張表
    view->show(); //再次進行顯示
}

 

 

這時運行程序,效果如下:

可以看到,這時已經將新添的記錄顯示出來了。

剛開始我們就講到,這個模型默認是隻讀的,所以我們在窗口上並不能對錶格中的內容進行修改。但是我們可以創建自己的模型,然後按照我們自己的意願來顯示數據和修改數據。要想使其可讀寫,需要自己的類繼承自QSqlQueryModel,並且重寫setData() 和 flags() 兩個函數。如果我們要改變數據的顯示,就要重寫data() 函數。

下面的例子中我們讓student表的id屬性列顯示紅色,name屬性列可編輯。

1.我們在工程中添加C++ Class,然後Class name設爲MySqlQueryModel,Base Class設爲QSqlQueryModel,如下:


 

2.我們將mysqlquerymodel.h中的內容更改如下:

class MySqlQueryModel : public QSqlQueryModel
{
public:
    MySqlQueryModel();
    //下面三個函數都是虛函數,我們對其進行重載
    Qt::ItemFlags flags(const QModelIndex &index) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role);

    QVariant data(const QModelIndex &item, int role=Qt::DisplayRole) const;
    //
private:
    bool setName(int studentId, const QString &name);
    void refresh();
};

然後將mysqlquerymodel.cpp文件更改如下:

#include "mysqlquerymodel.h"
#include <QSqlQuery>
#include <QColor>

MySqlQueryModel::MySqlQueryModel()
{
}

Qt::ItemFlags MySqlQueryModel::flags(
        const QModelIndex &index) const //返回表格是否可更改的標誌
{
    Qt::ItemFlags flags = QSqlQueryModel::flags(index);
    if (index.column() == 1) //第二個屬性可更改
        flags |= Qt::ItemIsEditable;
    return flags;
}

bool MySqlQueryModel::setData(const QModelIndex &index, const QVariant &value, int /* role */)
        //添加數據
{
    if (index.column() < 1 || index.column() > 2)
        return false;

    QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0);
    int id = data(primaryKeyIndex).toInt(); //獲取id號

    clear();

    bool ok;
    if (index.column() == 1) //第二個屬性可更改
        ok = setName(id, value.toString());

    refresh();
    return ok;
}

void MySqlQueryModel::refresh() //更新顯示
{
    setQuery("select * from student");
    setHeaderData(0, Qt::Horizontal, QObject::tr("id"));
    setHeaderData(1, Qt::Horizontal, QObject::tr("name"));
}

bool MySqlQueryModel::setName(int studentId, const QString &name) //添加name屬性的值
{
    QSqlQuery query;
    query.prepare("update student set name = ? where id = ?");
    query.addBindValue(name);
    query.addBindValue(studentId);
    return query.exec();
}

QVariant MySqlQueryModel::data(const QModelIndex &index, int role) const
        //更改數據顯示樣式
{
    QVariant value = QSqlQueryModel::data(index, role);

    if (role == Qt::TextColorRole && index.column() == 0)
        return qVariantFromValue(QColor(Qt::red)); //第一個屬性的字體顏色爲紅色
    return value;
}

在widget.cpp文件中添加頭文件:#include "mysqlquerymodel.h"

然後更改函數如下:

void Widget::on_pushButton_clicked()
{
    QSqlQueryModel *model = new QSqlQueryModel;
    model->setQuery("select * from student");
    model->setHeaderData(0, Qt::Horizontal, tr("id"));
    model->setHeaderData(1, Qt::Horizontal, tr("name"));
    QTableView *view = new QTableView;
    view->setModel(model);
    view->show();

    MySqlQueryModel *myModel = new MySqlQueryModel; //創建自己模型的對象
    myModel->setQuery("select * from student");
    myModel->setHeaderData(0, Qt::Horizontal, tr("id"));
    myModel->setHeaderData(1, Qt::Horizontal, tr("name"));
    QTableView *view1 = new QTableView;
    view1->setWindowTitle("mySqlQueryModel"); //修改窗口標題
    view1->setModel(myModel);
    view1->show();
}

運行效果如下:


可以看到我們要的效果已經出來了。

 


 

(2)QSqlTableModel


在上一篇我們講到只讀的QSqlQueryModel也可以使其可編輯,但是很麻煩。Qt提供了操作單表的QSqlTableModel,如果我們需要對錶的內容進行修改,那麼我們就可以直接使用這個類。

 

QSqlTableModel,該類提供了一個可讀寫單張SQL表的可編輯數據模型。我們下面就對其的幾個常用功能進行介紹,分別是修改,插入,刪除,查詢,和排序。

在開始講之前,我們還是新建Qt4 Gui Application工程,我這裏工程名爲tableModel ,然後選中QtSql模塊,Base class選QWidget。工程建好後,添加C++ Header File ,命名爲database.h,更改其內容如下:

#ifndef DATABASE_H
#define DATABASE_H

#include <QSqlDatabase>
#include <QSqlQuery>
#include <QObject>

static bool createConnection()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("database.db");
    if(!db.open()) return false;
    QSqlQuery query;
    query.exec(QObject::tr("create table student (id int primary key, name vchar)"));
    query.exec(QObject::tr("insert into student values (0,'劉明')"));
    query.exec(QObject::tr("insert into student values (1,'陳剛')"));
    query.exec(QObject::tr("insert into student values (2,'王紅')"));
    return true;

}

#endif // DATABASE_H

爲了在數據庫中能使用中文,我們使用了QObject類的tr()函數。而在下面的main()函數中我們也需要添加相應的代碼來支持中文。

然後將main.cpp的文件更改如下:

#include <QtGui/QApplication>
#include "widget.h"
#include "database.h"
#include <QTextCodec>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTextCodec::setCodecForTr(QTextCodec::codecForLocale());
    if(!createConnection())
        return 1;
    Widget w;
    w.show();
    return a.exec();
}

下面我們打開widget.ui,設計界面如下:

其中的部件有Table View 和 Line Edit,其餘是幾個按鈕部件。

然後在widget.h中加入頭文件: #include <QSqlTableModel>

在private中聲明對象:QSqlTableModel *model;

因爲我們要在不同的函數中使用model對象,所以我們在這裏聲明它。

我們到widget.cpp文件中的構造函數裏添加如下代碼:

model = new QSqlTableModel(this);
model->setTable("student");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select(); //選取整個表的所有行
// model->removeColumn(1); //不顯示name屬性列,如果這時添加記錄,則該屬性的值添加不上
ui->tableView->setModel(model);
// ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);   
//使其不可編輯

此時運行程序,效果如下:

可以看到,這個模型已經完全脫離了SQL語句,我們只需要執行select()函數就能查詢整張表。上面有兩行代碼被註釋掉了,你可以取消註釋,測試一下它們的作用。

第一,修改操作。

1.我們進入“提交修改”按鈕的單擊事件槽函數,修改如下:

void Widget::on_pushButton_clicked() //提交修改
{
    model->database().transaction(); //開始事務操作
    if (model->submitAll()) {
        model->database().commit(); //提交
    } else {
        model->database().rollback(); //回滾
        QMessageBox::warning(this, tr("tableModel"),
                             tr("數據庫錯誤: %1")
                             .arg(model->lastError().text()));
    }
}

這裏用到了事務操作,真正起提交操作的是model->submitAll()一句,它提交所有更改。

2.進入“撤銷修改”按鈕單擊事件槽函數,並更改如下:

void Widget::on_pushButton_2_clicked() //撤銷修改
{
     model->revertAll();
}

它只有簡單的一行代碼。

我們需要在widget.cpp文件中添加頭文件:

#include <QMessageBox>
#include <QSqlError>

此時運行程序,效果如下:

我們將“陳剛”改爲“李強”,如果我們點擊“撤銷修改”,那麼它就會重新改爲“陳剛”,而當我們點擊“提交修改”後它就會保存到數據庫,此時再點擊“撤銷修改”就修改不回來了。

可以看到,這個模型可以將所有修改先保存到model中,只有當我們執行提交修改後,纔會真正寫入數據庫。當然這也是因爲我們在最開始設置了它的保存策略:

model->setEditStrategy(QSqlTableModel::OnManualSubmit);

OnManualSubmit表明我們要提交修改才能使其生效。

第二,查詢操作。

1.我們進入“查詢”按鈕的單擊事件槽函數,更改如下:

void Widget::on_pushButton_7_clicked() //查詢
{
    QString name = ui->lineEdit->text();
    model->setFilter(QObject::tr("name = '%1'").arg(name)); //根據姓名進行篩選
    model->select(); 
//顯示結果
}

我們使用setFilter()函數進行關鍵字篩選,這個函數是對整個結果集進行查詢。爲了使用變量,我們使用了QObject類的tr()函數。

2.我們進入“返回全表”按鈕的單擊事件槽函數,更改如下:

void Widget::on_pushButton_8_clicked() //返回全表
{
    model->setTable("student");   //重新關聯表
    model->select();   //這樣才能再次顯示整個表的內容
}

爲了再次顯示整個表的內容,我們需要再次關聯這個表。

運行效果如下:

我們輸入一個姓名,點擊“查詢”按鈕後,就可以顯示該記錄了。再點擊“返回全表”按鈕則返回。

第三,排序操作。

我們分別進入“按id升序排列”和“按id降序排列”按鈕的單擊事件槽函數,更改如下:

void Widget::on_pushButton_5_clicked() //升序
{
    model->setSort(0,Qt::AscendingOrder); //id屬性,即第0列,升序排列
    model->select();
}

void Widget::on_pushButton_6_clicked() //降序
{
    model->setSort(0,Qt::DescendingOrder);
    model->select();

}

我們這裏使用了setSort()函數進行排序,它有兩個參數,第一個參數表示按第幾個屬性排序,表頭從左向右,最左邊是第0個屬性,這裏就是id屬性。第二個參數是排序方法,有升序和降序兩種。

運行程序,效果如下:

這是按下“按id降序排列”按鈕後的效果。

第四,刪除操作。

我們進入“刪除選中行”按鈕單擊事件槽函數,進行如下更改:

void Widget::on_pushButton_4_clicked() //刪除當前行
{
    int curRow = ui->tableView->currentIndex().row();
    //獲取選中的行
    model->removeRow(curRow);
    //刪除該行
    int ok = QMessageBox::warning(this,tr("刪除當前行!"),tr("你確定"
                                                           "刪除當前行嗎?"),
                         QMessageBox::Yes,QMessageBox::No);
    if(ok == QMessageBox::No)
    {

       model->revertAll(); //如果不刪除,則撤銷
    }
    else model->submitAll(); //否則提交,在數據庫中刪除該行
}

這裏刪除行的操作會先保存在model中,當我們執行了submitAll()函數後纔會真正的在數據庫中刪除該行。這裏我們使用了一個警告框來讓用戶選擇是否真得要刪除該行。

運行效果如下:

我們點擊第二行,然後單擊“刪除選中行”按鈕,出現了警告框。這時你會發現,表中的第二行前面出現了一個小感嘆號,表明該行已經被修改了,但是還沒有真正的在數據庫中修改,這時的數據有個學名叫髒數據(Dirty Data)。當我們按鈕“Yes”按鈕後數據庫中的數據就會被刪除,如果按下“No”,那麼更改就會取消。

第五,插入操作。

我們進入“添加記錄”按鈕的單擊事件槽函數,更改如下:

void Widget::on_pushButton_3_clicked() //插入記錄
{
    int rowNum = model->rowCount(); //獲得表的行數
   int id = 10;
    model->insertRow(rowNum); //添加一行
    model->setData(model->index(rowNum,0),id);
    //model->submitAll(); //可以直接提交
}

我們在表的最後添加一行,因爲在student表中我們設置了id號是主鍵,所以這裏必須使用setData函數給新加的行添加id屬性的值,不然添加行就不會成功。這裏可以直接調用submitAll()函數進行提交,也可以利用“提交修改”按鈕進行提交。

運行程序,效果如下:

按下“添加記錄”按鈕後,就添加了一行,不過在該行的前面有個星號,如果我們按下“提交修改”按鈕,這個星號就會消失。當然,如果我們將上面代碼裏的提交函數的註釋去掉,也就不會有這個星號了。

       可以看到這個模型很強大,而且完全脫離了SQL語句,就算你不怎麼懂數據庫,也可以利用它進行大部分常用的操作。我們也看到了,這個模型提供了緩衝區,可以先將修改保存起來,當我們執行提交函數時,再去真正地修改數據庫。當然,這個模型比前面的模型更高級,前面講的所有操作,在這裏都能執行。

 

 

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