1. 简介

1.1 什么是Qt

Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展以及一些宏,Qt很容易扩展,并且允许真正的组件编程。

Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境。Qt Creator可带来两大关键益处:提供首个专为支持跨平台开发而设计的集成开发环境(IDE),并确保首次接触Qt框架的开发人员能迅速上手和操作。即使不开发Qt应用程序,Qt Creator也是一个简单易用且功能强大的IDE。

Qt是一套应用程序开发库,但与MFC不同,Qt是跨平台的开发类库。跨平台意味着只需要编写一次程序,在不同平台上无需改动或只需要少许改动后在编译,就可以形成在不同平台上运行的版本。

1.2 Qt特征

1.面向对象

Qt具有模块设计和控件或元素的可重用性的特点。一个控件不需要找到它的内容和用途,通过Signal和slot与外界通信、交流。而且Qt的控件都可通过继承。

2.控件间的相互通信

Qt提供signal和slot概念,这是一种安全可靠的方法,它允许回调,并支持对象之间在彼此不知道对方信息的情况下,进行合作,这使Qt非常合适于真正的控件编程。

3.友好的联机帮助

Qt包括大量的联机参考文档,有超文本HTML方式、UNIX帮助页、man手册和补充的指南。对于初学者,指南将一步步地解释Qt编程。

4.用户自定义

其它的工具包在应用时都存在一个普遍的问题,就是经常没有真正适合需求的控件,生成的自定义控件对用户来说,也是一个黑匣子。比如,在Motif手册中就讨论了用户自定义的控件问题。而在Qt中,能够创建控件,具有绝对的优越性,生成自定义控件非常简单,并且容易修改控件。

5.方便性

由于Qt是一种跨平台的GUI工具包,所以它对编程者隐藏了在处理不同窗口系统时的潜在问题。为了将基于Qt程序更加方便,Qt包含了一系列类。该类能够使程序员避免了在文件处理、时间处理等方面存在依赖操作系统方面的细节问题。

2. Qt编译过程

1.编写代码

通过文本文档写入如下代码,后缀改为cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <QApplication>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout> //水平布局
#include <QVBoxLayout> //垂直布局
#include <QWidget> //窗口

int main(int argc, char *argv[])
{
QApplication app(argc, argv); //程序
QLabel *infoLabel = new QLabel; //信息标签对象
QLabel *openLabel = new QLabel; //打开标签对象
QLineEdit *cmdLineEdit = new QLineEdit; //文本行编辑框对象
QPushButton *commitButton = new QPushButton; //确定按钮
QPushButton *cancelButton = new QPushButton; //取消按钮
QPushButton *browseButton = new QPushButton; //浏览按钮

infoLabel->setText("input cmd:");
openLabel->setText("open");
commitButton->setText("commit");
cancelButton->setText("cancel");
browseButton->setText("browse");

QHBoxLayout *cmdLayout = new QHBoxLayout; //水平布局对象
cmdLayout->addWidget(openLabel);
cmdLayout->addWidget(cmdLineEdit);

QHBoxLayout *buttonLayout = new QHBoxLayout; //水平布局对象
buttonLayout->addWidget(commitButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addWidget(browseButton);

QVBoxLayout *mainLayout = new QVBoxLayout; //垂直布局对象
mainLayout->addWidget(infoLabel);
mainLayout->addLayout(cmdLayout); //将布局添加到布局是用addLayout函数
mainLayout->addLayout(buttonLayout);

QWidget w; //窗口对象
w.setLayout(mainLayout); //将主布局(所有内容都在这个布局里)嵌入到窗口
w.show();
return app.exec();
}

2.生成工程文件

  • cmd进入工程目录(最好是QT自带的)

  • 在过程目录下的cmd输入:qmake -project

  • 修改工程文件,在工程目录下会生成一个后缀为pro的文件,在该文件的最后一行添加:QT += widgets gui

3.生成makefile,终端输入qmake

4.输入mingw32-make,执行成功就可以在工程目录下的debug或release目录下生成可执行文件。

注:以上步骤可能存在环境变量文件,如果出现,想要配置

3. Qt Creator

在创建一个Qt的项目时,也就是创建一个主窗口,基类的选择有以下三种:

  • QmainWindow:继承QWidget,带有菜单栏、工具栏、状态栏的界面
  • QWidget:最基础的窗口类,qt里面能看到的东西的基类,不带菜单栏的界面,类似于QQ、微信的登录框
  • Qdialog:继承QWidget,对话框界面,完成一些业务时,弹出的对话框

Qt里面绝大部分的类都是继承自QObject,它是一个顶层类

下面创建了一个MainWindow窗口类,继承的基类是QWidget

下面是创建窗口后,提供的main()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, char *argv[])
{
//创建一个应用程序对象:维护qt应用程序生命的一个对象,每个qt有且只有一个对象
QApplication a(argc, argv);
//窗口类的一个对象
MainWindow w;
//把窗口显示出来
w.show();
//死循环让程序一直运行,生命循环,消息循环
/*
while(1){
if(点击x按钮){
break;
}
if(点击...){
执行相应操作
}
.....
}
*/
return a.exec();
}

