Qt5数据库读写、Xml文档读写、model\View模型视图示例

  • 本示例来自qt网站(https://doc.qt.io/qt-5.15/qtsql-masterdetail-example.html),但对原示例做部份修改,因为原示例存在很多bug,只能说能正常编译通过,但没有按预期的设计要求运行,有些功能不正常,比如:新艺术家插入专辑时不正常,可能插入空行;删除专辑时,没有刷新右边艺术家及专辑信息
  • 示例运行界面:
    示例运行界面

示例功能:

可以通过File菜单,用编程的方法新增、删除艺术家及专辑(不是通过TableView)
当选择Artist时,Details会显示这个艺术家的名字、专辑数量
当点击专辑时,Details会显示专辑标题及专辑列表

- 容易出错的地方:

经常有人问,向模型插入数据时,会插入空行,或者插入失败,首先要看是不是数据库的问题,比如:

首先QSqlRelationalTableModel类的对象,一般要求对应的数据表要有“主键”,并且不能设置成另一个表的外健;

其次QSqlRelationalTableModel类的对象有些方法有特别的要求,比如下面几个函数:

  • 当向模型的末尾插入一条记录,会用到命令insertRecord(-1, record),在Qt网站的技术文档里有这一句话:Inserts the record at position row. If row is negative, the record will be ppended to the end. Calls insertRows() and setRecord() internally,意思是:在row位置插入记录,如果行是负数,记录将被追加到末尾,在内部调用insertRows()和setRecord();在setRecord()的技术文档里有还有一句话:For edit strategies OnFieldChange and OnRowChange, only one row may be inserted at a time and the model may not contain other cached changes;意思是:对于 OnFieldChange 和OnRowChange编辑策略,一次只能插入一行,并且模型不能缓存未提交的更改,否则会插入失败,所以在我们用这一条命令时,要确保缓存的更改都已经提交数据库。
  • setRecord(rowindex,record)函数也有同样的要求:Qt文档写着:For edit strategies OnFieldChange and OnRowChange, a row may receive a change only if no other row has a cached change. 意思是: 对于OnFieldChange和OnRowChange编辑策略,只有当没有其他行具有缓存的更改时,一行才可能接收到更改
  • bool QSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)函数也有同样的要求, 对于编辑策略OnFieldChange,只有当没有其他索引缓存更改时,索引才会收到更改,更改会立即提交;对于OnRowChange,只有当没有其他行具有缓存的更改时,索引才可能接收到更改,更改不会自动提交

再次看看 virtual bool submit() override和 bool submitAll()两个方法的区别

  • 使用submit()方法:当用户停止编辑当前行时,该项委托调用这个槽函数,如果模型的策略设置为OnRowChange或OnFieldChange,则提交当前编辑的行, 对OnManualSubmit策略没有任何作用, 不会自动重新填充模型
  • 使用submitAll()方法:提交所有挂起的更改,并在成功时返回true,出错时返回false,在OnManualSubmit中,一旦成功,该模型将被重新填充,任何呈现它的视图都将丢失它们未提交的缓存,注意:在OnManualSubmit模式下,当submitAll()失败时,不会从缓存中清除已经提交的更改,这允许在不丢失数据的情况下回滚和重新提交事务

最后说说model与它的所有relationModel之间的关系:

  • 一个model可以对应n个的relationModel,当在OnRowChange或OnFieldChange模式下时,relationModel对应的数据表有insertRecord(-1, record)时,插入的操作为立即提交数据库,但不会重新填充model数据模型,一定要执行model->select()重新填充model数据模型,否则,在model中再次插入记录时,会插入空行,或者插入失败

QSqlRelationalTableModel类的对象的三种策略的区别:

常数 说明
QSqlTableModel::OnFieldChange 0 对模型的所有更改都将立即应用于数据库
QSqlTableModel::OnRowChange 1 当用户选择不同的行时,将应用对行的更改
QSqlTableModel::OnManualSubmit 2 在调用submitAll()或revertAll()之前,所有更改都将缓存在模型中

示例:

  • 用Qt Creator新建一个Application(Qt)项目,名称:XmlAndDBWriteRead,默认构建系统Define
    Build System选择qmake

在这里插入图片描述

  • 主窗体默认从QMainWindow派生

