目录
图形化的方式
使用QLabel
使用编辑框
使用按钮
纯代码的方式
使用QLabel
使用编辑框
使用按钮
关于对象树
观察现象
Qt的常用知识
快捷键
使用帮助文档
Qt 窗口体系
我们最开始学习语言时,第一个接触的知识就是输出字符串 "hello world" 吧,所以在学习Qt时,也学习一下,如何实现 hello world
两种方式实现 hello world:
①通过图形化的方式,在界面上创建出一个控件,显示 hello world
②通过纯代码的方式,通过编写代码,在界面上创建控件,显示 hello world
如果你当前程序界面内容是比较固定的,此时就会以图形化的方式来构造界面
但是如果你的程序界面经常要动态变化,此时就会以代码的方式来构造界面
这两种方式,哪种方便就用哪种,而且这两种方式也可以配合使用
图形化的方式
使用QLabel
在按照上篇博客所讲的,使用 Qt Creator 创建一个Qt项目后,双击右边的 widget.ui
就出现了图形化的界面设计器,可以进行窗口界面内容的编辑了
我们可以使用左侧栏中的一个简单的部件 Label 来完成
这时拖动这个Label到中间的界面,输入 hello world 即可显示出来:
此时在右上角处,通过树形结构,显示出了当前界面上都有哪些控件
刚才往界面上拖拽了一个 QLabel 控件此时, ui 文件的 xml 中就会多出来这一段代码:
进一步的 qmake 就会在编译项目的时候,基于这个内容生成一段 C++ 代码,通过这个 C++ 代码构建出界面内容了,是自动完成的
点击三角运行后,就能显示出 hello world 了:
此时打开与该项目的同级目录的文件,里面的 ui_widget.h 就是 qmake 根据 xml 的内容,生成的代码:
打开 ui_widget.h 后,能看到自动生成的代码:
使用编辑框
QLabel只能显示一个文本,无法做更多的交互性的工作
编辑框有两种:
①单行编辑框:QLineEdit
②多行编辑框:QTextEdit
下面使用单行编辑框,同样打开 widget.ui,点击左侧栏中的 Line Edit:
也将 Line Edit 拖到窗口上,并输入 hello world 文本:
这里的 hello world 也可以通过右侧的属性编辑区进行修改,下面是属于 Line Edit 的属性
想改变也可以点击 text 后面的框,输入进行改变:
并且在运行出来的窗口中,也可以进行输入文本:
使用按钮
继续打开 widget.ui 文件,点击左侧栏中的 Push Button:
继续将 Push Button 拖动到界面上去,输入 hello world:
点击运行,出现一个可以点击的按钮:
此处是可以点击的,但是没有反应,这个按钮有一个属性 objectName,叫做 pushButton,所以下面可以通过 ui->pushButton 获取到这个对象
这里就可以简单引入 Qt 中的信号槽的概念,后面会具体讲解
本质就是给按钮的点击操作,关联上一个处理函数当用户点击的时候,就会执行这个处理函数
下面就在 widget.cpp 中的构造函数中,使用connect函数
Qt 中的 connect 是 QObject 这个类提供的静态函数,这个函数的作用就是连接 信号和槽
这里的 connect函数 和 TCP 的建立连接操作没有任何关系
ui->pushButton 就表示从 ui 文件中访问到了 pushButton
QPushButton::clicked 就表示我们点击按钮时就会自动触发这个信号
this表示指定关联到 this 的槽函数上
&Widget::handleClick表示处理点击操作的函数
由于最后一个传入的 handleClick 函数还没有实现,所以现在实现一下,先在 widget.h 中声明:
再在 widget.cpp 中定义,实现了点击按钮后,可以 hello world 和 hello qt 来回切换:
下面的 text()函数 是获取文本的函数
此时运行程序:
点击一下按钮,变为:
所以在 qmake 自动生成后,ui widget.h 中自动生成的代码里就会包含一个 pushButton 的成员变量,这个变量的名字,就是根据 objectName 的值来确定的
在 objectName 中设置成什么值,生成的变量名就叫啥名字,就可以根据这个名字来获取到对应的控件的变量了
纯代码的方式
使用QLabel
一般通过代码来构造界面的时候,通常会把构造界面的代码放到 Widget/MainWindow 的构造函数中,构造函数在 widget.cpp 中定义
此时就可以 new 一个 Qlabel 对象:
Qt 中,每个类都有一个对应同名的头文件,如果用不了就显示包含一下头文件即可:
出现上述报错,就包含对应的头文件,与类同名:
在创建 label 对象时,可以选择像上面的在堆上创建,也可以选择在栈上创建,但是更推荐在堆上创建,具体原因在后面的对象树章节说明
new 对象时,最好传递一个参数 this
也就是给当前这个 label 对象,指定一个 父对象 this,这里的 this 就指向的当前的构造函数所对应的对象,也就是main函数中创建的 Widget 对象
接着就需要使用 setText 方法:
可以看到 setText 方法的参数是 QString,而不是 C++ 中的 std::string,是因为 Qt 是 1991年 诞生的,而 C++ 最早的标准也是 C++98了,即1998年产生的,在1991年,C风格字符串和C++的string都不太好用,所以Qt 为了让自己的开发能变的顺畅,就自己发明了一套轮子,搞了一系列的基础类,来支持 Qt 的开发
例如:
QString、QVector、QList、QMap等等
其中 QString 内部已经对于字符编码做了处理,不会出现乱码的问题
而 std:string,就啥都没干
所以直接传入 hello world即可:
在 QString 中也提供了 C风格字符串 作为参数的构造函数
不显式构造 QString,上述代码中的 C风格字符串 也会隐式构造成 QString 对象
并且 QString 对应的头文件,已经被很多 Qt 内置的其他类给间接包含了,因此一般不需要显式包含 QString 头文件
此时运行程序,如下所示:
通过代码创建 QLabel 默认是在左上角,如果想放到其他位置也是可以的(后续会讲)
使用编辑框
同样在 widget.cpp 的构造函数中添加对象:
此时运行程序:
效果和刚刚的图形化的方式是一样的,区别就是文本的位置在左上角
使用按钮
在 widget.cpp 中编写:
初步思路是上述代码,但是由于接下来实现 handleClick 函数时,无法获取到这里的局部变量 myButton,所以将 myButton 设置为成员变量:
widget.cpp 变为:
同样能够完成和图形化的方式一样的效果
点击变为:
关于对象树
这个代码,new 了对象之后,为什么最后没有 delete 呢,不 delete 不就出现内存泄漏了吗??
结论是:上述代码,在 Qt 中不会产生内存泄露,label 对象会合适的时候被析构释放
之所以能够把对象释放掉,主要是因为把这个对象是挂到了对象树上,也就是上述代码中创建对象时传入了this指针,指明了 父对象
Qt 中也是类似网页开发的对象数(DOM),也是搞了一个对象树,也是 N 叉树,把界面上的各种元素组织起来了,使用对象树把这些内容组织起来,最主要的目的,就是为了能够在合适的时机(也就是窗口关闭的时候),把这些对象统一进行释放
所以上述代码通过 new 的方式创建对象,也就是为了把这个对象的生命周期,交给 Qt 的对象树来统一管理
如果是在栈上创建的,运行时就不会看到 hello world,因为创建对象的代码是构造函数中实现的,当执行完构造函数后,该对象就会销毁,所以不会看到这个字符串
观察现象
下面创建一个 MyLabel 类,观察不手动释放 new 的对象,是否会自动释放
主要思路就是:
创建自定义的类,自定义析构函数,在析构函数中加上日志信息,观察关闭窗口后,在结果中会不会出现打印的日志
首先创建一个 MyLabel 的头文件和源文件:
选择C++,C++ Class:
接着指定我们要创建的类名为 MyLabel,下面的 Base Class 表示父类,手动输入父类为:QLabel
此时点击完成,就成功添加到项目中了:
此时点开 mylabel.h ,可以看到 Qt Creator 不认识 QLabel
所以需要手动包含,这也算是 Qt Creator 的其中一个体验不好的地方吧:
自动生成代码,但是却不自动生成所继承的父类的头文件,需要我们手动包含
下面我们改动一下MyLabel的构造函数,变为有参的构造函数,指定父元素,从而能够使得创建的 MyLabel 对象能够加入到对象树中:
在 mylabel.cpp 中也做以修改:
接着需要手动创建析构函数
在 mylabel.cpp 中实现打印时,不能使用 C++ 中的 std::cout,因为可能编码方式不匹配,所以这里使用 qDebug 来打印日志,需要包含头文件 QDebug
下面所使用的 qDebug 是一个宏,后面直接跟上需要输出的字符串即可,会自动处理编码问题
此时 widget.cpp 中,创建一个 MyLabel 对象,注意需要包含 mylabel.h 头文件:
此时运行程序,出现 hello world:
再点开输出界面,关闭这个窗口后,输出如下:
可以看到我们的代码中是没有 delete 的,而程序自动调用了析构函数,从而验证了我们上面所说的结论
小结:
①认识 QLabel 类,能够在界面上显示字符串,通过 setText 来设置的(参数是 QString)
②对象树,Qt 中通过对象树,来统一的释放界面的控件对象
Qt 还是推荐使用 new 的方式在堆上创建对象, 通过对象树,统一释放对象创建对象的时候,在构造函数中,指定父对象(此时才会挂到对象树上)
如果你的对象没有挂到对象树上,就必须要记得手动释放
③通过继承自 Qt 内置的类,就可以达到对现有控件进行功能扩展效果,Qt 内置的 QLabel,没法看到销毁过程的
为了看清楚,就创建类 MyLabel,继承自 QLabel,并重写了析构函数
在析构函数中,加上日志,可以直观的观察到对象释放的过程
也可以重写控件中的任何功能,不仅仅是析构函数,从而达到功能扩展目的
④乱码问题和字符集
如果使用 cout 就可能出现乱码问题,而乱码问题就是编码方式不匹配导致的
汉字不同于英文字母,汉字总计 6万 多个,并且表示同一个汉字有不同的方式,也就是字符集是不同的,不同的字符集,表示同一个汉字,所使用的数字是不相同的
目前表示汉字的字符集主要有两种方式:GBK、UTF-8(utf8)
GBK:主要在中国使用,使用 2 个字节表示一个汉字,我们使用的Windows 简体中文版,它默认的字符集就是 GBK
UTF-8:变长编码,表示一个符号,使用的字节数有变化,一般是2 - 4字节,但是在 utf8 中,一个汉字,一般是 3 个字节
⑤在 Qt 中打印日志
作为调试信息,使用 cout 固然可以,但是并不是上策 (因为cout的字符编码处理的不好,也不方便统一进行关闭)
Qt 中推荐使用 qDebug() 完成日志的打印,它可以自动进行编码处理的操作
并且使用 qDebug() 时,是可以通过编译开关统一关闭,cout则做不到这一点
Qt的常用知识
快捷键
-
注释:ctrl + /
-
运行:ctrl + R
-
编译:ctrl + B
-
字体缩放:ctrl + 鼠标滑轮
-
查找:ctrl + F
-
整行移动:ctrl + shift + ↑ / ↓
-
帮助文档:F1
-
自动对齐:ctrl + i
-
同名之间的 .h 和 .cpp 的切换:F4
-
生成函数声明的对应定义:alt + enter
-
进入函数定义:Ctrl + 鼠标左键
-
返回上一级文件:Alt + ←
使用帮助文档
方式一:光标放到要查询的类名/方法名上,直接按F1
方式二:Qt Creator 左侧边栏中直接用鼠标单击 "帮助" 按钮
方式三:Qt assistant 也是帮助文档
Qt 窗口体系
Qt 的坐标系是平面直角坐标系,也叫做笛卡尔坐标系
但是也与我们之前数学中的坐标系不同,如下所示:
①坐标系的原点 (0, 0) 就是 屏幕的左上角/窗口的左上角
②给 Qt 的某个控件,设置位置, 就需要指定坐标,对于这个控件来说,坐标系原点就是相对于父窗口/控件的
而 Widget 没有父元素,所以就是以桌面的左上角为原点设置的
所以我们就明白了,为什么上面使用纯代码的方式,创建出的按钮是在左上角的,默认情况下就是在 (0, 0)位置,也就是左上角的
运行可以看到是在左上角的:
可以使用 move函数 来设置位置,move中单位是像素:
运行程序可以看到:
也就是:
这里的像素其实我们的笔记本电脑中,显示设置-显示器分辨率中的 1920 × 1080,这两个数字的单位就是像素