11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)

Qt SQL 模块提供数据库编程的支持,Qt 支持多种常见的数据库,如 MySQL、Oracle、MS SQLServer、SQLite 等。Qt SQL 模块包括多个类,可以实现数据库连接、SQL 语句执行、数据获取与界面显示等功能,数据与界面之间使用Model/View架构,从而可以方便地实现数据的界面显示和操作。

文章目录

  • 1. Qt SQL模块概述
    • 1.1 Qt SQL支持的数据库
    • 1.2 SQLite数据库
    • 1.3 Qt SQL 模块的主要类
  • 2. QSqITableModel的使用
    • 2.1 主窗口设计
    • 2.2 打开数据表
      • 2.2.1 数据模型创建与属性设置
      • 2.2.2 表头设置
      • 2.2.3 选择模型及其信号的作用
      • 2.2.4 表示记录的类QSqlRecord
      • 2.2.5 表示字段的类QSqlField
      • 2.2.6 tableView的设置
      • 2.2.7 数据映射
      • 2.2.8 获取数据表的所有字段名称
    • 2.3 添加、插入与删除记录
    • 2.4 保存与取消修改
    • 2.5 设置和清除照片
    • 2.6 数据记录的遍历
    • 2.7 记录排序
    • 2.8 记录过滤
    • 2.9 实例源码

1. Qt SQL模块概述

要在项目中使用 Qt SQL 模块,需在项目配置文件中增加下面一条设置语句:
Qt += sql
在头文件或源程序文件中使用 Qt SQL 模块中的类,可以使用包含语句:
#include
这样会将 Qt SOL 模块中的所有类都包含进去,如果只使用其中的某些类,为避免冗余可以单独包含某个类。

1.1 Qt SQL支持的数据库

Qt SQL 提供了一些常见数据库的驱动,包括网络型数据库,如 Oracle、MS SQL Server等,也包括简单的单机型数据库,如 SQLite。Qt SQL 提供的数据库驱动见《Qt5.9 c++开发指南》中表 11-1。

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第1张图片

亦可参考以下链接:关于数据库的总结

1.2 SQLite数据库

在假设已经了解数据库基本概念和SQL语句基础知识的前提下,此处以SQLite数据库作为实例进行介绍。对于涉及到的基础知识可以参考,简简单单利用Qt实现sqlite的交互实现方法(利用Qt内部封装的类库,将SQLite语句发送给SQLite软件,QSqlDatabase实现数据库连接,QSqlQuery执行指定的SQLite语句),其中开始部分有提到基础知识的学习链接。

  • SQLite 是一种无需服务器、无需进行任何配置的数据库,所有的数据表、索引等数据库元素全都存储在一个文件里,在应用程序里使用 SQLite 数据库就完全可以当作一个文件来使用。SQLite是可以跨平台使用的数据库,在不同平台之间可以随意复制数据库。SQLite 的驱动库文件很小,包含完整功能的驱动可以小到只有 500 KB。
  • SQLite 是一种开源免费使用的数据库,可以从其官网下载最新版本的数据库驱动安装文件。
  • SQLite Expert 是 SQLite 数据库可视化管理工具,可以从其官网下载最新的安装文件,SQLite Expert 安装文件带有 SQLite 数据库驱动,所以安装 SQLite Expert 后无需再下载安装 SQLite 数据库驱动。

在SQLite Expert 软件中创建一个数据库demodb.db3,数据库中建立4个表,本章均使用此数据库作为数据库实例。具体界面如下图所示:

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第2张图片

各个表中的具体含义可以查看《Qt5.9 c++开发指南》,上图中的studInfo表中记录学生的所在学院采用了代码字段 departID,具体的学院名称需要通过查询departments 表中相同的 departID 的记录获得;majorID 记录了专业代码,具体的专业名称需要香找 majors 表中的记录获取。这两个字段都是代码字段,只存储代码,具体的意义需要查询关联数据表的相应记录获得,在实际的数据库设计中经常用到这种设计方式。

Qt SQL中使用QSqlRelationalTableModel 可以很方便地实现这种代码型数据表的显示与编辑。

1.3 Qt SQL 模块的主要类

Qt SQL 提供的主要类的简要功能描述见下图

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第3张图片

QSqlDatabase 用于建立与数据库的连接,一般是先加载需要的数据库驱动,然后设置数据库的登录参数,如主机地址、用户名、登录密码等,如果是单机型数据库,如 SQLite,只需设置数据库文件即可。

数据库的操作一般需要将数据库的内容在界面上进行显示和编辑,Qt 采用 Model/View 结构进行数据库内容的界面显示。QTableView 是常用的数据库内容显示视图组件,用于数据库操作的数据模型类有 QSqlQueryModel、QSqlTableModel 和 QSqlRelationalTableModel,这几个类的继承关系如下图所示:

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第4张图片

  • QSqlQueryModel通过设置 SELECT 语句查询获取数据库的内容但是 QSqlQueryModel 的数据是只读的,不能进行编辑。
  • QSqlTableModel 直接设置一个数据表的名称,可以获取数据表的全部记录,其结果是可编辑的,设置为界面上的QTableView 组件的数据模型后就可以显示和编辑数据
  • QSqlRelationalTableModel 编辑一个数据表,并且可以将代码字段通过关系与代码表关联,将代码字段的编辑转换为直观的内容选择编辑。
  • QSqlQuery 是另外一个经常使用的类,它可以执行任何 SQL 语句,特别是没有返回记录的语句,如UPDATE、INSERT、DELETE 等,通过SQL语句对数据库直接进行编辑修改。

2. QSqITableModel的使用

实例 sampl1_1 使用QSqlTableModel 显示实例数据库 demodb 中 employee 数据表的内容,实现编辑、插入、删除记录的操作,实现数据的排序和记录过滤,还实现 BLOB 类型字段 Photo 中存储照片的显示、导入等操作,运行界面如下图所示。

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第5张图片

上图中,左侧数据表格是一个 QTableView 组件,设置一个 QSqlTableModel 类的变量作为数据模型后就可以显示数据表的内容。右侧的一些编辑框、下拉列表框等。界面组件通过QDataWidgetMapper 类的实例设置为与某个字段关联,自动显示字段的内容。但是没有现成的组件可以通过字段映射显示 Photo 字段中的照片。为此使用一个 QLabel 组件,通过其 pixmap 属性显示图片,所以照片的显示、导入和清除需要单独操作,并且与数据模块的记录移动保持同步,如同将此界面组件与 Photo 字段映射了一样。

QTableView 显示内容有缺省的代理组件,一般是自动使用 QLineEdit 组件。但是对于某些字段期望通过下拉列表框来选择输入,例如“性别”和“部门”这两个字段。为此,还设计了自定义数据代理类,“性别”和“部门”两个字段使用QComboBox 进行编辑。

工具栏上的按钮根据当前状态自动可用或禁用,特别是“保存”和“取消”两个按钮在数据表的内容被修改后自动变为可用,当保存或取消修改后又变为不可用。

2.1 主窗口设计

实例samp11_1是主窗口继承自QMainWindow的应用程序,使用UI设计器可视化设计界面。整体布局如下图所示:
11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第6张图片

以下是主窗口MainWindow的定义(自动生成的界面组件的槽函数进行省略)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include    

#include    
#include    

#include    
#include    

#include    "qwcomboboxdelegate.h"


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    QSqlDatabase  DB;//数据库连接

    QSqlTableModel  *tabModel;  //数据模型

    QItemSelectionModel *theSelection; //选择模型

    QDataWidgetMapper   *dataMapper; //数据映射

    QWComboBoxDelegate   delegateSex; //自定义数据代理,性别
    QWComboBoxDelegate   delegateDepart; //自定义数据代理,部门

    void    openTable();//打开数据表
    void    getFieldNames();//获取字段名称,填充“排序字段”的comboBox
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_currentChanged(const QModelIndex &current, const QModelIndex &previous);

// QTableView的SelectionModel的行发生了变化,进行处理
    void on_currentRowChanged(const QModelIndex &current, const QModelIndex &previous);

......
    
private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

MainWindow 类中定义了几个私有变量。

  • QSqlDatabase DB,用于加载数据库驱动和建立与数据库之间的连接。
  • QSqlTableModel*tabModel,用于指定某一个数据表,作为数据表的数据模型。
  • QItemSelectionModel *theSelection,作为 tabModel 的选择模型,提供 currentChanged()、currentRowChanged()等信号,在 tabModel 选择的字段发生变化、当前记录发生变化时发射信号,以便程序进行响应。例如在 currentChanged()信号发射时,检查 tabModel 是否有数据被修改,从而更新界面上“保存”和“取消”两个按钮的使能状态。
  • QDataWidgetMapper *dataMapper 用于实现界面组件与 tabModel 的字段之间的映射。例如界面上的QLineEdit类型的 dbEditName 组件与数据表的 Name 字段映射,当前记录变化时会自动更新显示当前记录的 Name 字段的数据。
  • QWComboBoxDelegate 是自定义的基于QComboBox 的代理类,delegateSex 和delegateDepart 用作 tableView 中“性别”和“部门”字段的代理组件。

私有函数 openTable()用于打开数据表,getFieldNames()用于获取数据表 employee 的所有字段的名称,并填充界面上“排序字段”后的 ComboBox 组件。

定义了两个槽函数,功能如下:

  • on_currentChanged()用于检查数据表内容是否有修改,从而更新“保存”和“取消”两个按钮的使能状态。
  • on_currentRowChanged()用于记录发生变化时,从新的当前记录里提取Photo字段的内容,

MainWindow的构造函数代码如下,主要是对tableView的一些显示属性的设置

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->setCentralWidget(ui->splitter);

//   tableView显示属性设置
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->tableView->setAlternatingRowColors(true);
}

2.2 打开数据表

工具栏上的“打开”按钮由actOpenDB 生成,单击此按钮时将添加 SQLite 数据库驱动、打开数据库文件、连接 employee 数据表并设置显示属性,并且创建 tableView 显示的代理组件,设置数据源与界面组件的映射等。其响应代码如下:

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();
}

由打开文件对话框选择需要打开的 SQLite 数据库文件,本实例中需要打开 demodb.db3 数据,此文件在第 11 章实例程序总目录下。

私有变量 DB 是 QSqlDatabase 类。QSqlDatabase 用于数据库的操作,包括建立数据库连接,设置登录数据库的参数,打开数据库等。下面的一行语句使用 QSqlDatabase 的静态函数addDatabase()添加 SQLite 数据库的驱动。

DB=QSqlDatabase::addDatabase("QSQLITE");

然后需要使用 QSqlDatabase 的几个函数设置数据库登录参数,setDatabaseName()设置数据库名称,对于SQLite 数据库,就设置为数据库文件。如果是网络型数据库,如 Oracle、MS SQL Server等,还需要使用 setHostName()设置数据库主机名,setUserName()设置数据库用户名,setPassword()设置数据库登录密码。对于SQLite 数据库,只需用 setDatabaseName()设置数据库文件即可。

数据库连接与登录参数设置后,调用 QSlDatabaseopen()函数打开数据库。如果成功打开数据库,再调用自定义函数 openTable()打开数据表 employee,并进行相应的操作。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);
}

函数 openTable()主要是创建 QSqlTableModel 类型的私有变量 tabModel,指定需要打开的数据表为employee,设置与界面显示组件的关联等。QSqlTableModel 设置一个数据表名称后,作为数据表的数据模型,可以方便地编辑一个数据表。
使用的核心的类是 QSqlTableModel,其主要的函数功能见下表 (省略了函数中的 const 关键字,省略了缺省参数)。
11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第7张图片

函数openTable()的代码主要包括以下几部分的功能。

2.2.1 数据模型创建与属性设置

首先创建 QSqlTableModel 类型的私有变量 tabModel,并且在创建时指定了数据库连接,就是前面设置的私有变量 DB,然后用 setTable()函数指定数据表

setSort()函数设置排序字段和排序方式,其函数原型为:

void QSqlTableModel::setSort(int *column*, [Qt::SortOrder](../qtcore/qt.html#SortOrder-enum) *order*)

第1个参数 column 是排序字段的列号;第2个参数 order 是枚举类型 Ot:SortOrder,表示排序方式,取值 Qt:AscendingOrder 表示升序,Qt::DescendingOrder 表示降序。程序中设置为根据EmpNo 字段升序排序,即:

tabModel->setSort(tabModel->fieldIndex("empNo"),Qt::AscendingOrder); //排序

QSqlTableModel::setEditStrategy()函数用于设置编辑策略,参数是枚举类型 QSqlTableModel::EditStrategy,各取值的意义如下。

  • QSqlTableModel::OnFieldChange,字段值变化时立即更新到数据库。

  • QSqlTableModel:: OnRowChange,当前行 (就是记录)变换时更新到数据库。-

  • QSqlTableModel::OnManualSubmit,所有修改暂时缓存,手动调用 submitAll()保存所有修改,或调用revertAll()函数取消所有未保存修改

程序里设置编辑策略为 QSqlTableModel::OnManualSubmit,在修改数据后并不直接提交更新,只是让工具栏上的“保存”和“取消”按钮可用,用户手动提交或取消修改。

对 tabModel 设置这些属性后,调用 select()函数打开数据表,如果打开失败,可以通过 QSqITableModel::lastError()函数获取上一错误信息的文本说明。

2.2.2 表头设置

QSqlTableModel::setHeaderData()函数用于设置每个字段的表头数据,主要是设置显示标题。如果不进行表头设置,在 TableView 里显示时将显示字段名作为表头。为此,将每个字段的显示设置为相应的中文标题。例如,设置“Name”字段的显示标题为“姓名”的代码是:

tabModel->setHeaderData(tabModel->fieldIndex("Name"),Qt::Horizontal,"姓名");

setHeaderData()函数的第 1个参数是字段的序号,通过 fieldIndex()函数可以获取某个字段名在数据模型里的序号,避免直接使用数字时不便于理解和修改的问题。

2.2.3 选择模型及其信号的作用

为数据模型创建一个选择模型的代码如下:

theSelection=new QItemSelectionModel(tabModel);//关联选择模型

选择模型的作用是当用户在 TableView 组件上操作时,获取当前选择的行、列信息,并且在选择的单元格变化时发射currentChanged()信号,在当前行变化时发射currentRowChanged()信号。

为 currentChanged()信号编写槽函数 on_currentChanged(),获取 tabModel->isDirty()函数的值,根据是否有未提交的修改,更新工具栏按钮“保存”或“修改”的使能状态。槽函数on_currentChanged()的代码如下:

void MainWindow::on_currentChanged(const QModelIndex &current, const QModelIndex &previous)
{//更新actPost和actCancel 的状态
    Q_UNUSED(current);
    Q_UNUSED(previous);
    ui->actSubmit->setEnabled(tabModel->isDirty()); //有未保存修改时可用
    ui->actRevert->setEnabled(tabModel->isDirty());
}

为currentRowChanged()信号编写槽函数 on_currentRowChanged(),用于在当前行变化时,从新的记录里提取 Photo 字段的内容,并将图片在 QLabel组件中显示出来。on_currentRowChanged()的代码如下:

void MainWindow::on_currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
{
    Q_UNUSED(previous);
// 行切换时的状态控制
    ui->actRecDelete->setEnabled(current.isValid());
    ui->actPhoto->setEnabled(current.isValid());
    ui->actPhotoClear->setEnabled(current.isValid());

    if (!current.isValid())
    {
        ui->dbLabPhoto->clear(); //清除图片显示
        return;
    }

    dataMapper->setCurrentIndex(current.row()); //更细数据映射的行号

    int curRecNo=current.row();//获取行号
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录

    if (curRec.isNull("Photo"))  //图片字段内容为空
       ui->dbLabPhoto->clear();
    else
    {
        QByteArray data=curRec.value("Photo").toByteArray();
        QPixmap pic;
        pic.loadFromData(data);
        ui->dbLabPhoto->setPixmap(pic.scaledToWidth(ui->dbLabPhoto->size().width()));
    }
}

槽函数on_currentRowChanged()传递的参数current 是行切换后新的当前行的模型索引,首先根据 current 是否有效,更新 3 个Action 的使能状态。若 current 是有效的,更新数据映射 dataMapper的当前行,即:

dataMapper->setCurrentIndex(current.row()); //更细数据映射的行号

这将使界面上的编辑框、下拉列表框等与字段关联的界面组件显示当前记录的内容。

由于没有现成的界面组件可以通过数据映射显示 BLOB 字段内的图片,在此槽函数里通过编程获取 Photo 字段的数据,并将其显示为一个QLabel 组件的 pixmap。

用下面两行代码获取当前行的记录:

    int curRecNo=current.row();//获取行号
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录

curRec 是 QSqlRecord 类型,返回了当前记录的数据,可以获取当前记录的每个字段的内容。程序中它获取 Photo 字段的内容,并将其转换为图片后作为 ui->dbLabPhoto 组件的 pixmap来显示。

2.2.4 表示记录的类QSqlRecord

QSqlRecord 类记录了数据表的字段信息和一条记录(表中一行信息)的数据内容,QSqlTableModel 有两种参数的函数 record()可以返回一条记录。

  • QSqlRecord QSqlTableModel::record(),不带参数的record()函数,返回的一个QSqlRecord对象只有记录的字段定义,但是没有数据。这个函数一般用于获取一个数据表的字段定义。

  • QSqlRecord QSqlTableModel::record(int row),返回行号为 row 的记录,包括记录的字段定义和数据。

QSqlRecord 类封装了对记录的字段定义和数据的操作,其主要函数见下表,(省略了函数参数中的 const 关键字,对于具有不同参数的同名函数,只列出一种参数形式的函数)。
11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第8张图片

QSqlRecord 用于字段操作的函数一般有两种参数形式的同名函数,用字段序号或字段名表示一个字段,如 value()函数返回一个字段的值,有如下两种参数形式的函数:

  • QVariant value(int index),返回序号为index 的字段的值;
  • QVariant value(QString &name),返回字段名称为 name 的字段的值。

2.2.5 表示字段的类QSqlField

QSqlRecord 的 field()函数返回某个字段,返回数据类型是 QSqlField。QSqlField 封装了字段定义信息和数据。字段的定义一般在设计数据表时就固定了,不用在 QSqlRecord 里修改。QSqlRecord 用于字段数据读写的函数见下表 (省略了函数参数中的 const 关键字)。

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第9张图片

2.2.6 tableView的设置

界面上用一个QTableView 组件显示 tabModel 的表格数据内容,设置其数据模型和选择模型,并且将 Memo 和 Photo 两个字段的列设置为隐藏,因为在表格里难以显示备注文字和图片。

为在 tableView 中显示和编辑“性别”和“部门”两个字段,设计了基于QComboBox 的自定义代理组件类 QWComboBoxDelegate。自定义代理组件 QWComboBoxDelegate 的设计方法在第五章中有详细介绍,这里只是稍作改变,增加了一个 setItems()函数,用于初始化下拉列表框和设置是否可以编辑,这样一个QWComboBoxDelegate 可以创建多个代理组件实例,分别用于“性别”和“部门”两个字段。
下面是相对于 5.5 节的QWComboBoxDelegate 修改或增加的部分的代码,其余相同的部分没有列出。

#ifndef QWCOMBOBOXDELEGATE_H
#define QWCOMBOBOXDELEGATE_H

#include    
#include    

class QWComboBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

private:
    QStringList m_ItemList;//选择列表
    bool    m_isEdit; //是否可编辑

public:
    QWComboBoxDelegate(QObject *parent=0);

    void    setItems(QStringList items, bool isEdit);//初始化设置列表内容,是否可编辑
//自定义代理组件必须继承以下4个函数
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const Q_DECL_OVERRIDE;

};

#endif // QWCOMBOBOXDELEGATE_H

两个函数的实现代码如下:

void QWComboBoxDelegate::setItems(QStringList items, bool isEdit)
{
    m_ItemList=items;
    m_isEdit=isEdit;
}

QWidget *QWComboBoxDelegate::createEditor(QWidget *parent,
       const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QComboBox *editor = new QComboBox(parent);
    for (int i=0;i<m_ItemList.count();i++)   //从字符串列表初始下拉列表
        editor->addItem(m_ItemList.at(i));

    editor->setEditable(m_isEdit); //是否可编辑
    return editor;
}

2.2.7 数据映射

QDataWidgetMapper 用于建立界面组件与数据模型之间的映射,可以将界面的 QLineEdit、
QCombobox 等组件与数据模型的一个字段关联起来。

创建 QDataWidgetMapper 类的变量 dataMapper 后,用 setMode()设置关联的数据模型,setSubmitPolicy()函数设置数据提交策略,有自动(AutoSubmit) 和手动 (ManualSubmit)两种方式。addMapping用于设置界面组件与数据模型的列的映射,程序在界面上的各编辑组件与数据表的各字段之间建立了映射关系。Memo 字段可以与一个QPlainTextEdit 的组件映射,但是 Photo 是个 BLOB 字段,没有组件可以直接显示其内容。
QDataWidgetMapper 还有 toFirst()、toPrevious()、toNext()和 toLast()4 个函数用于在记录间移动,setCurrentIndex()和setCurrentModelIndex()可以直接移动到某一行记录,revert()和 submit()用于手工取消或提交当前记录的修改,当提交策略设置为QDataWidgetMapper::AutoSubmit 时,行切换时将自动提交修改。

2.2.8 获取数据表的所有字段名称

自定义函数 getFieldNames()获取数据表的所有字段名称,并填充到界面上的“排序字段”下拉列表框里,用于选择排序字段。getFieldNames()的实现代码如下:

void MainWindow::getFieldNames()
{ //获取所有字段名称
    QSqlRecord  emptyRec=tabModel->record();//获取空记录,只有字段名
    for (int i=0;i<emptyRec.count();i++)
        ui->comboFields->addItem(emptyRec.fieldName(i));
}

利用debug进行调试,查看其中内容

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第10张图片

2.3 添加、插入与删除记录

工具栏上有“添加”“插入”“删除”3 个按用于记录操作。actRecAppend 用于添加记录其槽函数代码如下:

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()信号里去处理
}