下面是窗口类的头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MainWindow : public QWidget
{
//宏,引入t信号槽的一个宏
Q_OBJECT

public:
//parent窗口制作,父窗口对象的指针
//如果parent为0或者NULL,表示当前窗口对象是个顶层窗口
//顶层窗口就是在任务栏可以找到的窗口(可以通过函数设置,所以这个说法不一定正确)
MainWindow(QWidget *parent = nullptr);
~newhello();

private:
Ui::MainWindow *ui;
};

下面是窗口类的构造函数以及一些程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//this->move(100,100); //主窗口MainWindow窗口也可以移动

//创建第一个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnA = new QPushButton(this); //指定父对象,这个按钮就可以内嵌到MainWindow这个窗口里面
btnA->move(10,10); //移动按钮的位置
btnA->setFixedSize(200,200); //给按钮设置固定大小

//创建第二个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnB = new QPushButton(btnA); //指定父对象,这个按钮就可以内嵌到btnA这个按钮窗口里面
btnB->move(10,10); //移动按钮的位置
btnB->setFixedSize(100,100); //给按钮设置固定大小

//创建第三个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnC = new QPushButton(btnB); //指定父对象,这个按钮就可以内嵌到btnB这个窗口里面
btnC->move(10,10); //移动按钮的位置
btnC->setFixedSize(50,50); //给按钮设置固定大小
}

在上面的程序中,ui 是一个指向UI类对象的指针,该对象通常通过UI设计器生成,并包含了所有UI组件的实例和布局信息。而setupUi 是UI类中的一个成员函数,它的作用是将UI组件添加到主窗口上,并设置它们的位置、大小等属性。this 是一个指向当前对象的指针,通常是主窗口的实例。setupUi 函数会使用这个指针来确定UI组件应该添加到哪个窗口上。

执行后的效果如下:

4. 基本知识1

QWidget类是所有用户界面对象的基类,它提供了创建和管理窗口部件的功能。另一方面,QObject类是Qt对象模型的基类,它提供了对象树、信号与槽机制、事件处理等基本功能。

4.1 父子关系

默认情况下按钮是没有认父亲的,也就是一个顶层窗口,想要按钮显示在窗口中,就要跟窗口构造父子关系,方法如下:

  • setParent
  • 构造函数传参

创建了一个MainWindow窗口类,继承的基类是QWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;

//这种情况没有建立父子关系,而QPushButton的基类是Widget,所以显示的是顶层窗口(重新显示在一个窗口中)
QPushButton btn1; //创建按钮对象
btn1.setText("按钮1");
btn1.show(); //将按钮显示出来

//建立父子关系:建立父子关系后,该按钮就不需要show了,只要父亲显示出来,它就可以显示出来
//方法1
QPushButton btn2;
btn2.setText("按钮2");
btn2.setParent(&w);

//方法2
QPushButton btn3("按钮3",&w);
btn3.move(100,100);

w.show();
return a.exec();
}

4.2 基础

1.关于Qt的坐标系是以父窗口的左上角为(0,0)的,以向右的方向为x的正方向,以向下的方向为y的正方向,顶层窗口就是以屏幕左上角为(0,0)的。

2.常用的API:

  • move:移动窗口到父窗口某个位置
  • resize:重新设置窗口的大小
  • setFixedSize():设置窗口的固定大小
  • setWindowTitle():设置窗口标题
  • setGeometry():同时设置窗口位置和大小,相当于是move和resize的结合体,参数(x轴,y轴,宽,高)

3.对象树

概念:各个窗口对象通过建立父子关系构造的一个关系树

内存管理:父对象释放的时候会自动释放各个子对象

Qt中有内存回收机制,即对象树,但不是所有被new出的对象被自动回收,满足下面两个条件才可以自动回收:

  1. 创建的对象必须是QObject类的子类(间接子类也可以),QObject类是没有父类的,Qt中有很大一部分类都是从这个类派生出去的
  2. 创建出的类对象,必须要指定其父对象是谁,一般情况下有两种操作方式:
    • 在构造对象时指定父对象
    • 通过调用QWidget的API指定父窗口对象,就是setParent()方法

