本节由不同Qt类支撑的三部分组成,QtSql模块层次结构如下图所示:
在这里插入图片描述

一、Qt操作SQLite数据库

Qt提供了一种进程内数据库SQLite。它小巧灵活,无须额外安装配置且支持大部分ANSI SQL92标准,是一个轻量级的数据库,概括起来具有以下优点。
(1)SQLite的设计目的是实现嵌入式SQL数据库引擎,它基于纯C语言代码,已经应用在非常广泛的领域内。
(2)SQLite在需要持久存储时可以直接读写硬盘上的数据文件,在无须持久存储时也可以将整个数据库置于内存中,两者均不需要额外的服务器端进程,即SQLite是无须独立运行的数据库引擎。
(3)开放源代码,整套代码少于3万行,有良好的注释和90%以上的测试覆盖率。
(4)少于250KB的内存占用容量(gcc编译情况下)。
(5)支持视图、触发器和事务,支持嵌套SQL功能。
(6)提供虚拟机用于处理SQL语句。
(7)不需要配置,不需要安装,也不需要管理员。
(8)支持大部分ANSI SQL92标准。
(9)大部分应用的速度比目前常见的客户端/服务器结构的数据库快。
(10)编程接口简单易用。

下面使用通过例子来学会如何操作数据库:
使用SQLite数据库完成大批量数据的增加、删除、 更新和查询操作并输出。
操作步骤如下。
(1)在“QSQLiteEx.pro”文件中添加如下代码:

QT += sql 

(2)源文件“main.cpp”的具体代码如下:

#include <QCoreApplication>
#include <QTextCodec>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QTime>
#include <QSqlError>
#include <QtDebug>
#include <QSqlDriver>
#include <QSqlRecord>
int main(int argc,char * argv[])
{
    QCoreApplication a(argc, argv);
    QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());
											//设置中文显示
    QSqlDatabase db =QSqlDatabase::addDatabase("QSQLITE");//以“QSQLITE”为数据库类型,在本进程地址空间内创建一个SQLite数据库。
    db.setHostName("easybook-3313b0");     	//设置数据库主机名
    db.setDatabaseName("qtDB.db");         	//以上创建的数据库以“qtDB.db”为数据库名。它是SQLite在建立内存数据库时唯一可用的名字。 
    db.setUserName("wang");            	//设置数据库用户名
    db.setPassword("123456");               	//设置数据库密码
    db.open();                             		//打开连接

    //创建数据库表
    QSqlQuery query;							//创建QSqlQuery对象。QtSql模块中的QSqlQuery类提供了一个执行SQL语句的接口,并且可以遍历执行的返回结果集。除QSqlQuery类外,Qt还提供了三种用于访问数据库的高层类,即QSqlQueryModel、QSqlTableModel和QSqlRelationTableModel。它们无须使用SQL语句就可以进行数据库操作,而且可以很容易地将结果在表格中表示出来。
    bool success=query.exec("create table automobile
                        (id int primary key,
                        attribute varchar,
                        type varchar,
                        kind varchar,
                        nation int,
                        carnumber int,
                        elevaltor int,
                        distance int,
                        oil int,
                        temperature int)");		//创建数据库表“automobil”,该表具有10个字段。在执行exec()函数调用后,就可以操作返回的结果了。 
    if(success)
        qDebug()<<QObject::tr("数据库表创建成功!\n");
    else
        qDebug()<<QObject::tr("数据库表创建失败!\n");
    //查询
    query.exec("select * from automobil");
    QSqlRecord rec = query.record();
    qDebug() << QObject::tr("automobil表字段数:" )<< rec.count();
    //插入记录
    QTime t;
    t.start();								//启动一个计时器,统计操作耗时
    query.prepare("insert into automobil values(?,?,?,?,?,?,?,?,?,?)");															//如果要插入多条记录,或者避免将值转换为字符串(即正确地转义),则可以首先调用prepare()函数指定一个包含占位符的query,然后绑定要插入的值。Qt对所有数据库均可以支持Qracle类型的占位符和ODBC类型的占位符。此处使用了ODBC类型的定位占位符
    long records=100;								//向表中插入任意的100条记录
    for(int i=0;i<records;i++)
    {
        query.bindValue(0,i);						//调用bindValue()或addBindValue()函数绑定要插入的值。 
        query.bindValue(1,"四轮");
        query.bindValue(2,"轿车");
        query.bindValue(3,"富康");
        query.bindValue(4,rand()%100);
        query.bindValue(5,rand()%10000);
        query.bindValue(6,rand()%300);
        query.bindValue(7,rand()%200000);
        query.bindValue(8,rand()%52);
        query.bindValue(9,rand()%100);
        success=query.exec();						//调用exec()函数在query中插入对应的值,之后,可以继续调用bindValue() 或addBindValue()函数绑定新值,然后再次调用exec()函数在query中插入新值。 
        if(!success)
        {
            QSqlError lastError=query.lastError();
            qDebug()<<lastError.driverText()<<QString(QObject::tr("插入失败"));
        }
    }
    qDebug()<<QObject::tr("插入 %1 条记录,耗时:%2 ms").arg(records). arg(t.elapsed());														//向表中插入任意的100条记录,操作成功后输出操作消耗的时间。
    //排序
    t.restart();								//重启计时器
    success=query.exec("select * from automobil order by id desc");
													//按id字段的降序将查询表中刚刚插入的100条记录进行排序。 
    if(success)
        qDebug()<<QObject::tr("排序 %1 条记录,耗时:%2 ms").arg(records).arg(t. elapsed());												//输出操作耗时
    else
        qDebug()<<QObject::tr("排序失败!");
    //更新记录
    t.restart();								//重启计时器
    for(int i=0;i<records;i++)
    {
       query.clear();
       query.prepare(QString("update automobil set attribute=?,type=?,"
                             "kind=?,nation=?,"
                             "carnumber=?,elevaltor=?,"
                             "distance=?,oil=?,"
                             "temperature=? where id=%1").arg(i));
													//更新操作与插入操作类似,只是使用的SQL语句不同。 
       query.bindValue(0,"四轮");
       query.bindValue(1,"轿车");
       query.bindValue(2,"富康");
       query.bindValue(3,rand()%100);
       query.bindValue(4,rand()%10000);
       query.bindValue(5,rand()%300);
       query.bindValue(6,rand()%200000);
       query.bindValue(7,rand()%52);
       query.bindValue(8,rand()%100);
       success=query.exec();
       if(!success)
       {
           QSqlError lastError=query.lastError();
           qDebug()<<lastError.driverText()<<QString(QObject::tr("更新失败"));
       }
    }
    qDebug()<<QObject::tr("更新 %1 条记录,耗时:%2 ms").arg(records).arg(t.elapsed());
    //删除
    t.restart();											//重启计时器
    query.exec("delete from automobil where id=15");	//执行删除id为15的记录的操作。 
	//输出操作耗时
    qDebug()<<QObject::tr("删除一条记录,耗时:%1 ms").arg(t.elapsed());
    return 0;
    //return a.exec();
} 