QSqlTableModel::insertRow(int row)函数在数据模型的 row 行前面插入一行记录,如果 row 大于或等于数据模型的总行数,则在最后添加一行记录。

actRecInsert 实现在当前行的前面插入一行,其槽函数代码如下:

void MainWindow::on_actRecInsert_triggered()
{//插入记录
    QModelIndex curIndex=ui->tableView->currentIndex();

    tabModel->insertRow(curIndex.row(),QModelIndex());

    theSelection->clearSelection();//清除已有选择
    theSelection->setCurrentIndex(curIndex,QItemSelectionModel::Select);
}

actRecDelete 用于删除当前记录,其槽函数实现代码如下:

void MainWindow::on_actRecDelete_triggered()
{//删除当前记录
    QModelIndex curIndex=theSelection->currentIndex();//获取当前选择单元格的模型索引
    tabModel->removeRow(curIndex.row()); //删除最后一行
}

在插入或删除记录操作,未提交保存之前,tableView 的左侧表头会以标记表示记录编辑状态,“*”表示新插入的记录,“!”表示删除的记录。在保存或取消修改后,这些标记就消失,删除的记录行也从 tableView 里删除。

2.4 保存与取消修改

在打开数据表初始化时,设置数据模型的编辑策略为 OnManualSubmit,即手动提交修改。当数据模型的数据被修改后,不管是直接修改字段值,还是插入或删除记录,在未提交修改前,QSqlTableModel::isDirty()函数返回 true,就是利用这个函数在自定义槽函数 on_currentChanged()里修改actSubmit 和actRevert 两个Action的使能状态