下面创建了一个主窗口MainWindow窗口类,继承的基类是QWidget,又创建了一个MyPushButton窗口类,使它继承的基类是QWidget。

以下是主窗口MainWindow类的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//局部变量在函数退出的时候就会自动释放(窗口就算没有关闭,它的构造函数也会很快执行结束),所以这种方式在窗口不能显示按钮
QPushButton btn("按钮1",this);
btn.show();

//解决办法,让按钮的生命周期长一点:static、类成员变量、new一个,动态内存分配
MyPushButton *btn2 = new MyPushButton(this);
btn2->setText("按钮2");
}

5. 信号槽机制

信号:各种事件;槽:相应信号的动作

信号和槽本质都是函数

当某个事件发送后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal),当某个对象接到这个信号之后,就会做一些相关的处理动作(slot)。但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号。这时需要建立连接,即connect()。

connect(信号发送者,信号,信号接收者,槽);

1
connect(btn, &QPushButton::clicked, this, &Widget::close);

使用connect的时候保留&符号的原因:

  • 提高代码可读性(提示这是一个指针)
  • 自动提示(会根据前面写的内容来提示你接下来可能输入的内容)

下面编写了多个类,类与类之间想要在Qt中使用信号槽机制,那么必须要满足如下条件:

  1. 这个类必须从QObject类或者是其子类进行派生
  2. 在定义类的头文件种加入Q_OBJECT宏

5.1 自定义信号与槽

1自定义信号要求:

  • 信号是类的成员函数
  • 返回值是void类型
  • 信号的名字可以根据实际情况进行指定
  • 参数可以随意指定,信号也可以重载
  • 信号需要使用signals关键字进行声明,类似于public
  • 信号函数只需要声明,不需要定义(没有函数体实现)
  • 在程序中发送自定义信号,发送的本质就是调用信号函数

建议:习惯性在信号函数前加关键字emit;其只是显示的声明信号被发送,没有特殊含义。底层emit == #define emit

2.槽函数就是信号的处理动作,自定义槽函数和自定义的普通函数写法是一样的,要求:

  • 返回值是void类型
  • 槽也是函数,因此也支持重载
  • Qt中的槽函数的类型有类的成员函数、全局函数、静态函数和lambda表达式(匿名函数)
  • 槽函数可以使用关键字进行声明:public slots、private slots、protected slots

3.信号槽使用扩展:

  • 一个信号可以连接多个槽函数,发送一个信号有多个处理函数

    • 需要写多个connect连接
    • 槽函数的执行顺序是随机的,和connect函数的调用顺序没有关系
    • 信号的接收者可以是一个对象,也可以是多个对象
  • 一个槽函数可以连接多个信号,多个不同的信号,处理动作是相同的

    • 写多个connect就可以
  • 信号可以连接信号:信号接收者可以接收信号,继续发出新的信号,即传递了数据,并没有进行处理

  • 信号槽是可以断开的,通过disconnect()函数,其与connect函数的参数一样

4.参数的二义性问题:当某个类具有两个相同名字的信号,一个带参数,一个不带参数,那么接收该信号的类不知道该执行哪个槽函数,所以会报错,需要通过下面两种方法来解决:

  • 使用函数指针赋值,让编译器自动挑选符合类型的函数
  • 使用static_cast强制转换,让编译器自动挑选符合类型的函数

下面是一个窗口类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

tea = new teacher(this); //创建teacher类型对象
stu = new Student(this); //创建Student类型对象

QPushButton* btn = new QPushButton("按钮1", this); //以窗口为父对象,创建一个按钮1

connect(btn, &QPushButton::clicked, this, [&](){ //点击按钮触发信号
emit tea->hungry();
emit tea->hungry("kfc");
});

//connect(tea, &teacher::hungry, stu, &Student::treat); //直接执行会报错

//方法1
void (teacher::*tq)(QString) = &teacher::hungry; //带参的函数指针,指向信号(也可以不带参)
void (Student::*sq)(QString) = &Student::treat; //带参的函数指针,指向槽函数(也可以不带参)
connect(tea, tq, stu, sq);

//方法2:强制类型转换为想要的信号和槽函数(下面是转换为不带参的)
connect(tea, static_cast<void (teacher::*)()>(&teacher::hungry), stu, static_cast<void (Student::*)()>(&Student::treat));
}

在上面程序中也可以信号连接信号,如果有同名但参数不同的信号,还需要进行转换

1
connect(btn, &QPushButton::clicked, tea, &teacher::hungry);

5.Qt4中的信号和槽

使用两个宏 SIGNAL和SLOT,它们的原理是将后面的参数转换成字符串,使用方式为:

connect(信号发送者, SIGNAL(函数原型), 信号接收者, SLOT(函数原型));

  • 好处:没有重载二义性的问题,有无参数可以直接写在connect里面
  • 坏处:写错了,编译期间不报错(在项目中就很难排查错误)

6.QDebug输出QString默认会转义,比如说当给信号传入的参数是emit tea->hungry("kfc\r\n");,控制台输出的也是"kfc\r\n"

解决办法:

  • 将QString转成char*:qDebug()<<"请吃"<<what.toUtf8().data();
  • 使用qDebug().noquote():qDebug().noquote()<<"请吃"<<what;

5.2 定时器程序

下面是一个非自定义的信号与槽,所以就需要先手动在ui界面添加两个按钮和两个标签。然后通过系统提供的按钮信号来触发槽函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

//创建定时器对象
QTimer* timer = new QTimer(this); //new一个定时器对象,给其指定一个父对象(构成对象树,当析构MainWindow时,先析构timer)

//修改定时器对象的精度(可有可无)--->添加后,显示时间时,1秒会只相差几毫秒,不然会相差有点大
timer->setTimerType(Qt::PreciseTimer); //PreciseTimer表示设置了最高精度

//按钮的点击事件:点击loopBtn会发送信号clicked,由主窗口接收,匿名函数执行对应的操作
connect(ui->loopBtn, &QPushButton::clicked, this, [=](){
if(timer->isActive()){ //如果定时器正在工作
timer->stop(); //关闭定时器
ui->loopBtn->setText("开始"); //设置按钮标题
}else{ //定时器还没有工作
ui->loopBtn->setText("关闭"); //设置按钮标题
timer->start(1000); //启动定时器,1000ms==1s,即1秒发一个timeout信号
}
});
connect(timer, &QTimer::timeout, this,[=](){ //发出一个timeout信号处理该动作
QTime tm = QTime::currentTime(); //得到当前的一个时间
QString tmstr = tm.toString("hh:mm:ss.zzz"); //格式化当前得到的系统时间
ui->curTime->setText(tmstr); //在标签上显示时间(动态显示,因为每1秒会发timeout)
});

//按钮的点击事件:点击onceBtn会发送信号clicked,由主窗口接收,匿名函数执行对应的操作
connect(ui->onceBtn, &QPushButton::clicked, this, [=](){
//获取2s以后的系统时间
QTimer::singleShot(2000,this,[=](){
QTime tm = QTime::currentTime(); //得到当前的一个时间
QString tmstr = tm.toString("hh:mm:ss.zzz"); //格式化当前得到的系统时间
ui->onceTime->setText(tmstr); //在标签上显示时间(静态显示,只有按下按钮才发信息)
});
});
}

6. QMainWindow

QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个停靠部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget),是许多应用程序的基础,如文本编辑器,图片编辑器等。具体结果如下:

1.下面创建了一个窗口类MainWindow,继承的基类是QMainWindow,在该窗口中添加各个部件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

//菜单栏,获取当前窗口的菜单栏,没有的话会自动创建一个
QMenuBar *mb = this->menuBar();
//添加菜单
QMenu *menuFile = mb->addMenu("文件");
QMenu * menuEdit = mb->addMenu("编辑");
//往菜单里面添加菜单项
QAction *actionNew = menuFile->addAction("新建");
QAction *actionOpen = menuFile->addAction("打开");

//添加分隔符
menuFile->addSeparator();

QAction *actionRename = menuFile->addAction("重命名"); //菜单项
//添加二级菜单
QMenu *menuRecent = menuFile->addMenu("最近打开的文件");
menuRecent->addAction("1.txt");

//工具栏(可以拖动),可以有多个
QToolBar *toolBar = this->addToolBar("");
//工具栏添加工具
toolBar->addAction(actionNew);
toolBar->addAction(actionOpen);
//默认停靠在左边
//this->addToolBar(Qt::LeftToolBarArea, toolBar);
//只允许停靠在左边或者右边
toolBar->setAllowedAreas(Qt::LeftToolBarArea|Qt::RightToolBarArea);
//设置工具栏不可以浮动
toolBar->setFloatable(false);
//设置工具栏不允许拖动
toolBar->setMovable(false);

//状态栏只有一个,这种就可以不需要添加
QStatusBar *sb = this->statusBar(); //获取窗口的状态栏
//往状态栏里面添加信息
//添加左侧信息
QLabel *labelLeft = new QLabel("左侧信息", this);
sb->addWidget(labelLeft);
//添加右侧信息
QLabel *labelRight = new QLabel("右侧信息", this);
sb->addPermanentWidget(labelRight);