在本进程地址空间内创建一个SQLite数据库时。要注意两点:
① 在进行数据库操作之前,必须首先建立与数据库的连接。数据库连接由任意字符串标识。在没有指定连接的情况下,QSqlDatabase可以提供默认连接供Qt其他的SQL类使用。建立一条数据库连接的代码如下:

QSqlDatabase db =QSqlDatabase::addDatabase("QSQLITE"); 
db.setHostName("easybook-3313b0"); //设置数据库主机名 
db.setDatabaseName("qtDB.db"); //设置数据库名 
db.setUserName("wang"); //设置数据库用户名 
db.setPassword("123456"); //设置数据库密码 
db.open(); 
//打开连接 

其中,静态函数QSqlDatabase::addDatabase()返回一条新建立的数据库连接,其原型为:

QSqlDatabase::addDatabase 
( 
const QString &type, 
const QString &connectionName=QLatin1String(defaultConnection) 
) 

(1)参数type为驱动名,本例使用的是QSQLITE 驱动。
(2)参数connectionName为连接名,默认值为默认连接,本例的连接名为connect。如果没有指定此参数,则新建立的数据库连接将成为本程序的默认连接,并且可以被后续不带参数的函数database()引用。如果指定了此参数(连接名),则函数database(connectionName)将获取这个指定的数据库连接。
② QtSql模块使用驱动插件(driver plugins)与不同的数据库接口通信。由于QtSql模块的应用程序接口是与具体数据库无关的,所以所有与数据库相关的代码均包含在这些驱动插件中。目前,Qt支持的数据库驱动插件见下图:
在这里插入图片描述
(3)打开“QSQLiteEx.pro”文件,添加语句:

QT += sql 

(4)运行结果如图所示:
在这里插入图片描述

二、Qt操作主/从视图及XML

以主/从视图的模式展现汽车制造商与生产汽车的关系。当在汽车制造商表中选 中某一个制造商时,下面的汽车表中将显示出该制造商生产的所有产品。当在汽车表中选中某个车型时,右边的列表将显示出该车的车型和制造商的详细信息,所不同的是,车型的相关信息存储在XML文件中。

1、主界面布局

(1)主窗口MainWindow类继承自QMainWindow类,定义了主显示界面,头文件“mainwindow.h” 的具体代码如下:

#include <QMainWindow> 
#include <QGroupBox> 
#include <QTableView> 
#include <QListWidget> 
#include <QLabel> 
class MainWindow : public QMainWindow 
{ 
Q_OBJECT 
public: 
MainWindow(QWidget *parent = 0); 
~MainWindow(); 
private: 
QGroupBox *createCarGroupBox(); 
QGroupBox *createFactoryGroupBox(); 
QGroupBox *createDetailsGroupBox(); 
void createMenuBar(); 
QTableView *carView; //显示汽车的视图
QTableView *factoryView; //显示汽车制造商的视图 
QListWidget *attribList; //显示车型的详细信息列表 
/* 声明相关的信息标签 */ 
QLabel *profileLabel; 
QLabel *titleLabel; 
}; 

(2)源文件“mainwindow.cpp”的具体内容如下:

#include "mainwindow.h" 
#include <QGridLayout> 
#include <QAbstractItemView> 
#include <QHeaderView> 
#include <QAction> 
#include <QMenu> 
#include <QMenuBar> 
MainWindow::MainWindow(QWidget *parent) 
: QMainWindow(parent) 
{ 
QGroupBox *factory = createFactoryGroupBox(); 
QGroupBox *cars = createCarGroupBox(); 
QGroupBox *details = createDetailsGroupBox(); 
uniqueCarId = carModel->rowCount(); 
uniqueFactoryId = factoryModel->rowCount(); 
//布局 
QGridLayout *layout = new QGridLayout; 
layout->addWidget(factory, 0, 0); 
layout->addWidget(cars, 1, 0); 
layout->addWidget(details, 0, 1, 2, 1); 
layout->setColumnStretch(1, 1); 
layout->setColumnMinimumWidth(0, 500); 
QWidget *widget = new QWidget; 
widget->setLayout(layout); 
setCentralWidget(widget); 
createMenuBar(); 
resize(850, 400); 
setWindowTitle(tr("主从视图")); 
} 

createFactoryGroupBox()函数的具体内容如下:

QGroupBox* MainWindow::createFactoryGroupBox() 
{ 
factoryView = new QTableView; 
factoryView->setEditTriggers(QAbstractItemView::NoEditTriggers); 
//对于可读写的模型类QSqlTableModel 和QSqlRelationalTableModel,视图允许用户编辑其中的字段,也可以通过调用此语句禁止用户编辑。 
factoryView->setSortingEnabled(true); 
factoryView->setSelectionBehavior(QAbstractItemView::SelectRows); 
factoryView->setSelectionMode(QAbstractItemView::SingleSelection); 
factoryView->setShowGrid(false); 
factoryView->setAlternatingRowColors(true); 
factoryView->setModel(factoryModel); 
connect(factoryView, SIGNAL(clicked (QModelIndex )), 
this, SLOT(changeFactory(QModelIndex )));
QGroupBox *box = new QGroupBox(tr("汽车制造商")); 
QGridLayout *layout = new QGridLayout; 
layout->addWidget(factoryView, 0, 0); 
box->setLayout(layout); 
return box; 
} 