在这里插入图片描述

  • 语言Language选择:Chinese(china),其它都默认。

在这里插入图片描述

  • 双击“mainwindow.ui”,把主窗体设计如下,其中Album里面是一个QTableView小部件,Details下面包含三个QTextLabel,一个QListWidget小部件

在这里插入图片描述

  • 类对象层次结构
    在这里插入图片描述

  • 右击项目名称,【Add New…】,增加一个资源文件,名称:XMLAndDataBase,直到完成
    在这里插入图片描述

  • 右键点击资源文件,选择【添加现有文件…】

在这里插入图片描述

  • 选择项目目录下的两个图片文件,完成

在这里插入图片描述

  • 右击项目名称,【Add New…】一个新增专辑及艺术家的对话框

在这里插入图片描述

  • 选择第一个,Dialog with Buttons Bottom,其它都默认为,直到完成

在这里插入图片描述

  • 把对话框修改成如下:

在这里插入图片描述

  • 底部的三个按钮在这里选择

在这里插入图片描述

  • 类对象层次结构
    在这里插入图片描述

  • 为Dialog对话框头文件包含sql、xml、小部件三个模块

#include <QtWidgets>
#include <QtSql>
#include <QtXml>
  • 为Dialog对话框增加一个能接受四个参数的构造函数,三个参数分别是专辑数据模型、保存albumdetails.xml数据参数、对应albumdetails.xml文件的QFile对象,在头文件声明如下
explicit Dialog(QSqlRelationalTableModel *albums, QDomDocument details,
                QFile *output, QWidget *parent = nullptr);
  • 在源文件实现
Dialog::Dialog(QSqlRelationalTableModel *albums, QDomDocument details,
               QFile *output, QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
   
    ui->setupUi(this);
}
  • 在Dialog对话框源文件#include的后面定义现个全局变量,保存专辑数量及艺术家数据
int uniqueAlbumId;
int uniqueArtistId;
  • 增加一个database.h的头文件,主要功能是新建默认数据库接连,类型为内存数据库,打开数据库,并新增3个表,为每个表增加几条记录;右击项目名称,【Add New…】,文件和类选择:“C/C++"类,右边选择”C/C++ Header File“的C/C++头文件,名称:database.h,直到完成
    在这里插入图片描述

下面开始编写代码:

  • 修改项目的pro文件,增加sql、xml模块的支持
QT       += core gui sql xml
  • 最终database.h代码如下
#ifndef DATABASE_H
#define DATABASE_H

#include <QMessageBox>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>

static bool createConnection()
{
   
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(":memory:");
    if (!db.open()) {
   
        QMessageBox::critical(nullptr, QObject::tr("Cannot open database"),
            QObject::tr("Unable to establish a database connection.\n"
                        "This example needs SQLite support. Please read "
                        "the Qt SQL driver documentation for information how "
                        "to build it.\n\n"
                        "Click Cancel to exit."), QMessageBox::Cancel);
        return false;
    }

    QSqlQuery query;
    // 歌手id,艺术家名字,专辑数量
    query.exec("create table artists (id int primary key, "
                                     "artist varchar(40), "
                                     "albumcount int)");

    query.exec("insert into artists values(0, '<all>', 0)");
    query.exec("insert into artists values(1, 'Ane Brun', 2)");
    query.exec("insert into artists values(2, 'Thomas Dybdahl', 3)");
    query.exec("insert into artists values(3, 'Kaizers Orchestra', 3)");
    // 专辑id,专辑标题,艺术家id,发行年份
    query.exec("create table albums (albumid int primary key, "
                                     "title varchar(50), "
                                     "artistid int, "
                                     "year int)");

    query.exec("insert into albums values(1, 'Spending Time With Morgan', 1, "
                       "2003)");
    query.exec("insert into albums values(2, 'A Temporary Dive', 1, 2005)");
    query.exec("insert into albums values(3, '...The Great October Sound', 2, "
                       "2002)");
    query.exec("insert into albums values(4, 'Stray Dogs', 2, 2003)");
    query.exec("insert into albums values(5, "
        "'One day you`ll dance for me, New York City', 2, 2004)");
    query.exec("insert into albums values(6, 'Ompa Til Du D\xc3\xb8r', 3, 2001)");
    query.exec("insert into albums values(7, 'Evig Pint', 3, 2002)");
    query.exec("insert into albums values(8, 'Maestro', 3, 2005)");

    return true;
}


