Qt教程-Hello World

这个系列的教程将直接基于Qt 5.8进行开发,目标是最终掌握使用Qt开发GUI和Qt各种常用模块的使用。
作为Qt的教程尝试第一个开发的 Qt 项目那必然是Hello World。
在正确的安装Qt和Qt Creator后,我们打开Qt Creator,新建一个projec(工程项目)。
如下图所示:
选择新建菜单。

传统的桌面GUI程序我们选择Qt Widgets Application就可以了。

输入项目名hello_world.

选择编译套件。

这里我们选择不创建UI。

最后完成创建。

创建完成后我们从项目中删除默认创建的其他文件,只留下main.cpp。

之后我们修改main.cpp的代码为

随后我们可以通过构建菜单进行构建,运行。
编译运行后可观察到运行结果。如下图所示:

这个程序很短,我们说明一下。
对于GUI程序,引入QApplication是必不可少的,QApplication这个对象为GUI引入了事件循环等一系列的东西。所以通常在main函数都要先创建一个QApplication对象(对于非GUI程序是可能是创建QCoreApplication)。之后我们定义了一个QLabel对象,QLabel对象是标签对象,通常用来显示一些内容。紧接着我们使用了resize成员对label对象的大小进行了更改,这里我们更改成长300,宽100,单位是像素。随后我们使用show函数来显示界面。最后我们调用exec启动事件循环,这样我们就能看到图上的运行结果了。事件循环会一直保持运行,exec函数也不会返回,会一直等待到窗口关闭然后进行返回,所以当窗口关闭时程序将正常退出。

Qt教程-Qt Creator中定制GUI(添加图标,菜单,工具栏,状态栏)

这里我们使用上一篇的MainWindow工程作为例子。
首先进入UI设计界面,我们新建一个help菜单,然后在help菜单下新建一个about菜单。
如下图所示:

接着我们想为about菜单添加一个工具栏按钮。工具栏按钮一般是以图标的形式存在,想在界面中添加图标,我们需要先新建一个资源文件。选择Qt Creator中的新建按钮,新建一个Qt Resource File 如下图所示:

这里我们新建一个名为res的资源文件。

最后选择项目控制系统,点击完成。

创建完毕资源文件后,在插入图标之前我们需要先添加一个资源前缀。

添加好前缀后我们可以在资源前缀下添加具体的资源了(比如图片资源)。这里直接选择一个前缀然后点击添加文件即可。

这里我们添加了一个/image的前缀,并且向这个前缀里添加了一个icon1.png,最后还给icon1.png这个文件取了个别名叫about。
保存后返回UI设计界面,选择about的Action即可添加图标。

到这里我们已经成功的添加了菜单,并且给about菜单前面加上了一个图标。
最后如果我们想把about菜单放到工具栏上的花,把about的Action拖拽到工具栏上即可。

之后我们可以编译然后运行察看效果。
运行效果如下图所示:

可能有朋友会看到.ui文件实际上是记录界面和控件属性的XML文件,XML很显然是不能经过C++编译器直接编译成二进制文件的,那么我们设计的界面Qt是怎么处理变成程序界面的呢?如果有心看一下编译使用的临时目录那么很容易就能发现答案。前面我们提到了moc这个东西,在这里也是一样,由moc负责把XML文件转换成C++编译器可编译的标准C++代码。
这里我直接贴出moc针对前面的ui文件生产的代码。

当界面创建时会自动调用 setupUi这个函数。我们可以看到函数先创建了一个名为actionabout的QAction对象,顾名思义,QAction这个类代表了窗口的一个“动作”,也就是代表一个要完成的操作,QAction包含了图标、菜单文字、快捷键、状态栏文字、浮动帮助等信息,当QAction被添加到菜单时,就是作为一个菜单的选项出现的,当QAction被添加到工具栏时就是作为一个工具栏的按钮出现。
我们还可以看到生成的代码中对每个对象都执行了setObjectName,同样顾名思义,是为Object对象设置了一个名字。而QStringLiteral则是一个宏,作用是从“字符串常量”创建QString对象,而QString对象我们可以简单的认为和std::string是差不多的,基本上我们可以认为QStringLiteral(“text”)大致上等于std::string(“text”)。
接着创建了一个QIcon对象,通过addFile加载icon。addFile的第一个参数是icon的路径其中以 : 开始,意味着从资源文件中查找资源(资源名可以使用文件名也可以使用别名),最后setIcon给QAction,这样我们的动作就有了图标。设置显示的文字和状态栏的提示是通过retranslateUi的setText来完成的。QApplication::translate函数是用来处理界面国际化问题的,作用是根据设置自动替换文本翻译,这样就可以做到中文环境显示中文,英文环境显示英文了。
我们继续看代码,代码中还创建了QMenuBar,QToolBar,QStatusBar这么几个对象,这几个对象分别是菜单栏,工具栏,状态栏。最后通过对应的set函数显示在界面上。
最后是QMenu,QMenu对象是一级菜单,创建了菜单之后通过addAction添加到菜单栏,之后再把之前创建的QAction添加到一级菜单,由此形成了我们看到的一级一级的菜单。

