正点原子 发表于 2021-7-19 12:40:54

《I.MX6U嵌入式Qt开发指南》第十三章 数据库

本帖最后由 正点原子 于 2021-8-11 12:28 编辑

1)实验平台:正点原子i.MX6ULL Linux阿尔法开发板
2)章节摘自【正点原子】《I.MX6U嵌入式Qt开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=603672744434
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子Linux技术交流群:1027879335   








第十三章 数据库
数据库是什么?简易言之,就是保存数据的文件。可以存储大量数据,包括插入数据、更新数据、截取数据等。用专业术语来说,数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。
什么时候需要数据库?在嵌入式里,存储大量数据,或者记录数据,就需要用到数据库。举个简单的例子,比如手机的闹钟就使用到了数据库,我们设置的闹钟数据将会保存到数据库里,闹钟程序运行时会从数据库里读取出上次保存的闹钟数据。如果没有数据库,则闹钟程序关机了数据不保存在物理储存设备里,下次运行闹钟时就没有上次设置的闹钟数据,这显然是不合理的。所以我们需要用到数据库。
本章认为读者已经基本了解数据库,已经对数据库有一定的认识,如果没有对数据库了解,请自行学习,毕竟本书是讲Qt的,不是讲数据库,数据库知识很多,而我们只是讲解Qt怎么去用数据库,对数据库的简单操作!目的就是在Qt里使用数据库!
想要在项目中使用Qt SQL模块,需要在项目配置文件里添加如下语句。
QT       += core gui sql


13.1 Qt SQL简介
       Qt SQL模块为数据库提供了编程支持,Qt支持很多种常见的数据库,如MySQL、Oracle、MS SQL Server、SQLite等。Qt SQL模块里包含了很多个类,可以轻松实现数据库的连接、执行SQL语句,获取数据库里的数据与界面显示等功能,一般数据与界面之间会采用Model/View架构,从而很方便的数据界面显示和操作数据库。
在嵌入式里,一般常用的数据库就是Sqlite3。SQLite 是非常小的,是轻量级的,完全配置时小于 400KiB,省略可选功能配置时小于250KiB。SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库不一样,您不需要在系统中配置。就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 可以直接访问其存储文件。
本章主要对Sqlite3进行实验。需要用其他数据库的请自行学习,在我们正点原子里Linux开发板里就是用sqlite3,文件系统里不提供其他数据库类型。嵌入式一般是用sqlite3,如需要其他类型数据库,请自行移植与学习!
13.2 应用实例
       本章不讲解数据库中的语法,本书认为读者是已经数据库语法有一定了解的了,请知悉!详细声明请看本章前言!本章前言。
       Model(模型),复杂的事情往往可以简单化,Qt提供了QSqlDatabase类用于建立数据库的连接,往往以指定加载的数据库驱动,然后设置数据库的登录参数,如主机地址,用户名、登录密码等。这些都是服务器类型的数据库所需要做的操作。恰好单机型(本地数据库类型)的Sqlite3数据库就不需要设置登录参数就可以方便的打开数据库进行操作了。在QSqlDatabase连接数据库后,用QSqlTableModel从数据库里读取出表格模型,然后通过Qt的QTableView类显示数据库的内容在我们面前。需要对数据库的数据进行修改可以使用QSqlQuery,或者直接修改QSqlTableModel对象,修改里面的模型数据即可!Qt对数据库的基本操作流程大概是这样子,当然Qt提供了很多操作数据库的类,我们只讲解基本的与常用的就已经足够了。下面用个图示来再对上面的操作深入了解一下。

13.2.1 实用闹钟(非QTableView显示)
一般显示数据库表格会使用QTableView显示,但是QTableView适合专业看数员看且适用于对界面操作要求不高的开发人员看。如果直接用这种表格展示给一般用户看,估计用户看数据得头皮发麻。本例就如本章开头所说,结合数据库开发一个闹钟实例,记录新建的闹钟数据,可以对闹钟进行增、删、改等操作。注意(闹钟不做响铃操作设计。可后期使用本例自行开发,本例主要讲解不使用QTableView如何对数据库表格的操作)。本小节开发的闹钟实例很好理解,它与手机的闹钟操作基本一模一样,读者理解起来不会很吃力。本例程序篇幅过长,请注意,我们只需要关注mainwindow.h和mainwindow.cpp这两个文件即可!其他的.h和.cpp文件是编者为了界面的好看参考了一些博客而设计的程序。主要实现了数字选择器的功能和闹钟开关按钮的功能,因为Qt C++里它本身没有这种好看的控件,所以得自行设计。由于篇幅过长,数字选择器与闹钟开关的代码不作分析(有兴趣自行分析),我们可以直接将它们当作普通的控件来用即可!重点是mainwindow.h和mainwindow.cpp里的数据库操作。编者写这个例子都要好长时间,希望读者不要一口吃个胖老虎,急于求成,本例界面看似简单,可能大多数读者可能对数据库并不是很了解!理解这个例子时,编者担心是在看天书一样!抓住我们想要理解的重点即可!不必要每句都去理解!
本例目的:了解不使用QTableView的情况下,也能把数据表完好展示在用户面前。
例17_sqlite_alarm,实用闹钟(难度:很难【为什么定义为很难,编者认为大多数读者对数据库没有一定的了解】)。项目路径为Qt/2/17_sqlite_alarm。
项目文件17_sqlite_alarm文件第一行添加的代码部分如下。
17_sqlite_alarm.pro编程后的代码
1   QT       += core gui sql
2
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4
5   CONFIG += c++11
6
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10# deprecated API in order to know how to port your code away from it.
11DEFINES += QT_DEPRECATED_WARNINGS
12
13# You can also make your code fail to compile if it uses deprecated APIs.
14# In order to do so, uncomment the following line.
15# You can also select to disable deprecated APIs only up to a certain version of Qt.
16#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18SOURCES += \
19      main.cpp \
20      mainwindow.cpp \
21      numberpicker.cpp \
22      switchbutton.cpp
23
24HEADERS += \
25      mainwindow.h \
26      numberpicker.h \
27      switchbutton.h
28
29# Default rules for deployment.
30qnx: target.path = /tmp/${TARGET}/bin
31else: unix:!android: target.path = /opt/${TARGET}/bin
32!isEmpty(target.path): INSTALLS += target
33
34RESOURCES += \
35      res.qrc
在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码
   /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   17_sqlite_example
    * @brief         mainwindow.h
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-15
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3
4   #include <QSqlDatabase>
5   #include <QSqlQuery>
6   #include <QMainWindow>
7   #include <QDialog>
8   #include <QHBoxLayout>
9   #include <QVBoxLayout>
10#include <QPushButton>
11#include <QListWidget>
12#include <QLabel>
13#include <QTime>
14#include <QSqlTableModel>
15#include "numberpicker.h"
16#include "switchbutton.h"
17
18class NumberPicker;
19class SwitchButton;
20
21/* ListWiget项结构体 */
22struct ItemObjectInfo {
23      /* 闹钟开关 */
24      SwitchButton *switchButton;
25      /* Widget容器 */
26      QWidget *widget;
27      /* 水平布局 */
28      QHBoxLayout *hBoxLayout;
29};
30
31
32class MainWindow : public QMainWindow
33{
34      Q_OBJECT
35
36public:
37      MainWindow(QWidget *parent = nullptr);
38      ~MainWindow();
39
40private:
41
42      /* 数据库连接类 */
43      QSqlDatabase sqlDatabase;
44
45      /* 数据库操作模型 */
46      QSqlTableModel *model;
47
48      /* 时针选择器 */
49      NumberPicker *hourPicker;
50
51      /* 分钟选择器 */
52      NumberPicker *minutePicker;
53
54      /* 弹出选择时间对话框 */
55      QDialog *alarmDialog;
56
57      /* 水平布局 */
58      QHBoxLayout *hBoxLayout;
59
60      /* 垂直布局 */
61      QVBoxLayout *vBoxLayout;
62
63      /* 显示闹钟列表 */
64      QListWidget *listWidget;
65
66      /* 主Widget */
67      QWidget *mainWidget;
68
69      /* 底部Wiget */
70      QWidget *bottomWidget;
71
72      /* 弹出对话框布局窗口选择时间容器 */
73      QWidget *timeWidget;
74
75      /* 弹出对话框布局窗口按钮容器 */
76      QWidget *btWidget;
77
78      /* 添加闹钟按钮 */
79      QPushButton *addAlarm;
80
81      /* 确认按钮 */
82      QPushButton *yesButton;
83
84      /* 取消按钮 */
85      QPushButton *cancelButton;
86
87      /* listWiget项信息存储 */
88      QVector<ItemObjectInfo> itemObjectInfo;
89
90private slots:
91      /* 添加闹钟按钮被点击 */
92      void addAlarmClicked();
93
94      /* 列表被点击 */
95      void listWidgetItemClicked(QListWidgetItem *);
96
97      /* 确认按钮被点击 */
98      void yesButtonClicked();
99
100   /* 取消按钮被点击 */
101   void cancelButtonClicked();
102
103   /* 开关按钮点击 */
104   void switchButtonClicked(bool);
105 };
106 #endif // MAINWINDOW_H
      头文件主要声明布局用的类和数据库,重要关注是QSqlDatabase和QSqlTableModel。这里声明的是全局变量。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码
   /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   17_sqlite_example
    * @brief         mainwindow.cpp
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-15
    *******************************************************************/