//停靠部件(可以拖动),可以有多个
QDockWidget *dockWidget = new QDockWidget("停靠部件", this);
//默认情况下没有核心部件作为参照物,停靠部件就会占完窗口
this->addDockWidget(Qt::BottomDockWidgetArea, dockWidget);

//添加核心部件
QTextEdit *textEdit = new QTextEdit(this); //创建一个编辑框
this->setCentralWidget(textEdit);
}

2.UI文件的使用

创建项目时保留UI,setupUI函数就是关联UI文件的代码到程序,原理就是qt将ui文件转化成了c++代码。

3.资源文件使用

创建资源文件:新建 —–> Qt —–> Qt Resource File —–> 名称为res

7. QDialog对话框

对话框是GUI程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中,比如用于完成一次性任务的功能(如登录功能、现在某个文件打开、保存文件)。对话框通常会是一个顶层窗口,出现在程序最上层,用于实习短期任务或者简洁的用户交互。

Qt中使用QDialog类实现对话框,但是声明一个QDialog对象的时候,不管这个对话框对象跟哪个窗口建立了父子关系,当它显示出来的时候都还是一个顶层的窗口。

1.对话框没有最大化、最小化按钮的窗口,其可以分为模态对话框非模态对话框

  • 模态对话框:就是对话框还没有关闭前不能操作同一个进程的其它窗口
    • 创建模态:QDialog::exec()函数,是一个阻塞的消息循环函数
  • 非模态对话框:就是对话框没有关闭前也能操作同一个进程的其它窗口
    • 使用show()函数来直接显示窗口就可以,非阻塞的情况下要使用new的方式来创建对话框对象,不然容易随着所在区域的释放而释放。
    • 内存泄漏问题,模态对话框关闭后可能并不会马上释放,所以就得通过设置窗口的属性来让其关闭后自动释放:
      • dlg->setAttribute(Qt::WA_DeleteOnClose);

下面通过两个按钮的发送信号来触发槽函数,从而实现创建模态和非模态对话框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void MainWindow::on_model_clicked()
{
//创建一个模态对话框
QDialog dlg(this);
dlg.exec();
qDebug()<<"hello model dialog";
}

void MainWindow::on_nonmodel_clicked()
{
//创建一个非模态对话框
//因为show()是非阻塞的函数,所以当dlg所在的区域释放后,dlg对话框也会释放,得通过new创建
//QDialog dlg(this);
QDialog *dlg = new QDialog(this);
//释放问题,只有父对象释放的时候子对象才在释放
dlg->setAttribute(Qt::WA_DeleteOnClose); //通过设置窗口的属性,让它关闭的时候自动释放
dlg->show();
qDebug()<<"hello nonmodel dialog";
}

2.系统标准对话框

QMessageBox用来提示用户某条信息,分为以下几种:

  • 错误提示框:QMessageBox::critical();
  • 警告提示框:QMessageBox::warning();
  • 信息提示框:QMessageBox::information();
  • 问题提示框:QMessageBox::question();
1
2
3
4
5
//参数依次是:父对象窗口;提示框标题;提示框提示的信息
QMessageBox::critical(this, "错误", "critical");
QMessageBox::warning(this, "警告", "warning");
QMessageBox::information(this, "信息", "information");
QMessageBox::question(this, "ONE PIECE", "Does it really exist?", QMessageBox::Ok|QMessageBox::Cancel);

问题提示框相较于其它几个提示框比较特殊,可以指定对话框的按钮,通过返回值来获取用户点击了哪个按钮。

3.文件对话框

使用QFileDialog来打开一个文件对话框,常用的函数是getOpenFileName来选择单一某文件,返回值是用户选择的文件路径。

在下面程序的中,设置的过滤器表示为将打开的目录种类分为PNG、ICO和all类型,默认先显示的是PNG类别的文件。

1
2
3
4
//默认打开的是当前项目所在的文件
QString fileName = QFileDialog::getOpenFileName(this, "打开一个文件");
//参数3和参数4是可选的,参3是设置打开的目录,参4是过滤器,将打开目录分为设置的机种类别
QString fileName = QFileDialog::getOpenFileName(this, "打开一个文件", "C:\\Qt\\Bird", "PNG (*.png) ;; ICO (*.ico) ;; all (*.*)");

4.布局

有如下两类布局:

  • 静态:就是位置和大小不会跟着外部窗口变化而变化
  • 动态:就是位置和大小会跟着外部窗口变化而变化

常用的动态布局:水平、垂直、栅格、表单布局,推荐使用widget的自带的布局功能