actSubmit 用于保存修改,actRevert 用于取消修改,对应工具栏上的“保存”和“取消”两个按钮,下面是这两个 Action 的槽函数的代码。

void MainWindow::on_actRevert_triggered()
{//取消修改
    tabModel->revertAll();
    ui->actSubmit->setEnabled(false);
    ui->actRevert->setEnabled(false);
}

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);
    }
}

2.5 设置和清除照片

employee 数据表的 Photo 字段是 BLOB 字段,用于存储图片文件。Photo 字段内容的显示已经在自定义槽函数on_currentRowChanged()里实现了,就是在当前记录变化时提取 Photo 字段的内容,并显示为 dbLabPhoto的 pixmap。
actPhoto 和 actPhotoClear 是设置照片和清除照片的两个 Action,与工具栏上的“设置照片”和“清除照片”按钮关联。actPhoto 的槽函数代码如下:

void MainWindow::on_actPhoto_triggered()
{
//设置照片
    QString aFile=QFileDialog::getOpenFileName(this,"选择图片文件","","照片(*.jpg)");
    if (aFile.isEmpty())
       return;

    QByteArray data;
    QFile* file=new QFile(aFile); //fileName为二进制数据文件名
    file->open(QIODevice::ReadOnly);
    data = file->readAll();
    file->close();

    int curRecNo=theSelection->currentIndex().row();
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录
    curRec.setValue("Photo",data); //设置字段数据
    tabModel->setRecord(curRecNo,curRec);

    QPixmap pic;
    pic.load(aFile);  //在界面上显示
    ui->dbLabPhoto->setPixmap(pic.scaledToWidth(ui->dbLabPhoto->width()));
}

代码功能是用文件对话框选择一个图片文件,读取文件内容到 QByteArray 类型的变量 data里,获取当前记录到变量 curRec 后,用 QSqlRecord 的 setValue()函数为 Photo 字段设置数据为 data,然后用 setRecord()函数更新当前记录。

    curRec.setValue("Photo",data); //设置字段数据
    tabModel->setRecord(curRecNo,curRec);

这里的更新只是更新到数据模型,并没有更新到数据库。

actPhotoClear 用于清除照片,其槽函数实现代码如下:

void MainWindow::on_actPhotoClear_triggered()
{
    int curRecNo=theSelection->currentIndex().row();
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录

    curRec.setNull("Photo");//设置为空值
    tabModel->setRecord(curRecNo,curRec);

    ui->dbLabPhoto->clear();
}

获取当前记录到变量 curRec 后,调用 setNull()函数将 Photo 字段设置为 NULL,就是清除了字段的内容,然后更新记录到数据模型。

2.6 数据记录的遍历

工具栏上的“涨工资”按钮用于将数据表内所有记录的 salary 字段的内容增加 10%,演示了记录遍历的功能。actScan 实现此按钮的功能,其槽函数实现代码如下:

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;irowCount();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, "消息", "涨工资计算完毕",
                             QMessageBox::Ok,QMessageBox::NoButton);
}

2.7 记录排序

QSqlTableModel 的 setSort()函数设置数据表根据某个字段按照升序或降序排列,实际上就是设置了SQL语句里的ORDER BY子句。
在打开数据库时,已经调用 getFicldNames()函数将数据表的所有字段名添加到界面上的comboFields 下拉列表框了。“排序字段”下拉列表框、“升序”和“降序”两个 RadioButton 的相应操作的槽函数实现按选择字段排序。

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();
}

