- 重点:面试考试大概率涉及,需要不借助任何资料掌握。
- 掌握:面试考试可能涉及,需要不借助任何资料掌握。
- 熟悉:面试考试可能涉及,可以稍微参考资料掌握。
- 了解:面试考试小概率涉及,面试拔高加分。
一、QT简介
1. Qt是什么?
这门课的定位:
- C++的实践课
- 系统性认识图像界面编程GUI
- 新的就业方向
Qt虽然被经常当做是一个基于C++语言的图形用户界面(GUI)的开发框架,用来开发图形用户界面的应用程序,但这不是Qt的全部。
除此之外,Qt还支持许多非图形用户界面的功能。
例如:文件IO、多线程、网络通信、数据库、图像处理、音视频处理等。
学习Qt五阶段
- 第一阶段 Qt简介、UI入门、信号槽
- 第二阶段 Qt中常用组件与常用类
- 第三阶段 多窗口编程
- 第四阶段 文件IO、数据库、网络编程
- 第五阶段 项目打包、项目实践
Qt在软件开发领域,使用广泛,主要分为以下三类:
1.纯软件开发
此方向与嵌入式的关系不大,这种方式开发的Qt程序本身作为一款独立的产品:
2.嵌入式上位机
用来控制和获取下位机的数据的应用程序,例如:
3. 直接成为嵌入式产品的控制程序
对于某些自带屏幕的嵌入式产品,内部可以使用Qt来开发交互程序。例:
Qt最重要的特点就是其跨平台特性
Qt的跨平台是 一次编程,到处编译
Java的跨平台是 一次编译, 到处运行
另外Qt还有一些通用特性:
- 面向对象开发
- 丰富的应用程序接口API
- 易用的开发环境
- 开源
- 大量的开发文档
2. 新建项目
本次Qt开发仍然使用C++课程中的Qt Creator,但是需要把软件的编码设置成初始值:
更改完成后,才可以建立一个正常支持中文的项目。操作步骤如下:
- 在Qt Creator中点击
- 在弹出的窗口中,按照下图操作
- 在弹出的窗口中设置路径和名称,注意不要包含中文字符!!
- 直接点击“下一步”
- 在弹出窗口中,选择基类为“QDialog”,取消“创建界面”选项,然后点击”下一步“。
- 在项目管理界面,直接点击完成。可以看到项目中包含的文件
- 点击可以编译并运行项目,项目运行后可以看到一个可视化窗口
3. 构建目录和工作目录
项目运行后,存在两个重要目录构建目录和工作目录
3.1 构建目录
存放编译过程中产生的文件,位置在
3.2 工作目录
工作目录就是创建项目第三步时设置的目录,用于存放项目的源文件、头文件以及开发文件。
工作目录下包含以下文件
4. 项目结构
4.1 项目配置文件.pro
此文件在C++项目中也存在,用于配置项目的参数。
在Windows的文件管理器中,直接双击.pro文件可以导入项目。
.pro结构如下:
#-------------------------------------------------
#
# Project created by QtCreator 2024-05-29T10:04:09
#
#-------------------------------------------------
# 添加core核心模块 gui 传统图像界面模块
QT += core gui# 当Qt的主版本号大于4时 添加widgets新的图像界面模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets# 设置的项目文件,生成的可执行文件名
TARGET = Day1_24031_helloQt
# 项目构建方式
TEMPLATE = app# 项目中包含的源文件
SOURCES += main.cpp\dialog.cpp
# 项目中包含的头文件
HEADERS += dialog.h
4.2 用户文件.user
用户文件在Qt Creator中不可见,但是确实存在于工作目录中(项目构建后)。
此文件时Qt根据当前计算机的开发环境自动生成的文件,每个计算机几乎不通用此文件。因此在提交作业时,先删除此文件。
4.3 主文件 main.cpp
程序的入口,包含主函数。通常不要主动修改代码。
#include "dialog.h"
#include <QApplication>
/*** @brief main 主函数,程序的入口* @return */
int main(int argc, char *argv[])
{// 创建了一个管家类栈内存对象,管理着整个程序QApplication a(argc, argv);// 创建了一个自定义对话框窗口的栈内存Dialog w;// 展示w.show();return a.exec(); // 进入主事件循环
}
4.4 头文件dialog.h
在Qt中一个自定义类的声明都写在.h中,这个类所用到的其他类头文件,也在此文件中引入。
#ifndef DIALOG_H
#define DIALOG_H
// Qt 自带类型通常以 Q 开头
#include <QDialog>
/*** @brief The Dialog class 自定义对话框窗口* 继承QDialog类(对话框窗口类)*/
class Dialog : public QDialog
{Q_OBJECT // 先不管public:Dialog(QWidget *parent = 0);// 构造函数的声明~Dialog(); // 析构函数的声明
};#endif // DIALOG_H
4.5 源文件dialog.cpp
包含与头文件配套的类外定义。
#include "dialog.h"
/*** @brief Dialog::Dialog 构造函数定义* @param parent*/
Dialog::Dialog(QWidget *parent): QDialog(parent) // 透传构造
{}// 析构函数
Dialog::~Dialog()
{}
5. 帮助文档
学习Qt一定要学习如何查阅帮助我呢当,通常对于不是很常用的内容在开发时都是随用随查,是官方的一手资料。
有三种查询文档的方式:
1.直接启动Assistant程序,是一个独立的文档程序。
2.在Qt Creator中点击,可以打开一个内置的文档Assistant程序。
3.光标点击到要查询的内容上,双击F1,可以直接跳转到对应文档内容。
需要关注文档重点的位置:
6. 调试信息
在C语言及C++中,无论是printf() 还是 cout输出的内容不区分前后台,但是在Qt中输出的内容是区分前后台的,通常前台指的是用户图形界面,用户可以直接看到,后台指的是在Qt Creator中(控制台)中,这里显示的后台信息是用户不可见,只对开发者开放。
使用QDebug类的qDebug()函数输出调试信息,支持中文。整个使用的方式与cout类似,但是有以下区别:
- 连续输出时,自动添加空格
- 每次输出语句结束时,自动添加换行
dialog.h
#ifndef DIALOG_H
#define DIALOG_H // 防止头文件被多次包含#include <QDialog> // 包含 QDialog 类的定义,Qt库中用于创建对话框窗口的类。
#include <QDebug> // 包含 QDebug 类的定义,用于调试输出信息class Dialog : public QDialog //定义了一个名为 Dialog 的类,并且该类继承自 QDialog 类。
{Q_OBJECT //一个Qt宏,支持信号和槽机制public: // 声明了 Dialog 类的公有成员函数Dialog(QWidget *parent = 0); // 接受一个 QWidget 指针作为父窗口参数~Dialog(); //析构函数声明。用于清理 Dialog 对象销毁时的资源
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent) // 构造函数的实现,接受一个 QWidget 指针作为父窗口参数。
{qDebug() << "构造函数" << "Hello World!";
}Dialog::~Dialog() / 析构函数的实现,用于在对象销毁时执行清理操作
{qDebug() << "析构函数";
}
tips:
当程序正在运行,再次运行可能会出现以下情况,只需要关闭正在运行的重复项目就可以了。
二、UI入门
1. QWidget类
QWidget是所有界面组件和窗口的基类,内部包含了一些最基础的界面特性。
常用属性:
- x : const int
横坐标,每个图像的左上角为定位点,横轴的零点在最左边,正方向向右
- y : const int
纵坐标,每个图像的左上角为定位点,纵轴的零点在最上边,正方向向下
虽然无法直接修改,可以通过以下函数来间接修改:
// 移动对象到(x,y)
// 参数1 新的横坐标
// 参数2 新的纵坐标
void move(int x, int y)
需要注意的是,位置包含边框
- width : const int
宽度,不包含边框
- height : const int
高度,不包含边框
虽然无法直接修改,可以通过以下函数来间接修改:
// 参数1 新的宽度
// 参数2 新的高度
void resize(int w, int h)
下面的函数可以同时设置以上四个属性:
void setGeometry(int x, int y, int w, int h)
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{qDebug() << "构造函数" << "Hello World!";// 将对话框窗口移动到屏幕坐标 (200, 200) 位置。this->move(200,200);// 将对话框窗口的大小设置为宽200,高600像素。resize(200,600);// 同时更改对话框的位置和大小。// 将对话框移动到屏幕坐标 (300, 300) 位置,并将其大小设置为宽600,高200像素。setGeometry(300,300,600,200);
}Dialog::~Dialog()
{qDebug() << "析构函数";
}
2. 添加子组件
上面的窗口什么都没有,实际上可以向窗口添加若干子组件,实现不同的显示与交互效果,本节以QPushButton(按压式按钮)组件为例。
QPushButton要持续存在,直到窗口关闭,因此使用堆内存。按照C++的内存回收逻辑,
子组件在父窗口的构造函数中创建,在析构函数销毁。
// 参数1 按钮上的文字
// 参数2 现阶段可以认为是给当前组件一个父窗口
QPushButton(const QString & text, QWidget * parent = 0)
以下是一个预设的QPushButton样式表,可以根据实际情况自行改动
#define QPushButton_STYTLE (QString("\
/*按钮普通态*/\
QPushButton\
{\font-family:Microsoft Yahei;\/*字体大小为20点*/\font-size:20pt;\/*字体颜色为白色*/\color:white;\/*背景颜色*/\background-color:rgb(14 , 150 , 254);\/*边框圆角半径为8像素*/\border-radius:8px;\
}\
/*按钮悬停态*/\
QPushButton:hover\
{\/*背景颜色*/\background-color:rgb(100 , 137 , 255);\
}\
/*按钮按下态*/\
QPushButton:pressed\
{\/*背景颜色*/\background-color:rgb(14 , 135 , 10);\/*左内边距为3像素,让按下时字向右移动3像素*/\padding-left:3px;\/*上内边距为3像素,让按下时字向下移动3像素*/\padding-top:3px;\
}"))
推荐两个配色网站:
在线颜色选择器 | RGB颜色查询对照表
Color Palette Generator - Create Beautiful Color Schemes
// 设置样式表
void setStyleSheet(const QString & styleSheet)
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>#define QPushButton_STYTLE (QString("\
/*按钮普通态*/\
QPushButton\
{\font-family:Microsoft Yahei;\/*字体大小为20点*/\font-size:20pt;\/*字体颜色为白色*/\color:white;\/*背景颜色*/\background-color:rgb(255, 0, 102);\/*边框圆角半径为8像素*/\border-radius:8px;\
}\
/*按钮悬停态*/\
QPushButton:hover\
{\/*背景颜色*/\background-color:rgb(100 , 137 , 255);\
}\
/*按钮按下态*/\
QPushButton:pressed\
{\/*背景颜色*/\background-color:rgb(14 , 135 , 10);\/*左内边距为3像素,让按下时字向右移动3像素*/\padding-left:3px;\/*上内边距为3像素,让按下时字向下移动3像素*/\padding-top:3px;\
}"))class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();
private:QPushButton *btn;// 成员变量 // 指向 QPushButton 对象的指针,表示一个按钮
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent) : QDialog(parent) //在构造 Dialog 对象时,调用 QDialog 基类的构造函数,并传递 parent 参数,初始化基类部分。
{move(300,300);resize(400,400);// 创建按钮按钮对象// 参数2使用了this指针+多态btn = new QPushButton("你好",this); // 按钮的父对象为当前的 Dialog 对象,使用了 this 指针。// 这样可以确保按钮在对话框内,并且在对话框销毁时一起销毁btn->setStyleSheet(QPushButton_STYTLE); // 设置按钮的样式表为宏定义的PushButton_STYTLEbtn->setGeometry(200,200,150,50);
}Dialog::~Dialog()
{// C++内存回收delete btn;
}//
//继承与多态:Dialog 类继承自 QDialog,并在构造函数中初始化基类部分。
//this 指针:在成员函数中使用 this 指针,指向当前对象。这在创建按钮时用于将按钮的父对象设置为当前 //Dialog 对象。
//内存管理:手动删除 btn,确保在 Dialog 对象销毁时释放按钮占用的内存,防止内存泄漏。
Tips:
帮助文档翻页技巧
- 直接点击小箭头
- Alt + 方向键
- 鼠标侧键
三、信号槽
1. 概念
信号和槽是两种函数,这是Qt在C++ 的基础上新增的特性,类似于其他技术里的回调或委托。
可以理解为信号槽机制就是:“如果A对象……则B对象……”
信号槽通过程序员提前设定的约定,可以实现对象之间的通信,有两个先决条件:
- 通信对象必须从QObject类中派生出来 QObject是所有Qt类型的基类
- 类中要有Q_OBJECT宏
2. 函数原型
信号槽需要在使用之前进行约定,这个约定被称为连接,使用下面的函数实现:
【例子】如果小曾考100,则彤姐请吃饭
// 参数1 发射者,表示因发起的对象,通常是一个名词(小曾)
// 参数2 信号函数,表示因发起的动作,通常是一个动词(考100),需要用SIGNAL()包裹
// 参数3 接收者,表示果发起的对象,通常是一个名词(彤姐)
// 参数4 槽函数,表示果发起的动作,通常是一个动词(请吃饭),需要用SLOT()包裹
QObject::connect(const QObject * sender, const char * signal, const QObject * receiver,const char * method)[static]
3. 实现
为了学习,把信号槽分为以下三种实现方式。
- 自带信号→自带槽
- 自带信号→自定义槽
- 自定义信号
3.1 自带信号→自带槽
这种连接方式是最简单的,因为信号函数和槽函数是Qt内置的,只要查询出函数后,使用connect函数连接即可。
【例子】点击按钮,关闭窗口
#include "dialog.h" // 包含自定义的头文件"dialog.h",其中声明了Dialog类// Dialog类构造函数,接受一个QWidget指针作为父窗口参数
Dialog::Dialog(QWidget *parent): QDialog(parent) // 调用基类QDialog的构造函数进行初始化
{move(300,300); // 将对话框移动到屏幕坐标 (300, 300) 位置resize(400,400); // 将对话框大小设置为 400x400 像素// 创建按钮对象,显示文本“你好”,将其父对象设置为对话框本身// 参数1:按钮显示的文本// 参数2:父对象,指定为当前对话框(this)btn = new QPushButton("你好",this);// 设置按钮的样式表,假设QPushButton_STYTLE是已定义的样式字符串btn->setStyleSheet(QPushButton_STYTLE);// 设置按钮的位置和大小// 参数1:按钮左上角的X坐标// 参数2:按钮左上角的Y坐标// 参数3:按钮的宽度// 参数4:按钮的高度btn->setGeometry(200,200,150,50);// 连接按钮的clicked信号到对话框的close槽函数// 使用新式信号和槽连接语法// 参数1:信号发送者对象(btn)// 参数2:信号(&QPushButton::clicked)// 参数3:信号接收者对象(this,即对话框)// 参数4:槽函数(&QDialog::close)connect(btn, &QPushButton::clicked, this, &QDialog::close);
}// Dialog类析构函数
Dialog::~Dialog()
{// 释放按钮对象所占用的内存,以防止内存泄漏delete btn;
}
3.2 自带信号→自定义槽
Qt不可能给我们内置所有动作,特别是一些复杂动作,需要开发者去手动编写槽函数。这种方法也是所有连接中最多的。
槽函数实际上是一种特殊的成员函数,在声明时权限的作用主要是修饰其作为普通成员函数的效果,不影响信号槽的连接效果。
【例子】点击按钮,向下和向右移动10个像素,同时输出当前窗口的坐标
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();
private:QPushButton *btn;// 自定义槽函数
private slots:// 最小权限法则void mySlot(); // 小驼峰命名法:第一个单词首字母小写,其他单词首字母大写
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{move(300,300);resize(400,400);btn = new QPushButton("移动",this);btn->move(200,200);connect(btn,SIGNAL(clicked()),this,SLOT(mySlot())); // 将按钮的 clicked() 信号连接到当前对象的 mySlot() 槽函数。// 当按钮被点击时,将调用 mySlot()。
}Dialog::~Dialog()
{}// 源文件定义
void Dialog::mySlot()
{// 先获得之前的坐标
// this 指针指向当前对象,而当前对象是一个 QDialog 对象。
// QDialog 继承自 QWidget,QWidget 类提供了用于获取窗口位置和大小的相关成员函数,
// 如 x()、y()、width() 和 height() 等
// 由于 Dialog 类继承自 QDialog,而 QDialog 又继承自 QWidget,
// 所以 this->x() 实际上是调用了 QWidget 的 x() 方法。
// QWidget 类的方法可以直接在 Dialog 对象中使用,因为 Dialog 是 QWidget 的子类。int x = this->x();int y = this->y();// 移动到目标位置move(x + 10,y + 10);// 输出当前位置qDebug() << this->x() << this->y();}
3.3 自定义信号
为了讲解,强行使用自定义信号,并非问题的最优解,主要学写法。
信号函数是一种非常特殊的函数,因为其只有声明,没有定义,即没有函数体,因此无法调用,只能使用emit关键字发射。
【例子】点击按钮,关闭窗口
3.1节信号槽的连接方式:
腾讯文档-流程图插件https://docs.qq.com/flowchart-addon
本节强行在中间添加一层自定义信号的转发过程:
腾讯文档-流程图插件
表示信号槽连接 表示内部实现
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();
private:QPushButton *btn;private slots:void mySlot(); // 自定义槽函数// 声明自定义信号
signals:void mySignal();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(400,400);move(400,400);btn = new QPushButton("关闭窗口",this);btn->move(200,200);// 第一个信号槽连接connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));// 第二个信号槽连接connect(this,SIGNAL(mySignal()),this,SLOT(close()));
}Dialog::~Dialog()
{delete btn;
}void Dialog::mySlot()
{qDebug() << "发射信号!";// 发射自定义信号emit mySignal();
}
3. 信号槽传参
【例子】点击按钮,按钮上显示点击的次数。
正常解法:
QPushButton的文字属性text : QString,可以使用setter函数来更改按钮文字
void setText(const QString & text)
腾讯文档-流程图插件
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();
private:QPushButton *btn;int count;
private slots:void mySlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);move(200,200);btn = new QPushButton("0",this);btn->move(200,200);connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));count = 0;
}Dialog::~Dialog()
{}
void Dialog::mySlot()
{// 静态局部变量// static int count = 0;count++;// 类型转换int → QStringQString text = QString::number(count);// 更改按钮文字btn->setText(text);
}
- 理论上可以传递任意多个多个参数,但是实际上以1-2个居多
- 信号的参数个数必须大于或等于槽函数参数的个数
- 信号的参数类型必须和槽函数的参数类型匹配
5. 对应关系
5.1 一对多
一对多是指一个信号可以连接多个槽函数,对于一对多的关系,可以合并成一对一,因为槽函数也是一个成员函数,可以整合到一个槽函数中调用。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H // 防止头文件被重复包#include <QDialog> // Qt 中的对话框基类
#include <QPushButton> // 包含了 QPushButton 类的定义
#include <QDebug> // 包含了 QDebug 类的定义,用于调试输出
class Dialog : public QDialog /声明了一个名为 Dialog 的类,并且它是 QDialog 的子类
{Q_OBJECT //宏 信号和槽机制会用到public:Dialog(QWidget *parent = 0); // 构造函数 接受一个父级窗口指针参数 默认没有父窗口~Dialog(); // 析构函数 销毁对象时清理private: //声明了两个私有成员变量QPushButton *btn1; // 指向 QPushButton 的指针,表示按钮QPushButton *btn2;private slots: // 声明了三个私有槽函数void slot1();void slot2();void slot3();};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent) // Dialog 类的构造函数实现,初始化列表将 parent 参数//传递给基类 QDialog 的构造函数。
{resize(500,500); // 设置对话框的尺寸为500x500像素move(200,200);btn1 = new QPushButton("一对多",this); // 创建一个新的按钮 btn1 // 并将其设置为 Dialog 对象的子对象btn1->move(200,200);btn2 = new QPushButton("一对一",this);btn2->move(200,300);// 一对多connect(btn1,SIGNAL(clicked()),this,SLOT(slot1()));//将 btn1 的 clicked 信号连接到 Dialog 类的 slot1() 槽函数。connect(btn1,SIGNAL(clicked()),this,SLOT(slot2()));//将 btn1 的 clicked 信号连接到 Dialog 类的 slot2() 槽函数。// 一对多的是可以灵活处理每一个对应关系// 例如可以随时断开某个信号槽的连接// 断开的方式与连接的方式完全一致,只要在函数名前加disdisconnect(btn1,SIGNAL(clicked()),this,SLOT(slot2()));// 一对一connect(btn2,SIGNAL(clicked()),this,SLOT(slot3()));// 将 btn2 的 clicked 信号连接到 Dialog 类的 slot3() 槽函数。
}Dialog::~Dialog() // 销毁对象时执行清理工作
{delete btn1; // 释放 btn1 按钮的内存delete btn2; // 释放 btn2 按钮的内存
}void Dialog::slot1()
{qDebug() << "槽函数1";
}void Dialog::slot2()
{qDebug() << "槽函数2";
}void Dialog::slot3()
{// 直接调用槽函数slot1();slot2();
}
5.2多对一
多对一指的是多个信号连接同一个槽函数,多对一的问题在于槽函数无法直接判断哪个信号触发了槽函数,但是可以通过sender函数来在槽函数中获得发射者的对象,通过对象比对来判断来源。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H //防止头文件重复定义#include <QDialog> //对话框基类#include <QPushButton> // 包含了 QPushButton 类的定义,这是一个按钮控件
#include <QDebug> // 包含了 QDebug 类的定义,用于调试输出class Dialog : public QDialog
{Q_OBJECT //声明一个名为 Dialog 的类,继承自 QDialog,并启用 Qt 的信号和槽机制public:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton *btn1;QPushButton *btn2;private slots:// 多对一槽函数void moreToOneSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn1 = new QPushButton("按钮1",this);btn2 = new QPushButton("按钮2",this);btn1->move(200,200);btn2->move(200,300);connect(btn1,SIGNAL(clicked()),this,SLOT(moreToOneSlot()));connect(btn2,SIGNAL(clicked()),this,SLOT(moreToOneSlot()));
}Dialog::~Dialog()
{delete btn1;delete btn2;
}void Dialog::moreToOneSlot()
{// 通过sender函数来获得发射者对象if(btn1 == sender()){qDebug() << "按钮1";}else if (btn2 == sender()){qDebug() << "按钮2";}
}
四、基本组件
1. Designer设计师
Qt包含了一个Designer 程序,用于通过可视化界面设计的开发界面
保存的文件格式为.ui(界面文件)。界面文件内部使用xml语法的标签式语言。
在Qt Creator中创建项目,选中界面文件选项,可以让自带的窗口使用创建使用界面文件。
可以看到项目里多了一个界面文件,双击此文件,可以直接使用内置的Designer程序打开并设计。
Designer的区域划分如下:
所有Designer中的操作都可以通过C++代码实现。
2.布局Layout
可以把布局看成一个透明的盒子,内部可以放置子组件,这些子组件会按照布局的预设的规则自动排序。
垂直布局:内部组件竖着排成一排。
水平布局:内部组件横着排成一排。
格栅布局:内部组件排布成n*m
表单布局:用户随意搭建
垂直布局和水平布局的使用方式类似,只是方向不同,常用属性如下:
选中布局后,点击可以打破布局。
布局可以贴合窗口,只要选中窗口对象,再次点击按钮之一即可。
使用伸展器可以填充空白。
布局是可以嵌套的,对于外层布局而言,内层布局相当于一个外层布局的子组件。
3. QWidget类
QWidget类的属性在Designer中显示为淡黄色,下面是一些常用属性:
4. 界面文件与C++代码的关系
5. QLabel标签
QLabel用于显示文字或图片,需要注意的是,QLabel不能与用户交互,只能展示使用,因此没有合适的信号函数。
QLabel常用属性如下:
Qt可以直接从本地读取图片,支持相对路径和绝对路径,但是不建议这样做,原因是换一台计算机运行程序时,图片的路径可能会存在变化。
建议先把图片导入到项目中,成为项目资源,直接使用Qt虚拟的资源路径导入图片,可以在任何环境下使用这些资源图片了。
Qt支持以下几种常用的图片格式:
jpg(不包含透明度)、png(包含透明度)、gif(动图)等
注意导入的图片不能特别大(分辨率过高或文件体积过大),因为图片的操作非常消耗资源。
下面是导入图片成为项目资源的操作步骤:
- 把命名好的图片文件(不包含中文)放到项目的工作目录中。
- 在Qt Creator中选中项目名称,鼠标右键,点击“添加新文件”
- 再弹出的窗口中,按照下图所示进行操作
- 在弹出的窗口中给资源文件命名,例:res
- 在项目管理界面直接点击完成,可以看到项目中多了一个.qrc格式的文件。
- 选中qrc文件,点击,可以给资源文件新建一个虚拟路径。
- 选中qrc文件,点击,可以导入图片到项目中作为资源。
- 导入成功后,可以在.qrc文件中看到导入成功的图片
- 点击重新构建项目,然后就可以在Designer中找到图片资源并使用。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPixmap> // 图片类
#include <QSize> // 大小类
#include <QMovie> // 电影类namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private:Ui::Dialog *ui;QMovie* movie;// 电影对象,用于播放gif
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);// 创建一个图片对象// 图片资源的路径(qrc文件中选择图片右键复制)QPixmap pic(":/new/prefix1/st.png");// 创建一个包含目标缩放大小的QSize// 参数1 宽度// 参数2 高度QSize size(ui->label_2->width(),ui->label_2->height());// 缩放// 参数1 QSize对象,表示目标尺寸// 参数2 缩放模式Qt::AspectRatioMode的枚举值// 参数3 以速度或质量优先 Qt::TransformationMode// 返回值 转换后的QPixmappic = pic.scaled(size,Qt::IgnoreAspectRatio,Qt::FastTransformation);// 使用组件显示图片ui->label_2->setPixmap(pic);// 创建电影对象// 参数为资源路径movie = new QMovie(":/new/prefix1/gege.gif");// 给QLabel设置电影ui->label->setMovie(movie);// 播放电影movie->start();
}Dialog::~Dialog()
{delete ui;delete movie;
}
需要注意的是,尽量在项目开发之前使用PS等软件预先处理好图片,减少代码运行的开销,提升代码的运行效率,减少资源占用。
6.QAbstractButton按钮类(掌握)
QAbstractButton类是按钮的抽象基类,内部包含按钮很多基础属性和函数。
常用属性:
文本
图标
图标尺寸
是否可选
是否已选
按钮类常用信号如下:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QDebug>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:// 与void toggled(bool checked)连接的槽函数void toggledSlot(bool);// 主动获取按钮的checked值void btnClickedSlot();private:Ui::Dialog *ui;
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);// 连接信号槽connect(ui->radioButton,SIGNAL(toggled(bool)),this,SLOT(toggledSlot(bool)));connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{delete ui;
}void Dialog::toggledSlot(bool checked)
{qDebug() << "今晚吃不吃KFC" << checked;
}void Dialog::btnClickedSlot()
{// 主动获取按钮状态,以肯德基为例bool result = ui->radioButton->isChecked();qDebug() << "今晚吃不吃KFC" << result;
}
可以使用QButtonGroup组件对多个按钮进行分组,这是一种按钮的逻辑分组,没有任何UI效果,其主要的目的是用一个信号槽同时监控多个按钮对象的状态。
信号函数如下:
参数中表示当前触发按钮的本身,int id表示当前触发按钮的序号。
// 向按钮组添加按钮
void QButtonGroup::addButton(QAbstractButton * button, int id = -1)
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private:Ui::Dialog *ui;private slots:// 与void valueChanged(int i)链接的槽函数void valueChangedSlot(int);
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);connect(ui->dial,SIGNAL(valueChanged(int)),this,SLOT(valueChangedSlot(int)));
}Dialog::~Dialog()
{delete ui;
}void Dialog::valueChangedSlot(int value)
{ui->horizontalScrollBar->setValue(value);ui->verticalScrollBar->setValue(value);ui->horizontalSlider->setValue(value);ui->verticalSlider->setValue(value);ui->spinBox->setValue(value);ui->doubleSpinBox->setValue(value);ui->progressBar->setValue(value);
}
五、常用类
1. QString字符串类
QString是Qt的字符串类,使用Unicode编码。
C++的std::string使用ASCII编码
QString的每个字符都是一个16位的QChar,而不是8位的char。
如果后续的学习工作中出现中文乱码问题,可以参考:
从此乱码是路人
QString几乎支持所有的std::string的API
除此之外也会增加一些新函数
// int → QString
// 参数1 要转换的数字
// 参数2 进制
// 返回值:转换后的新QString对象
QString QString::number(int n, int base = 10)[static]// int → QString
// 参数1 要转换的数字
// 参数2 进制
// 返回值:转换之后当前对象,支持链式调用
QString & QString::setNum(int n, int base = 10)// QString → int
// 参数1 转换成功或失败
// 参数2 进制
// 返回值 转换后的int数值,转换失败返回0
int QString::toInt(bool * ok = 0, int base = 10) const
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);QString str = "けこか你好吗";ui->label->setText(str);// int → QStringint a = 255;qDebug() << QString::number(a,16);// "ff"qDebug() << QString::number(a,8); // "377"qDebug() << str.setNum(a,2).append("!");// QString → intbool result = false;str = "0";qDebug() << str.toInt(&result);qDebug() << result;str = "lol";qDebug() << str.toInt(&result);qDebug() << result;
}Dialog::~Dialog()
{delete ui;
}
2. 容器类
Qt 容器类 C++的STL中的容器类
1.重写
2.速度和存储优化
3.减少了可执行文件的生成体积
4.兼容C++的STL容器的接口
5.更安全,多个线程中并发访问
2.1 顺序容器——QList类
- 在Qt Creator中选中项目名称,鼠标右键,点击“添加新文件”
- C++ --> C++ Class --> choose
- 在弹出的窗口中输入类名(使用帕斯卡命名法\大驼峰)
- 在项目管理界面点击完成,可以看到新的文件在项目中存在了
student.h
#ifndef STUDENT_H
#define STUDENT_H
// 引入Qt的头文件
#include <QString>class Student
{
public:Student(QString ,int ,QString);~Student();QString getName() const;void setName(const QString &value);int getAge() const;void setAge(int value);QString getMajor() const;void setMajor(const QString &value);private:QString name;int age;QString major;
};#endif // STUDENT_H
student.cpp
#include "student.h"Student::Student(QString name ,int age , QString major)
{this->name = name;this->age = age;this->major = major;
}Student::~Student()
{}
QString Student::getName() const
{return name;
}void Student::setName(const QString &value)
{name = value;
}
int Student::getAge() const
{return age;
}void Student::setAge(int value)
{age = value;
}
QString Student::getMajor() const
{return major;
}void Student::setMajor(const QString &value)
{major = value;
}
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QDebug>
#include <QList>
#include "student.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private:Ui::Dialog *ui;
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);// 创建一个QList对象QList<Student> class24031;// 准备一些学生对象Student s1("窦逸凡",18,"电子信息工程");Student s2("杨雨学",18,"LOL");Student s3("蔡徐坤",26,"唱跳rap篮球");Student s4("沈彤",25,"班主任");Student s5("韦朕",27,"PUBG");// 添加元素class24031.append(s1);// 向后追加class24031.push_back(s2);// 向后追加class24031.push_front(s3);// 向前追加class24031.prepend(s4);// 向前追加class24031 << s5;// 取出单个元素qDebug() << class24031.at(2).getName();// 插入元素// 参数1 插入的位置// 参数2 插入的元素class24031.insert(1,s5);qDebug() << "_____________________" << endl;// STL迭代器for(QList<Student>::const_iterator iter = class24031.begin();iter != class24031.end();iter++){Student s = *iter;qDebug() << s.getName() << s.getAge() << s.getMajor();}qDebug() << "_____________________" << endl;// Java 风格迭代器// 只读:QListIterator// 读写:QMutableListIteratorQListIterator<Student> iter(class24031);while(iter.hasNext()){Student s = iter.next();// 向后移动迭代器qDebug() << s.getName() << s.getAge() << s.getMajor();}QList<int> list;list << 34 << 12 << 56 << 34 << 77;list.removeAll(34); // 移除所有34list.removeFirst(); // 移除第一个元素list.removeLast(); // 移除最后一个元素list << 111 << 111 << 111;list.removeOne(111);// 移除第一个111list.removeAt(0); // 移除第一个元素// 修改元素// 参数1 修改元素的位置// 参数2 修改元素的数值list.replace(0,666);qDebug() << list;}Dialog::~Dialog()
{delete ui;
}
2.2 关联容器——QMap类
重新实现了STL里的std::map类
QMap也兼容了map类的所有API接口,也增加了新的Qt的API。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QMap>// 头文件
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{QMap<QString,QString> map;// 创建一个堆内存的关联容器// 插入数据// 参数1 KEY// 参数2 VALUEmap.insert("姓名","蔡徐坤");map.insert("年龄","25");map.insert("地址","北京东城区");map.insert("专业","练习生");map.insert("爱好","唱跳rap篮球");// 如果容器中的元素类型支持qDebug输出,则容器本身也支持qDebug() << map;// 删除键值对qDebug() << map.remove("地址"); // 1qDebug() << map.remove("地址"); // 0// 判断某个键在不在qDebug() << map.contains("地址");if(map.contains("爱好"))map["爱好"] = "唱跳";// 取出元素// 参数1 KEY// 参数2 取出VALUE失败时,输出的默认值qDebug() << map.value("年龄","empty");qDebug() << map.value("年龄2","empty");qDebug() << map;qDebug() << endl;// STL迭代器for(QMap<QString,QString>::iterator iter = map.begin();iter != map.end(); iter++){// 输出键与值qDebug() << iter.key() << iter.value();}// Java风格迭代器// 读写:QMutableMapIterator<Key, T>// 只读:QMapIterator<Key, T>QMapIterator<QString,QString> iter(map);while(iter.hasNext()){iter.next();// 输出键与值qDebug() << iter.key() << iter.value();}}Dialog::~Dialog()
{}
3. Qt数据类型(熟悉)
3.1 跨平台数据类型
Qt是一个跨平台的开发框架,所以必须保障每个平台的数据类型的长度保持一致,因此Qt为常见的数据类型定义了类型符号:
甚至可以用QVariant完成数据类型的转换:
3.2 QVariant 统一变量类型
QVariant类型可以与常见的Qt的类型完成相互转换,因此此类型具有类似于多态性质,通常作为中间值或函数的返回值。
// int → QString
qint64 a = 12345;QVariant qv(a);QString text = qv.toString();qDebug() << text; // "12345"
3.3 QStringList 字符串列表
几乎相当于QList<QString>
4. 时间与日期处理
Qt中使用QDate类处理日期,使用QTime类处理时间,使用QDateTime类处理时间和日期,以下以QDateTime类讲解。
需要注意的是,QDateTime的数据来自于系统时间和日期,所以修改系统数据会影响到QDateTime的数据。
// 返回1970年1月1日00:00:00到现在毫秒数
qint64 QDateTime::currentMSecsSinceEpoch()[static]
// 返回一个包含当前时间和日期的QDateTime对象
QDateTime QDateTime::currentDateTime()[static]
// 格式化时间与日期
// 参数为时间和日期的格式
QString QDateTime::toString(const QString & format) const
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QDateTime>
#include <QDebug>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private:Ui::Dialog *ui;
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{qint64 start = QDateTime::currentMSecsSinceEpoch();ui->setupUi(this);qint64 time = QDateTime::currentMSecsSinceEpoch();qDebug() << time - start;// 输出随机数// 生成随机数种子qsrand(time);// 1-10随机数int rand = qrand() % 10 + 1;qDebug() << rand;// 获取一个基于当前时间和日期的对象QDateTime dt = QDateTime::currentDateTime();// 时间和日期的格式化QString text = dt.toString("yyyy-MM-dd hh:mm:ss t");qDebug() << text;
}Dialog::~Dialog()
{delete ui;
}
还有一些与时间和日期相关的组件:
.ui
dialog.h
#ifndef DIALOG_H //防止同一个头文件被重复包含
#define DIALOG_H#include <QDialog> //Qt对话窗口基类
#include <QDateTime> //包含 QDataTime类的定义,处理日期时间
#include <QDebug> //将调试信息输出到控制台
#include <QDate> //包含QData类的定义,处理日期namespace Ui { //定义一个命名空间 Ui
class Dialog; //Dialog 的类的前向声明
}class Dialog : public QDialog //声明Dialog,继承QDialog
{Q_OBJECT //宏,实现信号和槽机制public:explicit Dialog(QWidget *parent = 0);//声明Dialog类的构造函数//显式构造,接受QWight指针作为它的父对象//默认为0,表示没有父对象~Dialog();//声明析构函数,清理对象分配的资源,释放内存private:Ui::Dialog *ui; //声明一个名为ui的Ui::Dialog类指针成员变量//访问Qt Designer中创建的用户界面元素private slots:// 与void currentPageChanged(int year, int month)链接的槽函数void pageSlot(int,int);// 与void selectionChanged()连接的槽函数void seletChangeSlot();
};#endif // DIALOG_H //关闭保护 防止同一个头文件被重复包含
dialog.cpp
#include "dialog.h" //dialog.h头文件
#include "ui_dialog.h" //Qt Designer生成的与UI文件交互的头文件Dialog::Dialog(QWidget *parent) : //Dialog类的构造函数,接受QWight类型的指针作为父对象,QDialog(parent), //在初始化列表中调用了QDialog的构造函数ui(new Ui::Dialog) //创建ui::Dialog类实例,赋值给成员变量ui
{//获取毫秒级别的时间戳,存储到start变量中qint64 start = QDateTime::currentMSecsSinceEpoch();//调用ui对象的setupUI函数,将组件加载到窗口ui->setupUi(this);//获取毫秒级别的时间戳,存储到time变量中qint64 time = QDateTime::currentMSecsSinceEpoch();//计算差值 qDebug() << time - start;// 输出随机数// 生成随机数种子qsrand(time);// 1-10随机数int rand = qrand() % 10 + 1; qDebug() << rand;// 获取一个基于当前时间和日期的对象QDateTime dt = QDateTime::currentDateTime();// 时间和日期的格式化QString text = dt.toString("yyyy-MM-dd hh:mm:ss t");qDebug() << text;connect(ui->calendarWidget,SIGNAL(currentPageChanged(int,int)),this,SLOT(pageSlot(int,int)));// 将 ui->calendarWidget 的 currentPageChanged(int,int) 信号
//连接到 Dialog 类的//(pageSlot(int,int) 槽函数。connect(ui->calendarWidget,SIGNAL(selectionChanged()),this,SLOT(seletChangeSlot()));}Dialog::~Dialog() //析构函数的实现
{delete ui; //清除对象资源分配,释放内存
}void Dialog::pageSlot(int year, int month)
{qDebug() << "翻页了" << year << "年" << month << "月";
}void Dialog::seletChangeSlot()
{// 获取当前日期QDate date = ui->calendarWidget->selectedDate();// 格式化qDebug() << "选择了" << date.toString("dddd-dd");
}
5. QTimer定时器类
QTimer类可以实现一个延时任务或周期任务。
QTimer的常用属性:
- interval : int 间隔时间,单位毫秒
- singleShot : bool 是否为一次性
- active : const bool 当前定时器的运行状态
QTimer类的常用函数:
// 构造函数
QTimer::QTimer(QObject * parent = 0)
// 启动定时器,如果定时器已经在运行,则会重启
void QTimer::start()[slot]
// 定时器触发时发射的信号
void QTimer::timeout()[signal]
// 停止定时器运行
void QTimer::stop()[slot]
使用LCD Number 七段数码管来显示当地时间
dialog.h
#ifndef DIALOG_H //防止同一个头文件被重复包含
#define DIALOG_H#include <QDialog> //包含QDialog类的头文件,Qt对话窗口基类
#include <QTimer> //包含QTimer类的头文件,定时
#include <QDebug> //包含QDebug类的头文件,将调试信息输出到控制台
#include <QDateTime> // 包含QDateTime类的头文件,日期和时间namespace Ui { //定义一个命名空间 Ui
class Dialog; //Dialog 的类的前向声明
}class Dialog : public QDialog //声明Dialog,继承QDialog
{Q_OBJECT //宏,实现信号和槽机制public:explicit Dialog(QWidget *parent = 0); //声明Dialog类的构造函数,//显式构造,接受QWight指针作为它的父对象//默认为0,表示没有父对象~Dialog(); //声明析构函数,清理对象分配资源,释放内存private:Ui::Dialog *ui; //声明一个名为ui的Ui::Dialog类指针成员变量//访问Qt Designer中创建的用户界面元素QTimer *timer; //声明了一个名为 timer 的指针,其类型为 QTimer
private slots:// 与void timeout()连接的槽函数void timeoutSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h" //对话框类的头文件
#include "ui_dialog.h" //Qt Designer 自动生成的用户界面文件的头文件。Dialog::Dialog(QWidget *parent) : //Dialog类的构造函数,接受QWight类型的指针作为父对象,QDialog(parent), //在初始化列表中调用了基类QDialog的构造函数,ui(new Ui::Dialog) //创建新的ui::Dialog类实例,赋值给成员变量ui
{//调用ui对象的setupUi函数,将组件加载到窗口ui->setupUi(this);// 创建定时器对象timer = new QTimer(this);// 设置触发时间timer->setInterval(1000);//1.增加刷新频率// 设置是否为单次触发timer->setSingleShot(false);//通过 connect() 函数将定时器的 timeout() 信号与 timeoutSlot() 槽函数连接起来,//这样每当定时器超时时,都会调用 timeoutSlot() 函数。connect(timer,SIGNAL(timeout()),this,SLOT(timeoutSlot()));timer->start();timeoutSlot(); //2.开启之后再采样
}Dialog::~Dialog() //析构函数实现
{delete ui; //清理对象分配资源,释放内存空间}void Dialog::timeoutSlot()
{qDebug() << "到点儿了";// 获得当前时间的对象QDateTime dt = QDateTime::currentDateTime();// 格式化QString text = dt.toString("hh:mm:ss");ui->lcdNumber->display(text);
}
六、多窗口编程
1. QMessageBox消息对话框
QMessageBox继承自QDialog,用于显示一个模态对话框,用于用户前台信息通知或询问用户问题并接受问题答案。
QMessageBox可以支持使用四种预设的风格样式:
QDialog的Qt源码中的派生类往往都是一些在特定场合下使用的预设好的对话框窗口,这些窗口的使用无需创建对象,直接使用静态成员函数弹窗,使用返回值来作为这个窗口的结果。
// 参数1 parent参数
// 参数2 窗台标题,相当于窗口类的windowsTitle属性
// 参数3 信息内容
// 返回值 用户点击按钮的类型
StandardButton QMessageBox::critical|information|question|warning
(
QWidget * parent, const QString & title, const QString & text)
[static]
自定义窗口:
// 设置窗口标题
void QMessageBox::setWindowTitle(const QString & title)
// 设置图标
void setIconPixmap(const QPixmap & pixmap)
// 添加按钮到QMessageBox
// 参数1 添加的按钮对象
// 参数2 添加按钮的规则
void QMessageBox::addButton(QAbstractButton * button, ButtonRole role)
dialog.h
#ifndef DIALOG_H //防止同一个头文件重复包含
#define DIALOG_H#include <QDialog> //Qt对话窗口基类
#include <QButtonGroup> //按钮组
#include <QMessageBox> //模态对话框namespace Ui { //定义一个命名空间 Ui
class Dialog; //Dialog 的类的前向声明
}class Dialog : public QDialog // 声明Dialog,继承QDialog;
{Q_OBJECT //宏,信号和槽机制public:explicit Dialog(QWidget *parent = 0);//声明Dialog类的构造函数//显式构造,接受QWidget指针作为它的父对象//默认为0,表示没有父对象~Dialog();//声明析构函数,清理对象分配的资源,释放内存private:Ui::Dialog *ui; //声明一个名为ui的Ui::Dialog类指针成员变量//访问Qt Designer中创建的用户界面元素QButtonGroup *group; //声明一个名为group的QButtonGroup类指针成员变量QMessageBox *box;// 显示自定义窗口void customMessageBox();// 按钮QPushButton *btn1;QPushButton *btn2;QPushButton *btn3;private slots:// 与void buttonClicked(int id)连接的槽函数void btnsClickedSlot(int);
};#endif // DIALOG_H //关闭防止同一个头文件重复包含保护
dialog.cpp
#include "dialog.h" //包含对话框类的头文件
#include "ui_dialog.h" //Qt Designer自动生成的用户界面文件的头文件Dialog::Dialog(QWidget *parent) ://Dialog类的构造函数,接受QWight类型的指针作为父对象QDialog(parent),//在初始化列表中调用了QDialog的构造函数并传递参数,//Dialog 对象就会作为一个 QDialog 对象进行初始化ui(new Ui::Dialog) //创建新的ui::Dialog类实例,赋值给成员变量ui
{//调用ui对象的setupUi函数,将组件加载到窗口ui->setupUi(this);group = new QButtonGroup(this);group->addButton(ui->pushButton,1);group->addButton(ui->pushButton_2,2);group->addButton(ui->pushButton_3,3);group->addButton(ui->pushButton_4,4);group->addButton(ui->pushButton_5,5);connect(group,SIGNAL(buttonClicked(int)),this,SLOT(btnsClickedSlot(int)));}Dialog::~Dialog()//构造函数实现
{delete ui; //清理对象分配资源,释放内存
}void Dialog::customMessageBox()
{// 创建一个QMessageBox对象box = new QMessageBox(this);// 设置标题box->setWindowTitle("自定义QMessageBox");// 图片对象QPixmap map(":/new/prefix1/image1.png");// 设置图片box->setIconPixmap(map);// 设置信息box->setText("这是一个我自己设计的QMessageBox");// 初始化按钮btn1 = new QPushButton("是",box);btn2 = new QPushButton("否",box);btn3 = new QPushButton("啥也不是",box);// 把按钮放置到QMessageBox 的规定位置box->addButton(btn1,QMessageBox::YesRole);box->addButton(btn2,QMessageBox::NoRole);box->addButton(btn3,QMessageBox::HelpRole);// 显示对话框box->show();
}void Dialog::btnsClickedSlot(int id)
{if(id == 1){QMessageBox::StandardButton result = QMessageBox::question(this,"问题","你今早吃了吗?");if(result == QMessageBox::Yes){close();}else if(result == QMessageBox::No){}}else if(id == 2){QMessageBox::information(this,"信息","今天早吃的油旋");}else if(id == 3){QMessageBox::warning(this,"警告","今天早上吃的不太舒服");}else if(id == 4){QMessageBox::critical(this,"错误","食物中毒了");}else if(id == 5){customMessageBox();}else{}
}
2. QWidget类
QWidget类是所有窗口和组件的基类,之前认识他更多的是站在组件的角度,实际上QWidget作为所有窗口的类似,也具有很多窗口的特性,窗口类的继承结构如下:
新建一个项目,使自带窗口类继承QWidget:
#include "widget.h"
#include <QApplication>int main(int argc, char *argv[])
{
QApplication a(argc, argv);// 当QWidget类的构造函数parent参数使用默认值0时,表示创建的是独立窗口// 当QWidget类的构造函数parent参数传递参数时(parent),新创建的QWidget对象// 会成为子窗口(内嵌窗口)
Widget w;
w.show();return a.exec();
}
QWidget类作为窗口的基类,内部也规定了很多处窗口的特性:
- windowTitle : QString
窗口标题
- windowFlags : Qt::WindowFlags
使用setter函数设置多个标记时,使用 | 分割(多个窗口的标记之间有可能会出现冲突),如下所示
setWindowFlags(Qt::WindowStaysOnTopHint|Qt::FramelessWindowHint)
// 设置窗口flags
void setWindowFlags(Qt::WindowFlags type)
// 设置窗口状态
void QWidget::setWindowState(Qt::WindowStates windowState)
窗口状态有时可能与窗口标记(windowFlags)冲突
示例代码:
setWindowState(Qt::WindowFullScreen);
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);// 设置窗口标题setWindowTitle("我的窗口");// 设置窗口最上层显示且无边框setWindowFlags(Qt::WindowStaysOnTopHint|Qt::FramelessWindowHint);// 设置窗口状态全屏setWindowState(Qt::WindowFullScreen);
}Widget::~Widget()
{delete ui;
}
3. parent参数
目前对parent参数的理解有以下几点:
- parent 参数表示子组件位于哪个窗口
- parent参数决定了QWidget对象是独立窗口还是内嵌窗口
实际上parent参数还表示Qt的内存回收机制,如果对象a作为对象b构造时的parent参数,表示对象a是对象b 的父对象(非继承),这是一种内存回收的依赖关系。即对象b会跟随对象a的销毁一并销毁,此时无需手动控制对象b的销毁过程(手动调用delete)。
绝大多数情况下,建议堆内存对象传递parent参数。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QDebug>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private:Ui::Dialog *ui;private slots:void btnYesClickedSlot();void btnNoClickedSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(btnYesClickedSlot()));connect(ui->pushButton_2,SIGNAL(clicked()),this,SLOT(btnNoClickedSlot()));
}Dialog::~Dialog()
{qDebug() << "析构函数";delete ui;
}void Dialog::btnYesClickedSlot()
{// 传递parent参数,在创建一个Dialog对象Dialog *d = new Dialog(this);d->show();
}void Dialog::btnNoClickedSlot()
{// 不传递parent参数,在创建一个Dialog对象Dialog *d = new Dialog;d->show();
}
4.堆栈窗口QStackedWidget
通常作为独立窗口的内嵌窗口(组件),并于QListWidget进行联动。
// 每次选择行改变时发射,参数为当前行号
void QListWidget::currentRowChanged(int currentRow)[signal]
// 设置当前页
void QStackedWidget::setCurrentIndex(int index)[slot]
// 添加一个Item
void QListWidget::addItem(const QString & label)
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);// 向QListWidget里添加Itemui->listWidget->addItem("红");ui->listWidget->addItem("绿");ui->listWidget->addItem("蓝");// 连接信号槽connect(ui->listWidget,SIGNAL(currentRowChanged(int)),ui->stackedWidget,SLOT(setCurrentIndex(int)));
}Dialog::~Dialog()
{delete ui;
}
5. QMainWindow 主窗口类
QMainWindow是最适合作为主窗口的类型,因为其包含多个组成成分
5.1 QMenuBar 菜单栏
菜单栏的构建可以通过Designer,也可以通过C++代码实现,但是不能混用。
相关C++函数如下:
// 向菜单栏中添加一级菜单// 参数为菜单的文字// 返回值是添加的菜单对象QMenu * QMenuBar::addMenu(const QString & title)
// 向菜单中添加下一级菜单// 参数为菜单的文字// 返回值是添加的菜单对象QMenu * QMenu::addMenu(const QString & title)
// 向菜单中添加动作// 参数为动作的文字// 返回值是添加的动作对象QAction * QMenu::addAction(const QString & text)
dialog.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);// 添加一级菜单QMenu* menuFile = ui->menuBar->addMenu("文件");QMenu* menuEdit = ui->menuBar->addMenu("编辑");// 向一级菜单中添加二级菜单QMenu* meunRecent = menuFile->addMenu("最近访问的文件...");// 向一级菜单中添加动作menuFile->addAction("新建文件或项目...");menuFile->addAction("打开文件或项目...");// 向二级菜单中添加动作meunRecent->addAction("Qt.cpp");meunRecent->addAction("Qt.h");
}MainWindow::~MainWindow()
{delete ui;
}
为了使QAction点击后有触发效果,需要使用对应的信号连接槽函数,QAction的信号函数如下:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QDebug>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0);~MainWindow();private:Ui::MainWindow *ui;private slots:void actionNewTriggledSlot();// 点击新建项目的槽函数void actionCppSlot();// 点击Qt.cpp的槽函数
};#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);// 添加一级菜单QMenu* menuFile = ui->menuBar->addMenu("文件");QMenu* menuEdit = ui->menuBar->addMenu("编辑");// 向一级菜单中添加二级菜单QMenu* meunRecent = menuFile->addMenu("最近访问的文件...");// 向一级菜单中添加动作QAction *actionNew = menuFile->addAction("新建文件或项目...");menuFile->addAction("打开文件或项目...");// 向二级菜单中添加动作QAction *actionCPP = meunRecent->addAction("Qt.cpp");meunRecent->addAction("Qt.h");// 连接信号槽connect(actionNew,SIGNAL(triggered(bool)),this,SLOT(actionNewTriggledSlot()));connect(actionCPP,SIGNAL(triggered(bool)),this,SLOT(actionCppSlot()));}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::actionNewTriggledSlot()
{ui->plainTextEdit->appendPlainText("新建了一个文档");
}void MainWindow::actionCppSlot()
{ui->plainTextEdit->appendPlainText("打开了Qt.cpp");
}
5.2 QToolBar 工具栏
工具栏的按钮往往使用菜单栏中的QAction对象,但是需要给QAction设置图标:
// 添加一个已有的QAction对象(提前设置图标)到工具栏
void QToolBar::addAction(QAction * action)
mainWindow.cpp
ui->mainToolBar->addAction(ui->actionQt_cpp);
5.3 QStatusBar 状态栏
可以通过以下两个槽函数进行信息的展示和消除:
// 在状态栏展示信息
// 参数1 展示信息的内容
// 参数2 信息显示的时间(单位毫秒),默认值0表示一直显示
void QStatusBar::showMessage(const QString & message, int timeout = 0)[slot]
// 清空状态栏显示
void QStatusBar::clearMessage()[slot]
ui->statusBar->showMessage("新建了!",3000);
6. 新建自定义窗口类
定义Qt窗口类的步骤如下:
- 在Qt Creator中选中项目名称,鼠标右键,点击“添加新文件”
- 在弹出的窗口中,如下所示操作
- 在弹出的窗口中,选择界面模版,点击“下一步”
- 在弹出的窗口中,输入类名后,点击“下一步”
- 在项目管理界面,直接点击“完成”。可以看到新的窗口类文件已经添加到项目中。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include "mydialog.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();private:Ui::Dialog *ui;private slots:void btnClickedSlot();// 按钮点击的槽函数
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{delete ui;
}void Dialog::btnClickedSlot()
{// 创建MyDialog对象并展示MyDialog *md = new MyDialog(this);md->show();// 限制按钮只能点一次ui->pushButton->setEnabled(false);
}
7. 对象传值
7.1 父对象→子对象
【例】转动黄球,蓝球跟着动
此处的父对象与子对象指的是Qt的parent参数的依赖关系,并非继承关系!!!后文同
这种情况最佳的解决方案是使用C++的成员函数传参。
7.2 子对象→父对象
【例子】转动蓝球,黄球一起跟着转
这种情况最佳解决方案是信号槽传参,子对象发射带参数的信号函数,父对象使用带参数的槽函数接收。
8. 事件机制(熟悉)
事件机制是Qt的一种底层机制,通过层层传递,程序员可以在传递的层级中检测或处理这些事件。
本次学习主要在窗口类中实现事件函数,从而检测到事件的传递。可以利用事件的触发时机从而实现一些特定的效果。事件函数的类型众多,包括但不限于:
// 绘制事件
void QWidget::paintEvent(QPaintEvent * event) [virtual protected]
// 大小改变事件
void QWidget::resizeEvent(QResizeEvent * event) [virtual protected] // 鼠标按压事件
void QWidget::mousePressEvent(QMouseEvent * event) [virtual protected]
// 鼠标释放事件
void QWidget::mouseReleaseEvent(QMouseEvent * event) [virtual protected]
// 鼠标双击事件
void QWidget::mouseDoubleClickEvent(QMouseEvent * event) [virtual protected]
// 鼠标移动事件
void QWidget::mouseMoveEvent(QMouseEvent * event) [virtual protected]// 移动事件
void QWidget::moveEvent(QMoveEvent * event) [virtual protected]// 按键按压事件
void QWidget::keyPressEvent(QKeyEvent * event) [virtual protected]
// 按键释放事件
void QWidget::keyReleaseEvent(QKeyEvent * event) [virtual protected]// 获取焦点事件
void QWidget::focusInEvent(QFocusEvent * event) [virtual protected]
// 失去焦点事件
void QWidget::focusOutEvent(QFocusEvent * event) [virtual protected]// 关闭事件
void QWidget::closeEvent(QCloseEvent * event) [virtual protected]// 鼠标进入事件
void QWidget::enterEvent(QEvent * event) [virtual protected]
// 鼠标离开事件
void QWidget::leaveEvent(QEvent * event) [virtual protected]
事件函数的基础使用只需要在对应的类中覆盖基类的事件函数即可。事件函数的参数包含了当前事件数据的对象。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QDebug>
#include <QPainter> // 画家类
#include <QKeyEvent> // 按键事件类namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = 0);~Dialog();
protected:// 绘制事件void paintEvent(QPaintEvent * event);// 按键按压事件void keyPressEvent(QKeyEvent * event);private:Ui::Dialog *ui;
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{ui->setupUi(this);
}Dialog::~Dialog()
{delete ui;
}void Dialog::paintEvent(QPaintEvent *event)
{// 创建一个画家对象QPainter painter(this);// 创建图片对象QPixmap map(":/new/prefix1/five.jpg");// 绘制图片// 参数1 横轴坐标// 参数2 纵轴坐标// 参数3 绘制宽度// 参数4 绘制高度// 参数5 绘制内容(图片对象)painter.drawPixmap(0,0,this->width(),this->height(),map);qDebug() << this->width() << this->height();qDebug() << this->x() << this->y();
}void Dialog::keyPressEvent(QKeyEvent *event)
{if(event->key() == Qt::Key_A) // 如果按键为A{int value = ui->progressBar->value();ui->progressBar->setValue(--value);}else if (event->key() == Qt::Key_D){int value = ui->progressBar->value();ui->progressBar->setValue(++value);}else if (event->key() == Qt::Key_W){ui->progressBar->setValue(100);}else if (event->key() == Qt::Key_S){ui->progressBar->setValue(0);}
}
【问题】事件函数和信号槽的区别?
- 事件由具体对象进行处理
- 信号由具体对象主动产生
- 改写事件处理函数可能导致程序行为发生改变
- 信号是否存在对应的槽函数不会改变程序的行为
七、文件IO
1. QFileDialog 文件对话框
与QMessageBox一样,QFileDialog也继承了QDialog类,直接使用静态成员函数弹窗,弹窗的结果通过返回值获取
// 获得一个打开或保存的文件路径
// 参数1 父对象
// 参数2 即windowTitle属性(界面标题)
// 参数3 在哪个目录下打开,默认值为文件的工作目录
// 参数4 文件格式过滤器
// 返回值:选择的文件路径,如果选择失败,返回空字符
QString QFileDialog::getOpenFileName|getSaveFileName(QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString())[stat]
2. QFileInfo文件信息类(熟悉)
只需要创建出对象后,通过各种成员函数来直接获取文件的信息
// 构造函数
// 参数为文件路径,如果文件合法,仍然可以创建出QFileInfo对象
QFileInfo::QFileInfo(const QString & file)
// 判断文件是否存在
// 如果存在返回true 否则返回false
bool QFileInfo::exists() const
// 返回文件的大小,单位字节
qint64 QFileInfo::size() const
// 获取文件的基础名称
QString QFileInfo::baseName() const
// 返回最后一次修改的日期和时间
QDateTime QFileInfo::lastModified() const
// 返回可读性
bool QFileInfo::isReadable() const
3. QFile文件读写类
在Qt中所有IO类都继承自QIODevice ,QIODevice类中规定了最基础的IO相关接
口,这些接口虽然在不同的派生类中实现有所区别,但调用方式一致。
。
// 构造函数
// 参数为文件的路径,如果是非法路径,也能创建出对象,但是不能正常IO
QFile::QFile(const QString & name)
// 判断QFile对应的文件是否存在
bool QFile::exists() const
// 打开文件流
// 参数为打开的模式
// 返回值为打开的结果
bool QIODevice::open(OpenMode mode)[virtual]
// 读取最大长度为maxSize的数据到返回值中
QByteArray QIODevice::read(qint64 maxSize)[virtual]
// 写出数据
// 参数为写出的内容
// 返回值为实际写出的字节数,出错返回-1
qint64 QIODevice::write(const QByteArray & byteArray)[virtual]
// 是否读到文件尾
bool QIODevice::atEnd() const[virtual]
// 关闭文件流
void QIODevice::close()[virtual]
// 清空缓存区
bool QFileDevice::flush()
// 返回输入流的大小,单位字节
qint64 QIODevice::size() const[virtual]
dialog.h
dialog.cpp
【思考】上面的代码真的没问题吗????
当拷贝大文件时,会出现程序卡顿,如果尝试关闭,则会触发:
4.UI与耗时操作
在默认情况下,Qt项目时单线程的,这个自带的线程用于处理程序的主要任务和UI交互,也被称为主线程或UI线程。
如果在主线程中执行耗时操作(IO或复杂算法)会导致主线程原本执行的操作被阻塞,甚至无法关闭,形成“假死”的现象。
当操作系统发现某个进程无法被正常关闭时,会弹出程序未响应窗口引导用户选择是否强制关闭当前进程。
解决以上问题的方法是使用多线程。
5. QThread 线程类
5.1复现程序未响应(熟悉)
QThread类是Qt的线程类,可以使用下面的函数来实现函数模拟耗时操作:
// 强制线程睡眠msecs个毫秒
void QThread::msleep(unsigned long msecs)[static]
dialog.h
dialog.cpp
5.2 创建并启动一个子线程(掌握)
主线程以外的线程都是 子线程,子线程不能去执行主线程UI操作,只能执行耗时操作。
下面是创建并启动一个自定义子线程的步骤:
- 在Qt Creator中选择项目名称,鼠标右键,添加新文件。
- 在弹出的窗口中,先设置类名,然后填写基类名称QObject,最后点击下一步
- 在项目管理界面,直接点击完成。可以看到线程类文件已经创建。
- 选择新建的头文件,把继承的QObject更改为QThread
- 选择新建的.cpp文件,把透传构造QObject改成QThread
- 在自定义线程中,覆盖基类的QThread的run函数。
// 此函数是子线程执行的起始点、也是子线程结束点
void QThread::run()[virtual protected]
- 在run函数的函数体中编写子线程的要执行的耗时操作。
- 创建自定义子线程对象,并调用start函数启动子线程
// 启动子线程,调用此函数后,会在子线程中自动执行run函数
//
void QThread::start(Priority priority = InheritPriority)[slot]
5.3 异步刷新(掌握)
在实际开发中,主线程和子线程不可能毫无关系的前提下各干各的,最常见的情况是主线程分配给子线程一个耗时任务,子线程需要把耗时任务的执行情况反馈给主线程。主线程刷新子线程的耗时操作,并展示UI效果。
例如,子线程拷贝文件,主线程显示拷贝文件进度。
通常子线程是主线程对象的子对象,因此异步刷新就是对象通信问题,使用信号槽解决。
写一个简单的伪拷贝的例子,使用for循环,模拟文件拷贝功能。
进度条到100时,弹出提示框(问题:频繁抖动窗口,会出现焦点抢夺问题,导致卡死)。解决方法使用hide()函数,来隐藏主窗口。
5.4 线程停止(掌握)
子线程往往执行耗时操作是会伴随着循环,因此不建议使用粗暴的方式直接停止线程,因为强行停止会导致耗时操作的资源无法回收等问题。
可以在循环的基础上设置标志位的方式来使线程停止。
dialog.h