createCarGroupBox()函数的具体代码如下:

QGroupBox* MainWindow::createCarGroupBox() 
{ 
QGroupBox *box = new QGroupBox(tr("汽车")); 
carView = new QTableView; 
carView->setEditTriggers(QAbstractItemView::NoEditTriggers); 
carView->setSortingEnabled(true); 
carView->setSelectionBehavior(QAbstractItemView::SelectRows); 
carView->setSelectionMode(QAbstractItemView::SingleSelection); 
carView->setShowGrid(false); 
carView->verticalHeader()->hide(); 
carView->setAlternatingRowColors(true); 
carView->setModel(carModel); 
connect(carView, SIGNAL(clicked(QModelIndex)), 
this, SLOT(showCarDetails(QModelIndex))); 
connect(carView, SIGNAL(activated(QModelIndex)), 
this, SLOT(showCarDetails(QModelIndex)));
QVBoxLayout *layout = new QVBoxLayout; 
layout->addWidget(carView, 0, 0); 
box->setLayout(layout); 
return box; 
} 

createDetailsGroupBox()函数的具体代码如下:

QGroupBox* MainWindow::createDetailsGroupBox() 
{ 
QGroupBox *box = new QGroupBox(tr("详细信息")); 
profileLabel = new QLabel; 
profileLabel->setWordWrap(true); 
profileLabel->setAlignment(Qt::AlignBottom); 
titleLabel = new QLabel; 
titleLabel->setWordWrap(true); 
titleLabel->setAlignment(Qt::AlignBottom); 
attribList = new QListWidget; 
QGridLayout *layout = new QGridLayout; 
layout->addWidget(profileLabel, 0, 0, 1, 2); 
layout->addWidget(titleLabel, 1, 0, 1, 2); 
layout->addWidget(attribList, 2, 0, 1, 2); 
layout->setRowStretch(2, 1); 
box->setLayout(layout); 
return box; 
} 

createMenuBar()函数的具体代码如下:

void MainWindow::createMenuBar() 
{ 
QAction *addAction = new QAction(tr("添加"), this); 
QAction *deleteAction = new QAction(tr("删除"), this); 
QAction *quitAction = new QAction(tr("退出"), this); 
addAction->setShortcut(tr("Ctrl+A")); 
deleteAction->setShortcut(tr("Ctrl+D")); 
quitAction->setShortcut(tr("Ctrl+Q")); 
QMenu *fileMenu = menuBar()->addMenu(tr("操作菜单")); 
fileMenu->addAction(addAction); 
fileMenu->addAction(deleteAction); 
fileMenu->addSeparator(); 
fileMenu->addAction(quitAction); 
connect(addAction, SIGNAL(triggered(bool)), this, SLOT(addCar())); 
connect(deleteAction, SIGNAL(triggered(bool)), this, SLOT(delCar())); 
connect(quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
} 

(3)运行结果如下图所示:
在这里插入图片描述

2、连接数据库

添加类“ConnDlg”,在“头文件”文本框中输入“connectdlg.h”;在“源文件”文本框中输入 “connectdlg.cpp”;在“界面文件”文本框中输入“connectdlg.ui”。
ui界面如图所示:
在这里插入图片描述
(2)在头文件“connectdlg.h”中,ConnDlg类继承自QDialog类,主要完成从界面获取用户设置的连接参数信息。ConnDlg类的定义中声明了需要的各种函数,其具体代码如下:

#include <QDialog> 
#include <QMessageBox> 
#include "ui_connectdlg.h" 
class QSqlError; 
class ConnDlg: public QDialog 
{ 
Q_OBJECT 
public: 
ConnDlg(QWidget *parent = 0); 
QString driverName() const; 
QString databaseName() const; 
QString userName() const; 
QString password() const; 
QString hostName() const; 
int port() const; 
QSqlError addConnection(const QString &driver, const QString &dbName, const QString &host,const 
QString &user, const QString &passwd, int port = -1); 
void creatDB(); 
void addSqliteConnection(); 
private slots: 
void on_okButton_clicked(); 
void on_cancelButton_clicked() { reject(); } 
void driverChanged(const QString &); 
private: 
Ui::QSqlConnectionDialogUi ui; 
}; 

(3)在源文件“connectdlg.cpp”中,ConnDlg类的构造函数完成了初始化ui界面及查找当前所有可用的Qt数据库驱动,并将其加入ui界面的驱动组合框中,以及其他一些功能,其具体代码如下:

#include "connectdlg.h" 
#include "ui_connectdlg.h" 
#include <QSqlDatabase> 
#include <QtSql> 
ConnDlg::ConnDlg(QWidget *parent) 
: QDialog(parent) 
{ 
ui.setupUi(this); 
QStringList drivers = QSqlDatabase::drivers(); //查找数据库驱动,以QStringList的形式返回所有可用驱动名。 
ui.comboDriver->addItems(drivers); //将这些驱动名加入ui界面的组合框。 
connect(ui.comboDriver,SIGNAL(currentIndexChanged( const QString & )),this, 
SLOT(driverChanged(const QString &))); //关联这个组合框的信号current IndexChanged(const QString&)与槽函数driverChanged(const QString &),以便每当用户在这个组合框中选取了不同的驱动时,槽函数driverChanged()都会被调用。 
ui.status_label->setText(tr("准备连接数据库!")); //设置当前程序运行状态。 
} 

槽函数driverChanged()的具体代码如下:

void ConnDlg::driverChanged(const QString &text) 
{ 
if(text =="QSQLITE") 
{ 
ui.editDatabase->setEnabled(false); 
ui.editUsername->setEnabled(false); 
ui.editPassword->setEnabled(false); 
ui.editHostname->setEnabled(false); 
ui.portSpinBox->setEnabled(false); 
}
else 
{ 
ui.editDatabase->setEnabled(true); 
ui.editUsername->setEnabled(true); 
ui.editPassword->setEnabled(true); 
ui.editHostname->setEnabled(true); 
ui.portSpinBox->setEnabled(true); 
} 
} 

driverName()函数的具体代码如下:

QString ConnDlg::driverName() const 
{ 
return ui.comboDriver->currentText(); 
} 

databaseName()函数的具体代码如下:

QString ConnDlg::databaseName() const 
{ 
return ui.editDatabase->text(); 
} 

userName()函数的具体代码如下:

QString ConnDlg::userName() const 
{ 
return ui.editUsername->text(); 
} 

password()函数的具体代码如下:

QString ConnDlg::password() const 
{ 
return ui.editPassword->text(); 
} 

hostName()函数的具体代码如下:

QString ConnDlg::hostName() const 
{ 
return ui.editHostname->text(); 
} 

port()函数的具体代码如下:

int ConnDlg::port() const 
{ 
return ui.portSpinBox->value(); 
} 

当用户单击“连接”按钮时,调用on_okButton_clicked()函数,其具体实现代码如下:

void ConnDlg::on_okButton_clicked() 
{ 
if(ui.comboDriver->currentText().isEmpty()) //检查用户是否选择了一个数据库驱动。 
{ 
ui.status_label->setText(tr("请选择一个数据库驱动!")); 
ui.comboDriver->setFocus(); 
}
else if(ui.comboDriver->currentText() =="QSQLITE") //根据驱动类型进行处理。如果是QSQLITE驱动, 则调用addSqliteConnection()函数创建一个内存数据库。  
{ 
addSqliteConnection(); //创建数据库表,如已存在则无须执行 
creatDB(); //当打开数据库连接成功时,程序使用SQL语句创建相关数据表,并插入记录信息。
accept(); 
}
else 
{ 
QSqlError err = addConnection(driverName(), databaseName(), hostName(), userName(), password(), 
port()); //如果是其他驱动,则调用addConnection()函数创建一个其他所选类型数据库的 
连接。 
if(err.type() != QSqlError::NoError) //在连接出错时显示错误信息。使用QSqlError类处理连接错误,QSqlError类提供与具体数据库相关的错误信息。 
ui.status_label->setText(err.text()); 
else //当连接没有错误时,在状态栏显示数据库连接成功信息。 
ui.status_label->setText(tr("连接数据库成功!")); //创建数据库表,如已存在则无须执行 
accept(); 
} 
}

addConnection()函数用来建立一条数据库连接,其具体实现内容如下:

QSqlError ConnDlg::addConnection(const QString &driver, const QString &dbName, const QString 
&host,const QString &user, const QString &passwd, int port) 
{ 
QSqlError err; 
QSqlDatabase db = QSqlDatabase::addDatabase(driver); 
db.setDatabaseName(dbName); 
db.setHostName(host); 
db.setPort(port); 
if(!db.open(user, passwd)) 
{ 
err = db.lastError(); 
}
return err; //返回这个错误信息 
} 

addSqliteConnection()函数建立一条QSQLITE数据库驱动对应的sqlite数据库连接,其具体内容如下:

void ConnDlg::addSqliteConnection() 
{ 
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); 
db.setDatabaseName("databasefile"); 
if(!db.open()) 
{ 
ui.status_label->setText(db.lastError().text()); 
return; 
}
ui.status_label->setText(tr("创建sqlite数据库成功!")); 
} 

ConnDlg::creatDB()函数创建了相关的两张数据表,并在其中插入适当信息。其具体代码如下:

void ConnDlg::creatDB() 
{ 
QSqlQuery query; //创建QSqlQuery对象。一旦数据库连接建立后,就可以使用QSqlQuery对象执行底层数据库支持的SQL语句,此方法所要做的仅是首先创建一个QSqlQuery对象,然后再调用QSqlQuery::exec()函数。 
query.exec("create table factory (id int primary key,manufactory varchar (40),address 
varchar(40))"); //此处是将SQL语句作为QSqlQuery::exec()的参数,但是它同样可以直接传给构造函数,从而使该语句立即被执行。 
query.exec(QObject::tr("insert into factory values(1, '一汽大众', '长春')")); 
query.exec(QObject::tr("insert into factory values(2, '二汽神龙', '武汉')")); 
query.exec(QObject::tr("insert into factory values(3, '上海大众', '上海')")); 
query.exec("create table cars (carid int primary key, name varchar(50), factoryid int, year int, 
foreign key(factoryid) references factory)"); //汽车表“cars”中有一个表示生产厂家的字段factoryid指向factory的id字段,即factoryid是一个外键。一些数据库不支持外键,如果将此语句去掉,程序仍然可以运行,但数据库将不强制执行参照完整性。 
query.exec(QObject::tr("insert into cars values(1,'奥迪A6',1,2005)")); 
query.exec(QObject::tr("insert into cars values(2,'捷达', 1, 1993)")); 
query.exec(QObject::tr("insert into cars values(3,'宝来', 1, 2000)")); 
query.exec(QObject::tr("insert into cars values(4,'毕加索',2, 1999)")); 
query.exec(QObject::tr("insert into cars values(5,'富康', 2, 2004)")); 
query.exec(QObject::tr("insert into cars values(6,'标致307',2, 2001)")); 
query.exec(QObject::tr("insert into cars values(7,'桑塔纳',3, 1995)")); 
query.exec(QObject::tr("insert into cars values(8,'帕萨特',3, 2000)")); 
} 

(4)修改“main.cpp”的代码如下:

#include "mainwindow.h" 
#include <QApplication> 
#include <QDialog> 
#include "connectdlg.h" 
int main(int argc, char *argv[]) 
{ 
QApplication a(argc, argv); 
ConnDlg dialog; 
if(dialog.exec() != QDialog::Accepted) 
return -1; 
dialog.show(); 
return a.exec(); 
} 

(5)在“SQLEx.pro”文件中添加如下内容:

QT += sql 

(6)运行程序,出现如下图所示的界面:
在这里插入图片描述

在“驱动:”栏中选择“QSQLITE”,单击“连接”按钮,在“状态:”栏中将显示“创建sqlite数据库成功!”,这说明之前编写的创建及连接数据库的代码是正确的,接下来实现用主/从视图模式浏览数据库中的信息。

3、主/从视图应用

(1)在头文件“mainwindow.h”中添加如下代码:

#include <QFile> 
#include <QSqlRelationalTableModel> 
#include <QSqlTableModel> 
#include <QModelIndex> 
#include <QDomNode> 
#include <QDomDocument> 
public: 
MainWindow(const QString &factoryTable, const QString &carTable, QFile *carDetails,QWidget *parent = 0); 
~MainWindow(); 
private slots:
void addCar(); 
void changeFactory(QModelIndex index); 
void delCar(); 
void showCarDetails(QModelIndex index); 
void showFactorytProfile(QModelIndex index); 
private: 
void decreaseCarCount(QModelIndex index); 
void getAttribList(QDomNode car); 
QModelIndex indexOfFactory(const QString &factory); 
void readCarData(); 
void removeCarFromDatabase(QModelIndex index); 
void removeCarFromFile(int id); 
QDomDocument carData; 
QFile *file; 
QSqlRelationalTableModel *carModel; 
QSqlTableModel *factoryModel; 

(2)在源文件“mainwindow.cpp”中添加如下代码:

#include <QMessageBox> 
#include <QSqlRecord> 
MainWindow::MainWindow(const QString &factoryTable, const QString &car Table, QFile *carDetails, 
QWidget *parent) : QMainWindow(parent) 
{ 
file = carDetails; 
readCarData(); //将XML文件里的车型信息读入QDomDocument类实例carData中,以便后面的操 
作 
carModel = new QSqlRelationalTableModel(this); //为汽车表“cars”创建一个QSqlRelationalTableModel模型。 
carModel->setTable(carTable); 
carModel->setRelation(2, QSqlRelation(factoryTable, "id", 
"manufactory")); //说明上面创建的QSqlRelationalTableModel模型的第二个字段(即汽车表“cars”中的factoryid字段)是汽车制造商表 “factory”中id字段的外键,但其显示为汽车制造商表“factory”的manufactory字段,而不是id字段。 
carModel->select(); 
factoryModel = new QSqlTableModel(this); //为汽车制造商表“factory”创建一个QSqlTableModel模 
型 
factoryModel->setTable(factoryTable); 
factoryModel->select(); 
} 

changeFactory()函数的具体代码如下:

void MainWindow::changeFactory(QModelIndex index) 
{ 
QSqlRecord record = factoryModel->record(index.row());//取出用户选择的这条汽车制造商记录。 
QString factoryId = record.value("id").toString(); //获取以上选择的汽车制造商的主键。QSqlRecord::value() 需要指定字段名或字段索引。 
carModel->setFilter("id = '"+ factoryId +"'") ; //在汽车表模型“carModel”中设置过滤器,使其只显示所选汽车制造商的车型。 
showFactorytProfile(index); //在“详细信息”中显示所选汽车制造商的信息。 
} 

在“详细信息”中显示所选汽车制造商的信息函数showFactorytProfile()的具体代码如下:

void MainWindow::showFactorytProfile(QModelIndex index) 
{ 
QSqlRecord record = factoryModel->record(index.row()); //取出用户选择的这条汽车制造商记录。 
QString name = record.value("manufactory").toString(); //从汽车制造商模型“factoryModel”中获得制造商的名称。 
int count = carModel->rowCount(); //从汽车表模型“carModel”中获得车型数量 
profileLabel->setText(tr("汽车制造商:%1\n产品数量: %2").arg(name).arg(count)); //在“详细信息”的 
profileLabel标签中显示这两部分信息 
profileLabel->show(); 
titleLabel->hide(); 
attribList->hide(); 
} 

showCarDetails()函数的具体代码如下:

void MainWindow::showCarDetails(QModelIndex index) 
{ 
QSqlRecord record = carModel->record(index.row()); //首先从汽车表模型“carModel”中获取所选记录。
QString factory = record.value("manufactory").toString(); //获得所选记录的制造商名factory字段。 
QString name = record.value("name").toString(); 
QString year = record.value("year").toString(); 
QString carId = record.value("carid").toString();  
showFactorytProfile(indexOfFactory(factory)); //重复显示制造商信息
titleLabel->setText(tr("品牌: %1 (%2)").arg(name).arg(year));// 在“详细信息”的titleLabel标签中显示该车型的品牌名和生产时间 
titleLabel->show(); 
QDomNodeList cars = carData.elementsByTagName("car");//记录了车型信息的XML文件中搜索匹配的车型 
for(int i = 0; i < cars.count(); i++) //找出所有car标签 
{
QDomNode car = cars.item(i); 
if(car.toElement().attribute("id") == carId) //在这些标签中找出id属性与所选车型主键carId相同的属性id。 
{
getAttribList(car.toElement()); //显示这个匹配的car标签中的相关信息(如信息编号number和该编号下的信息内容)。 
break; 
}
}
if(!attribList->count() == 0) 
attribList->show(); 
} 

getAttribList()函数检索以上获得的car标签下的所有子节点,将这些子节点的信息在“详细信息”的QListWidget窗体中显示。这些信息包括信息编号number和该编号下的信息内容,其具体代码如
下:

void MainWindow::getAttribList(QDomNode car) 
{ 
attribList->clear(); 
QDomNodeList attribs = car.childNodes(); 
QDomNode node; 
QString attribNumber; 
for (int j = 0; j < attribs.count(); j++) 
{ 
node = attribs.item(j); 
attribNumber = node.toElement().attribute("number"); 
QListWidgetItem *item = new QListWidgetItem(attribList); 
QString showText(attribNumber + ": " + node.toElement().text()); 
item->setText(tr("%1").arg(showText)); 
} 
} 

