当前位置:嗨网首页>书籍在线阅读

09-打开数据表

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

11.2.3 打开数据表

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

void MainWindow::on_actOpenDB_triggered()
{//打开数据库
   QString aFile=QFileDialog::getOpenFileName(this,"选择数据库文件","",
                      "SQLite数据库(*.db *.db3)");
   if (aFile.isEmpty())
     return;
   DB=QSqlDatabase::addDatabase("QSQLITE"); //添加 SQLITE数据库驱动
   DB.setDatabaseName(aFile); //设置数据库名称
   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()设置数据库文件即可。

数据库连接与登录参数设置后,调用QSqlDatabase::open()函数打开数据库。如果成功打开数据库,再调用自定义函数openTable()打开数据表employee,并进行相应的操作。openTable()函数代码如下:

void MainWindow::openTable()
{//打开数据表
   tabModel=new QSqlTableModel(this,DB);//数据表
   tabModel->setTable("employee"); //设置数据表
   tabModel->setSort(tabModel->fieldIndex("EmpNo"),Qt::AscendingOrder); 
   tabModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
   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,  "备注"); 
   tabModel->setHeaderData(tabModel->fieldIndex("Photo"),   Qt::Horizontal,  "照片"); 
   theSelection=new QItemSelectionModel(tabModel);//选择模型
   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); 
   strList.clear();
   strList<<"销售部"<<"技术部"<<"生产部"<<"行政部";
   isEditable=true;
   delegateDepart.setItems(strList,isEditable);
   ui->tableView->setItemDelegateForColumn( 
         tabModel->fieldIndex("Department"),&delegateDepart); 
//创建界面组件与数据模型的字段之间的数据映射
   dataMapper= new QDataWidgetMapper();
   dataMapper->setModel(tabModel);//设置数据模型
   dataMapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
//界面组件与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->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,其主要的函数功能见表11-7(省略了函数中的const关键字,省略了缺省参数)。

表11-7 QSqlTableModel类的主要函数

| 函数 | 功能描述 | | :----- | :----- | :----- | :----- | | QSqlDatabase database() | 返回其数据库连接 | | void setTable(QString &tableName) | 设置数据表名称,不立即读取记录,但会提取字段信息 | | QString tableName() | 返回设置的数据表名称 | | void setFilter(QString &filter) | 设置记录过滤条件 | | void setSort(int column, Qt::SortOrder order) | 设置排序字段和排序规则,需调用select()才生效 | | void sort(int column, Qt::SortOrder order) | 按列号column和排序规则立即进行排序并获取数据 | | void setEditStrategy(EditStrategy strategy) | 设置编辑策略 | | bool setHeaderData(int section, Qt::Orientation orientation, QVariant &value,) | 设置表头,一般用于设置字段的显示名称 | | bool isDirty() | 若有未更新到数据库的修改,就返回true;否则返回false | | int fieldIndex(QString &fieldName) | 根据字段名称返回其在模型中的字段序号,若字段不存在则返回-1 | | QSqlIndex primaryKey() | 返回数据表的主索引 | | int rowCount() | 返回记录条数 | | bool select() | 查询数据表的数据,并使用设置的排序和过滤规则 | | bool selectRow(int row) | 刷新获取指定行号的记录 | | void clear() | 清除数据模型,释放所有获取的数据 | | QSqlRecord record() | 返回一条空记录,只有字段名,可用来获取字段信息 | | QSqlRecord record(int row) | 返回行号为row的一条记录,包含记录的数据 | | bool setRecord(int row, QSqlRecord &values) | 更新一条记录的数据到数据模型,源和目标之间通过字段名称匹配,而不是按位置匹配 | | bool insertRecord(int row, QSqlRecord &record) | 在行号row之前插入一条记录 | | bool insertRows(int row, int count) | 在行号row之前插入count空行,编辑策略为OnFieldChange 或OnRowChange时只能插入一行 | | bool removeRows(int row, int count) | 从行号row开始,删除count行。若编辑策略为OnManualSubmit,需调用submitAll()才从数据表里删除 | | void revertRow(int row) | 取消行号为row的记录的修改 | | void revert() | 编辑策略为OnRowChange或OnFieldChange时,取消当前行的修改,对 OnManualSubmit编辑策略无效 | | void revertAll() | 取消所有未提交的修改 | | bool submit() | 提交当前行的修改到数据库,对OnManualSubmit编辑策略无效 | | bool submitAll() | 提交所有未更新的修改到数据库,若成功则返回true;否则返回false,错误的详细信息可以从lastError()获取 |

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