使用弹簧来调整布局的位置(居中),栅格布局可以将空间分为几行几列的表格,方便对齐

大小策略:默认情况下动态布局,子窗口的大小会跟着父窗口的大小变化而变化,调整水平或垂直策略,变为固定

调整子窗口和父窗口之间的间隙,设置父窗口的margin,调整子窗口之间的间隙就调整spacing

调整窗口的固定大小,就是将窗口的最大值和最小值都设为同一个值

5.按钮组

  • radio button单选按钮:互斥域的问题,如果想将某些单选按钮隔离开,就用容器将它们隔离,一般用Group Box

  • check box多选按钮:有三态(tristate),每次点击按钮的时候stateChange信号里边传进来

6.QListWidget窗口

往ListWidget窗口中添加内容有两种方式:

  • 往ListWidget窗口中,通过addItem添加QListWidgetItem对象(一个一个添加,可以修改item的属性)
  • 往ListWidget窗口中,通过addItems添加QStringList对象(一次性添加多个,不可以修改item的属性
1
2
3
4
5
6
7
8
9
//一次性添加一个
QListWidgetItem *item = new QListWidgetItem("嘻嘻哈哈");
item->setTextAlignment(Qt::AlignHCenter); //这是设置了item居中显示
ui->listWidget->addItem(item);

//一次性添加多个
QStringList list;
list<<"嘻嘻哈哈"<<"叽叽喳喳"<<"把卡把卡";
ui->listWidget->addItems(list);

7.treeWidget窗口

  • 设置标题,会根据setHeaderLabels函数里面的成员数生成对应的列数
  • 添加根节点通过addTopLevelItem函数
  • 根节点下面添加子节点通过addChild函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//1.设置标题
ui->treeWidget->setHeaderLabels(QStringList()<<"英雄"<<"简介");

//2.添加跟节点
QTreeWidgetItem *liliangItem = new QTreeWidgetItem(QStringList()<<"力量");
QTreeWidgetItem *minjieItem = new QTreeWidgetItem(QStringList()<<"敏捷");
QTreeWidgetItem *zhiliItem = new QTreeWidgetItem(QStringList()<<"智力");
ui->treeWidget->addTopLevelItem(liliangItem);
ui->treeWidget->addTopLevelItem(minjieItem);
ui->treeWidget->addTopLevelItem(zhiliItem);

//3.添加相应的子节点
QStringList heroL1, heroL2, heroM1, heroM2, heroZ1, heroZ2;
heroL1<<"亚瑟"<<"对拼刺刀";
heroL2<<"坦克"<<"血条厚";
heroM1<<"李白"<<"花里胡哨";
heroM2<<"刺客"<<"太秀了";
heroZ1<<"孙策"<<"无证驾船";
heroZ2<<"上单"<<"横冲直撞";
liliangItem->addChild(new QTreeWidgetItem(heroL1));
liliangItem->addChild(new QTreeWidgetItem(heroL2));
minjieItem->addChild(new QTreeWidgetItem(heroM1));
minjieItem->addChild(new QTreeWidgetItem(heroM2));
zhiliItem->addChild(new QTreeWidgetItem(heroZ1));
zhiliItem->addChild(new QTreeWidgetItem(heroZ2));

得到的效果图如下

8.tableWidget窗口

  • 设置行数通过setRowCount()函数、设置列数通过setColumnCount()函数
  • 设置水平的标题通过setHorizontalHeaderLabels()函数
  • 设置表格某行某列的数据setItem(row, col, item)
1
2
3
4
5
6
7
8
9
10
11
12
13
//设置行数、列数
ui->tableWidget->setRowCount(5);
ui->tableWidget->setColumnCount(3);
//设置标题
ui->tableWidget->setHorizontalHeaderLabels(QStringList()<<"英雄"<<"性别"<<"年龄");
//添加数据
QStringList heroNames = QStringList()<<"亚瑟"<<"妲己"<<"安其拉"<<"赵云"<<"孙悟空";
QStringList herogenders = QStringList()<<"男"<<"女"<<"女"<<"男"<<"雄性";
for(int row=0; row<5; row++){
ui->tableWidget->setItem(row,0, new QTableWidgetItem(heroNames[row]));
ui->tableWidget->setItem(row,1, new QTableWidgetItem(herogenders[row]));
ui->tableWidget->setItem(row,2, new QTableWidgetItem(QString::number(row + 18))); //需要将整数转为string类型
}

得到的效果图如下:

8_5

9.容器

stacked Widget 页面切换需要我们自己去实现,一般使用按钮点击的时候切换,通过setCurrentIndex()方式切换到第几页,序号从0开始

10、显示控件

  • label可以显示静态图,通过pixmap属性即可完成

  • label也可以显示动态图gif,需要先创建movie对象,再通过setMovie()函数设置电影,最后通过start()函数来播放动画

1
2
3
QMovie *movie = new QMovie(":/Image/mario.gif", QByteArray(), this);
ui->label_1->setMovie(movie);
movie->start();

11.自定义控件

当系统提供的控件不能满足我们生产中的一个功能时,就需要自己创建一些控件。

比如说一个自定义一个按钮控件,需要先创建一个自定义按钮类MyButton,让它继承QPushButton类,在MyButton类的析构函数中实现一些想要的功能,最后将主窗口中的按钮(QPushButton类)提升为MyButton类,这样就可以实现自定义按钮的一些功能了。

8. 事件

事件(event)是由系统或者Qt应用程序本身在不同的时刻发出的。当用户按下鼠标、敲下键盘、或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。

在所有组件的父类QWidget中,定义了很多事件处理的函数,如:

  • keyPressEvent():键盘按键按下事件
  • keyReleaseEvent():键盘按键松开事件
  • mouseDoubleClickEvent():鼠标双击事件
  • mouseMoveEvent():鼠标移动事件

1.在下面程序中,先创建一个主窗口widget,将一个Label标签拖入其中,再创建一个自定义标签类MyLabel,使它继承QLabel类,然后在该类里面添加对应的事件功能函数,最后将主窗口的Label标签提升为自定义标签类MyLabel。

自定义标签类的头文件:

1
2
3
4
5
6
7
8
9
10
11
protected:
//重写鼠标按键处理函数
void mousePressEvent(QMouseEvent *ev) override;
//重写鼠标移动的处理函数
void mouseMoveEvent(QMouseEvent *ev) override;
//重写event分发函数(事件的分发机制)
bool event(QEvent *e) override;

//重写事件过滤器eventFilter
bool eventFilter(QObject *watched, QEvent *event);

自定义标签对应的事件函数实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
//默认情况下,窗口不会主动跟踪鼠标(鼠标进入窗口,先不会触发任何事件)
//只有当某个鼠标按键按下的情况下才开始跟踪
//如果想一开始跟踪,就要使用以下函数
this->setMouseTracking(true);

//事件过滤器的使用
//1.窗口调用installEventFilter来安装一个事件过滤器
//2.参数是一个事件过滤器对象QObject,该对象的类要重写eventFilter的函数
//事件过滤的时候,事件会先到达事件过滤器的eventFilter函数
//返回值:true表示拦截,false表示不拦截,不拦截情况下事件会继续到达窗口
this->installEventFilter(this); //将当前窗口安装过滤器,对象可以使用自己作为自己的过滤器
}