因为addCar()函数在此时还没有实现具体的功能,所以代码部分暂时为空:

void MainWindow::addCar(){}

delCar()函数的具体代码如下:

void MainWindow::delCar() 
{ 
QModelIndexList selection = carView->selectionModel() ->selectedRows(0); 
if (!selection.empty()) //判断用户是否在汽车表中选中了一条记录
{ 
QModelIndex idIndex = selection.at(0); 
int id = idIndex.data().toInt(); 
QString name = idIndex.sibling(idIndex.row(), 1).data(). toString(); 
QString factory = idIndex.sibling(idIndex.row(), 2).data(). toString(); 
QMessageBox::StandardButton button; 
button = QMessageBox::question(this, tr("删除汽车记录"), 
QString(tr("确认删除由'%1'生产的'%2'吗?").arg(factory).arg 
(name)),QMessageBox::Yes | QMessageBox::No); //如果是,则弹出一个确认对话框,提示用户是否删除该记录。 
if (button == QMessageBox::Yes) //得到用户确认 
{ 
removeCarFromFile(id); 
//从XML文件中删除相关内容 
removeCarFromDatabase(idIndex); 
//从数据库表中删除相关内容 
decreaseCarCount(indexOfFactory(factory)); //调整汽车制造商表中的内容。  
} 
else //如果用户没有在汽车表中选中记录,则提示用户进行选择 
{ 
QMessageBox::information(this, tr("删除汽车记录"),tr("请选择要删除的记录。")); 
} 
} 
} 

removeCarFromFile()函数遍历XML文件中的所有car标签,首先找出id属性与汽车表中所选记录主键相同的节点,然后将其删除。其具体代码如下:

void MainWindow::removeCarFromFile(int id) 
{ 
QDomNodeList cars = carData.elementsByTagName("car"); 
for(int i = 0; i< cars.count(); i++) 
{
QDomNode node = cars.item(i); 
if(node.toElement().attribute("id").toInt() == id) 
{ 
carData.elementsByTagName 
("archive").item(0).removeChild(node); 
break; 
}
} 
} 

removeCarFromDatabase()函数将汽车表中所选中的行从汽车表模型“carModel”中移除,这个模型将自动删除数据库表中的对应记录,其具体代码如下:

void MainWindow::removeCarFromDatabase(QModelIndex index) 
{ 
carModel->removeRow(index.row()); 
} 

删除了某个汽车制造商的全部产品后,需要删除这个汽车制造商,decreaseCarCount()函数实现了此功能,其具体代码如下:

void MainWindow::decreaseCarCount(QModelIndex index) 
{ 
int row = index.row(); 
int count = carModel->rowCount(); //汽车表中的当前记录数。  
if(count == 0) //判断这个记录数,如果为0,则从汽车制造商表中删除对应的制造商。 
factoryModel->removeRow(row); 
} 

readCarData()函数的具体代码如下:

void MainWindow::readCarData() 
{ 
if(!file->open(QIODevice::ReadOnly)) 
return; 
if(!carData.setContent(file)) 
{ 
file->close(); 
return; 
}
file->close(); 
}

indexOfFactory()函数通过制造商的名称进行检索,并返回一个匹配的模型索引QModelIndex,供汽车制造商表模型的其他操作使用,其具体代码如下:

QModelIndex MainWindow::indexOfFactory(const QString &factory) 
{ 
for(int i = 0; i < factoryModel->rowCount(); i++) 
{ 
QSqlRecord record = factoryModel->record(i); 
if(record.value("manufactory") == factory) 
return factoryModel->index(i, 1); 
}
return QModelIndex(); 
} 

(3)源文件“main.cpp”的具体代码如下:

#include <QDialog> 
#include <QFile> 
#include "connectdlg.h" 
int main(int argc, char *argv[]) 
{ 
QApplication a(argc, argv); 
//MainWindow w; 
//w.show(); 
ConnDlg dialog; 
if(dialog.exec() != QDialog::Accepted) 
return -1; 
QFile *carDetails = new QFile("attribs.xml"); 
MainWindow window("factory", "cars", carDetails); 
window.show(); 
return a.exec(); 
} 

(4)新建一个XML文件,将该文件存放在该工程的目录下,以下是“attribs.xml”文件的详细内容。

<?xml version="1.0" encoding="gb2312"?>    
    <archive>
        <car id="1" >
            <attrib number="01" >排量:2393ml</attrib>
            <attrib number="02" >价格:43.26万元</attrib>
            <attrib number="03" >排放:4</attrib>
            <attrib number="04" >油耗:7.0l(90km/h) 8.3l(120km/h) </attrib>
            <attrib number="05" >功率:130/6000</attrib>
        </car>
        <car id="2" >
            <attrib number="01" >排量:1600ml</attrib>
            <attrib number="02" >价格:8.98万元</attrib>
            <attrib number="03" >排放:3</attrib>
            <attrib number="04" >油耗:6.1l(90km/h)</attrib>
            <attrib number="05" >功率:68/5800</attrib>
        </car>
        <car id="3" >
            <attrib number="01" >排量:1600ml</attrib>
            <attrib number="02" >价格:11.25万元</attrib>
            <attrib number="03" >排放:3带OBD</attrib>
            <attrib number="04" >油耗:6.0l(90km/h)8.1l(120km/h)</attrib>
            <attrib number="05" >功率:74/6000</attrib>
        </car>
        <car id="4" >
            <attrib number="01" >排量:1997ml</attrib>
            <attrib number="02" >价格:15.38万元</attrib>
            <attrib number="03" >排放:3带OBD</attrib>
            <attrib number="04" >油耗:6.8l(90km/h)</attrib>
            <attrib number="05" >功率:99/6000</attrib>
        </car>
        <car id="5" >
            <attrib number="01" >排量:1600ml</attrib>
            <attrib number="02" >价格:6.58万元</attrib>
            <attrib number="03" >排放:3</attrib>
            <attrib number="04" >油耗:6.5l(90km/h)</attrib>
            <attrib number="05" >功率:65/5600</attrib>
        </car>
        <car id="6" >
            <attrib number="01" >排量:1997ml</attrib>
            <attrib number="02" >价格:16.08万元</attrib>
            <attrib number="03" >排放:4</attrib>
            <attrib number="04" >油耗:7.0l(90km/h)</attrib>
            <attrib number="05" >功率:108/6000</attrib>
        </car>
        <car id="7" >
            <attrib number="01" >排量:1781ml</attrib>
            <attrib number="02" >价格:7.98万元</attrib>
            <attrib number="03" >排放:3</attrib>
            <attrib number="04" >油耗:7.2l(90km/h)</attrib>
            <attrib number="05" >功率:70/5200</attrib>
        </car>
        <car id="8" >
            <attrib number="01" >排量:1984ml</attrib>
            <attrib number="02" >价格:19.58万元</attrib>
            <attrib number="03" >排放:4</attrib>
            <attrib number="04" >油耗:7.1l(90km/h)</attrib>
            <attrib number="05" >功率:85/5400</attrib>
        </car>
    </archive> 