1   #include "mainwindow.h"
2   #include <QDebug>
3   #include <QSqlError>
4
5   MainWindow::MainWindow(QWidget *parent)
6       : QMainWindow(parent)
7   {
8       /* 设置主窗体的显示位置与大小 */
9       this->setGeometry(0, 0, 800, 480);
10
11      /* 查看本机可用的数据库驱动 */
12      QStringList drivers = QSqlDatabase::drivers();
13      foreach(QString driver, drivers) {
14          qDebug()<<driver;
15      }
16
17      /* 以QSQLITE驱动方式打开或者创建数据库 */
18      sqlDatabase = QSqlDatabase::addDatabase("QSQLITE");
19      sqlDatabase.setDatabaseName("alarm.db");
20      /* 以open的方式打开alarm.db数据库,则会创建一个alarm.db */
21      if (!sqlDatabase.open())
22          qDebug()<<"连接数据库错误"<<sqlDatabase.lastError()<<endl;
23      else
24          qDebug()<<"连接数据库成功"<<endl;
25
26      QSqlQuery query(sqlDatabase);
27      /* 使用指令式创建表 */
28      query.exec("create table alarm (id int primary key, time vchar(15), flag vchar(5))");
29      /* 以指令的方式插入数据 */
30      //query.exec("insert into alarm values(0, '06:00', 'false')");
31
32      model = new QSqlTableModel(this, sqlDatabase);
33
34      /* 模型设置表的名字,需要与数据库的表的名字相同 */
35      model->setTable("alarm");
36
37      /* 如果有修改则同步修改到数据库,
38       * 注意这个规则需要与tabview这样的控件才生效,
39       * 因为tabview可以直接编辑表里的内容 */
40      model->setEditStrategy(QSqlTableModel::OnFieldChange);
41
42      /* 成功则返回true,查看数据库里是否有alarm这个表格 */
43      model->select();
44
45      /* 如果数据表数据为空,则添加两个闹钟 */
46      if (model->rowCount() == 0) {
47          /* 插入一行 */
48          model->insertRow(model->rowCount());
49          /* 在该行插入数据 */
50          model->setData(model->index(0, 0), 1);
51          model->setData(model->index(0, 1), "06:00");
52          model->setData(model->index(0, 2), "false");
53          /* 插入数据后记得提交 */
54          model->submit();
55
56          /* 再插入一行 */
57          model->insertRow(model->rowCount());
58          model->setData(model->index(1, 0), 2);
59          model->setData(model->index(1, 1), "18:00");
60          model->setData(model->index(1, 2), "true");
61          /* 提交 */
62          model->submit();
63      }
64
65      hourPicker = new NumberPicker(this);
66      hourPicker->setRange(0, 24);
67
68      minutePicker = new NumberPicker(this);
69      minutePicker->setRange(0, 60);
70
71      /* 标签,用于显示时&分 */
72      QLabel *label;
73      label = new QLabel();
74      label = new QLabel();
75      label = new QLabel();
76
77      QFont font;
78      font.setBold(true);
79      font.setPixelSize(10);
80      QPalette pal;
81      pal.setBrush(QPalette::WindowText, QColor(0, 0, 0));
82
83      label->setFont(font);
84      label->setFont(font);
85      label->setFont(font);
86
87      label->setText(" ");
88      label->setText("时");
89      label->setText("分");
90
91      /* 主布局初始化 */
92      listWidget = new QListWidget();
93      mainWidget = new QWidget();
94      bottomWidget = new QWidget();
95      alarmDialog = new QDialog(this);
96      timeWidget = new QWidget();
97      btWidget = new QWidget();
98      addAlarm = new QPushButton();
99      yesButton = new QPushButton();
100   cancelButton = new QPushButton();
101   vBoxLayout = new QVBoxLayout();
102   vBoxLayout = new QVBoxLayout();
103   hBoxLayout = new QHBoxLayout();
104   hBoxLayout = new QHBoxLayout();
105   hBoxLayout = new QHBoxLayout();
106
107   addAlarm->setMaximumSize(84, 84);
108   addAlarm->setObjectName("addAlarm");
109   addAlarm->setMinimumSize(84, 84);
110   bottomWidget->setMinimumHeight(84);
111   bottomWidget->setMaximumHeight(84);
112   yesButton->setText("确认");
113   cancelButton->setText("取消");
114   yesButton->setMaximumSize(100, 50);
115   yesButton->setMinimumSize(100, 50);
116   cancelButton->setMinimumSize(100, 50);
117   cancelButton->setMaximumSize(100, 50);
118   btWidget->setMaximumHeight(70);
119   btWidget->setMinimumHeight(70);
120   alarmDialog->setMinimumSize(300, 300);
121   alarmDialog->setMaximumSize(300, 300);
122   alarmDialog->setModal(true);
123   yesButton->setObjectName("yesButton");
124   cancelButton->setObjectName("cancelButton");
125
126   /* 主布局 */
127   vBoxLayout->addWidget(listWidget);
128   vBoxLayout->addWidget(bottomWidget);
129   vBoxLayout->setContentsMargins(0, 0, 0, 0);
130
131   mainWidget->setLayout(vBoxLayout);
132
133   setCentralWidget(mainWidget);
134
135   /* 底部按钮布局 */
136   hBoxLayout->addWidget(addAlarm);
137   hBoxLayout->setContentsMargins(0, 0, 0, 0);
138   bottomWidget->setLayout(hBoxLayout);
139
140   /* 对话框布局 */
141   vBoxLayout->addWidget(timeWidget);
142   vBoxLayout->addWidget(btWidget);
143   vBoxLayout->setContentsMargins(0, 0, 0, 0);
144   alarmDialog->setLayout(vBoxLayout);
145
146   hBoxLayout->addWidget(label);
147   hBoxLayout->addWidget(hourPicker);
148   hBoxLayout->addWidget(label);
149   hBoxLayout->addWidget(minutePicker);
150   hBoxLayout->addWidget(label);
151   hBoxLayout->setContentsMargins(0, 0, 0, 0);
152   timeWidget->setLayout(hBoxLayout);
153
154   hBoxLayout->addWidget(yesButton);
155   hBoxLayout->addWidget(cancelButton);
156
157   btWidget->setLayout(hBoxLayout);
158
159   /* 打印出闹钟数据库里的信息 */
160   for (int i = 0; i < model->rowCount(); i++) {
161         for (int j = 0; j < 3; j++) {
162             QModelIndex qindex = model->index(i, j);
163             switch (j) {
164             case 0:
165               qDebug()<<"第"<<model->data(qindex).toInt()<<"行数据";
166               break;
167             case 1:
168               listWidget->addItem(model->data(qindex).toString());
169               qDebug()<<"闹钟时间为:"<<model->data(qindex).toString();
170               break;
171             case 2:
172               qDebug()<<"闹钟状态为:"
173                        <<model->data(qindex).toString()<<endl;
174               if (model->data(qindex).toString() != "true")
175                     listWidget->item(i)
176                           ->setTextColor(QColor(22, 22, 22, 60));
177               else
178                     listWidget->item(i)
179                           ->setTextColor(QColor(22, 22, 22, 225));
180               break;
181             default:
182               break;
183             }
184         }
185   }
186
187   /* 在列表里添加闹钟开关 */
188   for (int i = 0; i < model->rowCount(); i++) {
189         ItemObjectInfo info;
190         info.widget = new QWidget();
191         info.switchButton = new SwitchButton();
192         info.hBoxLayout = new QHBoxLayout();
193         info.switchButton->setMaximumSize(55, 30);
194         info.switchButton->setMinimumSize(55, 30);
195         info.hBoxLayout->setContentsMargins(0, 0, 0, 0);
196         info.hBoxLayout->setAlignment(Qt::AlignRight);
197         info.hBoxLayout->addWidget(info.switchButton);
198         info.widget->setLayout(info.hBoxLayout);
199         listWidget->setItemWidget(listWidget->item(i),
200                                 info.widget);
201         itemObjectInfo.append(info);
202
203         /* 连接信号槽 */
204         connect(info.switchButton,
205               SIGNAL(toggled(bool)),
206               this,
207               SLOT(switchButtonClicked(bool)));
208
209         /* 获取数据库里的闹钟开关状态 */
210         QModelIndex qindex = model->index(i, 2);
211         if (model->data(qindex).toBool())
212             /* 设置列表里的闹钟开关按钮状态 */
213             info.switchButton->setToggle(true);
214   }
215
216   /* 按钮 */
217   connect(addAlarm, SIGNAL(clicked()), this,
218             SLOT(addAlarmClicked()));
219
220   connect(yesButton, SIGNAL(clicked()), this,
221             SLOT(yesButtonClicked()));
222
223   connect(cancelButton, SIGNAL(clicked()), this,
224             SLOT(cancelButtonClicked()));
225
226   /* 列表 */
227   connect(listWidget,
228             SIGNAL(itemClicked(QListWidgetItem*)),
229             this,
230             SLOT(listWidgetItemClicked(QListWidgetItem*)));
231 }
232
233 MainWindow::~MainWindow()
234 {
235   /* 关闭数据库 */
236   sqlDatabase.close();
237 }
238
239 void MainWindow::addAlarmClicked()
240 {
241   /* 选择时间对话框里显示当前系统时间 */
242   hourPicker->setValue(QTime::currentTime().hour());
243   minutePicker->setValue(QTime::currentTime().minute());
244
245   /* 取消按钮显示文本为"取消" */
246   cancelButton->setText("取消");
247
248   /* 如果是点击添加闹钟的按钮,则设置闹钟列表的索引index为-1 */
249   listWidget->setCurrentRow(-1);
250
251   /* 显示对话框 */
252   alarmDialog->show();
253 }
254
255 void MainWindow::listWidgetItemClicked(QListWidgetItem *item)
256 {
257   /* 从被点击项里获取闹钟数据 */
258   QStringList list =
259             listWidget->item(listWidget->row(item))->text().split(":");
260
261   /* 选择时间对话框里显示被选择项的时间 */
262   hourPicker->setValue(list.at(0).toInt());
263   minutePicker->setValue(list.at(1).toInt());
264
265   /* 取消按钮显示文本为"删除" */
266   cancelButton->setText("删除");
267
268   /* 显示闹钟选择对话框 */
269   alarmDialog->show();
270
271   /* 作用使其失去选择 */
272   listWidget->clearSelection();
273 }
274
275 void MainWindow::yesButtonClicked()
276 {
277   /* 获取数值选择值的数据,转为字符串 */
278   QString hour;
279   QString minute;
280
281   if (hourPicker->readValue() < 10)
282         hour = "0" + QString::number(hourPicker->readValue()) + ":";
283   else
284         hour = QString::number(hourPicker->readValue()) + ":";
285
286   if (minutePicker->readValue() < 10)
287         minute = "0" + QString::number(minutePicker->readValue());
288   else
289         minute = QString::number(minutePicker->readValue());
290
291   /* 如果不是选中闹钟列表的数据 */
292   if (listWidget->currentRow() == -1) {
293         /* 插入一行数据,闹钟时间为选择的闹钟时间 */
294         int row = model->rowCount();
295
296         /* 插入数据到数据库 */
297         model->insertRow(row);
298         model->setData(model->index(row, 0), row + 1);
299         model->setData(model->index(row, 1), hour + minute);
300         model->setData(model->index(row, 2), "true");
301         model->submit();
302
303         /* 添加闹钟到列表 */
304         listWidget->addItem(hour + minute);
305
306         /* 添加到容器 */
307         ItemObjectInfo info;
308         info.widget = new QWidget();
309         info.switchButton = new SwitchButton();
310         info.hBoxLayout = new QHBoxLayout();
311         info.switchButton->setMaximumSize(55, 30);
312         info.switchButton->setMinimumSize(55, 30);
313         info.hBoxLayout->setContentsMargins(0, 0, 0, 0);
314         info.hBoxLayout->setAlignment(Qt::AlignRight);
315         info.hBoxLayout->addWidget(info.switchButton);
316         info.widget->setLayout(info.hBoxLayout);
317         info.switchButton->setToggle(true);
318
319         /* 连接信号槽 */
320         connect(info.switchButton, SIGNAL(toggled(bool)), this,
321               SLOT(switchButtonClicked(bool)));
322
323         listWidget->setItemWidget(
324                     listWidget->item(listWidget->count() - 1),
325                     info.widget);
326         itemObjectInfo.append(info);
327   } else {
328         /* 修改数据(更新闹钟数据) */
329         int row =listWidget->currentRow();
330         model->setData(model->index(row, 0), row + 1);
331         model->setData(model->index(row, 1), hour + minute);
332         model->setData(model->index(row, 2), "true");
333         model->submit();
334
335         /* 设置当前项的闹钟文本 */
336         listWidget->currentItem()->setText(hour + minute);
337   }
338
339   /* 再确保提交 */
340   if (model->isDirty())
341         model->submitAll();
342
343   /* 关闭对话框 */
344   alarmDialog->close();
345 }
346
347 void MainWindow::cancelButtonClicked()
348 {
349   if (cancelButton->text() == "删除") {
350         /* 删除数据库整一行数据 */
351         model->removeRow(listWidget->currentRow());
352         model->submit();
353         itemObjectInfo.remove(listWidget->currentRow());
354         listWidget->takeItem(listWidget->currentRow());
355   }
356
357   /* 再确保提交 */
358   if (model->isDirty())
359         model->submitAll();
360
361   /* 关闭对话框 */
362   alarmDialog->close();
363 }
364
365
366 /* 当点击闹钟开关时,将闹钟开关状态同步更新到数据库里 */
367 void MainWindow::switchButtonClicked(bool checked)
368 {
369   listWidget->clearSelection();
370
371   SwitchButton *button = (SwitchButton *)sender();
372   for (int i = 0; i < itemObjectInfo.count(); i++) {
373         if (button == itemObjectInfo.at(i).switchButton) {
374             if (checked) {
375               model->setData(model->index(i, 2), "true");
376               listWidget->item(i)
377                         ->setTextColor(QColor(22, 22, 22, 225));
378             } else {
379               model->setData(model->index(i, 2), "false");
380               listWidget->item(i)
381                         ->setTextColor(QColor(22, 22, 22, 60));
382             }
383
384             model->submit();
385             break;
386         }
387   }
388 }
      第5~231行,数据库的连接、建立模型和界面布局等。界面布局这些不再详细说,这些在前面章节已经讲过很多次。在这部分代码里,我们发现没有用到QTableView来展示我们的闹钟数据,原因很简单,因为我们的界面需要适合大众眼光,而不是展示一个表格,应该展示一个闹钟列表,进而编者设计使用了QListWidget这个控件。恰好与手机里的闹钟的列表相似。
      12~15行,查看本地主机可用的数据库驱动。一般Qt 安装时都会自带Sqlite3驱动。注意了,如果本地主机没有可用的数据库,则实验不可操作!不可生搬硬套到其他开发板子测试。所以查看本地主机的数据库操作是调试时候必须的!
      18~24行,添加一个数据库,以QSQLITE驱动方式打开或者连接名字为alarm.db的数据库文件。数据库存储的形式为一个alarm.db文件。
      26~28行,在数据库里创建一个名字为alarm的表格。如果已经创建,也会覆盖这个表格名字,但是不会覆盖表格内容。必须先创建表格,才可以对表格的数据进行操作(增删查减等)。
      32~43行,新建模型model,使用通过setTable()设置的表中的数据填充模型,使用指定的过滤器和排序条件,如果成功返回true;否则返回false。注意:调用select()将恢复任何未提交的更改,并删除任何插入的列。
      46~63行,编者在这里判断,如果是刚运行该程序,发现数据库表中没有数据,则默认设置两行闹钟数据,数据ID为1,闹钟时间为06:00,状态为关;另一条是数据ID为2,闹钟时间为18:00,状态为开。到这里我们就已经学会在数据库里插入数据,记得插入数据后需要手动执行submit()函数,表示提交。不提交是不会记录保存到数据库里的。
      328~336行,直接设置数据库里的行数据,即可覆盖该行数据的内容。
      347~363行,model对象直接移除某一行的数据就完成删除数据库里某行内容。注意可以移除一行或者一列,闹钟数据是以每行记录保存,所以这里是移除一行。移除之后记得提交。
      其他的内容都是一些逻辑与界面设计的内容,重点讲解的是Qt对数据库操作的步骤。其他内容请根据源码的注释理解即可。或者运行程序去理解本例逻辑。