void MyLabel::mousePressEvent(QMouseEvent *ev)
{
//获取鼠标坐标
int x = ev->x();
int y = ev->y();

//获取鼠标按键
Qt::MouseButton btn = ev->button(); //单个事件
QString strbutton = "";
if(btn == Qt::LeftButton){
strbutton = "LeftButton";
}
if(btn == Qt::RightButton){
strbutton = "RightButton";
}
if(btn == Qt::MidButton){
strbutton = "MidButton";
}

QString str = QString("pres[%1,%2][%3]").arg(x).arg(y).arg(strbutton); //label也可以显示html
this->setText(str);
}

void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
//获取鼠标坐标
int x = ev->x();
int y = ev->y();

//获取鼠标按键
Qt::MouseButtons btns = ev->buttons(); //多个事件
QString strbutton = "";
if(btns & Qt::LeftButton){
strbutton += "LeftButton;";
}
if(btns & Qt::RightButton){
strbutton += "RightButton;";
}
if(btns & Qt::MidButton){
strbutton += "MidButton;";
}

QString str = QString("move[%1,%2][%3]").arg(x).arg(y).arg(strbutton); //label也可以显示html
this->setText(str);
}

bool MyLabel::event(QEvent *e)
{
//返回值:true表示该事件得到处理,如果是false,表示没有被处理,事件会继续传递到父窗口
//QEvent就是所有事件(event类)的父亲
//判断event的类型
if(e->type() == QEvent::MouseMove){ //如果是鼠标移动事件
//如果注释掉下面这一行,将对鼠标移动事件起到屏蔽作用,因为当我们在主窗口移动鼠标的时候,该事件会先经过我们重写的分发函数,因为我们对该事件没有进行处理,直接就返回了,所以系统啥都不会做,如果是其它事件,因为这里我们没有处理其它事件的函数,所以会调用父类的分发函数去实现处理,一样会生效起作用。
//this->mouseMoveEvent(static_cast<QMouseEvent *>(e)); //调用鼠标移动处理函数
return true;
}
return QLabel::event(e); //其它事件这里没有处理,所以就给父类的分发函数去处理其它事件
}