void MainWindow::on_radioBtnDescend_clicked()
{//降序
    tabModel->setSort(ui->comboFields->currentIndex(),Qt::DescendingOrder);
    tabModel->select();
}

在调用 setSort()函数设置排序规则后,需要调用 QSqlTableModel::select()重新读取数据表的数据才会使排序规则生效。

2.8 记录过滤

QSqlTableModel 的 setFilter()函数设置记录过滤条件,实际上就是设置了 SQL 语句里的 WHERE字句。
实例里演示针对 Gender 字段设置字段过滤条件,窗口界面上“数据过滤”分组框里有 3个RadioButton,分别为“男”“女”“全显示”,3 个按钮的 clicked()信号的槽函数实现代码如下:

void MainWindow::on_radioBtnMan_clicked()
{
    tabModel->setFilter(" Gender='男' ");
//    tabModel->select();
}

void MainWindow::on_radioBtnWoman_clicked()
{
    tabModel->setFilter(" Gender='女' ");
//    tabModel->select();
}

void MainWindow::on_radioBtnBoth_clicked()
{
    tabModel->setFilter("");
}

调用 setFilter()后无需调用 select()函数就可以立即刷新记录,若要取消过滤条件,只需在setFilter()函数里传递一个空字符串。

2.9 实例源码

(1)程序框架

11-1_Qt 5.9 C++开发指南_数据库(Qt中数据库模块介绍;SQLite数据库;QSqITableModel直接设置一数据表名称,可获取数据表全部记录,其结果是可编辑,与对应View配合)_第11张图片

(2)qwcomboboxdelegate.h

#ifndef QWCOMBOBOXDELEGATE_H
#define QWCOMBOBOXDELEGATE_H

#include    
#include    

class QWComboBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

private:
    QStringList m_ItemList;//选择列表
    bool    m_isEdit; //是否可编辑

public:
    QWComboBoxDelegate(QObject *parent=0);

    void    setItems(QStringList items, bool isEdit);//初始化设置列表内容,是否可编辑
//自定义代理组件必须继承以下4个函数
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const Q_DECL_OVERRIDE;

    void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const Q_DECL_OVERRIDE;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const Q_DECL_OVERRIDE;
};

#endif // QWCOMBOBOXDELEGATE_H

(3)qwcomboboxdelegate.cpp

#include "qwcomboboxdelegate.h"

#include    

QWComboBoxDelegate::QWComboBoxDelegate(QObject *parent):QStyledItemDelegate(parent)
{

}

void QWComboBoxDelegate::setItems(QStringList items, bool isEdit)
{
    m_ItemList=items;
    m_isEdit=isEdit;
}

QWidget *QWComboBoxDelegate::createEditor(QWidget *parent,
       const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QComboBox *editor = new QComboBox(parent);
    for (int i=0;i<m_ItemList.count();i++)   //从字符串列表初始下拉列表
        editor->addItem(m_ItemList.at(i));

    editor->setEditable(m_isEdit); //是否可编辑
    return editor;
}

void QWComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QString str = index.model()->data(index, Qt::EditRole).toString();

    QComboBox *comboBox = static_cast<QComboBox*>(editor);
    comboBox->setCurrentText(str);
}

void QWComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QComboBox *comboBox = static_cast<QComboBox*>(editor);
    QString str = comboBox->currentText();
    model->setData(index, str, Qt::EditRole);
}

void QWComboBoxDelegate::updateEditorGeometry(QWidget *editor,
                const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index);
    editor->setGeometry(option.rect);
}

(4)mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include    

#include    
#include    

#include    
#include    

#include    "qwcomboboxdelegate.h"


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    QSqlDatabase  DB;//数据库连接

    QSqlTableModel  *tabModel;  //数据模型

    QItemSelectionModel *theSelection; //选择模型

    QDataWidgetMapper   *dataMapper; //数据映射

    QWComboBoxDelegate   delegateSex; //自定义数据代理,性别
    QWComboBoxDelegate   delegateDepart; //自定义数据代理,部门

    void    openTable();//打开数据表
    void    getFieldNames();//获取字段名称,填充“排序字段”的comboBox
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_currentChanged(const QModelIndex &current, const QModelIndex &previous);

// QTableView的SelectionModel的行发生了变化,进行处理
    void on_currentRowChanged(const QModelIndex &current, const QModelIndex &previous);