main      .cpp内容如下,主要是加载qss样式文件。
1   #include "mainwindow.h"
2
3   #include <QApplication>
4   #include <QFile>
5
6   int main(int argc, char *argv[])
7   {
8       QApplication a(argc, argv);
9       /* 指定文件 */
10      QFile file(":/style.qss");
11
12      /* 判断文件是否存在 */
13      if (file.exists() ) {
14          /* 以只读的方式打开 */
15          file.open(QFile::ReadOnly);
16          /* 以字符串的方式保存读出的结果 */
17          QString styleSheet = QLatin1String(file.readAll());
18          /* 设置全局样式 */
19          qApp->setStyleSheet(styleSheet);
20          /* 关闭文件 */
21          file.close();
22      }
23
24      MainWindow w;
25      w.show();
26      return a.exec();
27}
      style.qss样式文件如下。素材已经在源码处提供。注意下面的style.qss不能有注释!
1   QListWidget {
2   font-size: 30px;
3   outline:none;
4   }
5
6   QListWidget::item:active {
7   background: transparent;
8   }
9
10QListWidget::item {
11height:80;
12}
13
14QListWidget::item:selected:hover {
15background:#22222222;
16}
17
18QListWidget::item:selected {
19background:transparent;
20color:#ee222222;
21}
22
23QPushButton#addAlarm {
24border-image:url(:/icons/addalarm1.png);
25background:transparent;
26outline: none;
27}
28
29QPushButton#addAlarm:hover {
30border-image:url(:/icons/addalarm2.png);
31}
32
33QPushButton#yesButton {
34border: 1px solid #22222222;
35border-radius: 25px;
36background:#22222222;
37outline:none;
38}
39
40QPushButton#yesButton:pressed {
41background:#44222222;
42color:white;
43}
44
45QPushButton#cancelButton {
46border: 1px solid #22222222;
47border-radius: 25px;
48background:#22222222;
49outline:none;
50}
51
52QPushButton#cancelButton:pressed {
53background:#44222222;
54color:white;
55}
56
57QScrollBar:vertical {
58width:30px;
59background:rgba(255, 255, 255, 100%)
60}
61
62QScrollBar::handle:vertical {
63width:30px;
64background:rgba(200, 200, 200, 20%);
65border-radius:15px;
66}
67
68QScrollBar::add-line:vertical {
69width:0px; height:0px;
70}
71QScrollBar::sub-line:vertical {
72width:0px;
73height:0px;
74}
75QScrollBar::handle:vertical:hover {
76width:30px;
77background:rgba(200, 200, 200, 80%);
78border-radius:15px;
79}
80QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical {
81background:rgba(255, 255, 255, 100%)
82}

      其中数字选择器与闹钟开关按钮的代码,代码编者参考了一些博客优化后写成的代码。有兴趣可以细读代码,不作为本章注释讲解的代码。
      数字选择器的作用是选择闹钟的时间,效果如下,通过上下滑动可以选择数字作为时钟的时针和分针数据。通过点击对话框确认后存储到数据库里。

      数字选择器的头文件“numberpicker.h”代码如下。
