1、创建Qt项目
1.1 使用向导创建
打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项
弹出New Project对话框,选择Qt Widgets Application,
选择【Choose】按钮,弹出如下对话框
设置项目名称和路径,按照向导进行下一步,
选择编译套件
向导会默认添加一个继承自QMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),QWidget 是所有能看到的窗口或者控件的父类,QMainWindow、QDialog 都继承自它
这里我们可以先创建一个不带UI的界面,继续下一步
系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。
1.2 一个最简单的Qt应用程序
1.2.1 main函数中
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
main函数
QApplication 就是Qt里边每个应用程序有且仅有一个的应用程序对象
QApplication::exec() 程序的生命循环、消息循环 ,当作以下形式
解释:
- Qt系统提供的类头文件没有.h后缀
- Qt一个类对应一个头文件,类名和头文件名一致
- QApplication应用程序类
- 管理图形用户界面应用程序的控制流和主要设置。
- 是Qt生命,一个程序要确保一直运行,就肯定至少得有一个循环,这就是Qt主消息循环,在其中完成来自窗口系统和其它资源的所有事件消息处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
- 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,不论这个应用程序在同一时刻有多少个窗口。
a.exec()
程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
std::cout<<"befor exec"<<std::endl;
a.exec();
std::cout<<"after exec"<<std::endl;
return 0;
我们发现运行到exec会处于等待状态,知道窗口关闭才结束
如果我们要模仿这个函数,需要用到循环
while(1)
* {
* if(点击x按钮)
* break;
* if(点击了最小化按钮)
* 最小化动作;
* ...
* }
1.2.2 类头文件
#include <QWidget>
class MyWidget : public QWidget
{
//引入Qt信号和槽机制的一个宏
Q_OBJECT
public:
//构造函数中parent是指父窗口,父窗口对象的指针
//如果parent是0或者NULL,那么窗口就是一个顶层的窗口
MyWidget (QWidget *parent = 0);
~ MyWidget ();
};
//顶层窗口就是在任务栏可以找到的窗口
1.2.3 pro文件
.pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。类似于VS中的.sln 和vsproj文件
以下是.pro文件的一个案例:
#引入Qt的模块,core gui
QT += core gui
#如果qt版本大于4,那么引入widgets模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#生成最终文件的文件名,可执行文件exe
TARGET = 01_MyWidget
#项目类型,生成什么类型的文件,可执行程序还是库文件
TEMPLATE = app
#要编译的源文件列表
SOURCES += \
main.cpp \
mywidget.cpp
#要编译的头文件列表
HEADERS += \
mywidget.h
Qt里边绝大部分的类都是继承自QObject ,QObject是一个顶层类
- 注释
从“#”开始,到这一行结束。
- 模块引入
QT += 模块名,表示当前项目引入Qt哪些模块。
引入模块的意思就简单理解为引入C/C++头文件搜索路径,如果没引入对应模块就使用该头文件的话会报错说找不到该头文件。当然不必要的模块还是别引入,因为引入模块不仅仅是引入头文件搜索路径那么简单,还包括引入连接的库等一系列操作,会让程序变臃肿。
Qt详细模块有哪些可以参照附录。
-
模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:TEMPLATE = app
- app -建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用。
- lib - 建立一个库的makefile。
- vcapp - 建立一个应用程序的VisualStudio项目文件。
- vclib - 建立一个库的VisualStudio项目文件。
- subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。
-
指定生成的应用程序名:
TARGET = QtDemo
- 工程中包含的头文件
HEADERS += include/painter.h
- 工程中包含的.ui设计文件
FORMS += forms/painter.ui
- l工程中包含的源文件
SOURCES += sources/main.cpp sources
- 工程中包含的资源文件
RESOURCES += qrc/painter.qrc
-
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这条语句的含义是,如果QT_MAJOR_VERSION大于4
(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加``QT += widgets`一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写。
- 配置信息
CONFIG用来告诉qmake关于应用程序的配置信息。
CONFIG += c++11 //使用c++11的特性(qt5.6以上版本默认使用C++11)
在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。
1.2.3 命名规范
类名:单词首字母大写,单词和单词之间直接连接,无需连接字符
MyClass,QPushButton
class MainWindow
Qt中内置的类型,头文件和类命名同名。
#include <QString>
QSring str;
#include <QWidget>
QWidget w;
函数名字,变量名:首字母小写,之后每个单词首字母大写,单词和单词之间直接连接,无需连接字符
void connectTheSignal();
类的成员变量设置函数用使用 set+成员变量名,获取成员变量的函数直接用成员变量名(如果是bool类型,有可能会用一些表示状态的术语,如isVisilble,hasFocus):
//普通成员变量设置和获取
void setText(QString text);
QString text()const;
//bool的成员变量设置和获取
void setEnabled(bool enabled);
bool isEnabled()const;
1.2.4 QtCreator常用快捷键
运行 ctrl +R
编译 ctrl +B
帮助文档 F1 ,点击F1两次跳到帮助界面
跳到符号定义 F2 或者ctrl + 鼠标点击
注释 ctrl+/
字体缩放 ctrl + 鼠标滚轮
整行移动代码 ctrl + shift + ↑或↓
自动对齐 ctrl + i
同名之间的.h和.cpp文件跳转 F4
2、Qt 按键小程序
2.1 按钮的创建和父子关系
创建一个新的项目文件Qpushbutton,并设置为活动项目,设置为活动项目意思是执行哪个项目
#include <QPushButton>
//添加按钮
QPushButton btu;
btu.setText("按钮1");
//将按钮显示出来
btu.show();
按钮独立显示一个窗口,是因为默认情况下没有建立父子关系,显示的是顶层窗口
上面代码中,一个按钮其实就是一个QPushButton类的对象,如果只是创建出对象,是无法显示到窗口中的,所以我们需要依赖一个父窗口,也就是指定一个父亲,利用setParent函数或者按钮创建的时候通过构造函数传参,此时我们称两个窗口建立了父子关系。在有父窗口的情况下,窗口调用show会显示在父窗口中,如果没有父窗口,那么窗口调用show显示的会是一个顶层的窗口(顶层窗口是能够在任务栏中找到的,不依赖于任何一个窗口而独立存在)(按钮也是继承于QWidget,也属于窗口)。
如果想设置按钮上显示的文字可以用setText,移动按钮位置用move。
对于窗口而言,我们可以修改左上角窗口的标题setWindowTitle,重新指定窗口大小:resize,或者设置固定的窗口大小setFixedSize。
//建立父子关系
//1.setparent函数
QPushButton btn;
btn.setText("按钮1");
btn.setParent(&w);
再添加一个按钮
//2.构造函数传参
QPushButton btn2("按钮2",&w);
btn2.move(100,100);
按钮2覆盖了按钮1,移动(move)按钮btn2.move(100,100);
//设置窗口按键大小
btn2.resize(300,300);
QPushButton btn3("按钮3",&btn2);
//设置窗口标题
w.setWindowTitle("第一个项目");
//限制窗口大小
w.setFixedSize(600,400);
//第二种创建
QPushButton * btn2 = new QPushButton("按键1",this);
//重新指定窗口大小
this->resize(600,400);
//设置窗口标题
this->setWindowTitle("第一个项目");
//限制窗口大小
this->setFixedSize(600,400);
2.2 Qt窗口坐标体系
是以父窗口的左上角为0,0
以向右的方向为x的正方向
以向下的方向为y的正方向
顶层窗口就是以屏幕左上角为0,0
对于嵌套窗口,其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。
2.3 Qt常用API函数
move 移动窗口到父窗口某个坐标
resize 重新设置窗口的大小
setFixedSize 设置窗口的固定大小
setWindowTitle 设置窗口标题
setGeometry 同时设置窗口位置和大小,相当于move和resize的结合体
2.4 对象树模型
QObject是Qt里面绝大部分类的根类
QObject不佳而传统对象之间是以对象树的形式组织起来的
- 当两个Object(或父类)的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children(孩子)的list列表中。
- 当对对象析构的时候,这个列表中的所有对象也会被析构。(之u一,这里是说的父对象和子对象,不要理解成父类和子类)
//局部变量在函数退出的时候会自动释放
QPushButton btn("按钮1",this);
btn.show();
发现按键窗口一闪而过,生命周期只在函数执行到结束就消亡了,所以需要提高生命周期:
//解决 让按钮的生命周期长一点
//1 .static
//2. 类成员变量
//3. new 一个,动态内存分配
QPushButton *btn1 = new QPushButton("按钮1",this);
//在new分配内存了之后需要释放,但这里不需要delete
Qwindget是能够再屏幕上显示的一切组件的父类
我们再自己创建一个类打印验证一下,再继承一个按钮类,在子类的析构函数里打印信息
在本类中新建一个MyPushButton C++文件
#include "mypushbutton.h"
#include<QDebug>
MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent)
{
}
MyPushButton::~MyPushButton()
{
qDebug()<<"MyPushButton Destory";
}
在qt中我们使用QDebug这么一个函数代替cout会更合适,需包含头文件,还有string用Qstring一样
代表关闭窗口析构成功了
概念:各个窗口对象通过建立父子关系构造的一个关系树
内存管理:
父对象释放的时候会自动释放各个子对象(使用children列表)
以后基本都是用new的方式来创建窗口对象
注意点:
1 父对象能够被释放
2 父对象、子对象,直接或者间接继承自QObject
3、信号和槽机制
信号:各种事件
槽: 响应信号的动作
当某个事件发生后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal)。
某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)。
但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect)
3.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了,我们看下面的代码
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:
connect(sender, signal, receiver, slot);
参数解释:
- sender:信号发送者
- signal:信号
- receiver:信号接收者
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
这里要注意的是connect的四个参数都是指针,信号和槽是函数指针,使用connect的时候保留&符号。
系统自带的信号和槽如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个
这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。
3.2 自定义信号和槽
Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。
3.2.1 自定义信号使用条件
函数声明在类头文件的signals域下
没有返回值,void类型的函数
只有函数声明,没有实现定义
可以有参数,可以重载
通过emit关键字来触发信号,形式:emit object->sig(参数);
3.2.2 自定义槽函数使用条件
qt4 必须声明在类头文件的 private/public/protected slots域下面,qt5之后可以声明再类的任何位置,同时还可以是静态的成员函数,全局函数,lambda表达式
没有返回值,void类型的函数
不仅有声明,还得要有实现
可以有参数,也可以重载
3.2.3 使用自定义信号和槽
【定义场景】:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
首先定义一个学生类Student和老师类Teacher:
Teacher *pTeacher ;
Student *pStudent;
老师类中声明信号 饿了 hungry
signals:
void hungry();
学生类中声明槽 请客treat
public slots:
void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void Widget::classIsOver()
{
//触发老师饿了的信号
emit pTeacher->hungry();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
#include<QDebug>
void Student::treat()
{
qDebug() << "Student treat teacher";
}
在窗口中连接信号槽
pTeacher = new Teacher(this);
pStudent = new Student(this);
//建立连接
connect (pTeacher,&Teacher::hungry,pStudent,&Student::treat);
this->classIsOver();
带参数的自定义信号和槽,就声明函数的时候就带上参数就行
老师说他饿了,说要吃海底捞,学生就请吃海底捞
void hungry(QString name); 自定义信号
void treat(QString name ); 自定义槽
void Student::treat(QString name)
{
qDebug()<<"Student treat teacher with "<<name;
}
但是由于有两个重名的自定义信号和自定name义的槽,直接连接会报错,所以需要解决重载问题
//因为函数发生了重载,所以解决
/*
* 1 使用函数指针赋值,让编译器挑选符合类型的函数
* 2 使用static_cast 强制转换,也是让编译器自动挑选符合类型的函数
*/
重载有参函数,使用函数指针赋值:
void(Teacher::*teacher_qstring)(QString) = &Teacher::hungry;
void(Student::*student_qstring)(QString) = &Student::treat;
connect(pTeacher,teacher_qstring,pStudent,student_qstring);
this->classIsOver();
使用强制类型转换解决重载问题:
//也可以使用static_cast静态转换挑选我们要的函数
connect(
pTeacher,
static_cast<void(Teacher:: *)(QString)>(&Teacher:: hungry),
pStudent,
static_cast<void(Student:: *)(QString)>(& Student::treat));
this->classIsOver();
}
使用强制类型处理无参函数
//使用static_cast来转换无参的函数
connect(pTeacher,
static_cast<void(Teacher::*)()>(&Teacher::hungry),
pStudent,
static_cast<void(Student::*)()>(&Student::treat));
this->classIsOver();
调用带参数的信号函数 emit pTeacher->hungry(“海底捞”);
3.3 信号和槽的拓展
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。像上面的例子,可以将一个按钮点击信号连接到关闭窗口的槽函数,同时也连接到学生请吃饭的槽函数,点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。
- 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。
- 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。
#include<QPushButton>
pTeacher = new Teacher(this);
pStudent = new Student(this);
connect(pTeacher,
static_cast<void(Teacher::*)()>(&Teacher::hungry),
pStudent,
static_cast<void(Student::*)()>(&Student::treat));
this->classIsOver();
QPushButton *btn = new QPushButton("按钮1",this);
connect(btn,
&QPushButton::clicked,
pTeacher,
static_cast<void(Teacher::*)()>(&Teacher::hungry));
- 信号和槽可以断开连接
可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
connect(pTeacher,teacher_qstring,pStudent,student_qstring);//建立连接
disconnect(pTeacher,teacher_qstring,pStudent,student_qstring);//断开连接
- 信号和槽的关系信号和槽函数参数类型和个数必须同时满足两个条件
1) 信号函数的参数个数必须大于等于槽函数的参数个数
2) 信号函数的参数类型和槽函数的参数类型必须一一对应
hungry(QString) -> treat() ok
hungry(QString) -> treat(int) 编译出错
hungry(QString,int) -> treat(int) 编译出错
hungry(int,String) -> treat(int) ok
3.4 Qt4版本的信号槽写法
connect使用不一样,信号和槽函数声明差不多
connect(信号发送者,SIGNAL(函数原型) ,信号接收者,SLOT(函数原型))
//Qt4 的 信号和槽 ,实现teacher和student带参数的信号和槽连接
connect(pTeacher,SIGNAL(hungry(QString)),pStudent,SLOT(treat(QString)));
这里使用了SIGNAL和SLOT这两个宏,宏的参数是信号函数和槽函数的函数原型。
因为直接填入了函数原型,所有这里边编译不会出现因为重载导致的函数指针二义性的问题。但问题是如果函数原型填错了,或者不符合信号槽传参个数类型约定,编译期间也不会报错,只有运行期间才会看到错误log输出。
原因就是这两个宏将后边参数(函数原型)转化成了字符串。目前编译器还没有那么智能去判断字符串里边的内容符不符合运行条件。
SIGNAL和SLOT宏的原理,就是将后边的参数转成字符串 类似 #define toStr(arg) #arg -> “arg”
推荐:以后都用qt5的
3.5 QDebug
qdeubg输出QString默认会转义
//解决方法两个
//1 将QString转成 char *
//qDebug()<<"Student treat teacher with "<<what.toUtf8().data();
//2 使用qDebug().noquote()
qDebug().noquote()<<"Student treat teacher with "<<what;
3.5 Lambda表达式
C++11中的Lambda表达式用于定义匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}
[capture](parameters) opt ->retType
{
……;
}
//使用lambda的方式,使用函数指针
void(*p)()=
[]()
{
qDebug()<<"Hello Lambda";
};
p();
//简写
[]()
{
qDebug()<<"Hello Lambda";
}();
3.5.1 局部变量引入方式
[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体(A)里边,所以lambda表达式有可能会去访问这个(A)函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:
[] 表示lambda表达式不能访问外部函数体的任何局部变量
[a] 在函数体内部使用值传递的方式访问a变量
//局部变量捕获
int a = 10;
int b = 20;
error,a没有被捕获,我们需要传个参数过去,但是目的不是自己用的,是给别人用的,所以这里用捕获
[a,b]()
{
qDebug()<<"Hello Lambda";
qDebug()<<a <<b;
}();
捕获局部变量分两个方式:一种值传递,一直引用传递
[=] 函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本
[&] 引用的方式使用lambda表达式外部的所有变量
[&a,&b]()
{
qDebug()<<"Hello Lambda";
qDebug()<<"修改前"<<a <<b;
a = 100;
b = 200;
}();
qDebug()<<"修改后:"<<a <<b;
[=, &a] a使用引用方式, 其余是值传递的方式
[&,a] a使用值传递方式, 其余是引用传递的方式
[this] 在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入
由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。
所以在无特殊情况下建议使用[=](){}
的形式
3.5.2 函数参数
(params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
3.5.3 选项Opt
Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以
int a = 10 ;
[=]()
{
a=20;//编译报错,a引进来是const
}
[=]()mutable
{
a=20;//编译成功
};
3.5.4 槽函数使用Lambda表达式
以QPushButton点击事件为例:
QPushButton *btn = new QPushButton("按钮1",this);
int a = 10;
int b = 20;
connect(btn,&QPushButton::clicked,[=]()
{
qDebug()<<a <<b;
}
);
这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。
//可以在函数体执行下面这些函数功能
this->close();//关闭窗口
this->resize(500,600);//设置窗口固定大小
由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:
[=](){ }
的形式
3.5.5 函数返回值 ->retType
->retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值,编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错,如:文章来源:https://www.toymoban.com/news/detail-817048.html
[=]()mutable
{
int b = 20;
float c = 30.0;
if(a>0)
return b;
else
return c;//编译报错,两条return语句返回类型不一致
};
4、复习
文章来源地址https://www.toymoban.com/news/detail-817048.html
到了这里,关于【Qt】边学边写之Qt教程(零基础)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!