Qt教程-使用Qt Creator创建GUI程序

安装Qt Creator后,打开Qt Creator我们可以从文件菜单创建一个新项目。传统的桌面GUI程序我们选择Qt Widgets Application就可以了。

这里创建一个项目名为MainWindow的项目。

选择编译套件,笔者使用的是Linux,所以这里使用的是GCC。

单击下一步后这里我们可以选择各种界面模板生成默认代码,我们这里选择基类为QMainWindow,并且直接创建UI文件。

最后我们只需要选择一个版本控制系统,我们可以选择Git这样的版本控制系统方便后续管理项目,不过这里我们暂时选择不需要版本控制。

点击完成之后我们能看到这样的画面。

我们双击mainwindow.ui就可以编辑界面进行可视化开发。这里我们不做任何改动直接运行程序,结果如下图所示:

程序运行后的窗口上其实我们可以看到这么几个部分(虽然并不明显,但是确实存在)。

MenuBar就是我们通常在软件中看到的菜单栏,Qt中使用QMenuBar这个类来操作。
ToolBars是工具栏,Qt中使用QToolBar来操作。
DockWidget也就是我们熟悉的工具面板或者是工具窗口,DockWidget可以在几个区域中移动,或者是悬浮。
StatusBar是状态栏Qt中使用QStatusBar来操作。
如果要实现简单的界面,那么只要方便的拖控件就可以实现了。

Qt教程-自定义信号槽和信号槽的实现

我们说Qt不仅是一个GUI的库,可以说Qt是一套框架,除了提供GUI的功能Qt还提供了一系列的东西。我们之前提到的信号槽,也不仅仅只能在GUI程序中使用,信号槽是作为一种核心特性提供的,当然我们也可以注册自己的信号和槽函数。
有关语法我们可以直接看到custom_signal下面的custom_signal.hpp。

这里我们定义了2个类,一个是CCustom_Signal其中包含了名叫 clicked的信号,叫send的信号发送函数另一个是CCustom_Slot其中定义了一个叫recv的槽函数。
由于具体的介绍参见代码注释。
我们总结一下自定义信号和槽的步骤:
1、定义信号必须在一个类中,并且这个类必须是QObject的子类(也就是必须继承QObject)。
2、类中定义信号之前,必须包含Q_OBJECT宏。
3、声明信号函数之前必须使用signals标签。
4、定义信号时我们只需要声明信号函数,我们不要自己定义信号函数。
我们不难发现一个问题,Qt当中这些特有的关键字为什么能最终通过gcc这样的编译器进行编译,有些语法明明不符合C++的标准。当我们使用Qt进行开发时源代码交给标准 C++ 编译器(如 gcc )之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc(Meta-Object Compiler,也就是“元对象编译器”)。moc负责把这些扩展语法转换成标准的C++语法,这个过程就像预编译处理。在笔者的电脑上Qt的安装路径是/opt/Qt5,moc最后的路径是/opt/Qt5/5.8/gcc_64/bin/moc,在调用gcc编译之前我们可以看到会先调用moc进行预编译处理。
使用Qt后在程序编译上的流程就和普通的C++程序有些区别了,我们这里简单的梳理一下。
当我们在Qt Creator上按下编译按钮后会有下面的几个步骤
1、执行qmake。qmake会根据项目配置生成makefile。这样我们在执行make的时候就可以自动进行moc等操作。
2、执行moc转换代码为标准的C++代码。
3、由C++编译器进行编译。
4、由C++链接器进行链接。
当然,这里我们自定义了一个信号和槽一共两个类,那么要如何使用这两个类呢?这里我们也做一个对应的演示。