/******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   NumberPicker
    * @brief         numberpicker.h
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-14
    *******************************************************************/
1   #ifndef NUMBERPICKER_H
2   #define NUMBERPICKER_H
3
4   #include <QMainWindow>
5   #include <QPropertyAnimation>
6
7   class NumberPicker : public QWidget
8   {
9       Q_OBJECT
10
11      Q_PROPERTY(int deviation READ readDeviation WRITE setDeviation )
12public:
13      NumberPicker(QWidget *parent = nullptr);
14      ~NumberPicker();
15
16      /* 设置最大值与最小值的范围 */
17      void setRange(int min, int max);
18
19      /* 读取当前值 */
20      int readValue();
21
22protected:
23      void mousePressEvent(QMouseEvent *);
24
25      void mouseMoveEvent(QMouseEvent *);
26
27      void mouseReleaseEvent(QMouseEvent *);
28
29      void wheelEvent(QWheelEvent *);
30
31      void paintEvent(QPaintEvent *);
32
33public:
34      /* 描绘数字 */
35      void paintNum(QPainter &painter, int num, int deviation);
36
37      /* 使选中的数字回到屏幕中间 */
38      void homing();
39
40      /* 鼠标移动偏移量,默认为0 */
41      int readDeviation();
42
43      /* 设置偏移量 */
44      void setDeviation(int n);
45
46      /* 设置字体大小 */
47      void setNumSize(int);
48
49      /* 设置间隔大小 */
50      void setInterval(int);
51
52      /* 设置分格数量,一般设置为3、5、7... */
53      void setDevide(int);
54
55      /* 设置数字颜色,设置rgb的数值 */
56      void setNumberColor(QRgb rgb);
57
58      /* 设置当前值 */
59      void setValue(int value);
60
61signals:
62
63      void currentValueChanged(int value);
64
65      void deviationChange(int deviation);
66
67private:
68      /* 最小值 */
69      int minRange;
70
71      /* 最大值 */
72      int maxRange;
73
74      /* 当前选中的值 */
75      int currentValue;
76
77      /* 鼠标是否按下 */
78      bool isDragging;
79
80      /* 偏移量,记录鼠标按下后移动的垂直距离 */
81      int deviation;
82
83      /* 鼠标按下的垂直位置 */
84      int mouseSrcPos;
85
86      /* 数字大小 */
87      int numSize;
88
89      /* 动画 */
90      QPropertyAnimation *homingAni;
91
92      /* 间隔大小 */
93      int interval;
94
95      /* 分格数量 */
96      int devide;
97
98      /* 数字颜色 */
99      QColor numberColor;
100 };
101 #endif // NUMBERPICKER_H

      数字选择器的源文件“numberpicker.cpp”代码如下。
/******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   NumberPicker
    * @brief         numberpicker.cpp
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-14
    *******************************************************************/
1   #include <QMouseEvent>
2   #include <QDebug>
3   #include "numberpicker.h"
4   #include <QPainter>
5
6   NumberPicker::NumberPicker(QWidget *parent) :
7       /* 最小值默认为0 */
8       minRange(0),
9
10      /* 最大值默认60 */
11      maxRange(60),
12
13      /* 当前值默认0 */
14      currentValue(0),
15
16      /* 按下标志位为假 */
17      isDragging(false),
18
19      /* 默认偏移量为0 */
20      deviation(0),
21
22      /* 数值越大 */
23      numSize(15),
24
25      /* 间隔为1 */
26      interval(1),
27
28      /* 默认分成3格 */
29      devide(3),
30
31      /* 默认颜色黑色 */
32      numberColor(0, 0, 0)
33{
34      setParent(parent);
35      setMinimumSize(50, 150);
36      homingAni = new QPropertyAnimation(this, "deviation");
37      homingAni->setDuration(300);
38      homingAni->setEasingCurve(QEasingCurve::OutQuad);
39}
40
41NumberPicker::~NumberPicker()
42{
43
44}
45
46void NumberPicker::setRange(int min, int max)
47{
48      minRange = min;
49      maxRange = max;
50      if (currentValue < min) {
51          currentValue = min;
52      }
53      if (currentValue > max) {
54          currentValue = max;
55      }
56      repaint();
57}
58
59int NumberPicker::readValue()
60{
61      return currentValue;
62}
63
64void NumberPicker::mousePressEvent(QMouseEvent *e)
65{
66      homingAni->stop();
67      isDragging = true;
68      mouseSrcPos = e->pos().y();
69      QWidget::mousePressEvent(e);
70}
71
72void NumberPicker::mouseMoveEvent(QMouseEvent *e)
73{
74      if (isDragging){
75          deviation = e->pos().y() - mouseSrcPos;
76
77          /* 若移动速度过快,则进行限制 */
78          if (deviation > (height() - 1) / devide) {
79            deviation = (height() - 1) / devide;
80          } else if (deviation < -(height() - 1) / devide) {
81            deviation = -( height() - 1) / devide;
82          }
83
84          emit deviationChange(deviation / ((height() - 1) / devide));
85          repaint();
86      }
87}
88
89void NumberPicker::mouseReleaseEvent(QMouseEvent *)
90{
91      if (isDragging) {
92          isDragging = false;
93          homing();
94      }
95}
96
97void NumberPicker::wheelEvent(QWheelEvent *e)
98{
99      if (e->delta() > 0) {
100         deviation = (this->height() - 1) / devide;
101   } else {
102         deviation = -(this->height() - 1) / devide;
103   }
104
105   homing();
106   repaint();
107 }
108
109 void NumberPicker::paintEvent(QPaintEvent *)
110 {
111   QPainter painter(this);
112   painter.setRenderHint(QPainter::Antialiasing, true);
113   int Height = height() - 1;
114
115   if (deviation >= Height / devide && currentValue > minRange ) {
116         mouseSrcPos += Height / devide;
117         deviation -= Height / devide;
118         currentValue -= interval;
119   }
120
121   if (deviation <= -Height / devide && currentValue < maxRange ) {
122         mouseSrcPos -= Height / devide;
123         deviation += Height / devide;
124         currentValue += interval;
125   }
126
127   if (qAbs(int(currentValue)) >= int(maxRange))
128         currentValue = minRange;
129
130   paintNum(painter, qAbs(int(currentValue + maxRange) % maxRange),
131            deviation);
132
133   paintNum(painter,
134            qAbs((currentValue - interval + maxRange) % maxRange),
135            deviation - Height / devide);
136
137   paintNum(painter,
138            qAbs((currentValue + interval + maxRange) % maxRange),
139            deviation + Height / devide);
140
141   for (int i = 2; i <= devide / 2; ++i) {
142         if (qAbs(currentValue - interval * i) >= minRange) {
143             paintNum(painter,
144                      qAbs((currentValue - interval * i + maxRange)
145                           % maxRange),
146                      deviation - Height / devide * i);
147         }
148
149         if (qAbs(currentValue + interval * i) <= maxRange) {
150             paintNum(painter,
151                      qAbs((currentValue + interval * i + maxRange)
152                           % maxRange),
153                      deviation + Height / devide * i);
154         }
155   }
156 }
157
158 void NumberPicker::paintNum(QPainter &painter, int num, int deviation)
159 {
160   int Width = width() - 1;
161   int Height = height() - 1;
162
163   /* 偏移量越大,数字越小 */
164   //int size = (Height - qAbs(deviation)) / numSize;
165   int size = (Height - qAbs(deviation)) * numSize / 80;
166   int transparency = 255 - 255 * qAbs(deviation) / Height;
167   int height = Height / devide;
168   int y = Height / 2 + deviation - height / 2;
169
170   QFont font;
171   font.setPixelSize(size);
172   painter.setFont(font);
173   painter.setPen(QColor(numberColor.red(),
174                           numberColor.green(),
175                           numberColor.blue(),
176                           transparency));
177
178   if ( y >= 0 && y + height < Height) {
179         //painter.drawRect(0, y, Width, height);
180         if (num < 10)
181             painter.drawText(QRectF(0, y, Width, height),
182                              Qt::AlignCenter,
183                              "0" + QString::number(num, 'f', 0));
184         else
185             painter.drawText(QRectF(0, y, Width, height),
186                              Qt::AlignCenter,
187                              QString::number(num, 'f', 0));
188   }
189 }
190
191 void NumberPicker::homing()
192 {
193   if (deviation > height() / 10) {
194         homingAni->setStartValue((height() - 1 ) / 8 - deviation);
195         homingAni->setEndValue(0);
196         currentValue -= interval;
197   } else if (deviation > -height() / 10) {
198         homingAni->setStartValue(deviation);
199         homingAni->setEndValue(0);
200   } else if (deviation < -height() / 10) {
201         homingAni->setStartValue(-(height() - 1) / 8 - deviation);
202         homingAni->setEndValue(0);
203         currentValue += interval;
204   }
205
206   emit currentValueChanged(currentValue);
207   homingAni->start();
208 }
209
210 int NumberPicker::readDeviation()
211 {
212   return deviation;
213 }
214
215 void NumberPicker::setDeviation(int n)
216 {
217   deviation = n;
218   repaint();
219 }
220
221 void NumberPicker::setNumSize(int size)
222 {
223   numSize = size;
224   repaint();
225 }
226
227 void NumberPicker::setInterval(int n)
228 {
229   interval = n;
230   repaint();
231 }
232
233 void NumberPicker::setDevide(int n)
234 {
235   devide = n;
236   repaint();
237 }
238
239 void NumberPicker::setNumberColor(QRgb rgb)
240 {
241   numberColor.setRgb(rgb);
242   repaint();
243 }
244
245 void NumberPicker::setValue(int value)
246 {
247   if (value < minRange || value > maxRange) {
248         qDebug()<<"数值设置必须在"<<minRange
249                <<"和"<<maxRange<<"之间"<<endl;
250         return;
251   }
252   currentValue = value;
253   repaint();
254 }

      开关按钮的效果如下。运行时有动画效果,类似IOS手册里的开关按钮一样。

      开关按钮的头文件“switchbutton.h”代码如下。
/******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   17_sqlite_example
    * @brief         switchbutton.h
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-14
    *******************************************************************/
1   #ifndef SWITCHBUTTON_H
2   #define SWITCHBUTTON_H
3
4   #include <QWidget>
5   #include <QTimer>
6
7   class SwitchButton : public QWidget
8   {
9       Q_OBJECT
10
11public:
12      explicit SwitchButton(QWidget *parent = nullptr);
13
14      /* 返回开关状态 - 打开:true 关闭:false */
15      bool isToggled() const;
16
17      /* 设置开关状态 */
18      void setToggle(bool checked);
19
20      /* 设置背景颜色 */
21      void setBackgroundColor(QColor color);
22
23      /* 设置选中颜色 */
24      void setCheckedColor(QColor color);
25
26      /* 设置不可用颜色 */
27      void setDisbaledColor(QColor color);
28
29protected:
30      /* 绘制开关 */
31      void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
32
33      /* 鼠标按下事件 */
34      void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
35
36      /* 鼠标释放事件 - 切换开关状态、发射toggled()信号 */
37      void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
38
39      /* 大小改变事件 */
40      void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
41
42      /* 缺省大小 */
43      QSize sizeHint() const Q_DECL_OVERRIDE;
44      QSize minimumSizeHint() const Q_DECL_OVERRIDE;
45
46signals:
47      /* 状态改变时,发射信号 */
48      void toggled(bool checked);
49
50private slots:
51      /* 状态切换时,用于产生滑动效果 */
52      void onTimeout();
53
54private:
55      /* 是否选中 */
56      bool m_bChecked;
57      
58      /* 背景颜色 */
59      QColor m_background;
60      
61      /* 选中颜色 */
62      QColor m_checkedColor;
63      
64      /* 不可用颜色 */
65      QColor m_disabledColor;
66      
67      /* 拇指颜色 */
68      QColor m_thumbColor;
69      
70      /* 圆角 */
71      qreal m_radius;
72      
73      /* x点坐标 */
74      qreal m_nX;
75      
76      /* y点坐标 */
77      qreal m_nY;
78      
79      /* 高度 */
80      qint16 m_nHeight;
81      
82      /* 外边距 */
83      qint16 m_nMargin;
84      
85      /* 定时器 */
86      QTimer m_timer;
87};
88#endif // SWITCHBUTTON_H

      开关按钮的源文件“switchbutton.cpp”代码如下。
   /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   17_sqlite_example
    * @brief         switchbutton.cpp
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-14
    *******************************************************************/
