Qt开发 入门
Qt Hello World程序
使用"按钮”实现
纯代码方式实现
(1)创建工程
剩下的直接下一步即可
(2)编写代码,widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton *btn = new QPushButton; //QPushButton是Qt提供的一个标准按钮控件类。
btn->setText("Hello World"); //设置按钮上显示的文本为"Hello World"。
btn->setParent(this); //将按钮的父对象设置为当前的Widget实例,意味着按钮将出现在这个Widget窗口中
//也可以直接在在创建的时候传递this QPushButton *btn = new QPushButton(this);
}
Widget::~Widget()
{
delete ui;
}
可视化操作实现
(1) 双击:”widget.ui"文件;
(2)拖拽控件至 ui 界面窗口并修改内容:
(3)构建并运行,效果如下所示:
Qt Designer 右上角,通过树形结构,显示出了当前界面上都有哪些控件
刚才往界面上拖拽了一个 QPushButton 控件。此时,ui 文件的 xml 中就会多出来这一段代码~~
进一步的 qmake 就会在编译项目的时候,基于这个内容生成一段 C++ 代码,通过这个 C++ 代码构建出界面内容了
我们可以通过查看源文件查看生成的代码,源文件ui_weight.h
#ifndef UI_WIDGET_H
#define UI_WIDGET_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_Widget
{
public:
QPushButton *pushButton;
//这里就是具体的自动生成的代码
void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName("Widget");
Widget->resize(800, 600);
pushButton = new QPushButton(Widget);
pushButton->setObjectName("pushButton");
pushButton->setGeometry(QRect(260, 140, 121, 51));
retranslateUi(Widget);
QMetaObject::connectSlotsByName(Widget);
} // setupUi
void retranslateUi(QWidget *Widget)
{
Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));
pushButton->setText(QCoreApplication::translate("Widget", "Hello World", nullptr));
} // retranslateUi
};
namespace Ui {
class Widget: public Ui_Widget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WIDGET_H
使用“标签”实现
纯代码方式实现
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
#include <QFont>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLabel *lab = new QLabel(this);
lab->setText("Hello World"); //设置标签内容
setFixedSize(1000,900); //设置窗口大小
//设置字体
QFont font("宋体",64);
lab->setFont(font);
//设置标签内容显示的位置
lab->move(300,300);
//设置标签字体颜色
lab->setStyleSheet("color:blue");
}
Widget::~Widget()
{
delete ui;
}
可视化操作实现
(1) 双击:"widget.ui"文件;
(2)拖拽“标签”至 UI 设计界面中,并双击修改标签内容
项目文件解析
.pro 文件解析
工程新建好之后,在工程目录列表中有一个后缀为“pro"的文件,"pro"文件就是工程文件(project)。它是 qmake 自动生成的用于生产 makefile 的配置文件。
//test.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
“.pro” 文件的写法如下:
- 注释:从“#”开始,到这一行结束
- QT += core gui // Qt 包含的模块
- greaterThan(QT_MAJOR_VERSION,4):QT += widgets 这条语句的含义是,如果 QT_MAJOR_VERSION 大于 4 也就是当前使用的 Qt5及更高版本) 需要增加 widgets 模块。如果项目仅需支持 Ot5,也可以直接添加"QT += widgets"一句。不过为了保持代码兼容,最好还是按照 QtCreator 生成的语句编写。
- 指定生成的应用程序名:TARGET=QtDemo
- TEMPLATE = app //模板。告诉 qmake 为这个立用程序生成哪种 makefile。下面是可供选择的模板:
- app:建立一个应用程序的 makefile。这是默认值,所以如果模板没有被指定,这个将被使用。
- lib:建立一个库的 makefile。
- vcapp:建立一个应用程序的 VisualStudio 项目文件。
- vclib: 建立一个库的 VisualStudio 项目文件。
- subdirs:这是一个特殊的模板,它可以创一个能够进入特定目录的 makefile 并且为它调用 make 的 makefile。
- 工程中包含的源文件:SOURCES += main.cpp/widget.cpp
- 工程中包含的头文件:HEADERS += widget.h
- 工程中包含的资源文件:RESOURCES += painter.qrc
- 工程中包含的“ui"设计文件:FORMS += widget.ui
- 配置信息:CONFIG += c++17(使用c++17的特性) CONFIG 用来告诉 qmake 关于应用程序的配置信息。
widget.h 文件解析
在 Qt 中,如果要使用信号与槽 (signal和slot) 的机制就必须加入Q_OBJECT宏;
Ui::Widget*ui; 这个指针是用前面声明的namespace Ui里的 Widget 类定义的,所以指针 ui 是指向可视化设计的界面,后面要访问界面上的组件,都需要通过这个指针 ui 去访问。
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT //该宏允许类中使用信号和槽机制
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui; //通过该指针可以访问ui设计界面中的任意控件
};
#endif // WIDGET_H
main.cpp 文件解析
使用 Qt Creator 新建任意工程之后,main.cpp 文件中都会自动生成如下代码:
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
解释:
- Ot 系统提供的标准类名声明头文件没有 h 后缀;
- Qt 一个类对应一个头文件,类名就是头文件名;
- QApplication 为应用程序类;QApplication a;(a为应用程序对象,有且仅有一个。)
- QApplication 管理图形用户界面应用程序的控制流和主要设置
- QApplication 是 Qt 的整个后台管理的命脉。它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
- 对于任何一个使用 Qt 的图形用户界面应用程序,都正好存在一个 QApplication 对象,而不论这个应用程序在同一时间内是不是有 0、1、2 或更多个窗口。
- Widget w; //实例化窗口对象
- w.show(); //调用show函数显示窗口
- a.exec():程序进入消息循环,等待对用户输入进行响应。这里 main() 把控制权转交给 Qt,Qt 完成事件处理工作,当应用程序退出的时候 exec() 的值就会返回。在 exec() 中,Qt 接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
widget.cpp 文件解析
widget.cpp 文件是类 Widget 的实现代码,所有在窗体上要实现的功能添加在此文件中;
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
widget.ui 文件解析
widget.ui 是窗体界面定义文件,是一个 XML 文件,定义了窗口上的所有组件的属性设置、布局,及其信号与槽函数的关联等。用 UI 设计器可视化设计的界面都由 Qt 自动解析,并以XML文件的形式保存下来。在设计界面时,只需在 Ul 设计器里进行可视化设计即可,而不用管 widget.ui 文件是怎么生成的。
Qt 对象树
在 Qt 中创建很多对象的时候会提供一个 Parent 对象指针,下面来解释这个 parent 到底是干什么的。
QObject 是以对象树的形式组织起来的
当创建一个 QObject 对象时,会看到 QObject 的构造函数接收一个 QObject 指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建 QObject 对象时,可以提供一个其父对象,我们创建的这个 QObject 对象会自动添加到其父对象的 children() 列表。
当父对象析构的时候,这个列表中的所有对象也会被析构。 (注意,这里的父对象并不是继承意义上的父类!)
这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个 QShortcut (快捷键)对象作为其子对象。当删除按钮的时候,这个快捷键理应被删除。这是合理的。
QWidget 是能够在屏幕上显示的一切组件的父类
- QWidget 继承自 QObject ,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
- 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
Qt 引入对象树的概念,在一定程度上解决了内存问题
- 当一个 QObject 对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
- 任何对象树中的 QObject 对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的 children() 列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有 QObject 会被 delete 两次,这是由析构顺序决定的。
如果 QObject 在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下面的代码片段:
{
QWidget windows;
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 对象,并且大胆在堆上创建。
Qt 对象树如图:
Qt 窗口坐标体系
坐标体系:以左上角为原点 (0,0) ,X向右增加,Y向下增加。
对于嵌套窗口,其坐标是相对于父窗口来说的。
示例:使用 Qt 中的坐标系设置控件的位置;
运行结果如下图所示: