• Qt5开发及实例V2.0-第十三章-Qt数据库


    第13章 Qt 5数据库

    13.1 数据库基本概念

    1.数据和数据库(DB)
    利用计算机进行数据处理,首先需要将信息以数据形式存储到计算机中,因为数据是可以被计算机接收和处理的符号。根据所表示的信息特征不同,数据有不同的类别,如数字、文字、表格、图形/图像和声音等。
    数据库(DataBase,DB),顾名思义,就是存放数据的仓库,其特点是数据按照数据模型组织,是高度结构化的,可供多个用户共享并且具有一定的安全性。

    2.数据库管理系统(DBMS)
    数据库管理系统即DBMS(DataBase Management System),它是位于用户应用程序和操作系统之间的数据库管理系统软件,其主要功能是组织、存储和管理数据,高效地访问和维护数据,即提供数据定义、数据操纵、数据控制和数据维护等功能。常用的数据库管理系统有Oracle、Microsoft SQL Server和MySQL等。
    数据库系统即DBS(DataBase System),是指按照数据库方式存储和维护数据,并向应用程序提供数据访问接口的系统。DBS通常由数据库、计算机硬件(支持DB存储和访问)、软件(包括操作系统、DBMS及应用开发支撑软件)和数据库管理员(DataBase Administrator,DBA)四个部分组成。

    3.结构化查询语言SQL
    结构化查询语言(Structured Query Language,SQL)是用于关系数据库操作的标准语言,最早由Boyce和Chambedin在1974年提出,称为SEQUEL语言。
    SQL语言由以下三部分组成。
    (1)数据定义语言(Data Description Language,DDL),用于执行数据库定义的任务,对数据库及数据库中的各种对象进行创建、删除和修改等操作。数据库对象主要包括表、默认约束、规则、视图、触发器和存储过程等。
    (2)数据操纵语言(Data Manipulation Language,DML),用于操纵数据库中各种对象,检索和修改数据。
    (3)数据控制语言(Data Control Language,DCL),用于安全管理,确定哪些用户可以查看或修改数据库中的数据。

    4.表和视图
    (1)表(Table)。
    表是在日常工作和生活中经常使用的一种表示数据及其关系的形式,如表13.1为一个学生表。
    在这里插入图片描述
     表结构
    每个数据库包含若干个表。每个表具有一定的结构,称为表的“型”。所谓表型是指组成表的各列的名称及数据类型,也就是日常表格的“栏目信息”。
     记录
    每个表包含若干行数据,它们是表的“值”,表中的一行称为一个记录(Record)。因此,表是记录的有限集合。
     字段
    每个记录由若干个数据项构成,将构成记录的每个数据项称为字段(Field)。字段包含的属性有字段名、字段数据类型、字段长度及是否为关键字等。其中,字段名是字段的标识,字段的数据类型可以是多样的,如整型、实型、字符型、日期型或二进制型等。
     关键字
    在学生表中,若不加以限制,则每条记录的姓名、专业名、性别和出生时间这四个字段的值都有可能相同,但是学号字段的值对表中所有记录来说则一定不同,即通过“学号”字段可以将表中的不同记录区分开来。

    (2)视图(View)。
    视图是从一个或多个表(或视图)导出的表。
    视图与表不同,它是一个虚表,即视图所对应的数据不进行实际存储,数据库中只存储视图的定义,对视图的数据进行操作时,系统根据视图的定义操作与视图相关联的基本表。视图一经定义后,就可以像表一样被查询、修改、删除和更新。使用视图具有便于数据共享、简化用户权限管理和屏蔽数据库的复杂性等优点。

    13.2 常用SQL命令

    13.2.1 数据查询

    1.SELECT语句
    完备的SELECT语句很复杂,它主要的子句如下:

    SELECT [DISTINCT] [别名.]字段名或表达式 [AS 列标题]/* 指定要选择的列或行及其限定 */
    						//(a)
    FROM  table_source                          	/* FROM子句,指定表或视图 */
    [ WHERE  search_condition ]                	/* WHERE子句,指定查询条件 */
    						//(b)
    [ GROUP BY group_by_expression ]               
                                       	/* GROUP BY子句,指定分组表达式 */
    [ ORDER BY order_expression [ ASC | DESC ]]  
                                      	/* ORDER BY子句,指定排序表达式和顺序 */
    						//(c)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其中,SELECT和FROM子句是不可缺少的。
    (a) SELECT子句指出查询结果中显示的字段名,以及字段名和函数组成的表达式等。可用DISTINCT去除重复的记录行;AS列标题指定查询结果显示的列标题。当要显示表中所有字段时,可用通配符“*”代替字段名列表。
    (b) WHERE子句定义了查询条件。WHERE子句必须紧跟FROM子句之后,其基本格式为:

    WHERE 
    
    • 1

    其中,search_condition为查询条件,常用格式为:

    { [ NOT ]  | ( ) }
         [ { AND | OR } [ NOT ] {  | () } ]
    } [ ,…n ]
    
    • 1
    • 2
    • 3

    其中的predicate为判定运算,结果为TRUE、FALSE或UNKNOWN,格式为:

    { expression { = | < | <= | > | >= | <> | != | !< | !> } expression  								/* 比较运算 */
    | string_expression [ NOT ] LIKE string_expression [ ESCAPE 'escape_ character' ]	   					/* 字符串模式匹配 */
      | expression [ NOT ] BETWEEN expression AND expression/* 指定范围 */
      | expression IS [ NOT ] NULL                   	/* 是否空值判断 */
      | expression [ NOT ] IN ( subquery | expression [,…n] )/* IN子句 */
      | expression { = | < | <= | > | >= | <> | != | !< | !> } { ALL | SOME | ANY } ( subquery )                              					/* 比较子查询 */
      | EXIST ( subquery )                            	/* EXIST子查询 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    © GROUP BY子句和ORDER BY子句分别对查询结果分组和排序。

    下面用示例说明使用SQL语句对Student数据库进行的各种查询。
    (1)查询Student数据库。查询students表中各个同学的姓名和总学分。

    USE Student SELECT name,totalscore FROM students
    
    • 1

    (2)查询表中所有记录。查询students表中各个同学的所有信息。

    SELECT * FROM students
    
    • 1

    (3)条件查询。查询students表中总学分大于等于120的同学的情况。

    SELECT * FROM students WHERE totalscore >=  120
    
    • 1

    (4)多重条件查询。查询students表中所在系为“计算机”且总学分大于等于120的同学的情况。

    SELECT * FROM students WHERE department='计算机' AND totalscore >= 120
    
    • 1

    (5)使用LIKE谓词进行模式匹配。查询students表中姓“王”且单名的学生情况。

    SELECT * FROM students WHERE name LIKE '王_'
    
    • 1

    (6)用BETWEEN…AND指定查询范围。查询students表中不在1999年出生的学生情况。

    SELECT * FROM students
        WHERE birthday NOT BETWEEN '1999-1-1' and '1999-12-31'
    
    • 1
    • 2

    (7)空值比较。查询总学分尚不确定的学生情况。

    SELECT * FROM students
      WHERE totalscore IS NULL
    
    • 1
    • 2

    (8)自然连接查询。查找计算机系学生姓名及其“C程序设计”课程的考试分数情况。

    SLELCT name,grade
         FROM students, courses,grades,
         WHERE department = '计算机' AND  coursename= ' C程序设计'  AND
           students.studentid = grades.studentid  AND courses.courseid =
           grades.coursesid
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (9)IN子查询。查找选修了课程号为101的学生情况。

    SELECT * FROM students
    	WHERE studentid IN
    		  ( SELECT studentid FROM courses WHERE courseid = '101' )
    
    • 1
    • 2
    • 3

    在执行包含子查询的SELECT语句时,系统首先执行子查询,产生一个结果表,再执行外查询。本例中,首先执行子查询:

    SELECT studentid FROM courses, students,grades WHERE courseid = '101' 
     	  AND students.studentid = grades.studentid  AND courses.courseid =
     	     grades.coursesid
    
    • 1
    • 2
    • 3

    得到一个只含有studentid列的结果表,courses中courseid列值为101的行在该结果表中都有一行。

    (10)比较子查询。这种子查询可以认为是IN子查询的扩展,它是表达式的值与子查询的结果进行比较运算。查找课程号206的成绩不低于课程号101的最低成绩的学生学号。

    SELECT studentid FROM grades
    	 WHERE courseid = '206' AND grade !< ANY 
    	     ( SELECT grade FROM grades
              		WHERE courseid = '101'
    		 )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (11)EXISTS子查询。EXISTS谓词用于测试子查询的结果是否为空表,若子查询的结果集不为空,则EXISTS返回TRUE,否则返回FALSE。EXISTS还可与NOT结合使用,即NOT EXISTS,其返回值与EXISTS刚好相反。查找选修206号课程的学生姓名。

    SELECT name FROM students
          WHERE EXISTS
             ( SELECT * FROM grades
                     WHERE studentid = students.studentid AND courseid = '206' 
    		)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (12)查找选修了全部课程的同学姓名(即查找没有一门功课不选修的学生)。

    SELECT name FROM students
       WHERE NOT EXISTS
           ( SELECT * FROM courses
                 WHERE NOT EXISTS
                 		( SELECT * FROM grades
                       	WHERE studentid= students.studentid
                              AND courseid=courses.courseid
    		 		)
           )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (13)查询结果分组。将各课程成绩按学号分组。

    SELECT studentid,grade FROM grades
        GROUP BY studentid
    
    • 1
    • 2

    (14)查询结果排序。将计算机系的学生按出生时间先后排序。

    SELECT * FROM students
       WHERE department = '计算机'
         ORDER BY birthday
    
    • 1
    • 2
    • 3

    2.常用聚合函数
    对表数据进行检索时,经常需要对结果进行汇总或计算,如在学生成绩数据库中求某门功课的总成绩、统计各分数段的人数等。聚合函数用于计算表中的数据,返回单个计算结果。常用的聚合函数列于表13.2中。
    在这里插入图片描述
    本例对Students数据库表执行查询,使用常用的聚合函数。
    (1)求选修101课程学生的平均成绩。

    SELECT AVG(grade) AS ' 课程101平均成绩'
        FROM grades   
        WHERE courseid = '101'
    
    • 1
    • 2
    • 3

    (2)求选修101课程学生的最高分和最低分。

    SELECT MAX(grade) AS '课程101最高分' , MIN(grade) AS '课程101最低分'
        FROM grades   
        WHERE courseid = '101'
    
    • 1
    • 2
    • 3

    (3)求学生的总人数。

    SELECT COUNT(*) AS '学生总数'
       	FROM students
    
    • 1
    • 2

    13.2.2 数据操作

    1.插入数据语句INSERT
    INSERT可添加一条或多条记录至一个表中。
    INSERT有两种语法形式。
    语法1:

    INSERT INTO target [IN externaldatabase] (fields_list)		//(a)
    {DEFAULT VALUES|VALUES(DEFAULT|expression_list)}		//(b)
    
    • 1
    • 2

    语法2:

    INSERT INTO target [IN externaldatabase] fields_list
    {SELECT…|EXECUTE…}
    
    • 1
    • 2

    使用第1种形式将一个记录或记录的部分字段插入到表或视图中。第2种形式的INSERT语句插入来自SELECT语句或来自使用EXECUTE语句执行的存储过程的结果集。
    例如,用以下语句向students表添加一条记录:

    INSERT INTO students
      	VALUES('170206','罗亮', 0 ,'1/30/1998', 1, 150)
    
    • 1
    • 2

    2.删除数据语句DELETE
    DELETE用于从一个或多个表中删除记录。
    DELETE语句的语法格式如下:

    DELETE FROM table_names
    [WHERE…]
    
    • 1
    • 2

    例如,用以下语句从students表中删除姓名为“罗亮”的记录:

    DELETE FROM students
      	WHERE name = '罗亮'
    
    • 1
    • 2

    3.更新数据语句UPDATE
    UPDATE语句用于更新表中的记录。
    UPDATE语句的语法格式如下:

    UPDATE table_name
    SET Field_1=expression_1[,Field_2=expression_2…]
    [FROM table1_name|view1_name[,table2_name|view2_name…]]
    [WHERE…]
    
    • 1
    • 2
    • 3
    • 4

    其中,Field是需要更新的字段,expression表示要更新字段的新值表达式。
    例如,以下语句将计算机系学生的总分增加10:

    UPDATE students
    SET totalscore = totalscore +10
    WHERE department = '计算机'
    
    
    • 1
    • 2
    • 3
    • 4

    13.3 Qt操作数据库及实例

    这个模块由不同Qt类支撑的三部分组成,具体QtSql模块层次结构见表13.3。
    在这里插入图片描述

    13.3.1 Qt操作SQLite数据库

    Qt提供了一种进程内数据库SQLite。它小巧灵活,无须额外安装配置且支持大部分ANSI SQL92标准,是一个轻量级的数据库,概括起来具有以下优点。
    (1)SQLite的设计目的是嵌入式SQL数据库引擎,它基于纯C语言代码,已经应用于非常广泛的领域内。
    (2)SQLite在需要持久存储时可以直接读写硬盘上的数据文件,在无须持久存储时也可以将整个数据库置于内存中,两者均不需要额外的服务器端进程,即SQLite是无须独立运行的数据库引擎。
    (3)开放源代码,整个代码少于3万行,有良好的注释和90%以上的测试覆盖率。
    (4)少于250KB的内存占用容量(gcc编译情况下)。
    (5)支持视图、触发器和事务,支持嵌套SQL功能。
    (6)提供虚拟机用于处理SQL语句。
    (7)不需要配置,不需要安装,也不需要管理员。
    (8)支持大部分ANSI SQL92标准。
    (9)大部分应用的速度比目前常见的客户端/服务器结构的数据库快。
    (10)编程接口简单易用。

    【例】(难度中等)(CH1301)基于控制台的程序,使用SQLite数据库完成大批量数据的增加、删除、更新和查询操作并输出。
    实现步骤如下。
    (1)在“QSQLiteEx.pro”文件中添加如下代码:
    QT += sql
    (2)源文件“main.cpp”的具体代码。

    其中,
    (a) QSqlDatabase db =QSqlDatabase::addDatabase(“QSQLITE”):以“QSQLITE”为数据库类型,在本进程地址空间内创建一个SQLite数据库。此处涉及的知识点有以下两点。
    ① 在进行数据库操作之前,必须首先建立与数据库的连接。数据库连接由任意字符串标识。在没有指定连接的情况下,QSqlDatabase可以提供默认连接供Qt其他的SQL类使用。建立一条数据库连接的代码如下:
    QSqlDatabase db =QSqlDatabase::addDatabase(“QSQLITE”);
    db.setHostName(“easybook-3313b0”); //设置数据库主机名
    db.setDatabaseName(“qtDB.db”); //设置数据库名
    db.setUserName(“zhouhejun”); //设置数据库用户名
    db.setPassword(“123456”); //设置数据库密码
    db.open(); //打开连接

    ② QtSql模块使用驱动插件(driver plugins)与不同的数据库接口通信。由于QtSql模块的应用程序接口是与具体数据库无关的,所以所有与数据库相关的代码均包含在这些驱动插件中。目前,Qt中支持的驱动插件见表13.4。
    在这里插入图片描述

    (b) db.setDatabaseName(“qtDB.db”):以上创建的数据库以“qtDB.db”为数据库名。它是SQLite在建立内存数据库时唯一可用的名字。
    © QSqlQuery query:创建QSqlQuery对象。QtSql模块中的QSqlQuery类提供了一个执行SQL语句的接口并且可以遍历执行的返回结果集。除QSqlQuery类之外,Qt还提供了三种用于访问数据库的高层类,即QSqlQueryModel、QSqlTableModel和QSqlRelationTableModel。它们无须使用SQL语句就可以进行数据库操作,而且可以很容易地将结果在表格中表示出来。其各自的用途见表13.5。
    在这里插入图片描述

    (d) bool success=query.exec(“create table automobil…”):创建数据库表“automobil”,该表具有10个字段。在执行exec()函数调用后,就可以操作返回的结果了。
    (e) query.prepare(“insert into automobil values(?,?,?,?,?,?,?,?,?,?)”):如果要插入多条记录,或者避免将值转换为字符串(即正确地转义),则可以首先调用prepare()函数指定一个包含占位符的query,然后绑定要插入的值。Qt对所有数据库均可以支持Qracle类型的占位符和ODBC类型的占位符。此处使用了ODBC格式的定位占位符。

    等价于使用Oracle语法的有名占位符的具体形式如下:

    query.prepare("insert into automobile(id,attribute,type,kind,nation,
        carnumber,elevaltor,distance,oil,temperature) 
        values(:id, :attribute, :type, :kind, :nation,
        :carnumber,:elevaltor,:distance,:oil,:temperature)");
        long records=100;
        for(int i=0;i<records;i++)
        {
            query.bindValue(:id,i);
            query.bindValue(:attribute,"四轮");
            query.bindValue(:type,"轿车");
            query.bindValue(:kind,"富康");
            query.bindValue(:nation,rand()%100);
            query.bindValue(:carnumber,rand()%10000);
            query.bindValue(:elevaltor,rand()%300);
            query.bindValue(:distance,rand()%200000);
            query.bindValue(:oil,rand()%52);
            query.bindValue(:temperature,rand()%100);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (f) query.bindValue(0,i):调用bindValue()或addBindValue()函数绑定要插入的值。
    (g) success=query.exec():调用exec()函数在query中插入对应的值,之后,可以继续调用bindValue()或addBindValue()函数绑定新值,然后再次调用exec()函数在query中插入新值。
    (h) qDebug()< (i) success=query.exec(“select * from automobil order by id desc”):按id字段的降序将查询表中刚刚插入的100条记录进行排序。
    (j) query.prepare(QString(“update automobil set…”)):更新操作与插入操作类似,只是使用的SQL语句不同。
    (k) query.exec(“delete from automobil where id=15”):执行删除id为15的记录的操作。

    (3)打开“QSQLiteEx.pro”文件,添加语句:
    QT += sql
    (4)运行结果如图13.1所示。
    在这里插入图片描述

    13.3.2 Qt操作主/从视图及XML

    【例】(难度中上)(CH1302)以主/从视图的模式展现汽车制造商与生产汽车的关系。当在汽车制造商表中选中某一个制造商时,下面的汽车表中将显示出该制造商生产的所有产品。当在汽车表中选中某个车型时,右边的列表将显示出该车的车型和制造商的详细信息,所不同的是,车型的相关信息存储在XML文件中。
    1.主界面布局
    (1)主窗口MainWindow类继承自QMainWindow类,定义了主显示界面,头文件“mainwindow.h”的具体代码。
    (2)源文件“mainwindow.cpp”的具体内容。

    createFactoryGroupBox()函数的具体内容如下:

    QGroupBox* MainWindow::createFactoryGroupBox()
    {
        factoryView = new QTableView;
        factoryView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    						//(a)
        factoryView->setSortingEnabled(true);
        factoryView->setSelectionBehavior(QAbstractItemView::SelectRows);
        factoryView->setSelectionMode(QAbstractItemView::SingleSelection);
        factoryView->setShowGrid(false);
        factoryView->setAlternatingRowColors(true);
        QGroupBox *box = new QGroupBox(tr("汽车制造商"));
        QGridLayout *layout = new QGridLayout;
        layout->addWidget(factoryView, 0, 0);
        box->setLayout(layout);
        return box;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    createCarGroupBox()函数的具体代码如下:

    QGroupBox* MainWindow::createCarGroupBox()
    {
        QGroupBox *box = new QGroupBox(tr("汽车"));
        carView = new QTableView;
        carView->setEditTriggers(QAbstractItemView::NoEditTriggers);
        carView->setSortingEnabled(true);
        carView->setSelectionBehavior(QAbstractItemView::SelectRows);
        carView->setSelectionMode(QAbstractItemView::SingleSelection);
        carView->setShowGrid(false);
        carView->verticalHeader()->hide();
        carView->setAlternatingRowColors(true);
        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(carView, 0, 0);
        box->setLayout(layout);
        return box;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    createDetailsGroupBox()函数的具体代码如下:

    QGroupBox* MainWindow::createDetailsGroupBox()
    {
        QGroupBox *box = new QGroupBox(tr("详细信息"));
        profileLabel = new QLabel;
        profileLabel->setWordWrap(true);
        profileLabel->setAlignment(Qt::AlignBottom);
        titleLabel = new QLabel;
        titleLabel->setWordWrap(true);
        titleLabel->setAlignment(Qt::AlignBottom);
        attribList = new QListWidget;
        QGridLayout *layout = new QGridLayout;
        layout->addWidget(profileLabel, 0, 0, 1, 2);
        layout->addWidget(titleLabel, 1, 0, 1, 2);
        layout->addWidget(attribList, 2, 0, 1, 2);
        layout->setRowStretch(2, 1);
        box->setLayout(layout);
        return box;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    createMenuBar()函数的具体代码如下:

    void MainWindow::createMenuBar()
    {
        QAction *addAction = new QAction(tr("添加"), this);
        QAction *deleteAction = new QAction(tr("删除"), this);
        QAction *quitAction = new QAction(tr("退出"), this);
        addAction->setShortcut(tr("Ctrl+A"));
        deleteAction->setShortcut(tr("Ctrl+D"));
        quitAction->setShortcut(tr("Ctrl+Q"));
        QMenu *fileMenu = menuBar()->addMenu(tr("操作菜单"));
        fileMenu->addAction(addAction);
        fileMenu->addAction(deleteAction);
        fileMenu->addSeparator();
        fileMenu->addAction(quitAction);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (3)此时运行结果如图13.2所示。
    在这里插入图片描述
    2.连接数据库
    (1)右击项目名,选择“添加新文件”→“Qt”→“Qt设计师界面类”菜单项,如图13.3所示,单击“Choose…”按钮继续。
    在这里插入图片描述

    接下来在如图13.4所示的对话框中,模板选择“Dialog without Buttons”,单击“下一步”按钮继续。
    在这里插入图片描述

    类名设置为“ConnDlg”,在“头文件”后面的文本框中输入“connectdlg.h”;在“源文件”后面的文本框中输入“connectdlg.cpp”;在“界面文件”后面的文本框中输入“connectdlg.ui”,如图13.5所示,单击“下一步”按钮,单击“完成”按钮。
    在这里插入图片描述
    打开“connectdlg.ui”,单击“Form”的空白处修改“QDialog”的“objectName: QSqlConnectionDialogUi”。最后添加如图13.6所示的控件。
    在这里插入图片描述
    各控件的属性见表13.6。
    在这里插入图片描述
    (2)在头文件“connectdlg.h”中,ConnDlg类继承自QDialog类,主要完成从界面获取用户设置的连接参数信息。ConnDlg类的定义中声明了需要的各种函数,其具体代码。
    (3)在源文件“connectdlg.cpp”中,ConnDlg类的构造函数完成了初始化ui界面及查找当前所有可用的Qt数据库驱动,并将其加入ui界面的驱动组合框中,以及其他一些功能,其具体代码如下:

    #include "connectdlg.h"
    #include "ui_connectdlg.h"
    #include 
    #include 
    ConnDlg::ConnDlg(QWidget *parent)
        : QDialog(parent)
    {
         ui.setupUi(this);
         QStringList drivers = QSqlDatabase::drivers();		//(a)
         ui.comboDriver->addItems(drivers);			//(b)
         connect(ui.comboDriver,SIGNAL(currentIndexChanged( const QString & )),this,SLOT(driverChanged(const QString &)));			//(c)
    	ui.status_label->setText(tr("准备连接数据库!"));		//(d)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    槽函数driverChanged()的具体代码如下:

    void ConnDlg::driverChanged(const QString &text)
    {
    	if(text =="QSQLITE")			//(a)
    	{
    		ui.editDatabase->setEnabled(false);
    		ui.editUsername->setEnabled(false);
    		ui.editPassword->setEnabled(false);
    		ui.editHostname->setEnabled(false);
    		ui.portSpinBox->setEnabled(false);
    	}
    	else
    	{
    		ui.editDatabase->setEnabled(true);
    		ui.editUsername->setEnabled(true);
    		ui.editPassword->setEnabled(true);
    		ui.editHostname->setEnabled(true);
    		ui.portSpinBox->setEnabled(true);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    driverName()函数的具体代码如下:

    QString ConnDlg::driverName() const
    {
    	return ui.comboDriver->currentText();
    }
    
    • 1
    • 2
    • 3
    • 4

    databaseName()函数的具体代码如下:

    QString ConnDlg::databaseName() const
    {
         return ui.editDatabase->text();
    }
    
    • 1
    • 2
    • 3
    • 4

    userName()函数的具体代码如下:

    QString ConnDlg::userName() const
    {
    	 return ui.editUsername->text();
    }
    
    • 1
    • 2
    • 3
    • 4

    password()函数的具体代码如下:

    QString ConnDlg::password() const
    {
    	return ui.editPassword->text();
    }
    
    • 1
    • 2
    • 3
    • 4

    hostName()函数的具体代码如下:

    QString ConnDlg::hostName() const
    {
    	return ui.editHostname->text();
    }
    
    • 1
    • 2
    • 3
    • 4

    port()函数的具体代码如下:

    int ConnDlg::port() const
    {
    	return ui.portSpinBox->value();
    }
    
    • 1
    • 2
    • 3
    • 4

    on_okButton_clicked()函数,当用户单击“连接”按钮时,此函数被调用,其具体实现代码。

    函数addConnection()用来建立一条数据库连接,其具体实现内容如下:

    QSqlError ConnDlg::addConnection(const QString &driver, const QString &dbName, const QString &host,const QString &user, const QString &passwd, int port)
    {
    	QSqlError err;
        	QSqlDatabase db = QSqlDatabase::addDatabase(driver);
        	db.setDatabaseName(dbName);
        	db.setHostName(host);
        	db.setPort(port);
        	if(!db.open(user, passwd))						//(a)
    	{
    		err = db.lastError();
        	}
        	return err;					//返回这个错误信息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    addSqliteConnection()函数建立一条QSQLITE数据库驱动对应的sqlite数据库连接,其具体内容如下:

    void ConnDlg::addSqliteConnection()
    {
    	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    	db.setDatabaseName("databasefile");
    	if(!db.open())
    	{
    		ui.status_label->setText(db.lastError().text());
    		return;
    	}
    	ui.status_label->setText(tr("创建sqlite数据库成功!"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ConnDlg::creatDB()函数创建了相关的两张数据表,并在其中插入适当信息。其具体代码如下:

    void ConnDlg::creatDB()
    {
       	QSqlQuery query;				//(a)
       	query.exec("create table factory (id int primary key,manufactory varchar(40), address varchar(40))");				//(b)
        	query.exec(QObject::tr("insert into factory values(1, '一汽大众', '长春')"));
        	query.exec(QObject::tr("insert into factory values(2, '二汽神龙', '武汉')"));
        	query.exec(QObject::tr("insert into factory values(3, '上海大众', '上海')"));
        	query.exec("create table cars (carid int primary key, name varchar(50), factoryid int, year int, foreign key(factoryid) references factory)");	//(c)
        	query.exec(QObject::tr("insert into cars values(1,'奥迪A6',1,2005)"));
        	query.exec(QObject::tr("insert into cars values(2, '捷达', 1, 1993)"));
        	query.exec(QObject::tr("insert into cars values(3, '宝来', 1, 2000)"));
        	query.exec(QObject::tr("insert into cars values(4, '毕加索',2, 1999)"));
        	query.exec(QObject::tr("insert into cars values(5, '富康', 2, 2004)"));
        	query.exec(QObject::tr("insert into cars values(6, '标致307',2, 2001)"));
        	query.exec(QObject::tr("insert into cars values(7, '桑塔纳',3, 1995)"));
        	query.exec(QObject::tr("insert into cars values(8, '帕萨特',3, 2000)"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (4)修改“main.cpp”的代码如下:

    #include "mainwindow.h"
    #include 
    #include 
    #include "connectdlg.h"
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        ConnDlg dialog;
        if(dialog.exec() != QDialog::Accepted)
            return -1;
        dialog.show();
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (5)在“SQLEx.pro”文件中添加如下内容:
    QT += sql
    (6)运行程序,出现如图13.7所示的界面。
    在这里插入图片描述

    3.主/从视图应用
    (1)在头文件“mainwindow.h”中添加如下代码。
    (2)在源文件“mainwindow.cpp”中添加如下代码:

    #include 
    #include 
    MainWindow::MainWindow(const QString &factoryTable, const QString &car Table, QFile *carDetails, QWidget *parent) : QMainWindow(parent)
    {
        file = carDetails;
        readCarData();					//(a)
        carModel = new QSqlRelationalTableModel(this);		//(b)
        carModel->setTable(carTable);
        carModel->setRelation(2, QSqlRelation(factoryTable, "id", "manufactory"));						//(c)
        carModel->select();
        factoryModel = new QSqlTableModel(this);			//(d)
        factoryModel->setTable(factoryTable);
        factoryModel->select();}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    changeFactory()函数的具体代码如下:

    void MainWindow::changeFactory(QModelIndex  index)
    {
        QSqlRecord record = factoryModel->record(index.row());	//(a)
        QString factoryId = record.value("id").toString();		//(b)
        carModel->setFilter("id = '"+ factoryId +"'") ;			//(c)
        showFactorytProfile(index);					//(d)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在“详细信息”中显示所选汽车制造商的信息函数showFactorytProfile()的具体代码如下:

    void MainWindow::showFactorytProfile(QModelIndex index)
    {
         QSqlRecord record = factoryModel->record(index.row());	//(a)
         QString name = record.value("manufactory").toString();	//(b)
         int count = carModel->rowCount();			//(c)
         profileLabel->setText(tr("汽车制造商:%1\n产品数量: %2").arg(name). arg(count));								//(d)
         profileLabel->show();
         titleLabel->hide();
         attribList->hide();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    showCarDetails()函数的具体代码如下:

    void MainWindow::showCarDetails(QModelIndex index)
    {
        	QSqlRecord record = carModel->record(index.row());		//(a)
        	QString factory = record.value("manufactory").toString();		//(b)
        	QString name = record.value("name").toString();		//(c)
        	QString year = record.value("year").toString();			//(d)
        	QString carId = record.value("carid").toString();		//(e)
        	showFactorytProfile(indexOfFactory(factory));			//(f)
        	titleLabel->setText(tr("品牌: %1 (%2)").arg(name).arg(year));	//(g)
        	titleLabel->show();
        	QDomNodeList cars = carData.elementsByTagName("car");	//(h)
        	for(int i = 0; i < cars.count(); i++)			//找出所有car标签
        	{
           	QDomNode car = cars.item(i);
            	if(car.toElement().attribute("id") == carId)			//(i) 
             	{
                	getAttribList(car.toElement());					//(j)
                	break;
            	}
        	}
        	if(!attribList->count() == 0)
          	attribList->show();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    函数getAttribList()检索以上获得的car标签下的所有子节点,将这些子节点的信息在“详细信息”的QListWidget窗体中显示。这些信息包括信息编号number和该编号下的信息内容,其具体代码如下:

    void MainWindow::getAttribList(QDomNode car)
    {
        	attribList->clear();
        	QDomNodeList attribs = car.childNodes();
        	QDomNode node;
        	QString attribNumber;
        	for (int j = 0; j < attribs.count(); j++) 
        	{
            	  node = attribs.item(j);
            	  attribNumber = node.toElement().attribute("number");
            	  QListWidgetItem *item = new QListWidgetItem(attribList);
            	  QString showText(attribNumber + ": " + node.toElement().text());
            	  item->setText(tr("%1").arg(showText));
        	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    因为addCar()函数此时还没有实现具体的功能,所以代码部分暂时为空:

    void MainWindow::addCar(){}
    
    • 1

    delCar()函数的具体代码。

    removeCarFromFile()函数遍历XML文件中的所有car标签,首先找出id属性与汽车表中所选记录主键相同的节点,然后将其删除。其具体代码如下:

    void MainWindow::removeCarFromFile(int id)
    {
        	QDomNodeList cars = carData.elementsByTagName("car");
        	for(int i = 0; i< cars.count(); i++) 
        	{
          	QDomNode node = cars.item(i);
            	if(node.toElement().attribute("id").toInt() == id) 
            	{
    			carData.elementsByTagName
                       ("archive").item(0).removeChild(node);
                  break;
          	}
       	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    removeCarFromDatabase()函数将汽车表中所选中的行从汽车表模型“carModel”中移除即可,这个模型将自动删除数据库表中的对应记录,其具体代码如下:

    void MainWindow::removeCarFromDatabase(QModelIndex index)
    {
    	 carModel->removeRow(index.row());
    }
    
    • 1
    • 2
    • 3
    • 4

    删除了某个汽车制造商的全部产品后,需要删除这个汽车制造商,decreaseCarCount()函数实现了此功能,其具体代码如下:

    void MainWindow::decreaseCarCount(QModelIndex index)
    {
    	 int row = index.row();
        	 int count = carModel->rowCount();			//(a)
    	  if(count == 0)					//(b)
                factoryModel->removeRow(row);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    readCarData()函数的具体代码如下:

    void MainWindow::readCarData()
    {
    	 if(!file->open(QIODevice::ReadOnly))
            	return;
    	 if(!carData.setContent(file)) 
    	 {
    	     file->close();
              return;
    	 }
    	 file->close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中,在QGroupBox* MainWindow::createFactoryGroupBox()函数的“factoryView-> setAlternatingRowColors(true)和QGroupBox *box = new QGroupBox (tr(“汽车制造商”))”语句之间添加以下代码:

    factoryView->setModel(factoryModel);
    connect(factoryView, SIGNAL(clicked (QModelIndex )),
                     this, SLOT(changeFactory(QModelIndex )));
    
    • 1
    • 2
    • 3

    其中,在QGroupBox* MainWindow::createCarGroupBox() 函数的“carView-> set AlternatingRowColors(true)和QVBoxLayout *layout = new QVBoxLayout”语句之间添加以下代码:

    carView->setModel(carModel);
    connect(carView, SIGNAL(clicked(QModelIndex)),
         this, SLOT(showCarDetails(QModelIndex)));
    connect(carView, SIGNAL(activated(QModelIndex)),
         this, SLOT(showCarDetails(QModelIndex)));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中,在void MainWindow::createMenuBar()函数的最后添加如下代码:

    connect(addAction, SIGNAL(triggered(bool)), this, SLOT(addCar()));
    connect(deleteAction, SIGNAL(triggered(bool)), this, SLOT(delCar()));
    connect(quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
    
    • 1
    • 2
    • 3

    indexOfFactory()函数通过制造商的名称进行检索,并返回一个匹配的模型索引QModelIndex,供汽车制造商表模型的其他操作使用,其具体代码如下:

    QModelIndex MainWindow::indexOfFactory(const QString &factory)
    {
        for(int i = 0; i < factoryModel->rowCount(); i++) 
        {
             QSqlRecord record =  factoryModel->record(i);
             if(record.value("manufactory") == factory)
                  return factoryModel->index(i, 1);
        }
        return QModelIndex();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (3)源文件“main.cpp”的具体代码如下:

    #include 
    #include 
    #include "connectdlg.h"
    int main(int argc, char *argv[])
    {
    	  QApplication a(argc, argv);
        	  //MainWindow w;
        	  //w.show();
        	  ConnDlg dialog;
        	  if(dialog.exec() != QDialog::Accepted)
    	        return -1;
    	  QFile *carDetails = new QFile("attribs.xml");
           MainWindow window("factory", "cars", carDetails);
           window.show();
           return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (4)新建一个XML文件,将该文件存放在该工程的目录下,以下是“attribs.xml”文件的详细内容。
    (5)在“SQLEx.pro”文件中添加如下内容:
    QT += xml
    (6)此时运行程序,选择驱动QSQLITE,单击“连接”按钮,将弹出如图13.8所示的视图界面。
    在这里插入图片描述

    当用户在“操作菜单”中选择“删除”子菜单时,弹出如图13.9所示的“删除汽车记录”对话框。
    在这里插入图片描述

    4.添加记录功能
    (1)Dialog类继承自QDialog类,该类定义了“添加产品”对话框的界面及完成将新加入的记录分别插入汽车制造商表和汽车表,并且将详细的车型信息写入XML文件中的功能,其头文件“editdialog.h”的具体代码。
    (2)源文件“editdialog.cpp”的具体代码。
    Dialog::submit()函数的具体代码。

    findFactoryId()函数的具体代码如下:

    int Dialog::findFactoryId(const QString &factory)
    {
         int row = 0;
          while (row < factoryModel->rowCount()) 
    	 {
    	      QSqlRecord record = factoryModel->record(row);		//(a)
               if(record.value("manufactory") == factory)			//(b)
    	   	  	return record.value("id").toInt();		//(c)
            	  else
          	 	row++;
        	 }
        	 return -1;			//如果未查询到则返回“-1”
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    addNewFactory ()函数的具体代码如下:

    int Dialog::addNewFactory(const QString &factory,const QString &address)
    {
        QSqlRecord record;
        	int id = generateFactoryId();		//生成一个汽车制造商表的主键值
    	/* 在汽车制造商表中插入一条新记录,厂名和地址由参数传入 */
         QSqlField f1("id", QVariant::Int);
         QSqlField f2("manufactory", QVariant::String);
         QSqlField f3("address", QVariant::String);
        	f1.setValue(QVariant(id));
        	f2.setValue(QVariant(factory));
        	f3.setValue(QVariant(address));
        	record.append(f1);
        	record.append(f2);
        	record.append(f3);
        	factoryModel->insertRecord(-1, record);
        	return id;								//返回新记录的主键值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    addNewCar()函数与addNewFactory()函数的操作类似,其具体代码如下:

    int Dialog::addNewCar(const QString &name, int factoryId)
    {
      	int id = generateCarId();			//生成一个汽车表的主键值
        	QSqlRecord record;
    	/* 在汽车表中插入一条新记录 */
        	QSqlField f1("carid", QVariant::Int);
        	QSqlField f2("name", QVariant::String);
        	QSqlField f3("factoryid", QVariant::Int);
        	QSqlField f4("year", QVariant::Int);
        	f1.setValue(QVariant(id));
        	f2.setValue(QVariant(name));
        	f3.setValue(QVariant(factoryId));
        	f4.setValue(QVariant(yearEditor->value()));
        	record.append(f1);
        	record.append(f2);
        	record.append(f3);
        	record.append(f4);
        	carModel->insertRecord(-1, record);
        	return id;								//返回这条新记录的主键值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    addAttribs()函数实现了将录入的车型信息写入XML文件的功能,其具体代码。
    revert()函数实现了撤销用户在界面中的录入信息功能,其具体代码如下:

    void Dialog::revert()
    {
        	 factoryEditor->clear();
        	 addressEditor->clear();
        	 carEditor->clear();
        	 yearEditor->setValue(QDate::currentDate().year());
        	 attribEditor->clear();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    createInputWidgets()函数实现了输入界面的完成,其具体代码。
    createButtons()函数完成了按钮的组合功能,其具体代码如下:

    QDialogButtonBox *Dialog::createButtons()
    {
        	QPushButton *closeButton = new QPushButton(tr("关闭"));
        	QPushButton *revertButton = new QPushButton(tr("撤销"));
        	QPushButton *submitButton = new QPushButton(tr("提交"));
        	closeButton->setDefault(true);
        	connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
        	connect(revertButton, SIGNAL(clicked()), this, SLOT(revert()));
        	connect(submitButton, SIGNAL(clicked()), this, SLOT(submit()));	//(a)
        	QDialogButtonBox *buttonBox = new QDialogButtonBox;
        	buttonBox->addButton(submitButton, QDialogButtonBox::ResetRole);
        	buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole);
        	buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole);
        	return buttonBox;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    generateFactoryId()函数将全局变量uniqueFactoryId以顺序加1的方式生成一个不重复的主键值,并将其返回供添加操作使用,其具体代码如下:

    int Dialog::generateFactoryId()
    {
        	uniqueFactoryId += 1;
        	return uniqueFactoryId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    generateCarId()函数将全局变量uniqueCarId以顺序加1的方式生成一个不重复的主键值,并将其返回供添加操作使用,其具体内容如下:

    int Dialog::generateCarId()
    {
        	uniqueCarId += 1;
        	return uniqueCarId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)在源文件“mainwindow.cpp”中添加的代码如下:

    #include "editdialog.h"
    extern int uniqueCarId;
    extern int uniqueFactoryId;
    
    • 1
    • 2
    • 3

    在MainWindow构造函数中的
    QGroupBox *details = createDetailsGroupBox()和QGridLayout *layout = new QGridLayout语句之间添加以下代码:

    uniqueCarId = carModel->rowCount();			//(a)
    uniqueFactoryId = factoryModel->rowCount();		//(b)
    
    • 1
    • 2

    MainWindow::addCar()函数启动了一个添加记录的对话框,具体添加操作由该对话框完成,添加完成后进行显示,其具体实现内容如下:

    void MainWindow::addCar()
    {
        	Dialog *dialog = new Dialog(carModel, factoryModel,carData, file, this);
        	int accepted = dialog->exec();
        	if(accepted == 1) 
        	{
            	int lastRow = carModel->rowCount() - 1;
             	carView->selectRow(lastRow);
             	carView->scrollToBottom();
             	showCarDetails(carModel->index(lastRow, 0));
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (4)当用户选择“添加”菜单时,弹出如图13.10所示的“添加产品”对话框,在其中输入新添加的汽车品牌信息。
    操作之后,在主界面视图中就立即能够看到新加入的新品牌汽车的记录信息,如图13.11所示。
    在这里插入图片描述



    本章相关例程源码下载

    1.Qt5开发及实例_CH1301.rar 下载

    Qt5开发及实例_CH1301.rar

    2.Qt5开发及实例_CH1302.rar 下载

    Qt5开发及实例_CH1302.rar

  • 相关阅读:
    I420转RGB24,YUY2转RGB24,RGB24垂直翻转,RGB24水平翻转
    Linux安装Tomcat最新版
    自动控制原理9.2---线性系统的可控性与可观测性(中)
    pytorch 使用DataParallel 单机多卡和单卡保存和加载模型的正确方法
    PHP笔记-->读取JSON数据以及获取读取到的JSON里边的数据
    ADC驱动怎么应用以及中断顶半部和底半部
    「笔试刷题」:腐烂的苹果
    Windows的内存管理机制
    第一代高通S7和S7 Pro音频平台开启全新水平音频体验
    Oracle(1):Oracle简介
  • 原文地址:https://blog.csdn.net/kingpower2018/article/details/133034933