1   #include "switchbutton.h"
2
3   #include <QPainter>
4   #include <QMouseEvent>
5
6   SwitchButton::SwitchButton(QWidget *parent)
7       : QWidget(parent),
8         m_bChecked(false),
9         m_background(Qt::gray),
10      m_checkedColor(34, 131, 246),
11      m_disabledColor(190, 190, 190),
12      m_thumbColor(Qt::gray),
13      m_radius(12.5),
14      m_nHeight(16),
15      m_nMargin(3)
16{
17      /* 鼠标滑过光标形状 - 手型 */
18      setCursor(Qt::PointingHandCursor);
19
20      /* 连接信号槽 */
21      connect(&m_timer, SIGNAL(timeout()),
22            this, SLOT(onTimeout()));
23}
24
25/* 绘制开关 */
26void SwitchButton::paintEvent(QPaintEvent *event)
27{
28      Q_UNUSED(event)
29
30      QPainter painter(this);
31      painter.setPen(Qt::NoPen);
32      painter.setRenderHint(QPainter::Antialiasing);
33
34      QPainterPath path;
35      QColor background;
36      QColor thumbColor;
37      qreal dOpacity;
38      /* 可用状态 */
39      if (isEnabled()) {
40          /* 打开状态 */
41          if (m_bChecked) {
42            background = m_checkedColor;
43            thumbColor = m_checkedColor;
44            dOpacity = 0.600;
45            /* 关闭状态 */
46          } else {
47            background = m_background;
48            thumbColor = m_thumbColor;
49            dOpacity = 0.800;
50          }
51          /* 不可用状态 */
52      } else {
53          background = m_background;
54          dOpacity = 0.260;
55          thumbColor = m_disabledColor;
56      }
57      /* 绘制大椭圆 */
58      painter.setBrush(background);
59      painter.setOpacity(dOpacity);
60      path.addRoundedRect(QRectF(m_nMargin,
61                                 m_nMargin, width() - 2 * m_nMargin,
62                                 height() - 2 * m_nMargin),
63                        m_radius, m_radius);
64      painter.drawPath(path.simplified());
65
66      /* 绘制小椭圆 */
67      painter.setBrush(thumbColor);
68      painter.setOpacity(1.0);
69      painter.drawEllipse(QRectF(m_nX - (m_nHeight / 2),
70                                 m_nY - (m_nHeight / 2),
71                                 height(),
72                                 height()));
73}
74
75/* 鼠标按下事件 */
76void SwitchButton::mousePressEvent(QMouseEvent *event)
77{
78      if (isEnabled()) {
79          if (event->buttons() & Qt::LeftButton) {
80            event->accept();
81          } else {
82            event->ignore();
83          }
84      }
85}
86
87/* 鼠标释放事件 - 切换开关状态、发射toggled()信号 */
88void SwitchButton::mouseReleaseEvent(QMouseEvent *event)
89{
90      if (isEnabled()) {
91          if ((event->type() == QMouseEvent::MouseButtonRelease)
92                  && (event->button() == Qt::LeftButton)) {
93            event->accept();
94            m_bChecked = !m_bChecked;
95            emit toggled(m_bChecked);
96            m_timer.start(10);
97          } else {
98            event->ignore();
99          }
100   }
101 }
102
103 /* 大小改变事件 */
104 void SwitchButton::resizeEvent(QResizeEvent *event)
105 {
106   m_nX = m_nHeight / 2;
107   m_nY = m_nHeight / 2;
108   QWidget::resizeEvent(event);
109 }
110
111 /* 默认大小 */
112 QSize SwitchButton::sizeHint() const
113 {
114   return minimumSizeHint();
115 }
116
117 /* 最小大小 */
118 QSize SwitchButton::minimumSizeHint() const
119 {
120   return QSize(2 * (m_nHeight + m_nMargin),
121                  m_nHeight + 2 * m_nMargin);
122 }
123
124 /* 切换状态 - 滑动 */
125 void SwitchButton::onTimeout()
126 {
127   if (m_bChecked) {
128         m_nX += 1;
129         if (m_nX >= width() - m_nHeight - m_nHeight / 2 ) {
130             m_timer.stop();
131             m_nX -= 1;
132         }
133   } else {
134         m_nX -= 1;
135         if (m_nX <= m_nHeight / 2) {
136             m_timer.stop();
137             m_nX += 1;
138         }
139   }
140   update();
141 }
142
143 /* 返回开关状态 - 打开:true 关闭:false */
144 bool SwitchButton::isToggled() const
145 {
146   return m_bChecked;
147 }
148
149 /* 设置开关状态 */
150 void SwitchButton::setToggle(bool checked)
151 {
152   m_bChecked = checked;
153   m_timer.start(10);
154 }
155
156 /* 设置背景颜色 */
157 void SwitchButton::setBackgroundColor(QColor color)
158 {
159   m_background = color;
160 }
161
162 /* 设置选中颜色 */
163 void SwitchButton::setCheckedColor(QColor color)
164 {
165   m_checkedColor = color;
166 }
167
168 /* 设置不可用颜色 */
169 void SwitchButton::setDisbaledColor(QColor color)
170 {
171   m_disabledColor = color;
172 }

13.2.1.1 程序运行效果
点击程序正下方的“+”按钮则开始添加闹钟数据,根据当前系统的时间或者滑动选择闹钟的时间,点击确认即新增一条闹钟数据。

      修改或者删除数据,点击要修改闹钟的数据项目,弹出的对话框,可以重新设置闹钟或者删除闹钟。并保存记录到数据库里,下次运行打开时会从数据库里读取保存的闹钟数据。

13.2.2 数据库表格(QTableView显示)
本小节设计一个生活中的例子,使用数据库修改/查询员工的编号、姓名、年龄、性别与照片信息。
本例将数据库的内容显示到QTableView上。如果只是简单的显示数据库的内容到QTableView上,可以使用下面的方法,此方法QTableView上可以看到员工的编号、姓名、年龄、性别信息,同时可以双击表格进行项修改,修改完成将自动保存到数据库里。
1   /* 初始化表格模型 */
2   QSqlTableModel *model = new QSqlTableModel(this, sqlDatabase);
3
4   /* 设置要选中的表格名称 */
5   model->setTable("employee");
6   /* 如果有修改则同步修改到数据库,
7      * 注意这个规则需要与tabview这样的控件才生效,
8      * 因为tabview可以直接编辑表里的内容 */
9   model->setEditStrategy(QSqlTableModel::OnFieldChange);
10    /* 成功则返回true,查看数据库里是否有employee这个表格 */
11    model->select();
12    /* 设置表格的头信息,若不设置则显示数据库里的英文字段头信息 */
13    model->setHeaderData(model->fieldIndex("id"),
14                         Qt::Horizontal, tr("编号"));
15    model->setHeaderData(model->fieldIndex("name"),
16                         Qt::Horizontal, tr("姓名"));
17    model->setHeaderData(model->fieldIndex("age"),
18                         Qt::Horizontal, tr("年龄"));
19    model->setHeaderData(model->fieldIndex("sex"),
20                         Qt::Horizontal, tr("性别"));
21
22    QTableView *view = new QTableView;
23
24    /* 设置表格的模型为model */
25    view->setModel(model);
26    /* 不显示图片路径信息行 */
27    view->hideColumn(4);
28    /* 表格居中 */
29    setCentralWidget(view);
30    return;
      上面的程序可以修改数据库的内容也可以查看。但是看不到员工的照片信息。本例就讲解如何将数据库数据显示到QTableView上,及查看选择的员工项的全部信息显示。介绍Qt如何使用数据库存储照片的信息。我们知道数据库类型有个BLOB数据类型可以用于存储照片信息。但是本例并不那样做,当数据库数据很多时,将照片(二进制数据)存储到数据库里就不是一个明智的选择了。大字段数据会加重数据库的负担,拖慢数据库,数据库文件越小访问肯定越快,数据库也不用遍历那么多内容,或者加载那么大的数据到内存里,造成响应不及时等。计算机可能处理速度愉快,但是对于普通的单核和多核ARM开发板来说速度可能会跟不上啊!所以照片最好是存储照片的路径。照片路径属于字符串文本,不会占用太多空间。
好了现在上例子,例子就是查询员工的编号、姓名、年龄、性别与照片信息,简单实用好理解,不复杂。
本例目的:用QTableView显示数据库表的数据,显示员工的信息。
例18_sqlite_table,查询数据表(难度:一般)。项目路径为Qt/2/ 18_sqlite_table。
项目文件18_sqlite_table文件第一行添加的代码部分如下。
18_sqlite_table.pro编程后的代码
1   QT       += core gui sql
2
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4
5   CONFIG += c++11
6
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10# deprecated API in order to know how to port your code away from it.
11DEFINES += QT_DEPRECATED_WARNINGS
12
13# You can also make your code fail to compile if it uses deprecated APIs.
14# In order to do so, uncomment the following line.
15# You can also select to disable deprecated APIs only up to a certain version of Qt.
16#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22HEADERS += \
23      mainwindow.h
24
25# Default rules for deployment.
26qnx: target.path = /tmp/${TARGET}/bin
27else: unix:!android: target.path = /opt/${TARGET}/bin
28!isEmpty(target.path): INSTALLS += target
29
30RESOURCES +=
在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码
   /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   18_sqlite_table
    * @brief         mainwindow.h
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-18
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3
4   #include <QSqlDatabase>
5   #include <QSqlQuery>
6   #include <QMainWindow>
7   #include <QLabel>
8   #include <QSqlTableModel>
9   #include <QHBoxLayout>
10#include <QVBoxLayout>
11#include <QGridLayout>
12#include <QTableView>
13#include <QComboBox>
14#include <QLineEdit>
15#include <QDataWidgetMapper>
16#include <QSqlQueryModel>
17#include <QItemSelectionModel>
18#include <QSpinBox>
19
20class MainWindow : public QMainWindow
21{
22      Q_OBJECT
23
24public:
25      MainWindow(QWidget *parent = nullptr);
26      ~MainWindow();
27
28private:
29
30      /* 数据库连接类 */
31      QSqlDatabase sqlDatabase;
32
33      /* 用于查询数据 */
34      QSqlQueryModel *sqlQueryModel;
35
36      /* 数据映射 */
37      QDataWidgetMapper *dataWidgetMapper;
38
39      /* 选择模型 */
40      QItemSelectionModel * itemSelectionModel;
41
42      /* 水平布局 */
43      QHBoxLayout *hBoxLayout;
44
45      /* 垂直布局 */
46      QVBoxLayout *vBoxLayout;
47
48      /* 网格布局 */
49      QGridLayout *gridLayout;
50
51      /* 用于显示的表格*/
52      QTableView *tableView;
53
54      /* 主Widget */
55      QWidget *mainWidget;
56
57      /* 底部容器 */
58      QWidget *bottomWidget;
59
60      /* 底部网格布局容器 */
61      QWidget *gridWidget;
62
63      /* 照片容器 */
64      QWidget *photoWidget;
65
66      /* Label,用于显示照片 */
67      QLabel *imageLabel;
68
69      /* Label,底部显示文本 */
70      QLabel *label;
71
72      /* 性别下拉选择框,选择信息 */
73      QComboBox *comboBox;
74
75      /* 数值选择框, */
76      QSpinBox *spinBox;
77
78      /* 单行输入框*/
79      QLineEdit *lineEdit;
80
81private slots:
82      /* 表格当前行变化执行的槽函数 */
83      void on_currentRowChanged(const QModelIndex&, const QModelIndex&);
84};
85#endif // MAINWINDOW_H
86
      头文件主要声明布局用的类和数据库,重要关注是QSqlDatabase、QSqlQueryModel 、QdataWidgetMapper和QItemSelectionModel。这里声明的是全局变量。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码
    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   18_sqlite_table
    * @brief         mainwindow.cpp
    * @author      Deng Zhimao
    * @email         <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
    * @net            <a href="www.openedv.com" target="_blank">www.openedv.com</a>
    * @date         2021-05-18
    *******************************************************************/