#endif // DATABASE_H
  • QSqlDatabase::addDatabase(“QSQLITE”)是静态方法,原型: QSqlDatabase
    QSqlDatabase::addDatabase(const QString &type, const QString
    &connectionName = QLatin1String(defaultConnection))
    有两个参数,第一个参数表示“类型”,第二个参数是“连接名”,如果连接名省略,默认是使用“默认连接”defaultConnection,当我们要用数据库表填充数据模型对象时,通过构造函数QSqlRelationalTableModel(this)填充模型对象,实际上QSqlRelationalTableModel(this)省略了第二个参数,它的原型是:
    QSqlRelationalTableModel(QObject *parent = nullptr, QSqlDatabase db =
    QSqlDatabase()) 被省略的第二个默认参数是确定要访问的数据库,通过调用 QSqlDatabase
    database(const QString &connectionName =
    QLatin1String(defaultConnection), bool open = true) 方法来返回默认数据库。

  • 在mainwindow.h头文件中,包含如下sql模块、小部件模块、QFile文件模块

   #include <QtSql>
   #include <QtWidgets>
   #include <QFile>
   #include <QDomDocument>
   #include "dialog.h"
  • 并增加一个私有QSqlRelationalTableModel变量,用来填充数据库表的数据
 QSqlRelationalTableModel *model;
  • 在mainwindow.h头文件中增加一个构造函数,带四个参数,前面两个QString类型的参数分别对应数据库中artists、albums表,第三个参数对应磁盘中的Xml文档,如下
explicit MainWindow(const QString &artistTable, const QString &albumTable,
           QFile *albumDetails, QWidget *parent = nullptr);
  • 并在mainwindow.cpp文件中对此构造函数进行实现,增加如下语句,用数据库表albums填充model数据模型,并把artists、albums两个表做一个关联,并执行select查询。
   MainWindow::MainWindow(const QString &artistTable, const QString &albumTable,
               QFile *albumDetails, QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
   
    ui->setupUi(this);
    //使用默认数据库连接,构造model对象
    model = new QSqlRelationalTableModel(this);
    // 设置专辑albums数据表给model
    model->setTable(albumTable);
    // 2-歌手id,artist-歌手名字
    /* SELECT al.albumid,al.title,ar.artist,al.year
       FROM albumTable al
       INNER JOIN artistTable ar
       ON al.artistid = ar.id;
    */
    // 下面语句相当于上面的sql语句
    model->setEditStrategy(QSqlTableModel::OnFieldChange);
    model->setRelation(2, QSqlRelation(artistTable, "id", "artist"));
    /*
     * 使用指定的筛选和排序条件,用通过setTable()设置的表中的数据填充模型,
     * 如果成功,则返回true,否则返回false。
     * 注意:调用select()将恢复任何未提交的更改并删除任何插入的列。
    */
    model->select();
}
  • 修改main.cpp文件,包含如下模块
#include "database.h"
#include <QFile>
#include <QDir>
  • 并在mian函数的QApplication a(argc, argv);语句之后,增加访问数据库的语句,主要功能是新建默认数据库接连,类型为内存数据库,打开数据库,并新增3个表,为每个表增加几条记录
if (!createConnection())
    return EXIT_FAILURE;
  • 在项目目录下新建一个albumdetails.xml文件,内容如下
