目录
- QT 概述
- 创建项目
- 项目文件(. pro)
- main.cpp
- mainwindow.ui
- mainwindow.h
- mainwindow.cpp
- 窗口类
- QWidget 窗口显示
- QDialog 窗口
- QPushButton
- 创建
- 显示
- 对象树
- 基本概念
- 功能
- 坐标体系
- 控件
- Item Widgets
- QListWidget
- QTreeWidget
- QTableWidget
- 自定义控件
QT 概述
模块 | 功能 |
---|---|
Qt Core | Qt 类库的核心,所有其他模块都依赖于此模块 |
Qt GUI | 设计 GUI 界面的基础类,包括 OpenGL |
Qt Widgets | 用于构建 GUI 界面的 C++ 图形组件类 |
创建一个项目都会自动添加上述模块
注意点
- QtCreator 创建的
项目名称
不能包含中文,不能包含空格,项目目录
也不能含中文 - QtCreator 默认使用
UTF-8
格式编码对文件字符进行编码
创建项目
创建完后的
项目文件(. pro)
# 在项目文件中, 注释需要使用 井号(#)
# 项目编译的时候需要加载哪些底层模块
QT += core gui
# 如果当前Qt版本大于4, 会添加一个额外的模块: widgets
# Qt 5中对gui模块进行了拆分, 将 widgets 独立出来了
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 使用c++11新特性
CONFIG += c++11
#如果在项目中调用了废弃的函数, 项目编译的时候会有警告的提示
DEFINES += QT_DEPRECATED_WARNINGS
# 项目中的源文件
SOURCES += \
main.cpp \
mainwindow.cpp # 这是自定义窗口类名字时候的生成的源文件,在创建项目的时候就看看到
# 项目中的头文件
HEADERS += \
mainwindow.h # 这是自定义窗口类名字时候的生成的头文件
# 项目中的窗口界面文件
FORMS += \
mainwindow.ui # 这是自定义窗口类名字时候的生成的 ui 文件
main.cpp
#include "mainwindow.h" // 生成的窗口类头文件,创建项目时候的根据 class name 生成的
#include <QApplication> // 应用程序类头文件
int main(int argc, char *argv[])
{
// 创建应用程序对象, 在一个Qt项目中实例对象有且仅有一个
// 类的作用: 检测触发的事件, 进行事件循环并处理
QApplication a(argc, argv);
// 创建窗口类对象
// 这个 MainWindow 名字是创建项目时候自定义的那个 class name
MainWindow w;
// 显示窗口
w.show();
// 应用程序对象开始事件循环, 保证应用程序不退出
return a.exec();
}
mainwindow.ui
<!-----以文本编辑器打开,会找到这样一行代码-->
<class>MainWindow</class>
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow> // Qt标准窗口类头文件
QT_BEGIN_NAMESPACE
// mainwindow.ui 文件中也有一个类叫 MainWindow, 将这个类放到命名空间 Ui 中
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT // 这个宏是为了能够使用Qt中的信号槽机制
public:
// 发现和 ui 类中的 MainWindow 同名,这是为了在 cpp 中底层进行捆绑
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui; // 定义指针指向窗口的 UI 对象
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) // 始化了一个指向 `ui::MainWindow` 类的新对象的指针 `ui`
{
// 实例化 ui 指针对象,是因为让窗口实例化,这样才能展现出窗口
// 将 mainwindow.ui 的实例对象和当前类的对象进行关联
// 这样同名的两个类对象就产生了关联, 合二为一了
// 因此在 mainwindow 中就能对 ui 界面改变了
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
- 因此
mainwindow.h
,mainwindow.cpp
,mainwindow.ui
可以堪称是一个整体,是用两个同名对象关联
窗口类
-
QWidget
- 父类是
QObject
- 所有窗口类的基类
- Qt 中的控件 (按钮,输入框,单选框…) 也属于窗口, 基类都是
QWidget
- 可以内嵌到其他窗口中,没有边框
- 可以不内嵌单独显示,独立的窗口, 有边框
- 父类是
-
QDialog
- 对话框类,继承
QWidget
- 模态和非模态两种显示方式
- 不能内嵌到其他窗口中
- 对话框类,继承
-
QMainWindow
- 是专门设计用作应用程序的主窗口的类,它继承自
QWidget
,但提供了更高级的功能和布局。 - 它有内置的支持,用于管理常见的主窗口元素,比如菜单栏 (
QMenuBar
)、工具栏 (QToolBar
)、状态栏 (QStatusBar
)、和中央窗口区 (centralWidget
) - 不能内嵌到其他窗口中
- 是专门设计用作应用程序的主窗口的类,它继承自
QWidget 窗口显示
// MainWindow 是自己创建项目取的名字
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
TestWidget *w = new TestWidget;
w->show();
}
- 这个就是显示两个独立窗口
- 一个是创建
MainWindow
对象时,Qt 框架会自动创建一个基本的窗口,因为MainWindow
继承于QMainWindow
,然后main()
函数中调用show
方法,窗口显示 - 一个是自定义的
TestWidget
窗口,由于没有设置父亲,单独显示,但前提是调用show()
方法
// 和上面其他一致,就定义不同
// explicit TestWidget (QWidget *parent = nullptr); 这是传参的原型
TestWidget *w = new TestWidget(this);
// 这个语法也是没问题的,MainWindow 是 QWidget 的子类,父类指针指向子类,类似于多态了
- 这个就是只显示一个窗口,只显示
MainWindow
窗口,TestWidget
窗口内嵌在了父窗口里面 - 这是设置了父亲的显示,不是独立窗口
QDialog 窗口
QDialog
窗口都是独立作为一个窗口显示,都需要调用show
方法,不管是否设置父亲
// 非模态
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
TestWidget *w = new TestWidget;
w->show();
TestDialog *d = new TestDialog;
d->show();
}
- 调用
show()
方法就是非模态显示,焦点即我们的鼠标可以
随意切换到任意一个窗口
// 将上面的 show 方法变成 exec 方法,变成模态
d->exec();
- 此时就是模态显示
- 焦点即我们的鼠标
不可以
随意切换到任意一个窗口,只能点击当前的QDialog
窗口 - 它还会阻塞程序的执行,也就是将程序阻塞到当前位置,这里的显示就是
MainWindow
窗口一直不显示,因为调用是在main
函数里,程序确阻塞在这里 - 只有关闭
QDialog
窗口之后,才能够焦点随意切换,MainWindow
窗口显示出
QPushButton
创建
// 创建按钮
QPushButton *btn = new QPushButton;
btn->show();
// 需要指定父亲,不然会作为单独窗口显示
// 两种方式
btn->setParent(this);
QPushButton *btn = new QPushButton(this);
显示
// 显示文本
// void setText(const QString &text);
// QString(const char *str); 可以隐式转换为 QString
btn->setText("第一个按钮");
QPushButton *btn2 = new QPushButton("第二个按钮", this);
// 但是这个方法会按照控件的大小创建窗口
// 显示窗口标题
setWindowTitle("第一个窗口"); // 设置当前的类对象即 this 的窗口
TestWidget *w = new TestWidget;
w->setWindowTitle("指定窗口"); // 指定设置某个窗口标题
// 重置大小
// void resize(int w, int h);
resize(600, 400); // 重置当前 this 的窗口大小
btn->resize(100, 100); // 重置 btn 按钮的大小
// 设置固定窗口大小
setFixedSize(600, 400); // 设置之后,用户随意方向拖拽,长度都无法拉伸,固定了
setFixedHeight(); // 设置固定高度
setFixedWidth(); // 设置固定宽度
对象树
上面代码中,很容易发现我们 new 出来对象,却没有去手动释放,这就是对象树的功劳
基本概念
- 在 Qt 中,大多数对象(特别是那些继承自
QObject
的类)都可以有一个父对象和多个子对象 - 父对象与子对象之间形成了一种层次结构,称为对象树
- 当你创建一个 Qt 对象并指定一个父对象时,新的对象会自动被添加到父对象的子对象列表中
QPushButton *btn = new QPushButton(this);`
- 这行代码会创建一个
QPushButton
对象,并将其添加到this
即当前窗口的子对象列表中 - 对象树由 Qt 自动维护,通常不需要手动管理它
图例:
创建顺序从上到下,很好理解,析构反着来,可以用栈知识理解,先进后出 由对象树也能得出一个结论,创建出的类对象必须设置父对象
功能
内存管理
- Qt 的对象树结构自动管理对象的内存
- 当一个父对象被销毁时,它的所有子对象也会被自动销毁,这就意味着你不需要手动删除每一个子对象,这大大减少了内存泄漏的风险
- 例如,当你关闭一个窗口时,窗口中的所有控件(如按钮、标签等)会被自动销毁
事件传播
- Qt 中的事件(如鼠标点击、键盘输入等)会沿着对象树的层次结构传播
- 通常,事件会首先发送到目标对象(如按钮),如果目标对象未处理该事件,则事件会传递给其父对象,直到顶层对象(如窗口)
- 这种机制允许你在父对象中统一处理某些事件,而不需要在每个子对象中重复代码
坐标体系
- 坐标原点是左上角即为 (0, 0),X 向右递增,Y 向下递增
- 每个窗口的坐标体系规则是根据父窗口,注意不是顶级父类,是上一个父类,如上图的(10, 10) 是相对于 (0, 0) 的坐标,而 (20, 20) 是相对于 (10, 10) 的坐标,也就是以 (10, 10) 为起点往右 20 像素和往下 20 像素
- X 即为宽,Y 即为高
示例一个层层套娃
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
//创建一个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnA = new QPushButton(this);
// 移动按钮的位置
btnA->move(10,10);
btnA->setText(" AAAA");
//给按钮设置固定大小
btnA->setFixedSize(200, 200);
// 创建第二个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnB = new QPushButton(btnA);
//移动按钮的位置
btnB->move(10,10);
btnB->setText("BBBB");
//给按钮设置固定大小
btnB->setFixedSize(100, 100);
//创建第三个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnC = new QPushButton(btnB);
//移动按钮的位置
btnC->move(10,10);
btnC->setText("C");
// 给按钮设置固定大小
btnC->setFixedSize(50,50);
}
效果:
控件
Item Widgets
Item Widget
是用来表示单个可视化条目(例如列表中的一个项目、树形结构中的一个节点、表格中的一个单元格等)的控件
QListWidget
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QListWidgetItem*item = new QListWidgetItem("悯农");
ui->listwidget->addItem(item);
item->setTextAlignment(Qt::AlignHCenter);
QStringList list;
list << "锄禾日当午" << "汗滴禾下土" << "谁知盘中餐" << "粒粒皆辛苦";
ui->listwidget->addItems(list);
}
效果:
QTreeWidget
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//treewidget树控件使用
//设置水平头
ui->treewidget->setHeaderLabels(QStringList() << "爱好" << "具体");
QTreeWidgetItem *liItem = new QTreeWidgetItem(QStringList() << "水果");
QTreeWidgetItem *pItem = new QTreeWidgetItem(QStringList() << "运动");
//加载顶层的节点
ui->treewidget->addTopLevelItem(liItem);
ui->treewidget->addTopLevelItem(pItem);
//追加子节点
QStringList h1;
h1 << "苹果" << "好吃";
QTreeWidgetItem *l1 = new QTreeWidgetItem(h1);
liItem->addChild(l1);
//追加子节点
QStringList h2;
h2 << "篮球" << "爱玩";
QTreeWidgetItem *l2 = new QTreeWidgetItem(h2);
pItem->addChild(l2);
}
效果:
QTableWidget
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置列数
ui->tablewidget->setColumnCount(3);
//设置水平表头
ui->tablewidget->setHorizontalHeaderLabels (QStringList ()<<"姓名"<<"性别"<<"年龄");
//设置行数
ui->tablewidget->setRowCount(3);
//设置正文
QMap<QString, QString> personMap;
personMap.insert("张三", "男");
personMap.insert("李四", "男");
personMap.insert("王五", "男");
int row = 0;
for(auto it = personMap.begin(); it != personMap.end(); ++it) {
int col = 0;
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(it.key()));
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(it.value()));
// int 转换为 QString
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(QString::number(row + 18)));
}
}
效果:
QMap 遍历
// QMap 遍历
// 法二
for (auto t : personMap) {
int col = 0;
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(personMap.key(t)));
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(t));
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(QString::number(row + 18)));
row++;
}
// 法三
for (auto t : personMap.toStdMap()) {
int col = 0;
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(t.first));
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(t.second));
ui->tablewidget->setItem(row, col++, new QTableWidgetItem(QString::number(row + 18)));
row++;
}
定义关联性类型
// 结构体
struct Person {
QString name;
QString sex;
int age;
};
QList<Person> personList = {
{"张三", "男", 18},
{"李四", "男", 19},
{"王五", "男", 20}
};
// QPair
QList<QPair<QString, QString>> personData = {
{"张三", "男"},
{"李四", "男"},
{"王五", "男"}
};
// 调用就是 second, first
自定义控件
这里实现滑动条滑动,对应的
spinBox
也增加,相互制约的关系
效果:
- 新建一个
QWidget
类SmallWidget
,包含ui
界面,在里面布局好要封装的控件 - 在新建的类的
cpp
文件中书写自定义控件的规则
void(QSpinBox:: *sp)(int) = &QSpinBox::valueChanged;
connect(ui->spinBox, sp, ui->hslider, &QSlider::setValue);
connect(ui->hslider, &QSlider::valueChanged, ui->spinBox, &QSpinBox::setValue);
- 这样就封装完成了,接下来就是使用这个类
- 方法一就是在主窗口(需要调用这个类的窗口)的
ui
界面,使用一个widget
容器,然后点击提升为,类名书写封装好的类,这样就 OK 了 - 方法二是在主窗口(需要调用这个类的窗口)的头文件中定义封装好的类的对象,然后在
cpp
文件中实例化对象,就可以实现了
- 方法一就是在主窗口(需要调用这个类的窗口)的
- 当然封装的不仅仅是简单的关联起了自定义控件,还需向外部提供一些必要的接口
// 头文件先定义,然后再 smallwidget.cpp 中实现
int SmallWidget::getNum() {
return ui->spinBox->value();
}
void SmallWidget::setNum(int num) {
ui->spinBox->setValue(num);
}
- 接下来就是在外部调用这些接口了,为了方便实现,在主窗口
ui
界面新增了两个按钮,btn1
,btn2
,然后实现点击获取当前大小和设置大小- 方法一调用封装类调用接口方法
connect(ui->btn1, &QPushButton::clicked, ui->widget, [=]{
qDebug() << ui->widget->getNum();
});
connect(ui->btn2, &QPushButton::clicked, ui->widget, [=]{
ui->widget->setNum(50);
});
// widget 是使用的 QWidget 容器的名字
// 这种形式理解为就是一个 widget 指针指向了这个类,进行管理,类似于定义对象实例化对象
- 方法二形式的调用
one = new SmallWidget(this);
connect(ui->btn2, &QPushButton::clicked,one, [=]{
one->setNum(50);
});
// one 是在头文件 SmallWidget * one; 定义的,这里需要实例化一下再使用
说明:参考学习 https://subingwen.cn/