1.数据模型创建与属性设置

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

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

void QSqlTableModel::setSort(int column, Qt::SortOrder order)

第1个参数column是排序字段的列号;第2个参数order是枚举类型Qt::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()函数打开数据表,如果打开失败,可以通过QSqlTableModel::lastError()函数获取上一错误信息的文本说明。

2.表头设置

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

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

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

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)
{//更新actSubmit和actRevert的状态
   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-><em>setCurrentIndex</em>(current.row());

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

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

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

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

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

4.表示记录的类QSqlRecord

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

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

QSqlRecord类封装了对记录的字段定义和数据的操作,其主要函数见表11-8(省略了函数参数中的const关键字,对于具有不同参数的同名函数,只列出一种参数形式的函数)。

表11-8 QSqlRecord类的主要函数

| 函数原型 | 功能描述 | | :----- | :----- | :----- | :----- | | void clear() | 清除记录的所有字段定义和数据 | | void clearValues() | 清除所有字段的数据,将字段数据内容设置为null | | bool contains(QString &name) | 判断记录是否含有名称为name的字段 | | bool isEmpty() | 若记录里没有字段,返回true;否则返回false | | int count() | 返回记录的字段个数 | | QString fieldName(int index) | 返回序号为index的字段的名称 | | int indexOf(QString &name) | 返回字段名称为name的字段的序号,如果字段不存在,返回-1 | | QSqlField field(QString &name) | 返回字段名称为name的字段对象 | | QVariant value(QString &name) | 返回字段名称为name的字段的值 | | void setValue(QString &name, QVariant &val) | 设置字段名称为name的字段的值为val | | bool isNull(const QString &name) | 判断字段名称为name的字段数据是否为null | | void setNull(const QString &name) | 设置名称为name的字段的值为null |

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

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

5.表示字段的类QSqlField

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

表11-9 QSqlField类的主要函数

| 接口函数 | 功能描述 | | :----- | :----- | :----- | :----- | | void clear() | 清除字段数据,设置为NULL。如果字段是只读的,则不清除 | | bool isNull() | 判断字段值是否为NULL | | bool setReadOnly(bool readOnly) | 设置一个字段为只读,只读的字段不能用setValue()函数设置值,也不能用clear()函数清除值 | | QVariant value() | 返回字段的值 | | void setValue(QVariant &value) | 设置字段的值 |

6.tableView的设置

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

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

下面是相对于5.5节的QWComboBoxDelegate修改或增加的部分的代码,其余相同的部分没有列出。

class QWComboBoxDelegate : public QStyledItemDelegate
{
   Q_OBJECT
private:
   QStringList  m_ItemList;//选择列表
   bool   m_isEdit; //是否可编辑
public:
   void   setItems(QStringList items, bool isEdit);//初始化设置列表内容
   QWidget  *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
};

下面是两个函数的实现代码:

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;iaddItem(m_ItemList.at(i));
   editor->setEditable(m_isEdit); //是否可编辑
   return editor;
}

7.数据映射

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

创建QDataWidgetMapper类的变量dataMapper后,用setModel()设置关联的数据模型,setSubmitPolicy()函数设置数据提交策略,有自动(AutoSubmit)和手动(ManualSubmit)两种方式。addMapping()用于设置界面组件与数据模型的列的映射,程序在界面上的各编辑组件与数据表的各字段之间建立了映射关系。Memo字段可以与一个QPlainTextEdit的组件映射,但是Photo是一个BLOB字段,没有组件可以直接显示其内容。

QDataWidgetMapper还有toFirst()、toPrevious()、toNext()和toLast() 4个函数用于在记录间移动,setCurrentIndex()和setCurrentModelIndex()可以直接移动到某一行记录,revert()和submit()用于手工取消或提交当前记录的修改,当提交策略设置为QDataWidgetMapper::AutoSubmit时,行切换时将自动提交修改。

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

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

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

这里使用了QSqlTableModel::record()函数获取一个空的记录,包括数据表的所有字段信息。

所有这些初始化完成后,更新窗口上相关Actions的使能状态。