//安装了该过滤器的窗口都会执行该函数,所以一个过滤器可能会有多个窗口使用,则参数1(watched)表示目前传过来的窗口对象,参2则是存储触发事件的一些信息
bool MyLabel::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::MouseMove){
//返回ture表示拦截该事件
return true;
}
return false;
}

2.定时器事件timerEvent

闹钟就是定时器,闹钟响了就是定时器事件

在头文件中需要重写定时器事件:void timerEvent(QTimerEvent* event);里面实现的功能即为定时器时间到后执行的操作

在需要的位置通过startTimer来启动一个定时器,返回值就是定时器的id,参数是毫秒,每隔相应的时间就会触发一次定时器事件

通过函数killtimer()来杀死一个定时器,参数就是要杀死定时器的id

timerEvent定时器事件处理函数中可以通过event参数获取到当前事件是哪个定时器发出的,如:event->timerId()

3.系统封装好的定时器QTimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);

timer = new QTimer(this); //创建定时器对象
connect(timer, &QTimer::timeout, [=](){
static int num = 1;
this->ui->lcdNumber->display(num++); //主窗口的lcd插件显示对应的数字
});
}
void Widget::on_start_clicked() //start按钮触发的槽函数
{
timer->start(10); //启动定时器,10毫秒发出一次信号
}

void Widget::on_end_clicked() //end按钮触发的槽函数
{
timer->stop(); //停止定时器
}

4.绘图事件

  • 什么时候画

    • 绘图事件:窗口需要重新显示的时候(大小发生变化、窗口切换等),就会收到一个绘图事件paintEvent,收到绘图事件之后,窗口就要将自己画出来。
  • 怎么画

    • 通过定义一个画家QPainter,给出一个画图设备QPaintDevice(窗口)作为参数。

5.创建了一个窗口Widget,在该窗口头文件重写了画图事件,下面为该画图事件函数里面的功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void Widget::paintEvent(QPaintEvent *event)
{
//创建一个画家,绘图设备为当前窗口
QPainter painter(this);

//画家偏移,搬动画家到某个坐标上开始画画
painter.translate(100,0); //现在默认窗口的(100,0)为画家的起点了

//创建一支画笔
QPen pen;
pen.setColor(QColor(255,0,0)); //设置颜色
pen.setWidth(3); //设置笔宽
pen.setStyle(Qt::DashLine); //设置画笔的风格
//画家设置画笔
painter.setPen(pen);

//创建一个画刷(用来填充)
QBrush brush;
brush.setColor(Qt::cyan); //填充的颜色
brush.setStyle(Qt::Dense4Pattern); //默认情况下,画刷不会填充,还得设置风格
//画家设置画刷
painter.setBrush(brush);

//画一条线
painter.drawLine(0,0,100,100); //起点为(0,0),终点为(100,100)

//画矩形
painter.drawRect(20,20,50,50); //左上角点为(20,20),长为50,宽为50

//画圆形,使用椭圆(自己调整)
painter.drawEllipse(QPoint(100,100),50,50); //圆心,x轴半径,y轴半径

//画文字
painter.drawText(200, 100, "好好学习,天天向上"); //文字坐标,文本
}

6.手动触发绘图事件

在一些应用场景,我们或许希望能手动的触发绘图事件,而不是等到窗口发生变化再触发绘图事件,所以就可以使用下面方法完成。

  • 可以使用两个函数
    • repaint:会马上触发绘图事件,当某一处函数调用了多次repaint时,会触发多次绘图事件
    • update:update做了一些优化,当某一处函数调用了多次update时,只会触发一次绘图事件

注意:不要再绘图事件paintEvent()函数中再触发绘图事件,会导致无线循环

下面程序实现了将本地存在的图片绘制到窗口

1
2
3
4
//painter绘制已经存在到达图片
QPainter painter(this);
QPixmap pixmap("C:\\Qt\\素材\\llfcchat-master\\llfcchat-master\\client\\llfcchat\\res\\head_5.jpg");
painter.drawPixmap(0,0,pixmap);

7.绘图设备

绘图设备是指继承QPainterDevice的子类,Qt一共提供了4个这样的类,分别是QPixmap、QBitmap、QImage和QPicture,其中:

  • QPixmap专本为图像在屏幕上的显示做了优化
  • QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
  • QImage专门为图像的像素级访问做了优化
  • QPicture则可以记录和重现QPainter的各条命令。