QT学习笔记
简介
Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能,它是完全面向对象的很容易扩展;并且允许真正的组件编程。
安装
Index of /archive/qt
1.MSVC(Microsoft Visual C++)编译器支持
- MSVC 2015 64-bit:支持使用 Microsoft Visual C++ 2015 编译的 64 位应用程序。
- MSVC 2015 32-bit:支持使用 Microsoft Visual C++ 2015 编译的 32 位应用程序。
- MSVC 2017 64-bit:支持使用 Microsoft Visual C++ 2017 编译的 64 位应用程序。
- MSVC 2017 32-bit:支持使用 Microsoft Visual C++ 2017 编译的 32 位应用程序。
适用场景:
这些选项适合你使用 MSVC 编译器进行开发,比如配合 Visual Studio(2015 或 2017 版本)。根据你的系统位数(64 位或 32 位)选择合适的版本。
2. MinGW(Minimalist GNU for Windows)编译器支持
- MinGW 7.3.0 32-bit:支持使用 MinGW 7.3.0 编译的 32 位程序。
- MinGW 7.3.0 64-bit:支持使用 MinGW 7.3.0 编译的 64 位程序。
适用场景:
如果你不使用 MSVC,而是选择开源的 MinGW 编译器进行开发,就需要选择其中一个版本(32 位或 64 位)。
3. UWP(Universal Windows Platform)支持
- UWP ARMv7 (MSVC 2015):支持在 ARMv7 架构(如某些 Windows 移动设备)上运行的 UWP 程序,用 MSVC 2015 编译。
- UWP x64 (MSVC 2015):支持在 64 位系统上运行的 UWP 程序,用 MSVC 2015 编译。
- UWP ARMv7 (MSVC 2017):支持在 ARMv7 架构上运行的 UWP 程序,用 MSVC 2017 编译。
- UWP x64 (MSVC 2017):支持在 64 位系统上运行的 UWP 程序,用 MSVC 2017 编译。
- UWP x86 (MSVC 2017):支持在 32 位系统上运行的 UWP 程序,用 MSVC 2017 编译。
适用场景:
这些选项适用于开发 Windows 应用商店的应用(UWP 应用)。如果你的程序需要支持 ARM 或桌面系统上的 UWP,请选择对应的版本。
4. Android
提供用于开发 Android 应用的 Qt 模块和工具支持。
适用场景:开发跨平台的 Qt 程序时,如果需要支持 Android 平台,勾选此项。
5. Sources
Qt 的完整源代码,供查看或自行编译。
适用场景:如果需要自定义 Qt 或调试 Qt 本身,可以选择安装源码;普通开发者通常不需要。
6. Qt 附加模块
- Qt Charts:提供用于创建 2D 图表的库,比如折线图、柱状图和饼图。
- Qt Quick 3D (Technology Preview):提供 3D 图形和动画功能,适用于 UI 或游戏开发(技术预览版,可能不稳定)。
- Qt Data Visualization:支持 3D 数据可视化,比如绘制曲面图或散点图。
- Qt Lottie Animation (Technology Preview):支持使用 JSON 文件加载和渲染 Adobe After Effects 创建的动画。
- Qt Purchasing:提供应用内购买支持,比如移动端应用中的支付功能。
- Qt Virtual Keyboard:一个支持多种语言的虚拟键盘模块,适合触屏设备。
- Qt WebEngine:基于 Chromium 的嵌入式浏览器引擎,用于嵌入网页内容。
- Qt Network Authorization:提供 OAuth2 等网络授权支持,适合需要登录授权的应用程序。
- Qt WebGL Streaming Plugin:支持将 Qt 应用通过 WebGL 流式传输到浏览器。
- Qt Script (Deprecated):支持 JavaScript 脚本的模块,但已被废弃,不建议使用。
- Qt Quick Timeline (Technology Preview):用于管理和实现时间轴动画。
适用场景:
根据项目需求选择模块。如果你的程序需要显示网页、创建动画或支持支付功能等,可以勾选相应模块。
Developer and Designer Tools(开发和设计工具)
这部分提供与 Qt 开发相关的工具支持:
- Qt Creator 4.11.1:Qt 官方的 IDE,用于编写、调试和设计 Qt 程序。
- Qt Creator 4.11.1 CDB Debugger Support:为 MSVC 编译器提供调试支持(基于 Microsoft CDB 调试器)。
- MinGW 7.3.0 32-bit/64-bit:与前面提到的 MinGW 编译器对应的开发工具。
- Strawberry Perl 5.22.1.3:一个 Perl 发行版,Qt 的一些工具(比如 QMake 或测试工具)可能需要使用。
安装建议
- 编译器选择:
- 如果你使用 Visual Studio(2015/2017),选择相应的
MSVC
项目。 - 如果你使用开源工具链,选择
MinGW
项目。
- 如果你使用 Visual Studio(2015/2017),选择相应的
- 功能模块:根据你的需求勾选模块,比如需要 3D 图形支持时选择
Qt Quick 3D
,需要内嵌浏览器时选择Qt WebEngine
。 - 开发工具:建议至少安装
Qt Creator
和相关调试支持。
卸载
win10平台或Linux平台卸载Qt_maintenancetool.exe.new-CSDN博客
第一个程序
使用Qt Creator(QT开发工具)创建。
选择模版
选择编译器
选择类名
QWidget:
QWidget
是Qt中最基础的窗口类,是所有界面控件的基类。- 它代表了一个普通的窗口或控件,可以是主窗口、对话框或任何其他窗口组件。
QWidget
没有内置的菜单栏、工具栏、状态栏等功能,它是一个非常基础的窗口类,适合用于简单的界面。QMainWindow:
QMainWindow
是QWidget
的子类,专门用于创建应用程序的主窗口。- 它是一个功能更强大的窗口类,内置了很多专用于构建主窗口的控件,如菜单栏、工具栏、状态栏等。
QMainWindow
提供了更加结构化的界面设计,使得开发者可以方便地管理主窗口的布局和各种常见的窗口元素。
QDialog
是用于创建对话框的基类。它通常用于显示信息、获取用户输入等。QDialog
支持模态和非模态对话框。
创建完的目录:
项目管理文件(.pro
文件)
此文件用于记录项目的设置和包含的文件列表。定义了项目的配置和构建规则。它定义了项目的名称、类型、使用的 Qt 模块,以及源文件、头文件和界面文件等。
例如,.pro
文件可能包含以下内容:
#-------------------------------------------------
#
# Project created by QtCreator 2024-12-31T11:21:00
#
# 此文件定义了项目的配置和构建规则
#
#-------------------------------------------------
# 指定项目使用 Qt 的核心模块和图形用户界面模块。
QT += core gui
# 表示如果 Qt 的主版本号大于 4,则添加 widgets 模块。这确保了在使用 Qt 5 及以上版本时,包含必要的部件模块。
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#定义了生成的可执行文件的名称
TARGET = first
#指定项目类型为应用程序
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
#这行代码添加了编译器定义 QT_DEPRECATED_WARNINGS,使编译器在使用已标记为弃用的 Qt 功能时发出警告。这有助于开发者识别和避免使用过时的 API。
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
#指定项目使用 C++11 标准进行编译
CONFIG += c++11
#列出了项目的源文件、头文件和 Qt Designer 生成的 UI 文件。
#通过在相应部分添加文件路径,qmake 将在构建过程中包含这些文件。
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
头文件(.h
文件)
这些文件声明类和函数的接口。通常包括:
widget.h
:QWidget
派生类的头文件,声明窗口类及其成员函数和变量。
前向声明
在 Qt 中,前向声明 (forward declaration
) 是指声明一个类、结构体、枚举或联合类型,而不需要提供其具体的定义。它仅仅告诉编译器某个类型存在,以便可以在代码中引用该类型,稍后再定义它的具体实现。
目的:前向声明允许你在不暴露结构体的实现的情况下,在类或函数中使用它。它对于减少头文件依赖和编译时间非常有帮助。
什么时候使用前向声明:
- 避免循环依赖:在两个类相互引用时,使用前向声明可以打破依赖循环。
- 减少头文件依赖:如果你只需要在类中使用指针或引用,而不需要知道类的具体实现,前向声明可以减少头文件的包含,从而加速编译过程。
源文件(.cpp
文件)
这些文件包含应用程序的实现代码。通常包括:
main.cpp
:程序的入口点,包含main()
函数。widget.cpp
:QWidget
派生类的实现文件,定义了窗口的具体行为和功能。
界面文件(.ui
文件)
这是使用 Qt Designer 设计的用户界面文件,采用 XML 格式,描述了窗口的布局和控件。例如,widget.ui
文件定义了窗口中各个控件的位置和属性。
uic
会自动为你根据 UI 文件(通常是 .ui
后缀的文件)生成一个与界面相对应的 C++ 类,在这里,Widget
是这个类的名字。这个类负责管理界面中所有控件(比如按钮、文本框、标签等)的创建、布局和初始化。它包含了所有控件的指针,以及设置和控制这些控件的方法。
命令/指令
qmake -project
这个命令会在当前目录下生成一个
.pro
文件。.pro
文件是 Qt 项目的配置文件,包含了源代码、头文件、资源文件等的信息。通常,你会在一个空目录中运行这个命令,然后
qmake
会根据你当前目录的文件生成该文件。
qmake
运行
qmake
之后,Qt 会使用.pro
文件来生成适用于目标平台的 Makefile 文件。这个文件包含了构建项目所需的规则。之后,你可以使用
mingw32-make
来实际构建项目。
mingw32-make
这个命令会根据
qmake
生成的 Makefile 文件来进行编译和构建项目。
mingw32-make
是 Windows 平台下 MinGW 工具链的 Make 工具,主要用于执行构建过程。
#pragma
#pragma
是 C 和 C++ 中的预处理指令,用于向编译器提供特定的指令或控制编译行为。具体的用法取决于编译器的实现,因此 #pragma
是非标准的,可能在不同编译器之间有所不同。
以下是一些常见的 #pragma
用法示例:
1. 防止多重包含
在现代 C++ 中,#pragma once
是一种常见的方式,用来防止头文件被多次包含。它比传统的头文件保护(#ifndef
)更简单高效:
#pragma once
// 头文件内容
class MyClass {
// 类定义
};
等价于:
#ifndef MYCLASS_H
#define MYCLASS_H
// 头文件内容
class MyClass {
// 类定义
};
#endif // MYCLASS_H
2. 控制编译警告
许多编译器允许通过 #pragma
来启用或禁用特定警告。例如,在 Microsoft Visual C++ 中:
#pragma warning(disable : 4996) // 禁用4996号警告
// 在 GCC/Clang 中,可以使用:
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
还有一些没用到,用到百度即可。
#define
#define
是 C 和 C++ 中的预处理指令,用来定义宏常量或宏函数。通过 #define
定义的常量会在编译之前由预处理器替换掉代码中的所有该宏的引用。
- 宏常量是一个简单的替换,它的值在编译时被直接替换进代码中。
- 与
const
或constexpr
不同,#define
并不具有类型信息,它只是一个简单的文本替换。
常量引用和常量指针
private:
const DeviceConfig &config_;
const DeviceCmd* device_cmd_;
常量引用(
const &
)
语法:
const Type &var
含义:
const
修饰的是引用的对象,表示你不能修改引用所指向的对象的内容。- 引用本身是一个别名,指向某个对象,一旦绑定到某个对象,它不能再绑定到其他对象。
特点:
- 引用绑定到某个对象后,不可以修改该对象(
const
限制的是对象本身,而非引用)。- 你不能将引用重新绑定到其他对象,但可以通过引用访问对象。
- 引用通常用于传递较大的对象时避免拷贝,且在不希望修改对象时,使用常量引用可以确保数据的安全性。
使用场景:
- 常量引用通常用于传递较大的对象(例如类、结构体等),避免拷贝,同时保证对象不会被修改。
常量指针(
const *
)
语法:
const Type *ptr
或Type *const ptr
const Type *ptr
:指针本身是常量,指向的数据是可以修改的。Type *const ptr
:指针指向的地址是常量,指针本身不可修改,但可以通过它修改数据。const Type *const ptr
:指针本身和指针指向的数据都不可修改。含义:
const Type *ptr
:指针指向的数据是只读的,但指针可以指向不同的对象。Type *const ptr
:指针本身不可更改,但可以通过它修改数据。const Type *const ptr
:指针和指针指向的数据都不可修改。特点:
- 常量指针主要用来限制通过指针修改对象的内容,或者限制指针本身的修改。
- 你可以根据需求控制是否修改指针指向的数据、是否可以改变指针指向的地址。
使用场景:
- 常量指针常用于函数参数中,限制通过指针修改数据,或者限制指针指向的数据不可被修改。
信号与槽
QT的信号和槽机制是其核心之一。信号用于表示事件的发生,槽用于响应信号。QT通过这种机制实现对象之间的通信。
控件发出某种信号,指定的槽(回调函数)会执行。
信号种类
Q_OBJECT
宏
- 这是 Qt 信号和槽机制所必须的宏。
- 它使得类能够定义和使用信号(
signals
)和槽函数(slots
),实现组件间的通信。 - 使用
Q_OBJECT
后,Qt 的元对象编译器(moc
)会生成额外的代码,为信号和槽机制提供支持。
连接信号和槽
在 Qt 中,连接信号和槽的方式有两种常见的方式:传统的 SIGNAL/SLOT 机制 和 新的函数指针方式。
传统的 SIGNAL/SLOT 机制
这是 Qt 经典的信号与槽连接方法,适用于 Qt 5 及以前的版本,虽然在 Qt 5 和 Qt 6 中仍然有效,但通常不推荐在新项目中使用。
connect(sender, SIGNAL(signalName()), receiver, SLOT(slotName()));
connect(ui->cmdText, SIGNAL(returnPressed()), this, SLOT(on_commitButton_clicked()));
sender
:信号发送者对象,谁发出信号。signalName()
:信号名称,发出什么信号,通常没有参数,但也可以有参数。receiver
:槽接收者对象,谁处理这个信号。slotName()
:槽函数名称,回调函数,怎么处理这个信号。
新的函数指针方式(Qt 5.0 及以上)
在 Qt 5.0 以后,Qt 提供了一个更强大、更类型安全的信号槽连接方式,使用函数指针的语法。这种方式允许我们显式地指定信号和槽的参数类型,并且不再使用字符串来表示信号和槽。
connect(sender, &Sender::signalName, receiver, &Receiver::slotName);
connect(ui->cmdText, &QLineEdit::returnPressed, this, &Widget::on_commitButton_clicked);
&Sender::signalName
:信号名称,使用类名和成员函数指针语法。&Receiver::slotName
:槽函数名称,使用类名和成员函数指针语法。这行代码的意思是:当
ui->cmdText
控件的returnPressed
回车信号被触发时,调用当前对象(this
)的on_commitButton_clicked
槽函数。
Lambda 表达式
Qt 5 和 Qt 6 中的 Lambda 表达式 来连接信号和槽。它使用了 lambda 函数,即匿名函数,并且利用了 C++11 引入的 lambda 表达式功能。
当回调函数逻辑比较简单,即可使用lambda表达式。
connect(ui->scanButton,&QPushButton::clicked,[this](){
QMessageBox::information(this, "信息", "点击浏览");
});
[this]() {...}
:这是一个 lambda 表达式,作为信号的槽函数。这里使用了捕获列表[this]
来捕获当前对象this
的指针,从而可以在 lambda 内部访问当前对象的成员函数和变量。
连接信号和信号
当你将一个信号连接到另一个信号时,一个信号发射时,另一个信号会被自动触发。但是,你需要确保两个信号的参数类型和数量相匹配,否则连接会失败。
自定义信号
在 Qt 中,自定义信号是通过使用 signals
关键字在类中声明的。自定义信号可以被其他对象连接到它们的槽函数,从而触发某些操作。通常,信号是在类的 public
部分声明的,并且它们通常与类的事件或状态变化相关。
步骤:
- 声明信号:在类中使用
signals
关键字声明自定义信号。 - 定义信号:信号通常不需要显式定义实现。只需声明它们,Qt 会为你提供必要的机制来处理它们。
- 发射信号:使用
emit
关键字发射信号。发射信号时,任何与该信号连接的槽函数都会被调用。 - 连接信号与槽:通过
QObject::connect()
方法将信号与槽连接,信号被发射时,槽函数会被执行。
#include <QWidget>
#include <QPushButton>
#include <QDebug>
class MyWidget : public QWidget
{
Q_OBJECT // 需要这个宏来启用信号和槽机制
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent)
{
// 创建一个按钮
QPushButton *button = new QPushButton("Click Me", this);
// 连接按钮的点击信号到自定义信号
connect(button, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
}
signals:
// 定义一个自定义信号
void myCustomSignal(int value);
public slots:
// 一个槽函数,当按钮被点击时发射信号
void onButtonClicked()
{
qDebug() << "Button clicked!";
emit myCustomSignal(42); // 发射自定义信号
}
// 自定义信号的槽函数
void handleCustomSignal(int value)
{
qDebug() << "Custom Signal received with value:" << value;
}
};
// 连接信号和槽
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyWidget widget;
// 连接自定义信号到槽
QObject::connect(&widget, &MyWidget::myCustomSignal, &widget, &MyWidget::handleCustomSignal);
widget.show();
return app.exec();
}
- 声明信号:
signals
关键字后面是自定义的信号myCustomSignal(int value)
,它带有一个整数参数。- 发射信号:
emit myCustomSignal(42)
语句会发射myCustomSignal
信号,通知所有连接到该信号的槽函数。- 连接信号和槽:
QObject::connect()
用于连接信号与槽,当信号被发射时,槽函数会被调用。
连接类型
Qt::AutoConnection
:默认连接类型,根据信号和槽是否在同一线程中决定使用哪种方式。- 同一线程内:直接调用槽函数。
- 不同线程间:使用事件队列+参数拷贝来异步调用槽函数,确保线程安全。
Qt::DirectConnection
:当信号发射时,槽函数会立即被调用,直接在发射信号的线程中执行槽函数。这种方式不管信号和槽是否在同一线程。- 适用于信号和槽都在同一线程,或者槽函数的执行不会阻塞 UI 线程的情况
Qt::QueuedConnection
:如果信号和槽在不同的线程之间,使用队列连接。这意味着信号的处理会被推送到接收者线程的事件队列中,确保线程安全。- 当信号在一个线程中发射时,槽函数会被异步调用,等待事件循环处理。
Qt::BlockingQueuedConnection
:在不同线程时,信号发出后会等待槽执行完毕。
Qt::QueuedConnection
含义:
- 信号与槽之间的调用通过事件队列异步执行。
- 即信号发出后不会立即执行槽函数,而是将执行请求插入事件队列,等待合适的时间处理。
适用场景:
- 信号与槽运行在不同线程中。
- 保证线程安全,槽函数在接收方的线程中执行。
工作原理
信号和槽的基本原理是:信号 是由对象发出的事件通知,槽 是用于响应这些通知的函数。当信号发出时,连接到这个信号的所有槽会被自动调用。
一个信号可以连接到多个槽,多个信号也可以连接到同一个槽。每次信号发射时,所有连接到该信号的槽都会被依次调用。
异步调用
默认情况下,信号和槽的连接是同步的,即当信号发出时,它会阻塞当前线程并等待槽函数执行完毕后再继续。然而,在某些场景下,我们可能希望信号和槽的调用是异步的,即信号发射后,不阻塞当前线程,槽函数会在事件循环中异步执行。
一个信号可以连接到多个槽,多个槽会按连接的顺序被依次调用,那这样不是很慢,多个槽同步执行?
1、显式使用
Qt::QueuedConnection
进行异步调用如果你希望显式地使用异步连接(无论信号和槽是否在同一线程),可以在
connect()
函数中指定连接类型为Qt::QueuedConnection
。connect(sender, SIGNAL(signalName()), receiver, SLOT(slotName()), Qt::QueuedConnection);
这意味着无论信号和槽在哪个线程中,信号发射后都会将槽函数放入接收者线程的事件队列,槽函数会异步执行。
放入接收者线程的事件队列,什么时候执行?
2. 使用
Qt::AutoConnection
自动决定连接方式如果你不显式指定连接类型,Qt 会自动根据发射信号和槽所在的线程来决定连接方式:
- 如果信号和槽在同一线程中,Qt 会使用 同步调用。
- 如果信号和槽在不同线程中,Qt 会使用 异步调用(即
Qt::QueuedConnection
)。
槽函数异步调用的实现事件队列,信号发出后,槽函数放入接收者的事件队列中,然后信号发出后就可以继续执行代码了,而不用阻塞执行槽函数。
那么放入接收者的事件队列中的槽函数是何时执行的?
事件队列中的槽函数会在以下情况下执行:
事件循环(Event Loop)正在运行: Qt 中的事件循环由
QCoreApplication::exec()
启动,它会持续运行,处理各类事件。线程必须运行事件循环,才能处理它的事件队列中的事件。事件队列有事件时: 当信号被发射,并且槽函数通过
Qt::QueuedConnection
放入接收者线程的事件队列后,事件会在接收者线程的事件循环中处理。只有当事件循环到达事件队列中该事件时,槽才会被执行。
定时器
QObbject
// 重写QObject中的timerEvent方法
virtual void timerEvent(QTimerEvent *event);
// 调用QObject中的startTimer方法启动定时器
startTimer(int msec)
使用
startTimer()
启动一个定时器时,Qt 会在后台为你管理定时器。定时器到期时,Qt 会自动产生一个QTimerEvent
事件,并将其传递到与定时器关联的对象上,调用该对象的timerEvent
函数。
QTimer
QTimer
是 Qt 中用于定时执行任务的类,常用于周期性执行某些操作,比如定时更新 UI 或定时进行某些计算等。它通过发出 timeout()
信号来触发定时操作,你可以连接这个信号到相应的槽函数。
// 调用QTimer对象的start方法
// 开始定时器 定时器每隔一段时间就会发出timeout信号 需要定义槽函数处理
timer->start(1000);
// 连接timeout信号和槽函数
connect(timer,&QTimer::timeout,this,&Widget::timeoutSlot);
QTimer::singleShot
可以用于延迟执行某些操作。QTimer::singleShot(1, this, &Fileview::SetFilename);
这个调用的作用是:
- 在 1 毫秒后,调用
Fileview
类中的SetFilename
槽函数。this
指定了调用槽函数的对象(即当前Fileview
类的实例)。&Fileview::SetFilename
是一个指向SetFilename
函数的指针,它会在定时器超时后被调用。
消息事件
键盘和鼠标消息
在 Qt 中,键盘和鼠标的消息(事件)通常通过事件处理机制来管理。你可以重载相关的事件处理函数,以便响应键盘和鼠标的输入。
键盘事件通常通过重载
keyPressEvent()
和keyReleaseEvent()
来处理。keyPressEvent()
处理按下键的事件,而keyReleaseEvent()
处理松开键的事件。
QKeyEvent
包含有关按键的详细信息(例如,按键代码、是否修饰键被按下等)。event->key()
返回按下的键的代码,可以与Qt::Key_
枚举值进行比较。keyPressEvent()
被调用时,事件会触发所有键盘按下的动作。keyReleaseEvent()
在松开键时被调用。
#include <QWidget>
#include <QKeyEvent>
#include <QDebug>
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}
protected:
// 重载 keyPressEvent 来处理按键按下事件
void keyPressEvent(QKeyEvent *event) override {
if (event->key() == Qt::Key_Enter) {
qDebug() << "Enter key was pressed";
}
else if (event->key() == Qt::Key_Escape) {
qDebug() << "Escape key was pressed";
}
}
// 重载 keyReleaseEvent 来处理按键释放事件
void keyReleaseEvent(QKeyEvent *event) override {
if (event->key() == Qt::Key_Space) {
qDebug() << "Space key was released";
}
}
};
鼠标事件通常通过重载
mousePressEvent()
、mouseMoveEvent()
和mouseReleaseEvent()
来处理。这些事件分别对应鼠标按下、移动和释放的情况。
QMouseEvent
包含有关鼠标事件的信息,例如鼠标位置、按下的按钮等。event->pos()
返回鼠标事件发生时的坐标。event->button()
返回鼠标按键(如左键、右键或中键)。
#include <QWidget>
#include <QMouseEvent>
#include <QDebug>
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}
protected:
// 重载 mousePressEvent 处理鼠标按下事件
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
qDebug() << "Left mouse button pressed at" << event->pos();
}
else if (event->button() == Qt::RightButton) {
qDebug() << "Right mouse button pressed at" << event->pos();
}
}
// 重载 mouseMoveEvent 处理鼠标移动事件
void mouseMoveEvent(QMouseEvent *event) override {
qDebug() << "Mouse moved to" << event->pos();
}
// 重载 mouseReleaseEvent 处理鼠标释放事件
void mouseReleaseEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
qDebug() << "Left mouse button released at" << event->pos();
}
}
};
protected:
void contextMenuEvent(QContextMenuEvent *event) override; // 右键菜单事件
数据库
QSqlDatabase: QMysql driver not loaded
Qt 默认并不内置 MySQL 驱动,而是需要你手动配置
【QT连接Mysql时报错:QSqlDatabase: QMYSQL driver not loaded。解决方案(亲测成功)】-CSDN博客
Qt-解决Qt与MySQL连接过程中出现“QSqlDatabase: QMYSQL driver not loaded”问题 - Iriczhao - 博客园
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); // 使用MySQL数据库驱动,其他类型的数据库使用相应的驱动
db.setHostName(ip); // 数据库主机地址
db.setDatabaseName("test"); // 数据库名称
db.setUserName(username); // 用户名
db.setPassword(password); // 密码
// 尝试打开数据库
if (!db.open()) {
qDebug() << "数据库连接失败:";
}else {
qDebug() << "数据库连接成功!";
}
封装工具类
#ifndef DATABASEHELPER_H
#define DATABASEHELPER_H
#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QString>
class DatabaseHelper : public QObject
{
Q_OBJECT
public:
explicit DatabaseHelper(QObject *parent = nullptr);
~DatabaseHelper();
// 打开数据库连接
bool openDatabase(const QString &dbName);
// 关闭数据库连接
void closeDatabase();
// 执行查询
bool executeQuery(const QString &queryStr);
QSqlQuery executeQuery2(const QString &queryStr);
// 获取错误信息
QString getLastError() const;
private:
QSqlDatabase m_db;
};
#endif // DATABASEHELPER_H
#include "DatabaseHelper.h"
#include <QSqlError>
#include <QDebug>
DatabaseHelper::DatabaseHelper(QObject *parent)
: QObject(parent)
{
m_db = QSqlDatabase::addDatabase("QSQLITE"); // 默认使用 SQLite 数据库
}
DatabaseHelper::~DatabaseHelper()
{
closeDatabase();
}
bool DatabaseHelper::openDatabase(const QString &dbName)
{
m_db.setDatabaseName(dbName);
if (!m_db.open()) {
qDebug() << "Failed to open database:" << m_db.lastError().text();
return false;
}
qDebug() << "Database opened successfully!";
return true;
}
void DatabaseHelper::closeDatabase()
{
if (m_db.isOpen()) {
m_db.close();
qDebug() << "Database closed.";
}
}
bool DatabaseHelper::executeQuery(const QString &queryStr)
{
QSqlQuery query;
if (!query.exec(queryStr)) {
qDebug() << "Query execution failed:" << query.lastError().text();
return false;
}
qDebug() << "Query executed successfully!";
return true;
}
QSqlQuery DatabaseHelper::executeQuery2(const QString &queryStr)
{
QSqlQuery query;
if (!query.exec(queryStr)) {
qDebug() << "Query execution failed:" << query.lastError().text();
query.clear(); // 清空 query 的内容
return query;
}
qDebug() << "Query executed successfully!";
return query;
}
QString DatabaseHelper::getLastError() const
{
return m_db.lastError().text();
}
使用
bool SignalSeparateEntrance::insertConfigINI(){
DatabaseHelper dbHelper;
// 打开数据库
if (dbHelper.openDatabase("Signal.db")) {
// 创建表
QString createTableQuery = R"(
CREATE TABLE IF NOT EXISTS ConfigINI
(
ID INTEGER PRIMARY KEY AUTOINCREMENT,
config_schema_name TEXT NOT NULL,
frontNoise INTEGER,
localLat REAL
);
)";
if (!dbHelper.executeQuery(createTableQuery)) {
qDebug() << "Failed to create table.";
}
// 插入数据
// 构建插入 SQL 语句
QString insertDataQuery = QString("INSERT INTO ConfigINI (config_schema_name, radar,) "
"VALUES ('%1', '%2', '%3', ...)")
.arg(config_schema_name)
.arg(radar);
if (!dbHelper.executeQuery(insertDataQuery)) {
qDebug() << "Failed to insert data.";
QMessageBox::information(this, "提示", "保存失败!");
}else{
QMessageBox::information(this, "提示", "保存成功!");
refreshData();
}
// 关闭数据库
dbHelper.closeDatabase();
}
}
void SignalSeparateEntrance::updateConfig(){
qDebug()<<"updateConfig";
DatabaseHelper dbHelper;
// 打开数据库
if (dbHelper.openDatabase("Signal.db")) {
// 创建更新的 SQL 语句
QString updateDataQuery = QString("UPDATE ConfigINI SET "
"config_schema_name = '%1', "
...
"WHERE ID = '%53';")
.arg(configInfoDialog->lineVector[0]->text()) // config_schema_name
...
.arg(curConfigID); // Where clause
// 执行更新查询
if (!dbHelper.executeQuery(updateDataQuery)) {
qDebug() << "Failed to update data.";
QMessageBox::information(this, "提示", "更新失败!");
} else {
qDebug() << "Data updated successfully!";
QMessageBox::information(this, "提示", "更新成功!");
refreshData();
refreshComponentData(configList.at(0),true);
}
// 关闭数据库
dbHelper.closeDatabase();
}
}
void SignalSeparateEntrance::deleteConfigSchemaBtnSlot(){
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Question", "你确定删除吗?",
QMessageBox::Yes|QMessageBox::No);
if (reply == QMessageBox::Yes) {
qDebug()<<"删除:"<<curConfigID;
QString deleteSQL = QString("delete from ConfigINI where ID = %1").arg(curConfigID);
DatabaseHelper dbHelper;
// 打开数据库
if (dbHelper.openDatabase("Signal.db")) {
if(dbHelper.executeQuery(deleteSQL)){
qDebug()<<"删除成功:"<<curConfigID;
refreshData();
refreshComponentData(configList.at(0),true);
}else{
// 删除失败
}
}else {
// 打开数据库失败
}
} else {
qDebug()<<"取消删除:"<<curConfigID;
}
}
void SignalSeparateEntrance::selectAllData(){
DatabaseHelper dbHelper;
if (dbHelper.openDatabase("Signal.db")) {
QString selectSQL = "SELECT * FROM ConfigINI";
QSqlQuery query = dbHelper.executeQuery2(selectSQL);
configList.clear(); // 清空旧的
// 遍历查询结果
while (query.next()) {
ConfigINI_CPP config;
// 将查询结果填充到结构体中
config.ID = query.value("ID").toLongLong();
config.config_schema_name = query.value("config_schema_name").toString();
config.localLat = query.value("localLat").toDouble();
// 添加到列表
configList.append(config);
}
}
}
多线程
自定义一个线程类
#ifndef TCPTHREAD_H
#define TCPTHREAD_H
#include <QObject>
#include <QThread>
#include <QTcpSocket>
#include <QWidget>
#include <QByteArray>
#include <QDebug>
class tcpThread : public QThread
{
Q_OBJECT
public:
explicit tcpThread();
tcpThread(QTcpSocket *client);
protected:
virtual void run();
private:
QTcpSocket *client; // 客户端连接对象
signals:
// 自定义信号
void sendClientMessage(QByteArray ba);
public slots:
};
#endif // TCPTHREAD_H
线程实现类
#include "tcpthread.h"
// 无参构造
tcpThread::tcpThread()
{
}
// 有参构造
tcpThread::tcpThread(QTcpSocket *client )
{
this->client = client;
}
// 线程运行会执行run方法
void tcpThread::run(){
// 收到客户端连接
qDebug()<<QThread::currentThread()->objectName()<<"收到客户端连接";
connect(client,&QTcpSocket::readyRead,[this](){
QByteArray message = client->readAll();
// this tcpThread 发出信号
emit this->sendClientMessage(message);
});
}
使用
// 使用多线程处理客户端连接 一个连接起一个线程来处理
tcpThread *t = new tcpThread(tcpSocket);
t->setObjectName("myThread-" + QString::number(QDateTime::currentMSecsSinceEpoch()));
t->start(); // 启动线程
异步任务
第一种就是使用上面的,使用 QThread
(多线程)
第二种
同步机制
std::mutex
和 std::unique_lock
是 C++11 提供的同步机制,通常用于确保多个线程在访问共享资源时不会发生冲突或数据不一致的情况。它们是实现 互斥锁 (mutex) 的核心组件,保证了在多线程环境下的线程安全,保证不会同时修改共享资源导致共享资源不一致。
1、std::mutex
是一种基本的互斥锁,用于保护共享资源的访问。其基本用法是:当线程需要访问某个共享资源时,首先尝试获取该资源的锁。如果锁已经被其他线程持有,当前线程会等待,直到该锁被释放。
// 声明:std::mutex 通过 std::mutex 声明一个互斥锁对象。
std::mutex mutex_;
// 加锁:在访问共享资源之前,必须加锁,加锁后就不能同时操作共享资源。
mutex_.lock(); // 获取锁
// 操作共享资源
mutex_.unlock(); // 释放锁
2、std::unique_lock
是一个更加灵活的锁管理工具,它提供了比 std::mutex
更强的功能。std::unique_lock
用来自动管理锁的获取与释放,避免手动加锁和解锁带来的错误(如忘记解锁)。
// 声明:声明一个 std::unique_lock 对象,并传入一个 std::mutex 对象作为参数。
// 它会在构造时获取锁,并在销毁时释放锁。
// std::unique_lock 提供了自动管理锁的生命周期,它会在作用域结束时自动释放锁。
// 这个机制非常适用于防止因忘记调用 unlock() 而导致死锁的情况。
std::unique_lock<std::mutex> lock(mutex_); // 加锁,自动获取锁
// 操作共享资源
// lock 的作用域结束时,锁会被自动释放
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex_; // 声明一个互斥锁
int shared_resource = 0; // 共享资源
void increment_resource() {
// 使用 std::unique_lock 自动管理锁的获取与释放
std::unique_lock<std::mutex> lock(mutex_);
shared_resource++; // 对共享资源的访问
std::cout << "Shared resource incremented: " << shared_resource << std::endl;
// 锁会在 lock 的生命周期结束时自动释放
}
int main() {
// 创建多个线程,访问共享资源
std::thread t1(increment_resource);
std::thread t2(increment_resource);
std::thread t3(increment_resource);
t1.join();
t2.join();
t3.join();
return 0;
}
BUG
BUG调试
程序启动突然报错异常结束,但是没有输出任何原因,通过输出大法大概确定了位置,然后DEBUG调试到某一行代码弹出下面框,"程序因接收到操作系统的信号而停止" 这一错误消息通常表示程序在运行时遇到了某些严重问题,操作系统发出了终止信号。
16:43:54: 程序异常结束。
16:43:54: The process was ended forcefully.
SIGSEGV
SIGSEGV
(Segmentation Fault,段错误)通常发生在程序试图访问非法的内存地址时,或者试图进行越界的内存访问。例如:
- 访问未初始化的指针
- 解引用空指针(NULL)
- 访问已经释放的内存(悬空指针)
- 数组越界
- 使用无效的指针
- 空指针访问
检查是否有出现上面的情况。
SIGFPE
SIGFPE
是 "Floating Point Exception" 的缩写,表示程序在执行过程中发生了与浮点数运算相关的错误。通常,这种错误出现在以下几种情况:
- 除以零:如果程序尝试将一个浮点数除以零,就会触发
SIGFPE
信号。 - 无效的浮点数操作:比如计算平方根负数(在没有复数支持的情况下)或者其他非法的浮点数操作。
- 溢出:浮点数运算超出了可表示的数值范围。
- 下溢:计算结果过小,无法表示为正常的浮点数。0
Unknown signal
情况1:访问数组越界会报这个异常
Cannot retrieve debugging output.
问题描述:
项目中并没有定义QT_NO_DEBUG_OUTPUT,在使用Qt Creator调试程序时,IDE的“应用程序输出”提示:Cannot retrieve debugging output解决方案:
发现开了两个Qt Creator,而另外一个Qt Creator也正在调试输出,只是那是个没有界面的程序,导致被我忽略了!把另外一个Qt Creator进程结束掉(只打开一个IDE)就可以看到调试输出了
原文链接:https://blog.csdn.net/hellokandy/article/details/106279415
multiple definition
"multiple definition" 错误通常是因为某个函数或变量被重复定义。
可能的原因:
-
函数在头文件中被重复定义: 如果
PRITypeToString
函数在头文件中直接定义,而这个头文件又被多个源文件包含,就会导致多个源文件中都有该函数的定义,从而引发"multiple definition"错误。 -
未正确使用
inline
或extern
: 如果该函数只是声明而没有定义(或在多个地方定义),需要使用inline
或extern
关键字来避免重复定义。
QT-Creator调试报错
解决QT无法调试问题-----the cdb process terminated - 南枝 - 博客园
数据结构
QMap
在 Qt 中,QMap
是一个模板类,用于存储键值对数据结构。它类似于标准库中的 std::map
,但是针对 Qt 的特性进行了优化,提供了一些额外的功能。
QMap
是一个有序的键值对容器,键是唯一的,值可以重复。
#include <QMap>
#include <QString>
#include <QDebug>
int main() {
QMap<QString, int> map;
// 插入键值对
map.insert("Alice", 25);
map.insert("Bob", 30);
map.insert("Charlie", 35);
// 使用下标操作符插入或更新
map["David"] = 40;
// 输出所有键值对
for (auto it = map.begin(); it != map.end(); ++it) {
qDebug() << it.key() << ":" << it.value();
}
// 使用 value 获取指定键的值:
qDebug() << map.value("Alice"); // 输出 25
map.remove("Charlie");
if (map.contains("Bob")) {
qDebug() << "Bob is in the map.";
}
qDebug() << map.value("Unknown", -1); // 输出 -1
// 迭代器
QMapIterator<QString, int> i(map);
while (i.hasNext()) {
i.next();
qDebug() << i.key() << ":" << i.value();
}
return 0;
}
QVector
QVector
是 Qt 框架提供的动态数组容器,类似于 C++ 标准库的 std::vector
,用于高效地存储和管理一组对象。它适用于需要快速随机访问和动态增长的场景。
#include <QVector>
#include <QDebug>
int main() {
QVector<int> numbers; // 创建一个整数类型的 QVector
// 添加元素
numbers.append(10);
numbers.append(20);
numbers << 30 << 40; // 另一种添加元素的方式
// 访问元素
qDebug() << "First element:" << numbers.first();
qDebug() << "Last element:" << numbers.last();
qDebug() << "Element at index 1:" << numbers.at(1);
// 遍历方式1:基于索引
for (int i = 0; i < numbers.size(); ++i) {
qDebug() << "Index" << i << ":" << numbers[i];
}
// 遍历方式2:基于迭代器
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
qDebug() << "Iterator value:" << *it;
}
return 0;
}
resize
void QVector<T>::resize(int size)
调整向量大小:将向量长度修改为指定的
size
。扩容行为:
如果
size > 当前大小
:在末尾添加默认构造的元素(对于基本类型是零初始化,对于类类型调用默认构造函数)。如果
size < 当前大小
:从末尾删除多余元素(会调用元素的析构函数)。QVector<int> vec; vec.resize(5); // 现在 vec = [0, 0, 0, 0, 0] vec.resize(3); // 截断为 [0, 0, 0] vec.resize(5); // 扩展为 [0, 0, 0, 0, 0]
reserve
append
用于在向量的末尾添加元素
#include <QVector> #include <QDebug> int main() { QVector<int> vec; // 使用 append 添加元素 vec.append(10); vec.append(20); vec.append(30); // 也可以使用 << 运算符(效果相同) vec << 40 << 50; // 输出 QVector 的内容 qDebug() << vec; // 输出: QVector(10, 20, 30, 40, 50) return 0; }
QList
QList
是一个动态数组,它允许存储任何类型的元素,并自动管理内存。相比传统的数组,QList
更加灵活,能够动态扩展,支持高效的随机访问和插入。
QList<PDW_Origin> list; list.append(PDW_Origin()); // 添加一个 PDW_Origin 对象 list.append(PDW_Origin()); // 再添加一个 qDebug() << "list size:" << list.size(); // 输出: 2
- 对象是按值存储的(即
QList<PDW_Origin>
直接存储PDW_Origin
,不会存储指针)。- QList 负责内存管理,list 的作用域结束,它的内存会被自动释放,并且
QList
内部存储的PDW_Origin
对象会被 自动销毁。你可以动态添加或删除元素。- 作用域结束时,QList 会自动销毁所有存储的对象,无需手动释放内存。
list
是一个 QList 容器,它存储 PDW_Origin 类型的对象。QList<PDW_Origin*> list; PDW_Origin* pdw = new PDW_Origin(); // 动态创建对象 list.append(pdw); // 添加指针到列表 delete pdw; // 手动释放内存
list
是一个QList
容器,其中 存储的是PDW_Origin*
指针,即每个元素是一个指向PDW_Origin
对象的指针。- 内存管理:由于
list
只存储指针,因此,指针指向的对象必须手动分配内存(通常使用new
),并且 手动释放内存(使用delete
或delete[]
)。list
本身并不会负责指针所指向对象的内存管理。- 适用于 需要动态分配内存且希望管理指针的集合。
PDW_Origin *pdws = new PDW_Origin[10]; // 在堆上创建 10 个 PDW_Origin 对象 pdws[0].someMethod(); // 访问数组元素 pdws[1].someVariable = 100; // 修改成员变量 delete[] pdws; // 释放数组内存,避免泄漏 pdws = nullptr; // 避免野指针
pdws
是一个指针,指向 堆(heap)上 分配的 10 个 PDW_Origin 对象 的数组。- 这些对象是通过 默认构造函数 逐个初始化的。
- 需要手动释放内存,否则会造成 内存泄漏。
- 数组大小固定(分配后不能调整)。
排序
QList<int> rowList= elseRowSet.toList();
std::sort(rowList.rbegin(), rowList.rend());
std::sort 是 C++ 标准库中的排序函数,用于对容器中的元素进行排序。
rbegin() 和 rend() 返回的是 QList 的反向迭代器,分别指向列表的最后一个元素和第一个元素。
通过传递反向迭代器给 std::sort,你可以实现倒序排序,也就是从大到小排序。
QVector<QLineEdit*> classicParamLineVetor; 使用普通成员变量,自动管理内存
// 使用指向 QVector 的引用列表
QList<QVector<QLineEdit*>*> loopLineVectors = {&classicParamLineVetor, ¶mSeparateLineVetor, &fingerFeatureLineVetor, &entityRecognizeLineVetor};
QVector<QLineEdit*>& lineEditVetor = *loopLineVectors[0]; // 使用解引用获取引用
这样往lineEditVetor添加元素,就是操作classicParamLineVetor数组。
错误方式:
QVector<QLineEdit*> classicParamLineVetor;
QList<QVector<QLineEdit*>> loopLineVectors = {classicParamLineVetor,paramSeparateLineVetor,fingerFeatureLineVetor,entityRecognizeLineVetor};
QVector<QLineEdit*>& lineEditVetor = loopLineVectors[i];
// 这个好像是因为超过了对象使用范围会报错。
QSet
qss样式
QLabel {
background-color: #90ee90; /* 浅绿色 */
border: 2px solid #006400; /* 深绿色边框 */
border-radius: 5px; /* 圆角边框 */
}
/*背景透明*/
background-color: transparent;
组件
QWidget
QWidget
是 Qt 中所有 UI 元素(如窗口、按钮、标签、文本框等)的基类。它是 Qt 的核心元素之一,所有可视化控件和容器类都直接或间接继承自 QWidget
。通过 QWidget
,我们可以创建和管理应用程序的用户界面。
- setWindowTitle():设置窗口的标题。
- resize():改变窗口的大小。
- move():移动窗口到指定位置。
- setGeometry():同时设置窗口的位置和大小。
- setStyleSheet():使用 Qt 样式表自定义控件的外观。
- setFixedSize():设置固定大小,无法调整窗口大小。
- show():显示窗口。
- hide():隐藏窗口。
setSizePolicy
用于设置 Qt 控件的大小策略(QSizePolicy
),控件的大小策略决定了它如何响应父控件的大小变化。
setSizePolicy
接受两个参数,分别指定控件在水平方向和垂直方向上的大小策略。常见的大小策略包括:
QSizePolicy::Fixed
:控件的大小是固定的,不允许调整。QSizePolicy::Minimum
:控件可以缩小到它的最小尺寸,但不允许扩展。QSizePolicy::Maximum
:控件可以扩展到它的最大尺寸,但不能缩小。QSizePolicy::Preferred
:控件在可能的情况下,会根据它的首选大小来调整。QSizePolicy::Expanding
:控件会尽可能地扩展,占据可用的空间。QSizePolicy::Ignored
:控件完全忽略大小的调整。
setFont
// 创建字体对象
QFont font;
font.setFamily("Arial"); // 设置字体家族
font.setPointSize(12); // 设置字体大小
font.setBold(true); // 设置为粗体
font.setItalic(false); // 设置为非斜体
// 将字体应用到QWidget
setFont(font);
// 使用样式表设置字体
setStyleSheet("font-family: Arial; font-size: 12pt; font-weight: bold;");
QStackedWidget
弹簧
在 Qt 中,通过在布局(如 QHBoxLayout
或 QVBoxLayout
)中添加弹簧,可以控制界面元素之间的间距和位置。弹簧会占据多余的空间,从而帮助实现元素的动态布局。
水平弹簧
水平弹簧可以让元素在水平方向上分布均匀。
QWidget *widget = new QWidget(); QHBoxLayout *hLayout = new QHBoxLayout(widget); QPushButton *button1 = new QPushButton("按钮1", widget); QPushButton *button2 = new QPushButton("按钮2", widget); hLayout->addWidget(button1); hLayout->addStretch(); // 添加水平弹簧 hLayout->addWidget(button2); widget->setLayout(hLayout);
垂直弹簧
垂直弹簧可以在垂直方向上调整元素的位置。
QWidget *widget = new QWidget(); QVBoxLayout *vLayout = new QVBoxLayout(widget); QPushButton *button1 = new QPushButton("按钮1", widget); QPushButton *button2 = new QPushButton("按钮2", widget); vLayout->addWidget(button1); vLayout->addStretch(); // 添加垂直弹簧 vLayout->addWidget(button2); widget->setLayout(vLayout);
QCustomPlot
Qt Plotting Widget QCustomPlot - Introduction
QCPGraph
、QCPGraphDataContainer
和QCPGraphData
之间有着紧密的关系:
QCPGraph
是QCustomPlot
中用来表示数据图形的对象,通常一个QCustomPlot
中可以包含多个QCPGraph
(即多个曲线)。每个QCPGraph
显示的是一组数据点,并通过key
(通常是 X 轴)和value
(通常是 Y 轴)来表示这些数据。
QCPGraphDataContainer
是一个容器类,用来存储QCPGraph
中的所有数据点。用途:
QCPGraphDataContainer
是QCPGraph
存储数据的地方,它负责管理数据点的集合。通过它,你可以访问、插入、删除或修改图形的具体数据
QCPGraphData
是一个简单的结构体,它表示单个数据点。每个QCPGraphData
包含两个成员:key
(X 坐标)和value
(Y 坐标)。
QCustomPlot包含多个QCPGraph
图像对象(曲线),每个QCPGraph
有一个QCPGraphDataContainer
用于存储数据点,QCPGraphDataContainer
包含多个QCPGraphData
数据点。
QCPGraph
QCPGraph
是QCustomPlot
中用来表示数据图形的对象,通常一个QCustomPlot
中可以包含多个QCPGraph
(即多个曲线)。每个QCPGraph
显示的是一组数据点,并通过key
(通常是 X 轴)和value
(通常是 Y 轴)来表示这些数据。
getOptimizedLineData
方法:
QCPGraph::getOptimizedLineData
是QCustomPlot
中QCPGraph
类的一个成员函数,它的作用是从图形的数据容器中获取经过优化的数据点,以便更高效地绘制图形。这个方法通常用于在图形的渲染过程中,优化图形绘制的性能,减少不必要的计算和数据处理。
getOptimizedLineData
方法的作用是从QCPGraphDataContainer
中获取一段范围(由begin
和end
标定)的数据,并对这些数据进行优化处理(如减少不必要的数据点、平滑曲线等),然后将优化后的数据存储到lineData
中,以便后续图形绘制使用。
拉伸方向
customPlot->axisRect()->setRangeZoom(Qt::Horizontal); // 仅允许 X 轴缩放
customPlot->axisRect()->setRangeZoom(Qt::Vertical); // 仅允许 Y 轴缩放
customPlot->axisRect()->setRangeZoom(Qt::Horizontal | Qt::Vertical); // XY 轴都允许缩放
内部方法
QCustomPlot::replot()
是QCustomPlot
提供的 重绘 方法,用于刷新并重新绘制整个绘图区域。调用此方法后,所有曲线、轴、网格等都会被重新渲染,以反映数据的最新变化。customPlot->replot(QCustomPlot::rpImmediateRefresh);
枚举值 作用 rpImmediateRefresh
立即刷新并强制重绘整个窗口,适用于需要立即更新的情况,但会增加 CPU 负担。 rpQueuedRefresh
延迟刷新,等待 Qt 的事件循环处理完其他 UI 事件后再重绘,适用于高频更新的情况(减少 CPU 占用)。 rpRefreshHint
自动选择合适的刷新方式,通常更高效。 replot()
最终会执行以下关键步骤:
准备绘制数据:
每个QCPGraph
会调用getOptimizedLineData
生成优化后的数据点(用于实际绘制)。执行绘制:
使用优化后的数据渲染曲线。
属性结构
QVector<DataType> mData
作用:存储绘图数据的核心容器(如 QCPGraphData 或 QCPBarsData)。数据类型:
对于曲线图(QCPGraph),DataType 是 QCPGraphData(包含 key 和 value)。
对于柱状图(QCPBars),DataType 是 QCPBarsData。QCPGraph
QCPGraph 是 QCustomPlot 中用来表示数据图形的对象,通常一个 QCustomPlot 中可以包含多个 QCPGraph(即多个曲线)。
每个 QCPGraph 显示的是一组数据点,并通过 key(通常是 X 轴)和 value(通常是 Y 轴)来表示这些数据。QCPGraphDataContainer
QCPGraphDataContainer 是一个容器类,用来存储 QCPGraph 中的所有数据点。
用途:QCPGraphDataContainer 是 QCPGraph 存储数据的地方,它负责管理数据点的集合。通过它,你可以访问、插入、删除或修改图形的具体数据QCPGraphData
QCPGraphData 是一个简单的结构体,它表示单个数据点。每个 QCPGraphData 包含两个成员:key(X 坐标)和 value(Y 坐标)。关系:
QCustomPlot包含多个QCPGraph图像对象(曲线),每个QCPGraph有一个QCPGraphDataContainer 用于存储数据点,
QCPGraphDataContainer 包含多个QCPGraphData 数据点。
QGroupBox
QTableView
QTableView
是 Qt 中的一个视图类,用于显示表格数据。它是 QAbstractItemView
类的子类,可以与不同的数据模型(如 QStandardItemModel
)配合使用来显示数据。
功能:
- 显示数据:
QTableView
会使用一个数据模型来填充表格,并以表格的形式展示数据。 - 编辑: 默认情况下,
QTableView
允许对数据进行编辑(通过双击单元格进行编辑)。可以控制哪些单元格可以编辑,哪些不能。 - 排序:
QTableView
支持对表格进行排序,排序功能通常由模型提供。 - 自定义显示: 你可以自定义单元格的显示方式,例如背景颜色、字体、对齐方式等。
- 选择:
QTableView
提供了选择模式,允许用户选择单元格、行或列。 - 头部: 支持显示行头和列头,方便用户进行排序和交互。
主要方法:
setModel(QAbstractItemModel *model)
:设置数据模型。常见的模型有QStandardItemModel
和QSqlTableModel
等。setItemDelegate(QAbstractItemDelegate *delegate)
:设置委托,用于自定义单元格的编辑和显示方式。setSelectionMode()
、setSelectionBehavior()
:设置选择模式和行为。resizeColumnsToContents()
:调整列宽,以适应每列中内容的最大宽度。
#include <QApplication>
#include <QTableView>
#include <QStandardItemModel>
#include <QStandardItem>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建一个 QTableView 控件
QTableView tableView;
// 创建一个 QStandardItemModel 数据模型
QStandardItemModel model(3, 3); // 3 行 3 列
// 向模型中添加数据
model.setHorizontalHeaderLabels({"Name", "Age", "Gender"});
model.setItem(0, 0, new QStandardItem("John"));
model.setItem(0, 1, new QStandardItem("25"));
model.setItem(0, 2, new QStandardItem("Male"));
model.setItem(1, 0, new QStandardItem("Alice"));
model.setItem(1, 1, new QStandardItem("30"));
model.setItem(1, 2, new QStandardItem("Female"));
model.setItem(2, 0, new QStandardItem("Bob"));
model.setItem(2, 1, new QStandardItem("22"));
model.setItem(2, 2, new QStandardItem("Male"));
// 设置数据模型给 QTableView
tableView.setModel(&model);
// 显示表格
tableView.show();
return a.exec();
}
QStandardItemModel
QStandardItemModel
是 Qt 中用于表示表格数据的标准模型类。它是 QAbstractItemModel
的子类,适合用于小型到中型的表格数据管理。
QSortFilterProxyModel
QDialog
QDialog
是 Qt 中用于创建对话框窗口的基类。它通常用于处理用户交互、显示信息、获取用户输入或提供一些设置等功能。QDialog
可以是模态或非模态的。
布局
QGridLayout
关键字
static
static float AOA = 0;
static
关键字使得变量的生命周期为整个程序的运行期间,即变量会在函数外部存在,且它的值不会丢失。每次函数调用时,AOA
的值不会被重置,而是保持上次调用时的值。也就是说,它的作用域是函数内,但生命周期是程序整个运行周期。
运算符
取余
%
运算符仅适用于整数类型,不能直接用于double
类型。如果你想对double
类型进行取余操作,可以使用std::fmod
函数
对象创建方式
栈上创建对象(自动管理内存)
void createObject() { QPushButton button("Click Me"); // 在栈上创建对象 button.show(); } // 作用域结束,button 自动释放 PDW_Origin pdw; // 会调用无参构造创建对象
- 这种方式适用于生命周期较短的对象,它们在离开作用域时会自动释放,不需要手动管理内存,离开作用域自动释放。不需要手动
delete
。- 运行效率高,分配和释放开销小。
- 缺点是作用域结束后对象被销毁,不能在作用域外使用。
- 当不需要在多个共享这个对象的时候,就可以使用普通对象。
堆上创建对象(手动管理内存)
void createObject() { QPushButton *button = new QPushButton("Click Me"); // 在堆上创建对象 button->show(); delete button; // 需要手动释放内存 } PDW_Origin *pdw2; // 是指针,不会自动创建对象,必须手动 new
- Qt 中,通常使用
new
关键字在堆上创建对象。这样对象的生命周期不受函数作用域限制,但需要手动delete
释放,或者设置父对象自动管理。- 缺点是需要手动
delete
,否则会导致内存泄漏。
现代 C++ 推荐使用智能指针(
std::unique_ptr
和std::shared_ptr
),而不是裸指针:#include <memory> std::unique_ptr<PDW_Origin> pdw3 = std::make_unique<PDW_Origin>(); // 自动管理 pdw3->show(); // 像普通指针一样使用
这样可以自动释放内存,避免内存泄漏。
参数传递方式
值传递(pass by value)
QVector<double> showTableview(QList<QPoint> listPosX);
listPosX
作为值传递参数,调用时会拷贝整个QList<QPoint>
对象。- 在函数内部对
listPosX
进行的修改不会影响原来的QList<QPoint>
。- 由于
QList
内部使用了 隐式共享(Copy-on-Write, COW) 机制,所以如果函数内部没有修改listPosX
,那么不会真正拷贝数据,而只是增加引用计数,性能开销较小。- 但如果
listPosX
在函数内被修改(比如append()
、removeAt()
),那么会触发深拷贝,导致额外的性能开销。
指针传递(pass by pointer)
QVector<double> showTableview2(QList<QPoint> *listPosX);
函数需要传递QList<QPoint>类型的指针
QList<QPoint> myList; QVector<double> result = showTableview2(&myList);
- 在函数内部,可以直接修改
listPosX
指向的原始数据,调用者会看到修改后的结果。- 一般不推荐使用指针传递,而是使用引用传递(见
showTableview3
),除非确实需要支持nullptr
作为无效输入。
引用传递(pass by reference)
QVector<double> showTableview3(QList<QPoint> &listPosX); QList<QPoint> myList; // 创建 QList<QPoint> 变量 myList.append(QPoint(10, 20)); myList.append(QPoint(30, 40)); QVector<double> result = showTableview3(myList); // 传递 myList
- 传递的是
QList<QPoint>
的引用,不会产生拷贝,也不会触发QList
的 COW 机制。- 在函数内部对
listPosX
进行的修改,调用者能看到(与指针传递类似)。- 但不能传递
nullptr
,引用必须绑定到实际对象。- 更安全,因为引用不能为
nullptr
,不像指针那样可能会导致空指针访问错误。