要使用在头文件中定义的类,那么首先我们理所当然的要include对应的文件。
这里我定义了一个csig类并把csig对象中的clicked对象和cslot这个对象中的recv函数连接了起来。
recv函数最后会调用传入的QPushButton对象的clicked函数,其实也就是向这个QPushButton对象发出clicked信号。在Qt中emit实际上是一个宏,但是这个宏是空的,没有做任何操作。emit这个关键字只是用来明确标识是在发送信号。
最后我们看到我们使用了csig的send函数,并传入了button对象的引用。
也就是说这里实际上是向button对象发送了点击信号,所以当exec执行后就会弹出2个对话框。
如下图所示:

工程源代码下载地址:
https://www.exvs.org/wp-content/uploads/public/Qt_Tutorial/custom_signal_6F457F93.7z

Qt教程-信号槽

要使用Qt就不得不提到信号槽,信号槽(signal 和 slot)是QT的核心机制之一。
我们都知道界面是通过用户给出的操作进行反馈的。比如Windows系统上的一个窗口上存在一个按钮,当用户在这个按钮上按下鼠标左键后,Windows会向这个窗口发送一条WM_LBUTTONDOWN消息,当用户抬起鼠标左键时Windows会向窗口发送一条WM_LBUTTONUP消息,程序通过一个线程循环处理这些窗口上发生的消息作出响应,最终形成的效果可能是,用户单击了一个按钮,程序弹出了一个提示信息。
通过循环响应各种消息程序就能作出各种各样的反映。在传统的开发模式中开发者需要自己创建消息循环,处理各种消息,然后再调用对应的处理函数。不得不说这样实在很麻烦。
信号槽这个机制本身有点像处理消息队列,有所不同的是开发者无需处理自己不关心的事件。当一个事件发生的时候(比如:鼠标点击事件)会产生一个信号,Qt会寻找和这个信号关联在一起的函数,并调用这个函数,通过这样的方式完成响应。在这样的流程下开发者要做的事情就很简单了,把响应操作的函数和信号关联起来就行了。和传统的处理消息队列相比,不用自行维护一个消息处理循环,不用考虑线程阻塞等一些问题,只要专注与自己想处理的信号就可以了,这样无疑方便了开发者。
信号槽的使用非常的简单,使用connect函数连接信号和处理函数就可以了。连接之后一旦有信号发生则会自动调用处理函数。
我们可以看到例子hello_world.cpp

编译运行后运行能看到一个按钮,按下按钮后会产生如下图的效果。

QApplication类我们前面介绍过用来管理GUI程序的控制流和主设置。它包含主事件循环,用来处理窗口的各种事件。
QLabel是一个标签控件,标签控件通常用于显示各种信息。
QPushButton则是按钮控件,界面上的各种按钮由此控件提供。
QObject::connect是信号槽的连接函数。
我们可以看到,代码中定义了2个QLabel控件,分别是label和label2,分别使用Hello, world和Hello, world2这两个字符串进行初始化。随后我们还定义了一个QPushButton控件,QPushButton是按钮控件用来提供界面上的各种按钮。我们使用Show Hello初始化QPushButton类,最终的效果就是按钮上显示的文字为Show Hello。
随后我们调用了2次connect函数,QObject::connect函数的作用是吧信号和槽关联起来。
connect有下面几种重载

我们简化一下,常用的大约有这样的两种形式。

我们这里的调用同样也符合这两种形式,sender我们都为button对象,也就是按钮。signal在本例中都是QPushButton::clicked,QPushButton::clicked这个信号在被鼠标左键点击的时候会发出。本例中第一次调用connect符合connect(sender,signal,function)这种形式,Lambda表达式可以当成是一个没有名字的函数。第二次的调用符合connect(sender,signal,receiver,slot)这种形式,发出的信号由label对象作为接收者,最后调用show这个函数。
一个信号可以连接多个槽函数,所以最终会看到启动了2个新窗口。前面的例子中演示了connect的两种用法。SIGNAL和SLOT宏作用是把函数转换成字符串。在QT5之前这样是标准做法,但是编译器无法检查字符串的合法性,容易出错,故新版本中推荐直接connect而尽可能不要使用SIGNAL和SLOT宏。