在创建项目时,使用的基类Base Class为QWidget
1. 使用图形化界面的方式实现"Hello World"
- 双击文件:
widget.ui,进入designer模式:

-
在"控件盒子"的"Display Widgets"中找到"Label",并拖放到白板中

-
双击刚刚拖放到
Label,输入"Hello World"
-
最后直接运行即可:

此时我们回过头来看widget.ui和编译生成的ui_widget.h文件,都发生了变化:
cpp
// widget.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>230</x>
<y>110</y>
<width>81</width>
<height>51</height>
</rect>
</property>
<property name="text">
<string>"Hello World"</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
// ui_widget.h
/********************************************************************************
** Form generated from reading UI file 'widget.ui'
**
** Created by: Qt User Interface Compiler version 6.7.3
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_WIDGET_H
#define UI_WIDGET_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_Widget
{
public:
QLabel *label;
void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName("Widget");
Widget->resize(800, 600);
label = new QLabel(Widget);
label->setObjectName("label");
label->setGeometry(QRect(230, 110, 81, 51));
retranslateUi(Widget);
QMetaObject::connectSlotsByName(Widget);
} // setupUi
void retranslateUi(QWidget *Widget)
{
Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));
label->setText(QCoreApplication::translate("Widget", "\"Hello World\"", nullptr));
} // retranslateUi
};
namespace Ui {
class Widget: public Ui_Widget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WIDGET_H
2. 用代码的方式实现"Hello World"
一般来说,在实现图形化界面时,一般在继承类的构造函数中进行编写,如这里的widget.h
实现如下:
cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLabel* label = new QLabel(this);
label->setText("\"Hello World\"");
}
Widget::~Widget()
{
delete ui;
}
-
Qt中每个类都有其同名的头文件,要使用类
QLabel,就需要包含头文件<QLabel> -
在定义
QLabel对象时,一般建议在堆上创建,同时建议将这个对象挂到对象树上,其父节点就是this -
方法
label->setText的形参实际上是类型QString- Qt的诞生时间比C++标准形成的时间要早,因此,对于一些常见的数据结构,Qt自己造了一些轮子
- 例如:
QString、QVector、QList、QMap等 - C++标准库中的容器也可以很方便的和Qt中的容器类相互转换
最后运行结果如图:

内存泄露问题
可以注意到,为了使用QLable对象,我们只用了new语句:QLabel* label = new QLabel(this);
我们在后面没有delete掉,不就内存泄露了吗?
这里可以告诉大家结论:
- 上述代码在Qt中,不会产生内存泄露, 及在合适的时候 **,**
label会被自动释放 - 而原因就是,
label 对象被挂到对象树上了
对象树
引入对象树的主要目的,就是将诸如QLable这样的空间进行统一管理,这样就可以统一在合适的时候进行释放
- 所谓合适的时候,就是图形界面窗口关闭/销毁的时候
- 而如果不适用对象树,就可能导致一些资源的提前释放,具体到图形化界面中就可能导致一些控件元素不会正常显示
- 在上面的代码中,将
QLabel对象开辟在堆空间上,就是为了将其生命周期交给对象树进行管理
而如果开辟在栈上,就可能引发问题:
cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// QLabel* label = new QLabel(this);
// label->setText("\"Hello World\"");
QLabel label(this);
label.setText("\"Hello World\"");
}
Widget::~Widget()
{
delete ui;
}
运行:

发现预期的"Hello World"不见了
为了验证如果一个对象被挂到对象树上,会被自动清理,我么可以新建一个类myQLabel,其继承于QLabel
- 新建文件

-
选择C++中的
C++ class -
填写自定义类的信息

代码如下:
cpp
// myQLabel.h
#ifndef MYQLABEL_H
#define MYQLABEL_H
#include <QLabel>
class myQLabel : public QLabel
{
public:
myQLabel(QWidget* parent);
~myQLabel();
};
#endif // MYQLABEL_H
// myQlabel.cpp
#include "myqlabel.h"
#include <iostream>
myQLabel::myQLabel(QWidget* parent): QLabel(parent) {}
myQLabel::~myQLabel()
{
std::cout << "MyQLabel 被释放" << std::endl;
}
// widget.cpp
#include "widget.h"
#include "./ui_widget.h"
// #include <QLabel>
#include "myqlabel.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myQLabel* label = new myQLabel(this);
label->setText("\"Hello World\"");
}
Widget::~Widget()
{
delete ui;
}
可以看到,在关闭图形化窗口时,会输出:

乱码问题
从上面的输出我们可以看到,本来应该被打印的汉字"被释放",输出竟然是乱码,这是为什么?
首先要说明结论:乱码之所以出现,其最主要的原因就是编码方式不一致
- 源代码文件的编码格式
- 终端(控制台)的显示编码格式
- 字符串本身的编码处理方式
接下来,我们同样用该清楚,一个汉字到底占几个字节?
- 对于这个问题,我们首先要区分汉字是采用哪种编码方式进行编码的------GBK,UTF-8
- 如果采用GBK方式编码,那一个汉字就占2个字节
- 如果采用UTF-8方式编码,那一个汉字就占2~4个字节,一般为3字节
我们先来查看输出汉字的文件myqlable.cpp是采用哪种方式进行编码:

- 可以看到,编码方式为UTF-8
- 而如果显示为
ANSI,那么编码方式就是GBK
既然myqlabel.cpp的编码方式为utf8,但输出到控制台却出现乱码,说明终端控制台的编码方式就不是UTF-8
qDebug
为了解决这一问题,我们不是用C++标准的标准输出std::cout,而采用Qt提供的QDebug类:
cpp
#include "myqlabel.h"
#include <QDebug>
myQLabel::myQLabel(QWidget* parent): QLabel(parent) {}
myQLabel::~myQLabel()
{
qDebug() << "MyQLabel 被释放";
}
QDebug这个类重载了移位运算符<<,不直接使用类QDebug;而qdebug是一个宏,封装了QDebug对象,我们可以像使用std::cout一样来使用它- 使用
qDebug()还有另一个好处,我们可以使用开关来对日志输出进行关闭, 防止日志信息对用户的干扰
3. 使用输入框实现 "Hello World"
3.1 使用图形化界面的方式
-
双击文件
widget.ui,进入designer模式后找到控件:Line Edit
-
将其拖放到白板中,双击控件,输入
"Hello World"
-
保存并运行即可

3.2 使用代码的方式
和QLabel的操作方式一样,将widget.cpp文件修改如下:
cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QLineEdit>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLineEdit* line_edit = new QLineEdit(this);
line_edit->setText("\"Hello World\"");
}
Widget::~Widget()
{
delete ui;
}
运行:

4. 使用按钮的方式实现"Hello World"
4.1 使用图形化界面的方式
- 双击文件
widget.ui,进入designer模式后找到控件:Push Button

-
将其拖放到白板中,双击控件,输入
"Hello World"
同时可以看到,在
QObject中出现了objectName和对应的值:-
在
Qt Designer创建一个控件的时候,此时会给这个控件分配一个objectName属性,这个属性的值要求在界面中是唯一的 。我么也可以自己指定这个值 -
当
CMake在预处理.ui文件的时候,就会根据objectName来生成同名的类。例如上面的控件pushButton被生成的类名就是myPushButton -
我们可以在生成的
ui_widget.h中看到这样的代码:
-
-
进入编辑界面,修改
widget.cpp如下:
cpp
// 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();
void clickHandler();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->myPushButton, &QPushButton::clicked, this, &Widget::clickHandler);
}
Widget::~Widget()
{
delete ui;
}
// 当控件内容为"Hello World",点击按钮后,就会变为"Hello Qt"
// 当空间内容为"Hello Qt",点击按钮后,就会变为"Hello World"
void Widget::clickHandler()
{
if (ui->myPushButton->text() == "\"Hello World\"") {
ui->myPushButton->setText("\"Hello Qt\"");
} else {
ui->myPushButton->setText("\"Hello World\"");
}
}
上述出现的connect是类QObject提供的静态函数,其功能是连接"信号"和"槽",其4个参数的作用分别为:


4.2 使用代码的方式
代码如下:
cpp
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void clickHandler();
private:
Ui::Widget *ui;
QPushButton* myPushButton_ = nullptr;
};
#endif // WIDGET_H
// widget.cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, myPushButton_(new QPushButton(this))
{
ui->setupUi(this);
myPushButton_->setText("\"Hello World\"");
connect(myPushButton_, &QPushButton::clicked, this, &Widget::clickHandler);
}
Widget::~Widget()
{
delete ui;
}
void Widget::clickHandler()
{
if (myPushButton_->text() == "\"Hello World\"") {
myPushButton_->setText("\"Hello Qt\"");
} else {
myPushButton_->setText("\"Hello World\"");
}
}
5. 图形化方式开发和代码方式开发
实际开发中,图形化方式开发界面和以纯代码方式代码界面都是很常用的方法
- 如果界面内容是比较固定的,此时就会使用图形化界面来构造
- 如果界面内容经常要动态变化,就需要以代码的方式进行开发
- 此外,这两种方式也可以配合使用
6. Qt 坐标系
Qt的坐标系是 平面直角坐标系,其X轴从左往右增长,Y轴从上往下增长

坐标系的原点就是屏幕的左上角/窗口的左上角
给Qt的控件指定位置,就需要设置坐标,对于这个控件来说,其原点是相对于父窗口/父控件的

而如果要指定控件或窗口的位置,就需要用到对应的move成员函数:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, myPushButton_(new QPushButton(this))
{
ui->setupUi(this);
myPushButton_->setText("\"Hello World\"");
myPushButton_->move(200, 300);
this->move(100, 200);
}
- move的第一个参数为水平移动距离,单位为像素px
- move的第二个参数为竖直移动距离,单位为像素px
运行如图:
