背景知识:
Qt SQL的API分为不同层:
- 驱动层
驱动层 对于QT是基于C++来实现的框架,该层主要包括QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorbase、QSqlDriverPlugin and QSqlResult。这一层提供了特定数据库和SQL API层之间的底层桥梁。
- SQL API层
SQL API层 对于SQL API 层提供了数据库的访问相关类,其中,QSqlDataBase类进行连接,QSqlQuery完成数据库的交互。除此之外,还有QSqlError、QSqlField、QSqlIndex and QSqlRecord类。
- 用户接口层
用户接口层 用户接口层的几个类实现将数据库中的数据链接到窗口部件上,这些类是使用模型/视图框架实现的,他们是更高层次的抽象,主要包括QSqlQureyModel,QSqlTableModel and QSqlRelationalTableModel。
用户接口层的类使用模型/视图框架实现了将数据库中的数据链接到窗口控件上。
QTableView是常用的内容显示视图组件。数据模型类有:QSqlQueryModel 、QSqlTableModel 、QSqlRelationalTableModel
QSqlQueryModel :通过设置SELECT语句查询获取内容,Model数据是只读的,不能进行编辑。
QSqlTableModel : 直接设置一个数据表的名称,可以获取数据表的全部记录,结果是可编辑的。实现对数据的编辑、插入、删除等操作。实现数据的排序和过滤。
QSqlRelationalTableModel: 编辑一个数据表,将代码字段通过关系与代码表关联,将代码字段的编辑转换为直观的内容选择编辑。
QSqlTableModel
直接设置一个数据表的名称,可以获取数据表的全部记录,结果是可编辑的。实现对数据的编辑、插入、删除等操作。实现数据的排序和过滤。
一般是模型使用QSqlTableModel,视图使用QTableView。
优点:
- 简单易用,可以通过model直接操作字段数据
- 支持编辑,直接在视图中添加、修改、删除数据。
- 内置排序和过滤功能
缺点
- 只有用户单张表数据,无法执行自定义的sql语句
- 性能稍差,需要额外处理字段信息和刷新模型
- 不支持事务操作
常用的api函数
//设置数据表的名称,不立即读取数据
virtual void setTable(const QString &tableName);
//从字段获取到字段的索引号
int fieldIndex(const QString &fieldName) const;
//数据表的主索引
QSqlIndex primaryKey() const;
//设置编辑策略
virtual void setEditStrategy(EditStrategy strategy);
enum EditStrategy {OnFieldChange, OnRowChange, OnManualSubmit};
OnFieldChange:字段变化立即更新到数据库
OnRowChange:当前行变换时更新到数据库
OnManualSubmit:所有修改暂时缓存,只有手动调submitAll才保存修改到数据库
bool isDirty() const;//若有未更新到数据库的修改,返回true.
bool submitAll();//提交所有未更新的修改到数据库
void revertAll();//取消所有未更新的修改
QSqlError lastError() const;//获取错误信息
//设置模型的表头
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value,nt role = Qt::EditRole) override;
//模型的行数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
//一条空记录获取字段名,没有值
QSqlRecord record() const;
//获取某行的记录数据
QSqlRecord record(int row) const;
//QSqlRecord的value函数,获取某个字段的值
QVariant value(const QString& name) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
//QModelIndex 类的data函数,从而得到某行某列的数据
inline QVariant data(int role = Qt::DisplayRole) const;或者
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
//QSqlRecord类设置某个字段的值
void setValue(int i, const QVariant& val);
void setValue(const QString& name, const QVariant& val);
//插入列
inline bool insertColumn(int column, const QModelIndex &parent = QModelIndex());
//在某row行前插入数据
inline bool insertRow(int row, const QModelIndex &parent = QModelIndex());
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool insertRecord(int row, const QSqlRecord &record);
//设置某行某列的数据
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
//删除某行记录
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
inline bool removeRow(int row, const QModelIndex &parent = QModelIndex());
//修改某行的值
bool setRecord(int row, const QSqlRecord &record);
//调用其基类QSqlQueryModel的setQuery函数,实现精准过滤,并显示部分字段。
void setQuery(const QSqlQuery &query);
void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
tabModel->QSqlQueryModel::setQuery("select empNo, Name from employee where Name='王五'");
setQuery函数相当于QSqlQuery对象执行了exec函数
//按某一列排序和排序规则,需要调用select函数才生效,实际上是sql上午ORDER BY子句
virtual void setSort(int column, Qt::SortOrder order);
//设置记录数据的过滤条件,过滤的字符串实际上为sql语句where后的字段。调用setFilter后无需调用select函数就可以立即刷新记录
virtual void setFilter(const QString &filter);
//查询数据表的数据,并使用设置的排序和过滤规则。这是查询并显示全部的字段数据
virtual bool select();
//清除释放所有数据
void clear() override;
例子
- 数据库使用SQLite数据库,格式为.db3
- 模型使用QSqlTableModel,视图使用QTableView
- 因为QSqlTableModel可编辑,可使用代理处理编辑操作。
- QSqlTableModel的数据和界面其他控件通过QDataWidgetMapper进行关联
- 这里使用的OnManualSubmit:所有修改暂时缓存,只有手动调submitAll才保存修改到数据库。
打开数据库:
void MainWindow::on_actOpenDB_triggered()
{
QString aFile=QFileDialog::getOpenFileName(this,"选择数据库文件","",
"SQL Lite数据库(*.db *.db3)");
if (aFile.isEmpty()) //选择SQL Lite数据库文件
return;
//打开数据库
DB=QSqlDatabase::addDatabase("QSQLITE"); //添加 SQL LITE数据库驱动
DB.setDatabaseName(aFile); //设置数据库名称
// DB.setHostName();
// DB.setUserName();
// DB.setPassword();
if (!DB.open()) //打开数据库
{
QMessageBox::warning(this, "错误", "打开数据库失败",
QMessageBox::Ok,QMessageBox::NoButton);
return;
}
//打开数据表
openTable();
}
打开数据表、设置表头、选择模型、数据映射、信号槽
void MainWindow::openTable()
{//打开数据表
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(),
QMessageBox::Ok,QMessageBox::NoButton);
return;
}
//字段显示名
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("Height"),Qt::Horizontal,"身高");
tabModel->setHeaderData(tabModel->fieldIndex("Birthday"),Qt::Horizontal,"出生日期");
tabModel->setHeaderData(tabModel->fieldIndex("Mobile"),Qt::Horizontal,"手机");
tabModel->setHeaderData(tabModel->fieldIndex("Province"),Qt::Horizontal,"省份");
tabModel->setHeaderData(tabModel->fieldIndex("City"),Qt::Horizontal,"城市");
tabModel->setHeaderData(tabModel->fieldIndex("Department"),Qt::Horizontal,"部门");
tabModel->setHeaderData(tabModel->fieldIndex("Education"),Qt::Horizontal,"学历");
tabModel->setHeaderData(tabModel->fieldIndex("Salary"),Qt::Horizontal,"工资");
tabModel->setHeaderData(tabModel->fieldIndex("Memo"),Qt::Horizontal,"备注"); //这两个字段不再tableView中显示
tabModel->setHeaderData(tabModel->fieldIndex("Photo"),Qt::Horizontal,"照片");
theSelection=new QItemSelectionModel(tabModel);//关联选择模型
//theSelection当前项变化时触发currentChanged信号
connect(theSelection,SIGNAL(currentChanged(QModelIndex,QModelIndex)),
this,SLOT(on_currentChanged(QModelIndex,QModelIndex)));
//选择行变化时
connect(theSelection,SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
this,SLOT(on_currentRowChanged(QModelIndex,QModelIndex)));
ui->tableView->setModel(tabModel);//设置数据模型
ui->tableView->setSelectionModel(theSelection); //设置选择模型
ui->tableView->setColumnHidden(tabModel->fieldIndex("Memo"),true);//隐藏列
ui->tableView->setColumnHidden(tabModel->fieldIndex("Photo"),true);//隐藏列
//tableView上为“性别”和“部门”两个字段设置自定义代理组件
QStringList strList;
strList<<"男"<<"女";
bool isEditable=false;
delegateSex.setItems(strList,isEditable);
ui->tableView->setItemDelegateForColumn(
tabModel->fieldIndex("Gender"),&delegateSex); //Combbox选择型
strList.clear();
strList<<"销售部"<<"技术部"<<"生产部"<<"行政部";
isEditable=true;
delegateDepart.setItems(strList,isEditable);
ui->tableView->setItemDelegateForColumn(tabModel->fieldIndex("Department"),&delegateDepart); //Combbox选择型
//创建界面组件与数据模型的字段之间的数据映射
dataMapper= new QDataWidgetMapper();
dataMapper->setModel(tabModel);//设置数据模型
dataMapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);//
// dataMapper->setItemDelegate(new QSqlRelationalDelegate(this)); //含有外键的
//界面组件与tabModel的具体字段之间的联系
dataMapper->addMapping(ui->dbSpinEmpNo,tabModel->fieldIndex("empNo"));
dataMapper->addMapping(ui->dbEditName,tabModel->fieldIndex("Name"));
dataMapper->addMapping(ui->dbComboSex,tabModel->fieldIndex("Gender"));
dataMapper->addMapping(ui->dbSpinHeight,tabModel->fieldIndex("Height"));
dataMapper->addMapping(ui->dbEditBirth,tabModel->fieldIndex("Birthday"));
dataMapper->addMapping(ui->dbEditMobile,tabModel->fieldIndex("Mobile"));
dataMapper->addMapping(ui->dbComboProvince,tabModel->fieldIndex("Province"));
dataMapper->addMapping(ui->dbEditCity,tabModel->fieldIndex("City"));
dataMapper->addMapping(ui->dbComboDep,tabModel->fieldIndex("Department"));
dataMapper->addMapping(ui->dbComboEdu,tabModel->fieldIndex("Education"));
dataMapper->addMapping(ui->dbSpinSalary,tabModel->fieldIndex("Salary"));
dataMapper->addMapping(ui->dbEditMemo,tabModel->fieldIndex("Memo"));
// dataMapper->addMapping(ui->dbPhoto,tabModel->fieldIndex("Photo")); //图片无法直接映射
dataMapper->toFirst();//移动到首记录
getFieldNames();//获取字段名称列表,填充ui->groupBoxSort组件
//更新actions和界面组件的使能状态
ui->actOpenDB->setEnabled(false);
ui->actRecAppend->setEnabled(true);
ui->actRecInsert->setEnabled(true);
ui->actRecDelete->setEnabled(true);
ui->actScan->setEnabled(true);
ui->groupBoxSort->setEnabled(true);
ui->groupBoxFilter->setEnabled(true);
}
添加操作
void MainWindow::on_actRecAppend_triggered()
{//添加记录
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,0),2000+tabModel->rowCount()); //自动生成编号
tabModel->setData(tabModel->index(currow,2),"男");
// 插入行时设置缺省值,需要在primeInsert()信号里去处理
}
删除
void MainWindow::on_actRecDelete_triggered()
{//删除当前记录
// tabModel->removeRow(theSelection->currentIndex().row());
QModelIndex curIndex=theSelection->currentIndex();//获取当前选择单元格的模型索引
tabModel->removeRow(curIndex.row()); //删除最后一行
}
保存
void MainWindow::on_actSubmit_triggered()
{//保存修改
bool res=tabModel->submitAll();
if (!res)
QMessageBox::information(this, "消息", "数据保存错误,错误信息\n"+tabModel->lastError().text(),
QMessageBox::Ok,QMessageBox::NoButton);
else
{
ui->actSubmit->setEnabled(false);
ui->actRevert->setEnabled(false);
}
}
取消
void MainWindow::on_actRevert_triggered()
{//取消修改
tabModel->revertAll();
ui->actSubmit->setEnabled(false);
ui->actRevert->setEnabled(false);
}
排序
void MainWindow::on_radioBtnAscend_clicked()
{//升序 tabModel->setSort(ui->comboFields->currentIndex(),Qt::AscendingOrder);
tabModel->select();
}
void MainWindow::on_radioBtnDescend_clicked()
{//降序 tabModel->setSort(ui->comboFields->currentIndex(),Qt::DescendingOrder);
tabModel->select();
}
过滤
void MainWindow::on_radioBtnMan_clicked()
{
tabModel->setFilter(" Gender='男' ");
}
void MainWindow::on_radioBtnWoman_clicked()
{
tabModel->setFilter(" Gender='女' ");
}
void MainWindow::on_radioBtnBoth_clicked()
{
tabModel->setFilter("");
}
或者是直接调用QSqlQueryModel的setQuery函数,这样视图就会按实际查询的字段显示
void MainWindow::on_actPhotoClear_triggered()
{
tabModel->QSqlQueryModel::setQuery("select empNo, Name from employee where Name='王五'");
}