【Qt 从入门到入土】下篇
一个非常好的学习 Qt 的视频
本文目录
- 1. Qt 概述
- 1.1 什么是 Qt
- 1.2 Qt 的发展史
- 1.3 支持的平台
- 1.4 Qt 的版本
- 1.5 Qt 的优点
- 1.6 成功案例
- 2. 创建 Qt 项目
- 2.1 使用向导创建
- 2.2 手动创建
- 2.3 .pro文件
- 2.4 一个最简单的 Qt 应用程序
- 2.5 Qt 命名规范和常用快捷键
- 3. 第一个 Qt 小程序
- 3.1 按钮的创建
- 3.2 对象模型(对象树)
- 3.3 Qt 窗口坐标体系
- 4. 信号和槽机制
- 4.1 系统自带的信号和槽
- 4.2 自定义信号和槽
- 4.3 信号槽的拓展
- 4.4 Qt4 版本的信号槽写法
- 4.5 Lambda表达式
- 4.6 信号和槽的总结
- 5. QMainWindow
- 5.1 菜单栏
- 5.2 工具栏
- 5.3 状态栏
- 5.4 铆接部件
- 5.5 核心部件(中心部件)
- 5.6 资源文件
1. Qt 概述
1.1 什么是 Qt
Qt 是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。
1.2 Qt 的发展史
- 1991年 Qt最早由奇趣科技开发;
- 1996年 进入商业领域,它也是目前流行的Linux桌面环境KDE的基础;
- 2008年 奇趣科技被诺基亚公司收购,Qt称为诺基亚旗下的编程语言;
- 2012年 Qt又被Digia公司收购;
- 2014年 4月 跨平台的集成开发环境Qt Creator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt实现了对iOS、Android、WP等各平台的全面支持。
1.3 支持的平台
- Windows – XP、Vista、Win7、Win8、Win2008、Win10等
- Uinux/X11 – Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/OS、和其他很多X11平台
- Macintosh – Mac OS X
- Embedded – 有帧缓冲支持的嵌入式Linux平台,Windows CE
1.4 Qt 的版本
Qt按照不同的版本发行,分为商业版和开源版:
- 商业版:为商业软件提供开发,他们提供传统商业软件发行版,并且提供在商业有效期内的免费升级和技术支持服务。
- 开源的LGPL版本:为了开发自有而设计的开放源码软件,它提供了和商业版本同样的功能,在GNU通用公共许可下,它是免费的。
1.5 Qt 的优点
- 跨平台,几乎支持所有的平台;
- 接口简单,容易上手,学习QT框架对学习其他框架有参考意义;
- 一定程度上简化了内存回收机制;
- 开发效率高,能够快速的构建应用程序;
- 有很好的社区氛围,市场份额在缓慢上升;
- 可以进行嵌入式开发。
1.6 成功案例
- Linux桌面环境KDE
- WPS Office 办公软件
- Skype 网络电话
- Google Earth 谷歌地图
- VLC多媒体播放器
- VirtualBox虚拟机软件
- …
2. 创建 Qt 项目
2.1 使用向导创建
打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项
弹出New Project对话框,选择Qt Widgets Application
选择【Choose】按钮
设置项目名称和路径,按照向导进行下一步
选择编译套件
向导会默认添加一个继承自CMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),这里我们可以先创建一个不带UI的界面,继续下一步
系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。
2.2 手动创建
添加一个空项目
选择【choose】进行下一步。设置项目名称和路径 —> 选择编译套件 --> 修改类信息 --> 完成(步骤同上),生成一个空项目。在空项目中添加文件:在项目名称上单击鼠标右键弹出右键菜单,选择【添加新文件】
弹出新建文件对话框
在此对话框中选择要添加的类或者文件,根据向导完成文件的添加。
2.3 .pro文件
在使用Qt向导生成的应用程序.pro文件格式如下:
QT += core gui //包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于Qt4版本 才包含widget模块
TARGET = QtFirst //应用程序名 生成的.exe程序名称
TEMPLATE = app //模板类型 应用程序模板
SOURCES += main.cpp\ //源文件
mywidget.cpp
HEADERS += mywidget.h //头文件
.pro 就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。.pro 文件的写法如下:
- 注释:从“#”开始,到这一行结束。
- 模板变量告诉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
- #工程中包含的源文件: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的特性
在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。
2.4 一个最简单的 Qt 应用程序
main入口函数中
#include "widget.h"
#include <QApplication> //包含一个应用程序类的头文件
int main(int argc, char *argv[]) //argc:命令行变量的数量,argv:命令行变量的数组
{
QApplication a(argc, argv); //a:应用程序对象,在Qt中,应用程序对象有且仅有一个
Widget w; //窗口对象 myWidget父类->QWidget
w.show(); //窗口对象 默认不会显示,必须调用show方法显示窗口
return a.exec();
//让应用程序对象进入消息循环,也就是让代码阻塞到这行,类似于system("pause")
}
解释:
- Qt系统提供的标准类名声明头文件没有.h后缀
- Qt一个类对应一个头文件,类名就是头文件名
- QApplication应用程序类
- 管理图形用户界面应用程序的控制流和主要设置。
- 是Qt的整个后台管理的命脉它 包含主事件循环,在其中来自窗口系统和其它资源的 所有事件处理和调度。它也处理 应用程序的初始化和结束,并且 提供对话管理。
- 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口。
- a.exec()
程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
2.5 Qt 命名规范和常用快捷键
- 命名规范:
- 类名:首字母大写,单词和单词之间首字母大写。
- 函数名和变量名称:首字母小写,单词和单词之间首字母大写。
- 快捷键:
- 注释 ctrl + /
- 运行 ctrl + r
- 编译 ctrl + b
- 查找 ctrl + f
- 帮助文档 F1
- 自动对齐 ctrl + i
- 同名的.h和.cpp切换 F4
3. 第一个 Qt 小程序
3.1 按钮的创建
在Qt程序中,最常用的控件之一就是按钮了,首先我们来看下如何创建一个按钮:
#include "mywidget.h"
#include <QPushButton> //按钮控件的头文件
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
{
//创建第一个按钮
QPushButton * btn = new QPushButton;
//btn->show();//show以顶层方式弹出窗口控件,也就是单独显示一个窗口
btn->setParent(this);//设置父亲,让btn对象依赖在myWidget窗口中
btn->setText("第一个按钮"); //显示文本
//创建第二个按钮 按照控件的大小创建窗口
QPushButton * btn2 = new QPushButton("第二个按钮",this);
btn2->move(100,100); //移动btn2按钮
btn2->resize(100,50); //重新指定按钮的大小
this->resize(600,400); //重置窗口大小,用户可以拖拽修改窗口大小
//设置固定窗口大小,用户不能拖拽修改窗口大小
this->setFixedSize(600,400);
//设置窗口标题,在这里可以像上面一样加this,也可以不加
setWindowTitle("第一个窗口");
}
在上面的代码中,一个按钮其实就是一个QPushButton类下的对象,如果只是创建出对象,是无法显示到窗口中的,所以我们需要依赖一个父窗口,也就是指定一个父亲利用setParent函数即可,如果想设置按钮上显示的文字利用setText,移动按钮位置用move。
对于窗口而言,我们可以修改左上角窗口的标题setWindowTitle,重新指定窗口大小:resize,或者设置固定的窗口大小setFixedSize。
按钮常用API总结:
- show() // 以顶层方式弹出窗口控件
- setParent() // 选择依赖方式
- setText() // 设置文本
- resize() // 重置窗口大小
- move() // 移动
- setWindowTitle() // 设置窗口标题
- setFixedSize() // 设置固定窗口大小
3.2 对象模型(对象树)
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个 parent 到底是干什么的。
-
QObject是以对象树的形式组织起来的。
- 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。 - 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
- 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
-
QWidget是能够在屏幕上显示的一切组件的父类。
- QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件**。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
- 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
- 当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
- 任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下面的代码片段:
{
QWidget window;
QPushButton quit("Quit", &window);
}
作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
但是,如果我们使用下面的代码:
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
3.3 Qt 窗口坐标体系
坐标体系:
以左上角为原点(0,0),X向右增加,Y向下增加。
对于嵌套窗口,其坐标是相对于父窗口来说的。
4. 信号和槽机制
信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
4.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了,我们看下面的代码:
QPushButton * quitBtn = new QPushButton("关闭窗口", this);
connect(quitBtn, &QPushButton::clicked, this, &MyWidget::close);
//参数1:信号的发送者 参数2:发送的信号(函数地址)
//参数3:信号的接收者 参数4;处理信号的槽函数(函数地址)
第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式。
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
参数解释:
- sender:发出信号的对象
- signal:发送对象发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
那么系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个:
void clicked(bool checked = false) //点击
void pressed() //按下
void released() //释放
void toggled(bool checked) //切换状态
这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。
信号槽的优点:松散耦合,信号发送端和接收端本身是没有关联的,通过connect连接,将两端耦合在一起。
4.2 自定义信号和槽
使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。
下面我们看看使用 Qt 的信号槽:
首先定义一个学生类和老师类:
老师类中声明信号 饿了 hungry
signals:
void hungury();
学生类中声明槽 请客 treat
public slots:
void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void MyWidget::ClassIsOver()
{
//发送信号
emit teacher->hungury();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
void Student::eat()
{
qDebug() << "该吃饭了!";
}
在窗口中连接信号槽
teacher = new Teacher(this);
student = new Student(this);
connect(teacher,&Teacher::hungury,student,&Student::treat);
并且调用下课函数,测试打印出 “该吃饭了”
自定义的信号 hungry带参数,需要提供重载的自定义信号和自定义槽
void hungury(QString name); 自定义信号
void treat(QString name ); 自定义槽
但是由于有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来指向函数地址, 然后在做连接
void (Teacher:: * teacherSingal)(QString) = &Teacher::hungury;
void (Student:: * studentSlot)(QString) = &Student::treat;
connect(teacher,teacherSingal,student,studentSlot);
自定义信号槽需要注意的事项:
- 发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外)
- 信号和槽函数返回值是 void
- 信号只需要声明,不需要实现
- 槽函数需要声明也需要实现
- 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响
- 使用 emit 在恰当的位置发送信号
- 使用connect()函数连接信号和槽
- 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致
- 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)
4.3 信号槽的拓展
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。 - 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。 - 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。 - 信号和槽函数的参数类型要一一对应
- 信号的参数个数可以多于槽函数的参数个数
- 槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。 - 信号槽可以断开
利用disconnect关键字是可以断开信号槽的。 - 使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。
4.4 Qt4 版本的信号槽写法
connect(zt, SIGNAL(hungry(QString)), st, SLOT(treat(QString)));
这里使用了 SIGNAL 和 SLOT 这两个宏,将两个函数名转换成了字符串。注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
Qt5在语法上完全兼容Qt4,而反之是不可以的。
4.5 Lambda表达式
C++11中的Lambda表达式用于 定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
[capture](parameters) mutable ->return-type
{
statement
}
[函数对象参数](操作符重载函数参数)mutable ->返回值{函数体}
- 函数对象参数
[]
,标识一个 Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
-
空。没有使用任何函数对象参数。
-
=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是 值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
-
&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是 引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
-
this。函数体内可以使用Lambda所在类中的成员变量。
-
a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
-
&a。将a按引用进行传递。
-
a, &b。将a按值进行传递,b按引用进行传递。
-
=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
-
&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
- 操作符重载函数参数
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
- 可修改标示符
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
QPushButton * myBtn = new QPushButton (this);
QPushButton * myBtn2 = new QPushButton (this);
myBtn2->move(100,100);
int m = 10;
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; });
connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
qDebug() << m;
- 函数返回值
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
- 是函数体
{},标识函数的实现,这部分不能省略,但函数体可以为空。
Lambda表达式最常用 [=](){}
4.6 信号和槽的总结
- 信号
- 普通函数,不需要实现(使用 signals 关键信号和槽的总结字声明),只有函数声明
- 可以自定义信号
- 没有返回值 - void 类型函数
- 可以带参数 - void sig1(int, double, QString)
- 可以使用 emit 关键字发送信号
- 槽
- 可自定义槽函数
- 类的任意成员函数
- 静态函数
- 全局函数
- Lambda表达式
- 槽函数没有返回值- void 类型
- 可以带参数 - void slot1(int, double, QString)
- 连接
- Qt4
- 通过宏:SIGNAL SLOT
- SIGNAL() / SLOT() 两个宏 将函数体转->字符串
- 缺点:编译时不会做错误检查
- 优点:参数直观
connect
(
sender,
SIGNAL(sig1(int, double, QString)),
receiver,
SLOT(slot1(int, double, QString))
);
- Qt5
- 信号槽使用的函数地址
- 优点:编译过程中会对参数类型,个数做检查
connect
(
sender, //信号发出者
&Sender::sig1, //信号
receiver, //信号接收者
&Receiver::slot1 //信号处理函数
);
- 注意事项
- 信号/槽函数参数个数是可以不一样的
- 槽函数参数个数可以少于信号的参数个数
- 不可逆
- 信号/槽函数参数必须一一对应
- 扩展
- 信号可以连接信号
- 一个信号可以连接多个槽函数 - 槽函数的执行顺序是随机的,不能控制
- 一个槽函数可以同时被多个信号连接
- 槽函数可以使用Lambda表达式
- 信号槽连接之后,可以被断开disconnect
5. QMainWindow
QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget),是许多应用程序的基础,如文本编辑器,图片编辑器等。
5.1 菜单栏
一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。
- 创建菜单栏,通过QMainWindow类的menubar()函数获取主窗口菜单栏指针
QMenuBar * menuBar() const
- 创建菜单,调用QMenu的成员函数addMenu来添加菜单
QAction* addMenu(QMenu * menu)
QMenu* addMenu(const QString & title)
QMenu* addMenu(const QIcon & icon, const QString & title)
- 创建菜单项,调用QMenu的成员函数addAction来添加菜单项
QAction* activeAction() const
QAction* addAction(const QString & text)
QAction* addAction(const QIcon & icon, const QString & text)
QAction* addAction(const QString & text, const QObject * receiver,
const char * member, const QKeySequence & shortcut = 0)
QAction* addAction(const QIcon & icon, const QString & text,
const QObject * receiver, const char * member,
const QKeySequence & shortcut = 0)
Qt 并没有专门的菜单项类,只是使用一个QAction类,抽象出公共的动作。当我们把QAction对象添加到菜单,就显示成一个菜单项,添加到工具栏,就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。
示例:
//创建菜单栏,菜单栏最多只能有一个
QMenuBar * bar = menuBar();
//将菜单栏放到窗口中
setMenuBar(bar);
//创建菜单
QMenu * fileMenu = bar->addMenu("文件");
QMenu * editMenu = bar->addMenu("编辑");
//创建菜单项
QAction * newAction = fileMenu->addAction("新建");
//添加分隔线
fileMenu->addSeparator();
QAction * openAction = fileMenu->addAction("打开");
5.2 工具栏
主窗口的工具栏上可以有多个工具条,通常采用一个菜单对应一个工具条的的方式,也可根据需要进行工具条的划分。
-
直接调用QMainWindow类的addToolBar()函数获取主窗口的工具条对象,每增加一个工具条都需要调用一次该函数。
-
插入属于工具条的动作,即在工具条上添加操作。
通过QToolBar类的addAction函数添加。 -
工具条是一个可移动的窗口,它的停靠区域由QToolBar的allowAreas决定,包括:
- Qt::LeftToolBarArea 停靠在左侧
- Qt::RightToolBarArea 停靠在右侧
- Qt::TopToolBarArea 停靠在顶部
- Qt::BottomToolBarArea 停靠在底部
- Qt::AllToolBarAreas 以上四个位置都可停靠
使用setAllowedAreas()函数指定停靠区域:
setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea)
使用setMoveable()函数设定工具栏的可移动性:
setMoveable(false)//工具条不可移动, 只能停靠在初始化的位置上
示例:
//创建工具栏,工具栏可以有多个
QToolBar * toolBar = new QToolBar(this);
addToolBar(Qt::LeftToolBarArea, toolBar);
//后期设置为只允许左右停靠
toolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
//设置浮动
toolBar->setFloatable(false);
//设置移动(总开关)
toolBar->setMovable(false);
//工具栏中添加内容
toolBar->addAction(newAction);
//添加分割线
toolBar->addSeparator();
toolBar->addAction(openAction);
//工具栏中添加控件
QPushButton * btn = new QPushButton("aaa", this);
toolBar->addWidget(btn);
5.3 状态栏
- 派生自QWidget类,使用方法与QWidget类似,QStatusBar类常用成员函数:
- 状态栏也只能最多有一个
//添加小部件
void addWidget(QWidget * widget, int stretch = 0)
//插入小部件
int insertWidget(int index, QWidget * widget, int stretch = 0)
//删除小部件
void removeWidget(QWidget * widget)
示例:
//创建状态栏,状态栏最多只有一个
QStatusBar * stBar = statusBar();
//将状态栏添加到窗口中
setStatusBar(stBar);
//添加标签控件
QLabel * label = new QLabel("左侧提示信息", this);
stBar->addWidget(label);
QLabel * label2 = new QLabel("右侧提示信息", this);
stBar->addPermanentWidget(label2);
5.4 铆接部件
铆接部件 QDockWidget,也称浮动窗口,可以有多个。
//添加铆接部件(浮动窗口),可以有多个
QDockWidget * dock = new QDockWidget("标题",this);
addDockWidget(Qt::LeftDockWidgetArea,dock);
//设置区域范围,只允许左右上停靠
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea);
5.5 核心部件(中心部件)
除了以上几个部件,中心显示的部件都可以作为核心部件,例如一个记事本文件,可以利用QTextEdit做核心部件
QTextEdit * edit = new QTextEdit(this);
setCentralWidget(edit);
5.6 资源文件
Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。
使用 Qt Creator 可以很方便地创建资源文件。我们可以在工程上点右键,选择“添加新文件…”,可以在 Qt 分类下找到“Qt 资源文件”:
点击“选择…”按钮,打开“新建 Qt 资源文件”对话框。在这里我们输入资源文件的名字和路径:
点击下一步,选择所需要的版本控制系统,然后直接选择完成。我们可以在 Qt Creator 的左侧文件列表中看到“资源文件”一项,也就是我们新创建的资源文件:
右侧的编辑区有个“添加”,我们首先需要添加前缀,比如我们将前缀取名为 images。然后选中这个前缀,继续点击添加文件,可以找到我们所需添加的文件。
这里,我们选择 document-open.png 文件。当我们完成操作之后,Qt Creator 应该是这样子的:
接下来,我们还可以添加另外的前缀或者另外的文件。这取决于你的需要。当我们添加完成之后,我们可以像前面一章讲解的那样,通过使用 : 开头的路径来找到这个文件。比如,我们的前缀是 /images,文件是 document-open.png,那么就可以使用:/images/document-open.png找到这个文件。
这么做带来的一个问题是,如果以后我们要更改文件名,比如将 docuemnt-open.png 改成 docopen.png,那么,所有使用了这个名字的路径都需要修改。所以,更好的办法是,我们给这个文件去一个“别名”,以后就以这个别名来引用这个文件。具体做法是,选中这个文件,添加别名信息:
这样,我们可以直接使用:/images/doc-open引用到这个资源,无需关心图片的真实文件名。
如果我们使用文本编辑器打开 res.qrc 文件,就会看到一下内容:
<RCC>
<qresource prefix="/images">
<file alias="doc-open">document-open.png</file>
</qresource>
<qresource prefix="/images/fr" lang="fr">
<file alias="doc-open">document-open-fr.png</file>
</qresource>
</RCC>
我们可以对比一下,看看 Qt Creator 帮我们生成的是怎样的 qrc 文件。当我们编译工程之后,我们可以在构建目录中找到 qrc_res.cpp 文件,这就是 Qt 将我们的资源编译成了 C++ 代码。