(5)在“SQLEx.pro”文件中添加如下内容:

QT += xml 

(6)运行程序,选择驱动QSQLITE,单击“连接”按钮,弹出如下图所示的主界面:
在这里插入图片描述

当用户在“操作菜单”中选择“删除”子菜单时,弹出如下图所示的“删除汽车记录”对话框。
在这里插入图片描述

4、添加记录功能

(1)Dialog类继承自QDialog类,该类定义了“添加产品”对话框的界面及完成将新加入的记录分别插入汽车制造商表和汽车表,并且将详细的车型信息写入XML文件中的功能。
(2)源文件“editdialog.cpp”的具体代码如下:

#include "editdialog.h" 
#include <QMessageBox> 
int uniqueCarId; 
int uniqueFactoryId; 
Dialog::Dialog(QSqlRelationalTableModel *cars, QSqlTableModel *factory, QDomDocument details,QFile 
*output, QWidget *parent) : QDialog(parent) 
{ 
carModel = cars; //将这些参数保存 
在Dialog类的私有变量中。 
factoryModel = factory; 
carDetails = details; 
outputFile = output; 
QGroupBox *inputWidgetBox = createInputWidgets(); 
QDialogButtonBox *buttonBox = createButtons(); 
//界面布局 
QVBoxLayout *layout = new QVBoxLayout; 
layout->addWidget(inputWidgetBox); 
layout->addWidget(buttonBox); 
setLayout(layout); 
setWindowTitle(tr("添加产品")); 
} 
Dialog::submit()函数的具体代码如下: 
void Dialog::submit() 
{ 
QString factory = factoryEditor->text(); //从界面获取用户输入的制造商名factory 
QString address = addressEditor->text(); 
QString name = carEditor->text(); 
if (factory.isEmpty() || address.isEmpty()||name.isEmpty()) 
{ 
QString message(tr("请输入厂名、厂址和商品名称!")); 
QMessageBox::information(this, tr("添加产品"), message); 
} //如果这三个值中的任意一个为空,则以提示框的形式要求用户重新输入。
else //如果这三个值都不为空,则首先调用findFactoryId()函数在汽车制造商表中查找录入的制造商factory的主键factoryId。 
{
int factoryId = findFactoryId(factory); 
if(factoryId == -1) //如果这三个值都不为空,则首先调用findFactoryId()函数在汽车制造商表中查找录入的制造商factory的主键factoryId。 
{ 
factoryId = addNewFactory(factory,address); 
} 
int carId = addNewCar(name, factoryId); //如果制造商存在,则调用addNewCar()函数在汽车表中插入一条新记录。 
QStringList attribs; 
attribs = attribEditor->text().split(";", QString::SkipEmptyParts); //从attribEditor编辑框中分离出“分号”间隔的各个属性,将它们保存在QStringList列表的attribs中。 
addAttribs(carId, attribs); //将录入的车型信息写入XML文件中 
accept(); 
} 
} 

findFactoryId()函数的具体代码如下:

int Dialog::findFactoryId(const QString &factory) 
{ 
int row = 0; 
while (row < factoryModel->rowCount()) 
{ 
QSqlRecord record = factoryModel->record(row); //检索制造商模型factoryModel中的全部记录。 
if(record.value("manufactory") == factory) //找出与制造商参数匹配的记录。 
return record.value("id").toInt(); //将该记录的主键返回 
else 
row++; 
}
return -1; 
//如果未查询到则返回“-1” 
} 

addNewFactory ()函数的具体代码如下:

int Dialog::addNewFactory(const QString &factory,const QString &address) 
{ 
QSqlRecord record; 
int id = generateFactoryId(); 
//生成一个汽车制造商表的主键值 
/* 在汽车制造商表中插入一条新记录,厂名和地址由参数传入 */ 
QSqlField f1("id", QVariant::Int); 
QSqlField f2("manufactory", QVariant::String); 
QSqlField f3("address", QVariant::String); 
f1.setValue(QVariant(id)); 
f2.setValue(QVariant(factory)); 
f3.setValue(QVariant(address)); 
record.append(f1); 
record.append(f2); 
record.append(f3); 
factoryModel->insertRecord(-1, record); 
return id; 
//返回新记录的主键值 

}
addNewCar()函数与addNewFactory()函数的操作类似,其具体代码如下:

int Dialog::addNewCar(const QString &name, int factoryId) 
{ 
int id = generateCarId(); 
//生成一个汽车表的主键值 
QSqlRecord record; 
/* 在汽车表中插入一条新记录 */ 
QSqlField f1("carid", QVariant::Int); 
QSqlField f2("name", QVariant::String); 
QSqlField f3("factoryid", QVariant::Int); 
QSqlField f4("year", QVariant::Int); 
f1.setValue(QVariant(id)); 
f2.setValue(QVariant(name)); 
f3.setValue(QVariant(factoryId)); 
f4.setValue(QVariant(yearEditor->value())); 
record.append(f1); 
record.append(f2); 
record.append(f3); 
record.append(f4); 
carModel->insertRecord(-1, record); 
return id; 
//返回这条新记录的主键值 
} 