///
    void on_actOpenDB_triggered();


    void on_actRecAppend_triggered();

    void on_actRecInsert_triggered();

    void on_actRevert_triggered();

    void on_actSubmit_triggered();

    void on_actRecDelete_triggered();

    void on_actPhoto_triggered();

    void on_actPhotoClear_triggered();

    void on_radioBtnAscend_clicked();

    void on_radioBtnDescend_clicked();

    void on_radioBtnMan_clicked();

    void on_radioBtnWoman_clicked();

    void on_radioBtnBoth_clicked();

    void on_comboFields_currentIndexChanged(int index);

    void on_actScan_triggered();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

(5)mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include    
#include    

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::getFieldNames()
{ //获取所有字段名称
    QSqlRecord  emptyRec=tabModel->record();//获取空记录,只有字段名
    for (int i=0;i<emptyRec.count();i++)
        ui->comboFields->addItem(emptyRec.fieldName(i));
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->setCentralWidget(ui->splitter);

//   tableView显示属性设置
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->tableView->setAlternatingRowColors(true);
//    ui->tableView->resizeColumnsToContents();
//    ui->tableView->horizontalHeader()->setStretchLastSection(true);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_currentChanged(const QModelIndex &current, const QModelIndex &previous)
{//更新actPost和actCancel 的状态
    Q_UNUSED(current);
    Q_UNUSED(previous);
    ui->actSubmit->setEnabled(tabModel->isDirty()); //有未保存修改时可用
    ui->actRevert->setEnabled(tabModel->isDirty());
}

void MainWindow::on_currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
{
    Q_UNUSED(previous);
// 行切换时的状态控制
    ui->actRecDelete->setEnabled(current.isValid());
    ui->actPhoto->setEnabled(current.isValid());
    ui->actPhotoClear->setEnabled(current.isValid());

    if (!current.isValid())
    {
        ui->dbLabPhoto->clear(); //清除图片显示
        return;
    }

    dataMapper->setCurrentIndex(current.row()); //更细数据映射的行号

    int curRecNo=current.row();//获取行号
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录

    if (curRec.isNull("Photo"))  //图片字段内容为空
       ui->dbLabPhoto->clear();
    else
    {
        QByteArray data=curRec.value("Photo").toByteArray();
        QPixmap pic;
        pic.loadFromData(data);
        ui->dbLabPhoto->setPixmap(pic.scaledToWidth(ui->dbLabPhoto->size().width()));
    }
}

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::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_actRecInsert_triggered()
{//插入记录
    QModelIndex curIndex=ui->tableView->currentIndex();

    tabModel->insertRow(curIndex.row(),QModelIndex());

    theSelection->clearSelection();//清除已有选择
    theSelection->setCurrentIndex(curIndex,QItemSelectionModel::Select);
}

void MainWindow::on_actRevert_triggered()
{//取消修改
    tabModel->revertAll();
    ui->actSubmit->setEnabled(false);
    ui->actRevert->setEnabled(false);
}

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_actRecDelete_triggered()
{//删除当前记录
    QModelIndex curIndex=theSelection->currentIndex();//获取当前选择单元格的模型索引
    tabModel->removeRow(curIndex.row()); //删除最后一行
}

void MainWindow::on_actPhoto_triggered()
{
//设置照片
    QString aFile=QFileDialog::getOpenFileName(this,"选择图片文件","","照片(*.jpg)");
    if (aFile.isEmpty())
       return;

    QByteArray data;
    QFile* file=new QFile(aFile); //fileName为二进制数据文件名
    file->open(QIODevice::ReadOnly);
    data = file->readAll();
    file->close();

    int curRecNo=theSelection->currentIndex().row();
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录
    curRec.setValue("Photo",data); //设置字段数据
    tabModel->setRecord(curRecNo,curRec);

    QPixmap pic;
    pic.load(aFile);  //在界面上显示
    ui->dbLabPhoto->setPixmap(pic.scaledToWidth(ui->dbLabPhoto->width()));
}

void MainWindow::on_actPhotoClear_triggered()
{
    int curRecNo=theSelection->currentIndex().row();
    QSqlRecord  curRec=tabModel->record(curRecNo); //获取当前记录

    curRec.setNull("Photo");//设置为空值
    tabModel->setRecord(curRecNo,curRec);

    ui->dbLabPhoto->clear();
}

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='男' ");
//    tabModel->select();
}

void MainWindow::on_radioBtnWoman_clicked()
{
    tabModel->setFilter(" Gender='女' ");
//    tabModel->select();
}

void MainWindow::on_radioBtnBoth_clicked()
{
    tabModel->setFilter("");
}

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_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;irowCount();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, "消息", "涨工资计算完毕",
                             QMessageBox::Ok,QMessageBox::NoButton);
}

你可能感兴趣的:(#,Qt,5.9,C++开发指南,数据库,qt,c++)