数据库连接、数据模型与界面组件所涉及的类之间的关系如下所示:
数据库类
QSqlDatabase 类用于建立与数据库的连接,QSqlDatabase 对象就表示这种连接。QSqlDatabase 类的功能主要分为三大部分:
1、创建数据库连接,即创建 QSqlDatabase 对象,加载指定类型的数据库驱动。
静态函数 QSqlDatabase::addDatabase()创建 QSqlDatabase 对象:
QSqlDatabase QSqlDatabase::addDatabase(const QString &type,
const QString &connectionName = QLatin1String(defaultConnection))
参数 type 是要连接的数据库类型:
如"QSQLITE"就是指 SQLite 数据库,参数 connectionName 是所创建的数据库连接的名称,如
果不设置这个参数,就采用应用程序默认的数据库连接。使用函数 addDatabase()创建 QSqlDatabase 对象时只是设置了驱动程序名称,设置的 connection Name 会被自动添加到应用程序的数据库连接名称列表。如果成功创建 QSqlDatabase 对象,QSqlDatabase 的函数 isValid()会返回 true。一个 QSqlDatabase 对象用于表示一个数据库连接,一个应用程序里可以创建多个 QSqlDatabase 对象,它们可分别连接一个数据库。使用静态函数 QSqlDatabase::connectionNames()返回应用程序里所有已 创建的数据库连接名称列表。如果一个应用程序里有多个数据库连接,可以使用静态函数 QSqlDatabase::database()通过 connectionName 来引用其中的某个数据库连接。
2、打开数据库,设置需要连接的数据库具体参数,例如数据库名称、用户名、用户密码等,
然后打开数据库。只有打开数据库之后,才可以进行数据查询和修改等操作。
运行函数 QSqlDatabase::addDatabase()只是加载了数据库驱动,创建了数据 库连接,还需要用 QSqlDatabase 的函数 open()打开一个具体的数据库。设置好连接数据库参数后,用函数 open()打开数据库,如果 open()的返回值为 true,表示成功打开数据库。若要关闭数据库,则要使用函数 close()。
3、对数据库进行事务(transaction)操作,获取数据库的一些信息。
cpp
bool MainWindow::openDatabase(QString aFile)
{
DB=QSqlDatabase::addDatabase("QSQLITE","Database1"); //添加 SQLITE数据库驱动
DB.setDatabaseName(aFile); //设置数据库名称
if (!DB.open()) //打开数据库失败
return false;
else
{
openTable(); //打开数据表
return true;
}
}
QSqlTableModel 是一个模型类,它与数据库中的一个数据表关联后就作为该数据表的模型。
QSqlTableModel 类的构造函数定义如下:
QSqlTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase())
创建 QSqlTableModel 对象时需要指定数据库连接,也就是设置一个 QSqlDatabase 对象。如果
不指定数据库连接,就使用应用程序的默认数据库连接。还需要使用 QSqlTableModel 的 setTable()函数设置需要连接的数据表。
函数 select()。这个函数的作用是根据当前设置的排序和过滤规则从数据表查询数据并将 其刷新到数据模型。在使用函数 setTable()设置数据表之后,还需要运行函数 select()才能将数据刷 新到模型。函数 select()的返回值若为 true,则表示数据查询操作成功;若为 false,则表示操作失 败,可以通过函数 lastError()获取错误信息。
cpp
tabModel=new QSqlTableModel(this,DB); //数据模型
tabModel->setTable("employee"); //设置数据表
tabModel->setEditStrategy(QSqlTableModel::OnManualSubmit); //数据保存方式,OnManualSubmit , OnRowChange
tabModel->setSort(tabModel->fieldIndex("empNo"),Qt::AscendingOrder); //排序
if (!(tabModel->select())) //查询数据失败
{
QMessageBox::critical(this, "错误信息",
"打开数据表错误,错误信息:\n"+tabModel->lastError().text());
return;
}
建立View组件与model的模型/视图
为一个 QTableView 组件设置一个 QSqlTableModel 模型后,它们就组成模型/视图结构,可以
显示和编辑数据表的数据。还可以为 QSqlTableModel 模型设置一个 QItemSelectionModel 对象作
为选择模型。在 QTableView 组件中可以使用自定义代理。
cpp
//Model/View结构
ui->tableView->setModel(tabModel); //设置数据模型
selModel=new QItemSelectionModel(tabModel,this); //关联选择模型
ui->tableView->setSelectionModel(selModel); //设置选择模型
建立界面组件与Model的映射
QDataWidgetMapper 类对象要设置一个 QSqlTableModel 模型,然后将数据表的某个字段与界面
上某个组件建立映射,界面组件就可以自动显示这个字段的数据,成为数据感知(data-aware)组件。这样这些界面组件就会自动显示关联字段的数据,并且在组件中修改数据后可以提交到模型。
一般的数值、字符串、备注等类型的字段可以用 QSpinBox、QLineEdit、QPlainTextEdit 等界
面组件作为数据感知组件,但是 BLOB 类型的字段不能直接与某个界面组件建立映射。如图片数据信息,需要单独编写代码实现图片的显示、导入和清除等操作。
QDataWidgetMapper::AutoSubmit:自动提交。当一个界面组件失去输入焦点时,所进行的
修改自动提交到模型。
QDataWidgetMapper 对象指向数据模型的某一行记录并将其作为当前行,函数 currentIndex()
返回当前行号,函数 setCurrentIndex()可以设置当前行号,toFirst()、toPrevious()、toNext()和 toLast() 函数可以移动当前行。QDataWidgetMapper 对象的当前行变化时,设置了映射字段的界面组件会 自动更新显示当前记录的数据
cpp
//创建界面组件与数据模型的字段之间的数据映射
dataMapper= new QDataWidgetMapper(this);
dataMapper->setModel(tabModel); //设置数据模型
dataMapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit); //数据更新方式
//界面组件与数据模型的具体字段之间的联系
dataMapper->addMapping(ui->dbSpinEmpNo,tabModel->fieldIndex("empNo"));
dataMapper->addMapping(ui->dbEditName,tabModel->fieldIndex("Name"));
dataMapper->addMapping(ui->dbComboSex,tabModel->fieldIndex("Gender"));
dataMapper->addMapping(ui->dbEditBirth,tabModel->fieldIndex("Birthday"));
dataMapper->addMapping(ui->dbComboProvince,tabModel->fieldIndex("Province"));
dataMapper->addMapping(ui->dbComboDep,tabModel->fieldIndex("Department"));
dataMapper->addMapping(ui->dbSpinSalary,tabModel->fieldIndex("Salary"));
dataMapper->addMapping(ui->dbEditMemo,tabModel->fieldIndex("Memo"));
dataMapper->toFirst(); //移动到首记录
界面的数据表格上点击 单元格,使数据模型 tabModel 的当前记录发生变化时,dataMapper 的当前行并不会自动变化。因此如果要确保视图组件 tableView 中的当前行和数据映射对象 dataMapper 的当前行是一致,在修改currentIndex发生变化时候,需要使用以下来更新
cpp
dataMapper->setCurrentIndex(current.row()); //更新数据映射的行号
设置字段显示的标题
函数 fieldIndex()。这个函数根据字段名称返回字段的序号。若字段不存在,则返回-1。使用时注意要与数据库的表格字段一致,以防找不到字段
函数 setHeaderData()。这个函数用于设置一个字段的表头数据,一般用于设置字段的显示标题。
bool QSqlQueryModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole)
其中,section 是字段序号;orientation 是方向,对于字段就是 Qt::Horizontal;value 是需要设置的数据;role 是数据的角色,默认为 Qt::EditRole。如果不进行表头设置,在 QTableView 组件里显示表格数据时,会将字段名作为表头。
cpp
tabModel->setHeaderData(tabModel->fieldIndex("empNo"), Qt::Horizontal, "工号");
tabModel->setHeaderData(tabModel->fieldIndex("Name"), Qt::Horizontal, "姓名");
tabModel->setHeaderData(tabModel->fieldIndex("Gender"), Qt::Horizontal, "性别");
tabModel->setHeaderData(tabModel->fieldIndex("Birthday"), Qt::Horizontal, "出生日期");
tabModel->setHeaderData(tabModel->fieldIndex("Province"), Qt::Horizontal, "省份");
tabModel->setHeaderData(tabModel->fieldIndex("Department"), Qt::Horizontal, "部门");
tabModel->setHeaderData(tabModel->fieldIndex("Salary"), Qt::Horizontal, "工资");
tabModel->setHeaderData(tabModel->fieldIndex("Memo"),Qt::Horizontal,"备注");
tabModel->setHeaderData(tabModel->fieldIndex("Photo"),Qt::Horizontal,"照片");
获取记录的字段
QSqlRecord 类记录了数据表的字段信息和一条记录的数据内容,QSqlTableModel 有两种参数
形式的函数 record()可以返回一条记录。
QSqlRecord QSqlTableModel::record() //返回字段定义
QSqlRecord QSqlTableModel::record(int row) //返回字段定义和数据
不带有参数的函数 record()返回的一个 QSqlRecord 对象只有记录的字段定义,没有各字段的
数据,一般用于获取一个数据表的字段定义。带有参数的函数 record()返回行号为 row 的记录,包括记录的字段定义和数据。
cpp
//获取所有字段名称
void MainWindow::getFieldNames()
{
QSqlRecord emptyRec=tabModel->record(); //获取空记录,只有字段名
for (int i=0;i<emptyRec.count();i++)
ui->comboFields->addItem(emptyRec.fieldName(i));
}
添加-修改-插入-删除项
insertRecord() 用于添加或插入记录,函数 setRecord()用于修改记录,删除记录使用的是函数 removeRow()。
在插入记录时,即使是必填字段(例如 Name 字段)没有被赋值,程序也不会出错,因为我们设置的数据模型 tabModel 的编辑策略是 OnManualSubmit。但是在保存修改到数据库时,如果必填字段没有设置数据,则会出现错误。
cpp
void MainWindow::on_actRecAppend_triggered()
{//添加一条记录
使用QSqlRecord
QSqlRecord rec=tabModel->record(); //获取一个空记录,只有字段定义
rec.setValue(tabModel->fieldIndex("empNo"),2000+tabModel->rowCount());
rec.setValue(tabModel->fieldIndex("Gender"),"男"); //设置数据
tabModel->insertRecord(tabModel->rowCount(),rec); //插入到数据模型的最后
selModel->clearSelection();
QModelIndex curIndex=tabModel->index(tabModel->rowCount()-1,1); //创建最后一行的ModelIndex
selModel->setCurrentIndex(curIndex,QItemSelectionModel::Select);
showRecordCount();
项数据模型操作方式,功能相同,但是不符合数据库的操作习惯
// tabModel->insertRow(tabModel->rowCount(),QModelIndex()); //在末尾添加一行
// QModelIndex curIndex=tabModel->index(tabModel->rowCount()-1,1); //创建最后一行的ModelIndex
// theSelection->clearSelection(); //清空选择项
// theSelection->setCurrentIndex(curIndex,QItemSelectionModel::Select); //设置刚插入的行为当前行
// int currow=curIndex.row(); //获得当前行编号
// tabModel->setData(tabModel->index(currow,tabModel->fieldIndex("empNo")),
// 2000+tabModel->rowCount()); //自动生成编号
// tabModel->setData(tabModel->index(currow,tabModel->fieldIndex("Gender")),"男"); //必填字段,需要赋值
// showRecordCount();
// 插入行时设置缺省值,需要在primeInsert()信号里去处理
}
void MainWindow::on_actRecInsert_triggered()
{//插入一条记录
使用QSqlRecord
QModelIndex curIndex=ui->tableView->currentIndex();
QSqlRecord rec=tabModel->record(); //获取一个空记录,只有字段定义
tabModel->insertRecord(curIndex.row(),rec); //在当前行前面插入一条记录
selModel->clearSelection();
selModel->setCurrentIndex(curIndex,QItemSelectionModel::Select); //设置当前行
showRecordCount();
//项数据模型操作方式,功能相同,但是不符合数据库的操作习惯
// QModelIndex curIndex=ui->tableView->currentIndex(); //当前行的模型索引
// tabModel->insertRow(curIndex.row(),QModelIndex()); //插入一行
// theSelection->clearSelection(); //清除已有选择
// theSelection->setCurrentIndex(curIndex,QItemSelectionModel::Select); //选择新插入的一行
// showRecordCount();
}
void MainWindow::on_actRecDelete_triggered()
{//删除当前记录
QModelIndex curIndex=selModel->currentIndex(); //获取当前选择单元格的模型索引
tabModel->removeRow(curIndex.row()); //删除当前行
showRecordCount();
}
保存或取消修改
数据模型 tabModel 的编辑策略为 OnManualSubmit时,即手动提交 修改。当数据模型的数据被修改后,不管是直接修改字段值,还是插入或删除记录,在未提交修改前, tabModel->isDirty()函数都返回 true。保存会将所有对Model进行的增删改同步到数据库.db文件中,取消会撤销对model的增删改操作。
函数 submitAll()用于将数据模型所有未提交的修改保存到数据库,函数 revertAll()取消所有修
改。调用 submitAll()保存数据时如果失败,可以通过函数 lastError()获取错误的具体信息。
cpp
void MainWindow::on_actRevert_triggered()
{//取消修改
tabModel->revertAll();
ui->actSubmit->setEnabled(false);
ui->actRevert->setEnabled(false);
showRecordCount();
}
void MainWindow::on_actSubmit_triggered()
{//保存修改
bool res=tabModel->submitAll();
if (!res)
QMessageBox::information(this, "消息", "数据保存错误,错误信息\n"+tabModel->lastError().text());
else
{
ui->actSubmit->setEnabled(false);
ui->actRevert->setEnabled(false);
}
showRecordCount();
}
遍历数据记录
获取model的count后,使用record(i)即可遍历每一条记录,通过value函数访问每个字段对应的数据,便可遍历修改后保存。
cpp
void MainWindow::on_actScan_triggered()
{//涨工资,记录遍历
if (tabModel->rowCount()==0)
return;
for (int i=0;i<tabModel->rowCount();i++)
{
QSqlRecord aRec=tabModel->record(i); //获取一条记录
float salary=aRec.value("Salary").toFloat();
salary=salary*1.1;
aRec.setValue("Salary",salary); //更新记录数据
tabModel->setRecord(i,aRec); //更新记录
}
// 索引方式刷新记录,速度一样
// float salary;
// for (int i=0;i<tabModel->rowCount();i++)
// {
// salary=tabModel->data(tabModel->index(i,10)).toFloat();
// salary=salary*1.1;
// tabModel->setData(tabModel->index(i,10),salary);
// }
if (tabModel->submitAll())
QMessageBox::information(this, "消息", "涨工资数据计算完毕");
}
记录排序
QSqlTableModel 模型里的记录可以按某个字段排序,对应 SQL 语句中的 ORDER BY 子句。
QSqlTableModel 有 setSort()和 sort()两个函数用于排序。
void QSqlTableModel::setSort(int column, Qt::SortOrder order) //设置排序条件
void QSqlTableModel::sort(int column, Qt::SortOrder order) //立刻排序
参数 column 表示排序字段的字段序号;参数 order 表示排序方式,可设置为升序(Qt::Ascending
Order)或降序(Qt::DescendingOrder)。
这两个函数稍有差别,函数 setSort()只是用于设置排序条件,需要再运行 select()函数才会刷
新数据模型的数据;函数 sort()则是用于根据设置的字段和排序方式直接排序并刷新数据模型,无
须调用 select()函数。
cpp
void MainWindow::on_comboFields_currentIndexChanged(int index)
{//选择字段进行排序
if (ui->radioBtnAscend->isChecked())
tabModel->setSort(index,Qt::AscendingOrder);
else
tabModel->setSort(index,Qt::DescendingOrder);
tabModel->select();
}
void MainWindow::on_radioBtnAscend_clicked()
{//升序排序
tabModel->setSort(ui->comboFields->currentIndex(),Qt::AscendingOrder);
tabModel->select(); //setSort()之后需要执行select()才会刷新数据
}
void MainWindow::on_radioBtnDescend_clicked()
{//降序排序
tabModel->sort(ui->comboFields->currentIndex(),Qt::DescendingOrder);
// tabModel->setSort(ui->comboFields->currentIndex(),Qt::DescendingOrder);
// tabModel->select();
}
记录过滤
QSqlTableModel 的 setFilter()函数可设置记录过滤条件,该函数原型定义如下:
void QSqlTableModel::setFilter(const QString &filter)
字符串类型的参数 filter 是过滤条件,实际上就是 SELECT 语句里 WHERE 子句的条件。
运行 setFilter()函数后无须调用 select()函数就可以立即刷新记录,若要取消过滤,只需在
setFilter()函数里传递一个空字符串。
cpp
void MainWindow::on_radioBtnMan_clicked()
{
tabModel->setFilter(" Gender='男' ");
showRecordCount();
}
void MainWindow::on_radioBtnWoman_clicked()
{
tabModel->setFilter(" Gender='女' ");
showRecordCount();
}
void MainWindow::on_radioBtnBoth_clicked()
{
tabModel->setFilter("");
showRecordCount();
}