addAttribs()函数实现了将录入的车型信息写入XML文件的功能,其具体代码如下:

void Dialog::addAttribs(int carId, QStringList attribs) 
{ 
/* 创建一个car标签 */ 
QDomElement carNode = carDetails.createElement("car"); 
carNode.setAttribute("id", carId); //将id属性设置为传入的车型主键carId。 
for(int i = 0; i < attribs.count(); i++) //将每一条信息作为子节点插入。 
{ 
QString attribNumber = QString::number(i+1); 
if(i < 10) 
attribNumber.prepend("0"); 
QDomText textNode = carDetails.createTextNode(attribs.at(i)); 
QDomElement attribNode = carDetails.createElement("attrib"); 
attribNode.setAttribute("number", attribNumber); 
attribNode.appendChild(textNode); 
carNode.appendChild(attribNode); 
}
QDomNodeList archive = carDetails.elementsByTagName("archive"); 
archive.item(0).appendChild(carNode); 
if(!outputFile->open(QIODevice::WriteOnly)) //通过输出文件指针outputFile将修改后的文件写回磁盘
{ 
return; 
}
else 
{
QTextStream stream(outputFile); 
archive.item(0).save(stream, 4); 
outputFile->close(); 
} 
} 
revert()函数实现了撤销用户在界面中的录入信息功能,其具体代码如下: 
void Dialog::revert() 
{ 
factoryEditor->clear(); 
addressEditor->clear(); 
carEditor->clear(); 
yearEditor->setValue(QDate::currentDate().year()); 
attribEditor->clear(); 
} 

createInputWidgets()函数实现了输入界面的完成,其具体代码如下:

QGroupBox *Dialog::createInputWidgets()
{
    	QGroupBox *box = new QGroupBox(tr("添加产品"));
    	QLabel *factoryLabel = new QLabel(tr("制造商:"));
    	QLabel *addressLabel = new QLabel(tr("厂址:"));		
    	QLabel *carLabel = new QLabel(tr("品牌:"));
    	QLabel *yearLabel = new QLabel(tr("上市时间:"));
    	QLabel *attribLabel = new QLabel(tr("产品属性 (由分号;隔开):"));
    	factoryEditor = new QLineEdit;
    	carEditor = new QLineEdit;
	addressEditor = new QLineEdit;
    	yearEditor = new QSpinBox;
    	yearEditor->setMinimum(1900);
    	yearEditor->setMaximum(QDate::currentDate().year());
    	yearEditor->setValue(yearEditor->maximum());
    	yearEditor->setReadOnly(false);
    	attribEditor = new QLineEdit;
    	QGridLayout *layout = new QGridLayout;
    	layout->addWidget(factoryLabel, 0, 0);
    	layout->addWidget(factoryEditor, 0, 1);
   	layout->addWidget(addressLabel, 1, 0);
    	layout->addWidget(addressEditor, 1, 1);
    	layout->addWidget(carLabel, 2, 0);
    	layout->addWidget(carEditor, 2, 1);
    	layout->addWidget(yearLabel, 3, 0);
    	layout->addWidget(yearEditor, 3, 1);
    	layout->addWidget(attribLabel, 4, 0, 1, 2);
    	layout->addWidget(attribEditor, 5, 0, 1, 2);
    	box->setLayout(layout);
    	return box;
} 

createButtons()函数完成了按钮的组合功能,其具体代码如下:

QDialogButtonBox *Dialog::createButtons() 
{ 
QPushButton *closeButton = new QPushButton(tr("关闭")); 
QPushButton *revertButton = new QPushButton(tr("撤销")); 
QPushButton *submitButton = new QPushButton(tr("提交")); 
closeButton->setDefault(true); 
connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); 
connect(revertButton, SIGNAL(clicked()), this, SLOT(revert())); 
connect(submitButton, SIGNAL(clicked()), this, SLOT(submit())); 
QDialogButtonBox *buttonBox = new QDialogButtonBox; 
buttonBox->addButton(submitButton, QDialogButtonBox::ResetRole); 
buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole); 
buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole); 
return buttonBox; 
} 

generateFactoryId()函数将全局变量uniqueFactoryId以顺序加1的方式生成一个不重复的主键值,并将其返回供添加操作使用,其具体代码如下:

int Dialog::generateFactoryId() 
{ 
uniqueFactoryId += 1; 
return uniqueFactoryId; 
} 

generateCarId()函数将全局变量uniqueCarId以顺序加1的方式生成一个不重复的主键值,并将其返回供添加操作使用,其具体内容如下:

int Dialog::generateCarId() 
{ 
uniqueCarId += 1; 
return uniqueCarId; 
} 

(3)在源文件“mainwindow.cpp”中添加的代码如下:

#include "editdialog.h" 
extern int uniqueCarId; 
extern int uniqueFactoryId; 

MainWindow::addCar()函数启动了一个添加记录的对话框,具体添加操作由该对话框完成,添加完成后进行显示,其具体实现内容如下:

void MainWindow::addCar() 
{ 
Dialog *dialog = new Dialog(carModel, factoryModel,carData, file, this); 
int accepted = dialog->exec(); 
if(accepted == 1) 
{
int lastRow = carModel->rowCount() -1; 
carView->selectRow(lastRow); 
carView->scrollToBottom(); 
showCarDetails(carModel->index(lastRow, 0)); 
} 
} 

(4)当用户选择“添加”菜单时,弹出如图所示的“添加产品”对话框,在其中输入新添加的汽车品牌信息。
在这里插入图片描述

操作之后,在主界面中就立即能够看到新加入的新品牌汽车的记录信息,如下图所示:
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