1   #include "mainwindow.h"
2   #include <QDebug>
3   #include <QHeaderView>
4   #include <QSqlError>
5   #include <QApplication>
6   #include <QSqlRecord>
7
8   MainWindow::MainWindow(QWidget *parent)
9       : QMainWindow(parent)
10{
11      /* 设置主窗体的显示位置与大小 */
12      this->setGeometry(0, 0, 800, 480);
13      
14      /* 查看本机可用的数据库驱动 */
15      QStringList drivers = QSqlDatabase::drivers();
16      foreach(QString driver, drivers) {
17          qDebug()<<driver;
18      }
19      
20      /* 以QSQLITE驱动方式打开或者创建数据库 */
21      sqlDatabase = QSqlDatabase::addDatabase("QSQLITE");
22      sqlDatabase.setDatabaseName("employee.db");
23      /* 以open的方式打开employee.db数据库,则会创建一个employee.db */
24      if (!sqlDatabase.open())
25          qDebug()<<"连接数据库错误"<<sqlDatabase.lastError()<<endl;
26      else
27          qDebug()<<"连接数据库成功"<<endl;
28      
29      QSqlQuery query(sqlDatabase);
30      /* 使用指令式创建表 */
31      query.exec("create table employee (id int primary key, name vchar(10), "
32               "age int, sex vchar(3), photo text)");
33      
34      QStringList photoPath;
35      /* 当前可执行程序的路径 */
36      QString path(QApplication::applicationDirPath());
37      photoPath<< path + "/photos/啊万.jpg"<< path + "/photos/啊棠.jpg";
38      
39      /* 以指令的方式插入数据,如果数据已经存在则不会成功不能插入 */
40      query.exec(tr("insert into employee values(1, '啊万', 27, '男', '%1')").arg(photoPath));
41      query.exec(tr("insert into employee values(2, '啊棠', 28, '男', '%1')").arg(photoPath));
42      
43      //    /* 初始化表格模型 */
44      //    QSqlTableModel *model = new QSqlTableModel(this, sqlDatabase);
45      
46      //    /* 设置要选中的表格名称 */
47      //    model->setTable("employee");
48      //    /* 如果有修改则同步修改到数据库,
49      //   * 注意这个规则需要与tabview这样的控件才生效,
50      //   * 因为tabview可以直接编辑表里的内容 */
51      //    model->setEditStrategy(QSqlTableModel::OnFieldChange);
52      //    /* 成功则返回true,查看数据库里是否有employee这个表格 */
53      //    model->select();
54      //    /* 设置表格的头信息,若不设置则显示数据库里的英文字段头信息 */
55      //    model->setHeaderData(model->fieldIndex("id"),
56      //                         Qt::Horizontal, tr("编号"));
57      //    model->setHeaderData(model->fieldIndex("name"),
58      //                         Qt::Horizontal, tr("姓名"));
59      //    model->setHeaderData(model->fieldIndex("age"),
60      //                         Qt::Horizontal, tr("年龄"));
61      //    model->setHeaderData(model->fieldIndex("sex"),
62      //                         Qt::Horizontal, tr("性别"));
63      
64      //    QTableView *view = new QTableView;
65      
66      //    /* 设置表格的模型为model */
67      //    view->setModel(model);
68      //    /* 不显示图片路径信息行 */
69      //    view->hideColumn(4);
70      //    /* 表格居中 */
71      //    setCentralWidget(view);
72      //    return;
73      
74      /* QSqlQueryModel适合用于查询数据,不能修改数据 */
75      sqlQueryModel = new QSqlQueryModel(this);
76      
77      /* 选择编号,姓名,年龄和性别的内容,显示到tableView上,
78       * 图片最后通过数据选择再读取Label上 */
79      sqlQueryModel->setQuery("select id, name, age, sex from employee");
80      
81      if (sqlQueryModel->lastError().isValid())
82          qDebug()<<"选择数据失败!"<<endl;
83      
84      sqlQueryModel->setHeaderData(0, Qt::Horizontal, "编号");
85      sqlQueryModel->setHeaderData(1, Qt::Horizontal, "姓名");
86      sqlQueryModel->setHeaderData(2, Qt::Horizontal, "年龄");
87      sqlQueryModel->setHeaderData(3, Qt::Horizontal, "性别");
88      
89      tableView = new QTableView();
90      tableView->setModel(sqlQueryModel);
91      
92      /* 设置显示平均分列 */
93      tableView->horizontalHeader()
94            ->setSectionResizeMode(QHeaderView::Stretch);
95      
96      mainWidget = new QWidget();
97      bottomWidget = new QWidget();
98      gridWidget = new QWidget();
99      photoWidget = new QWidget();
100   imageLabel = new QLabel();
101   
102   /* 设置照片属性 */
103   imageLabel->setScaledContents(true);
104   imageLabel->setMaximumSize(200, 200);
105   
106   
107   vBoxLayout = new QVBoxLayout();
108   hBoxLayout = new QHBoxLayout();
109   hBoxLayout = new QHBoxLayout();
110   gridLayout = new QGridLayout();
111   
112   for (int i = 0; i < 4; i++)
113         label = new QLabel();
114   
115   for (int i = 0; i < 2; i++) {
116         spinBox = new QSpinBox();
117         spinBox->setRange(1, 100);
118   }
119   
120   comboBox = new QComboBox();
121   comboBox->addItem("男");
122   comboBox->addItem("女");
123   
124   lineEdit = new QLineEdit();
125   
126   bottomWidget->setMinimumHeight(this->height() / 2 - 30);
127   gridWidget->setMaximumWidth(this->width() / 2 - 30);
128   
129   /* 垂直布局 */
130   vBoxLayout->addWidget(tableView);
131   vBoxLayout->addWidget(bottomWidget);
132   
133   mainWidget->setLayout(vBoxLayout);
134   setCentralWidget(mainWidget);
135   
136   /* 水平布局 */
137   hBoxLayout->addWidget(gridWidget);
138   hBoxLayout->addWidget(photoWidget);
139   bottomWidget->setLayout(hBoxLayout);
140   
141   QStringList list;
142   list<<"姓名:"<<"编号:"<<"年龄:"<<"性别:";
143   
144   /* 网格布局 */
145   for (int i = 0; i < 4; i++) {
146         gridLayout->addWidget(label, i, 0);
147         label->setText(list);
148         switch (i) {
149         case 0:
150             gridLayout->addWidget(lineEdit, i, 1);
151             break;
152         case 1:
153             gridLayout->addWidget(spinBox, i, 1);
154             break;
155         case 2:
156             gridLayout->addWidget(spinBox, i, 1);
157             break;
158         case 3:
159             gridLayout->addWidget(comboBox, i, 1);
160             break;
161         default:
162             break;
163         }
164   }
165   
166   gridWidget->setLayout(gridLayout);
167   hBoxLayout->addWidget(imageLabel);
168   photoWidget->setLayout(hBoxLayout);
169   
170   itemSelectionModel = new QItemSelectionModel(sqlQueryModel);
171   tableView->setSelectionModel(itemSelectionModel);
172   
173   /* 信号槽连接,表示表中行数据变化时,触发槽函数 */
174   connect(itemSelectionModel,
175             SIGNAL(currentRowChanged(QModelIndex, QModelIndex)),
176             this,
177             SLOT(on_currentRowChanged(QModelIndex, QModelIndex)));
178   
179   dataWidgetMapper = new QDataWidgetMapper(this);
180   /* 设置为自动提交 */
181   dataWidgetMapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
182   dataWidgetMapper->setModel(sqlQueryModel);
183   /* 创建数据映射,将前面的数据库内容映射到控件上 */
184   dataWidgetMapper->addMapping(lineEdit, 1);
185   dataWidgetMapper->addMapping(spinBox, 0);
186   dataWidgetMapper->addMapping(spinBox, 2);
187   dataWidgetMapper->addMapping(comboBox, 3);
188 }
189
190 MainWindow::~MainWindow()
191 {
192   /* 关闭数据库 */
193   sqlDatabase.close();
194 }
195
196 void MainWindow::on_currentRowChanged(const QModelIndex ¤t,
197                                       const QModelIndex &previous)
198 {
199   Q_UNUSED(previous)
200   /* 更新数据映射行号,初始化时映射到第0行 */
201   dataWidgetMapper->setCurrentModelIndex(current);
202   /* 获取当前行号 */
203   int row =itemSelectionModel->currentIndex().row();
204   /* 获取当前模型记录 */
205   QSqlRecord record = sqlQueryModel->record(row);
206   /* 获取id信息 */
207   int id = record.value("id").toInt();
208   QSqlQuery query;
209   /* 使用bindValue绑定prepare里语句的值,需要使用":",":"是占位符 */
210   query.prepare("select photo from employee where id = :ID");
211   query.bindValue(":ID", id);
212   query.exec();
213   /* 返回到选择的第一条记录,因为id是唯一的,也只有一条记录 */
214   query.first();
215   
216   /* 获取字段为photo的值,也就是存储照片的路径 */
217   QVariant temp = query.value("photo");
218   if (!temp.isValid()) {
219         qDebug()<<"数据无效!"<<endl;
220         return;
221   }
222   
223   /* 清空图片显示 */
224   imageLabel->clear();
225   
226   QImage image(temp.toString());
227   
228   if (image.isNull()) {
229         qDebug()<<"未找到"<<temp.toString()<<endl;
230         return;
231   }
232   
233   /* 显示照片 */
234   imageLabel->setPixmap(QPixmap::fromImage(image));
235 }
      第15~41行,连接数据库,创建数据库表,插入数据,与上一小节实用闹钟基本一样,不再赘述。
      第43~72行,被注释行,这部分程序就是本小节开头所说的,如果只想显示数据库表里的数据就打开这个被注释掉的内容即可。
      第75~168行,布局及一些设置的内容,不再解释。布局前面入门篇已经讲过。
      第170~187行,重点关注这几行代码,170~177行,QItemSelectionModel将sqlQueryModel作为项的选择模型,然后,tableView设置项的选择模型为itemSelectionModel,这个目的就是使用itemSelection的行发生的变化的信号currentRowChanged()。
      第179~187行,这里主要是是将sqlQueryModel的数据通过dataWidgetMapper这个对象映射到我们的普通控件类上。比如映射第4个数据(index = 3)性别数据到comboBox上。可以看出dataWidgetMapper只是一个搬运工而已,将指定的数据搬到我们需要显示的控件上。
      第196~235行,当我们点击表中的数据,行发生变化后,则这个槽函数触发,流程是从当前选择的行里,使用QSqlRecord记录当前行的数据,然后从当前行的数据提取出id的编号,再使用QSqlQuery的exec()方法执行sql的select语句获取出photo字段照片的路径数据,最后将获取的照片路径数据初始化一个QImage对象显示到imageLable上,这样就实现了数据显示的功能。并且映射到控件上的内容也发生了改变。
      当我们的数据库表足够大时,我们若想使用一些按钮来点击切换查询当前项的数据可以使用按钮连接到dataWidgetMapper的toFirst()、toLast()、toNext()和toPrevious()槽函数。意思是跳转到第一行,最后一行,下一行和前一行。最后将tableView设置为dataWidgetMapper当前行就会触发currentRowChanged(),就能实现按钮控制查询数据了。至于读者想加什么功能由读者自由设计,本例只是写了个大概框架。
main      .cpp内容如下,没有修改。
1   #include "mainwindow.h"
2
3   #include <QApplication>
4
5   int main(int argc, char *argv[])
6   {
7       QApplication a(argc, argv);
8       MainWindow w;
9       w.show();
10      return a.exec();
11}
13.2.2.1 程序运行效果
       运行本例时,先点击构建,构建完成后将本项目下的整个photos文件夹拷贝到构建出来的build-18_sqlite_table-Desktop_Qt_5_12_9_GCC_64bit-Debug目录下。因为本程序会从数据库里读取出员工的照片路径信息,所以需要提前将照片文件夹放至可执行程序同一级目录下。点击表中的项,当切换员工的信息里,我们可以看到左下角被映射的内容发生了改变,变成了当前选择行的员工信息,右下角的照片头像也变成了该员工的照片信息。本例实现的就是从数据库里取数据并显示到QTableView及搭配其他控件使用的例子。
       员工啊万的信息:



      员工啊棠信息:

页: [1]
查看完整版本: 《I.MX6U嵌入式Qt开发指南》第十三章 数据库