<archive>
    <album id="1" >
        <track number="01" >Humming one of your songs</track>
        <track number="02" >Are they saying goodbye</track>
        <track number="03" >On off</track>
        <track number="04" >I shot my heart</track>
        <track number="05" >Drowning in Those Eyes</track>
        <track number="06" >So you did it again</track>
        <track number="07" >One more time</track>
        <track number="08" >Headphone silence</track>
        <track number="09" >What I want</track>
        <track number="10" >Sleeping by the Fyris River</track>
        <track number="11" >Wooden Body</track>
        <track number="12" >Humming one of your songs (encore)</track>
    </album>
    <album id="2" >
        <track number="01" >To let myself go</track>
        <track number="02" >Rubber and Soul</track>
        <track number="03" >Balloon ranger</track>
        <track number="04" >My lover will go</track>
        <track number="05" >Temporary dive</track>
        <track number="06" >Laid in earth</track>
        <track number="07" >This voice</track>
        <track number="08" >Where friend rhymes with end</track>
        <track number="09" >Song No.6 feat Ron Sexsmith</track>
        <track number="10" >The Fight Song</track>
    </album>
    <album id="3" >
        <track number="01" >From Grace</track>
        <track number="02" >All's not last</track>
        <track number="03" >That Great October Sound</track>
        <track number="04" >Life Here Is Gold</track>
        <track number="05" >Tomorrow Stays The Same</track>
        <track number="06" >Postulate</track>
        <track number="07" >Adelaide</track>
        <track number="08" >John Wayne</track>
        <track number="09" >Love's Lost</track>
        <track number="10" >Dreamweaver</track>
        <track number="11" >Outro</track>
    </album>
    <album id="4" >
        <track number="01" >Rain down on me</track>
        <track number="02" >Cecilia</track>
        <track number="03" >Make a mess of yourself</track>
        <track number="04" >Pale green eyes</track>
        <track number="05" >Either way I'm gone</track>
        <track number="06" >Honey</track>
        <track number="07" >Rise in shame</track>
        <track number="08" >Stray dogs</track>
        <track number="09" >The willow</track>
        <track number="10" >Stay home</track>
        <track number="11" >Outro</track>
    </album>
    <album id="5" >
    </album>
    <album id="6" >
        <track number="01" >Kontroll på kontinentet</track>
        <track number="02" >Ompa til du dør</track>
        <track number="03" >Bøn fra helvete</track>
        <track number="04" >170</track>
        <track number="05" >Rullett</track>
        <track number="06" >Dr. Mowinckel</track>
        <track number="07" >Fra sjåfør til passasjer</track>
        <track number="08" >Resistansen</track>
        <track number="09" >Dekk bord</track>
        <track number="10" >Bak et halleluja</track>
        <track number="11" >Bris</track>
        <track number="12" >Mr. Kaizer, hans Constanze og meg</track>
    </album>
    <album id="7" >
        <track number="01" >Di grind</track>
        <track number="02" >Hevnervals</track>
        <track number="03" >Evig pint</track>
        <track number="04" >De involverte</track>
        <track number="05" >Djevelens orkester</track>
        <track number="06" >Container</track>
        <track number="07" >Naade</track>
        <track number="08" >Min kvite russer</track>
        <track number="09" >Veterans klage</track>
        <track number="10" >Til depotet</track>
        <track number="11" >Salt og pepper</track>
        <track number="12" >Drøm Hardt (Requiem Part I)</track>
    </album>
    <album id="8" >
        <track number="01" >KGB</track>
        <track number="02" >Maestro</track>
        <track number="03" >Knekker deg til sist</track>
        <track number="04" >Senor Flamingos Adieu</track>
        <track number="05" >Blitzregn baby</track>
        <track number="06" >Dieter Meyers Inst.</track>
        <track number="07" >Christiania</track>
        <track number="08" >Delikatessen</track>
        <track number="09" >Jævel av en tango</track>
        <track number="10" >Papa har lov</track>
        <track number="11" >Auksjon (i Dieter Meyers hall)</track>
        <track number="12" >På ditt skift</track>
    </album>
</archive>
  • 在实例化mainwindow对象语句前(MainWindow w),先创建QFile对象
QFile albumDetails(QDir::currentPath() + "/../XmlAndDBWriteRead/albumdetails.xml");
  • 并修改MainWindow w语句,如下
MainWindow w("artists", "albums", &albumDetails);
  • 最终main.cpp文件如下
#include "mainwindow.h"

#include <QApplication>
#include <QLocale>
#include <QTranslator>

int main(int argc, char *argv[])
{
   
    QApplication a(argc, argv);
    if (!createConnection())
        return EXIT_FAILURE;
    QTranslator translator;
    const QStringList uiLanguages = QLocale::system().uiLanguages();
    for (const QString &locale : uiLanguages) {
   
        const QString baseName = "XmlAndDBWriteRead_" + QLocale(locale).name();
        if (translator
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值