main.cpp
//基本形状显示了 Qt 3D 提供的四种基本形状:圆环、圆柱、立方体和球体。
//该示例还展示了如何将 Qt 3D 场景嵌入到小部件中并与其他小部件连接。
#include "scenemodifier.h"
#include <QGuiApplication>
#include <Qt3DRender/qcamera.h>
#include <Qt3DCore/qentity.h>
#include <Qt3DRender/qcameralens.h>
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QCommandLinkButton>
#include <QtGui/QScreen>
#include <Qt3DExtras/qtorusmesh.h>
#include <Qt3DRender/qmesh.h>
#include <Qt3DRender/qtechnique.h>
#include <Qt3DRender/qmaterial.h>
#include <Qt3DRender/qeffect.h>
#include <Qt3DRender/qtexture.h>
#include <Qt3DRender/qrenderpass.h>
#include <Qt3DRender/qsceneloader.h>
#include <Qt3DRender/qpointlight.h>
#include <Qt3DCore/qtransform.h>
#include <Qt3DCore/qaspectengine.h>
#include <Qt3DRender/qrenderaspect.h>
#include <Qt3DExtras/qforwardrenderer.h>
#include <Qt3DExtras/qt3dwindow.h>
#include <Qt3DExtras/qfirstpersoncameracontroller.h>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Qt3DExtras::Qt3DWindow *view = new Qt3DExtras::Qt3DWindow();
view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x4d4d4f)));
//创建一个 QWidget,可以将窗口嵌入到基于 QWidget 的应用程序中。
//窗口容器被创建为父级的子级并带有窗口标志标志。
//一旦窗口被嵌入到容器中,容器将控制窗口的几何形状和可见性。
//不建议在嵌入式窗口上显式调用 QWindow::setGeometry()、QWindow::show() 或 QWindow::hide()。
//容器接管窗口的所有权。 可以通过调用 QWindow::setParent() 从窗口容器中删除窗口。
//窗口容器作为本机子窗口附加到它是其子级的顶级窗口。
//当窗口容器用作 QAbstractScrollArea 或 QMdiArea 的子级时,
//它将为其父链中的每个小部件创建一个本机窗口,以允许在此用例中进行适当的堆叠和剪辑。
//为窗口容器创建本机窗口还允许正确堆叠和剪切。 这必须在显示窗口容器之前完成。 具有许多本机子窗口的应用程序可能会遇到性能问题。
//窗口容器有许多已知的限制:
//堆叠顺序;嵌入的窗口将作为不透明框堆叠在小部件层次结构的顶部。多个重叠窗口容器实例的堆叠顺序未定义。
//渲染集成;窗口容器不与 QGraphicsProxyWidget、QWidget::render() 或类似功能互操作。
//焦点处理;可以让窗口容器实例具有任何焦点策略,它会通过调用 QWindow::requestActivate()
//将焦点委托给窗口。然而,从 QWindow 实例返回到正常的焦点链将取决于 QWindow 实例实现本身。例如,当进入一个带有标签焦点的基于 Qt Quick 的窗口时,很可能进一步按下标签只会在 QML 应用程序内循环。
//此外, QWindow::requestActivate() 是否实际提供窗口焦点,取决于平台。
//在基于 QWidget 的应用程序中使用许多窗口容器实例会极大地损害应用程序的整体性能。
QWidget *container = QWidget::createWindowContainer(view);
QSize screenSize = view->screen()->size();
container->setMinimumSize(QSize(200, 100));
container->setMaximumSize(screenSize);
//QWidget 类是所有用户界面对象的基类
//小部件是用户界面的原子:它从窗口系统接收鼠标、键盘和其他事件,并在屏幕上绘制自己的表示。
//每个小部件都是矩形的,它们按 Z 顺序排序。 小部件被其父部件和它前面的小部件剪裁。
//未嵌入父窗口小部件的窗口小部件称为窗口。通常,窗口有一个框架和一个标题栏,尽管也可以使用合适的窗口标志创建没有这种装饰的窗口。
//在 Qt 中,QMainWindow 和 QDialog 的各种子类是最常见的窗口类型。
//每个小部件的构造函数都接受一两个标准参数:
//QWidget *parent = nullptr 是新小部件的父级。 如果它是 nullptr(默认值),新的小部件将是一个窗口。 如果不是,
//它将是父级的子级,并受父级几何图形的约束(除非您将 Qt::Window 指定为窗口标志)。
//Qt::WindowFlags f = { } (如果可用)设置窗口标志; 默认值适用于几乎所有小部件,但要获得例如没有窗口系统框架的窗口,您必须使用特殊标志。
//QWidget 有很多成员函数,但其中一些没有直接的功能;
//例如, QWidget 有一个 font 属性,但从不使用它本身。
//有许多提供真正功能的子类,例如 QLabel、QPushButton、QListWidget 和 QTabWidget。
//顶级和子小部件
//没有父窗口小部件的窗口小部件始终是一个独立的窗口(顶级窗口小部件)。 对于这些小部件, setWindowTitle() 和 setWindowIcon() 分别设置标题栏和图标。
//复合小部件
//当一个小部件用作容器来分组多个子小部件时,它被称为复合小部件。这些可以通过构建具有所需视觉属性的小部件(例如 QFrame)并向其添加子小部件(通常由布局管理)来创建。
//上图显示了这样一个使用 Qt Designer 创建的复合小部件。
//也可以通过对标准小部件(例如 QWidget 或 QFrame)进行子类化,并在子类的构造函数中添加必要的布局和子小部件来创建复合小部件。 Qt 提供的许多示例都使用这种方法,Qt 教程中也介绍了这种方法。
//自定义小部件和绘画
QWidget *widget = new QWidget;
//首先,我们创建要添加到布局中的小部件。 然后,我们创建 QHBoxLayout 对象,
//通过在构造函数中传递它来将窗口设置为父级; 接下来我们将小部件添加到布局中。
//widget 将是添加到布局中的小部件的父级。
//如果您没有在构造函数中传递父窗口,您可以稍后使用
//QWidget::setLayout() 将 QHBoxLayout 对象安装到窗口上。
//此时,布局中的小部件将重新设置父级以将 widget 作为其父级。
QHBoxLayout *hLayout = new QHBoxLayout(widget);
QVBoxLayout *vLayout = new QVBoxLayout();
vLayout->setAlignment(Qt::AlignTop);
hLayout->addWidget(container, 1);
hLayout->addLayout(vLayout);
//该宏在编译时从字符串文字 str 中生成 QString 的数据。 在这种情况下,
//从它创建 QString 是没有代价的,生成的字符串数据存储在编译目标文件的只读段中。
//如果您的代码如下所示:
/*
//hasAttribute 接受一个 QString 参数
if(node.hasAttribute("http-contents-length"))
*/
//然后将创建一个临时 QString 作为 hasAttribute 函数参数传递。
//这可能非常昂贵,因为它涉及内存分配和将数据复制/转换为 QString 的内部编码。
//可以通过使用 QStringLiteral 来避免此成本:
//if(node.hasAttribute(QStringLiteral(u"http-contents-length")))
//在这种情况下,QString 的内部数据会在编译时生成; 运行时不会发生转换或分配。
//使用 QStringLiteral 代替双引号普通 C++ 字符串文字可以显着加快从编译时已知数据创建 QString 实例的速度。
widget->setWindowTitle(QStringLiteral("Basic shapes"));
// Root entity
//Qt3DCore::QEntity 对象的行为由它引用的 Qt3DCore::QComponent 对象定义。
//每个 Qt3D 后端方面都能够通过识别实体由哪些组件组成来解释和处理实体。
Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity();
// Camera
//QCamera 类定义了一个视点,通过该视点渲染场景
Qt3DRender::QCamera *cameraEntity = view->camera();
//定义基于 fieldOfView、aspectRatio、nearPlane、farPlane 的透视投影。
cameraEntity->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
//将相机在 3D 空间中的位置设置为 position。
cameraEntity->setPosition(QVector3D(0, 0, 20.0f));
//将相机的Up向量设置为 upVector。
cameraEntity->setUpVector(QVector3D(0, 1, 0));
//将相机的视图中心设置为 viewCenter。
cameraEntity->setViewCenter(QVector3D(0, 0, 0));
Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(rootEntity);
//在 Qt 3D 场景中封装点光源对象
//用指定的父级构造一个新的 QPointLight。
Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight(lightEntity);
light->setColor("white");
light->setIntensity(1);
//添加light组建
//如果 Qt3DCore::QComponent 没有父级,则 Qt3DCore::QEntity 将自己设置为其父级,从而获得组件的所有权。
lightEntity->addComponent(light);
//构建一个对灯光的变换
Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity);
//设置lightTransform的平移坐标
lightTransform->setTranslation(cameraEntity->position());
//对添加lightTransform 变换
lightEntity->addComponent(lightTransform);
// 用于相机控制
//QFirstPersonCameraController 类允许从第一人称视角控制场景相机
Qt3DExtras::QFirstPersonCameraController *camController = new Qt3DExtras::QFirstPersonCameraController(rootEntity);
//保存当前控制的摄像机。
camController->setCamera(cameraEntity);
// 场景修改器
SceneModifier *modifier = new SceneModifier(rootEntity);
// 设置场景的根对象
view->setRootEntity(rootEntity);
// 创建控制小部件
//QCommandLinkButton 小部件提供了一个 Vista 风格的命令链接按钮
QCommandLinkButton *info = new QCommandLinkButton();
//此属性保存按钮上显示的文本
info->setText(QStringLiteral("Qt3D ready-made meshes"));
//此属性包含一个描述性标签以补充按钮文本
//设置此属性将在按钮上设置描述性文本,补充文本标签。 这通常会以比主要文本更小的字体显示。
info->setDescription(QString::fromLatin1("Qt3D provides several ready-made meshes, like torus, cylinder, cone, "
"cube, plane and sphere."));
//设置用于此按钮的图标大小。
//默认大小由 GUI 样式定义。 这是图标的最大尺寸。 较小的图标不会放大。
info->setIconSize(QSize(0,0));
//QCheckBox 小部件提供了一个带有文本标签的复选框
//QCheckBox 是一个选项按钮,可以打开(选中)或关闭(未选中)。
//复选框通常用于表示应用程序中可以启用或禁用而不影响其他功能的功能。
//可以实现不同类型的行为。。
//例如,一个 QButtonGroup 可用于逻辑分组检查按钮,允许独占复选框。
//但是, QButtonGroup 不提供任何视觉表示。
//每当一个
//复选框被选中或清除,它发出信号 stateChanged()。
//如果您想在每次复选框更改状态时触发操作,请连接到此信号。 您可以使用 isChecked() 来查询是否选中了复选框。
//除了通常的选中和未选中状态之外,QCheckBox 还可以选择提供第三种状态来指示“无变化”。
//当您需要为用户提供既不选中也不取消选中复选框的选项时,这很有用。
//如果您需要这第三个状态,请使用 setTristate() 启用它,并使用 checkState() 查询当前切换状态。
QCheckBox *torusCB = new QCheckBox(widget);
torusCB->setChecked(true);
torusCB->setText(QStringLiteral("Torus"));
QCheckBox *coneCB = new QCheckBox(widget);
coneCB->setChecked(true);
coneCB->setText(QStringLiteral("Cone"));
QCheckBox *cylinderCB = new QCheckBox(widget);
cylinderCB->setChecked(true);
cylinderCB->setText(QStringLiteral("Cylinder"));
QCheckBox *cuboidCB = new QCheckBox(widget);
cuboidCB->setChecked(true);
cuboidCB->setText(QStringLiteral("Cuboid"));
QCheckBox *planeCB = new QCheckBox(widget);
planeCB->setChecked(true);
planeCB->setText(QStringLiteral("Plane"));
QCheckBox *sphereCB = new QCheckBox(widget);
sphereCB->setChecked(true);
sphereCB->setText(QStringLiteral("Sphere"));
//将小部件添加到vLayout布局。 此函数使用 addItem()。
vLayout->addWidget(info);
vLayout->addWidget(torusCB);
vLayout->addWidget(coneCB);
vLayout->addWidget(cylinderCB);
vLayout->addWidget(cuboidCB);
vLayout->addWidget(planeCB);
vLayout->addWidget(sphereCB);
//一个信号可以连接到许多插槽和信号。许多信号可以连接到一个插槽。
//如果一个信号连接到多个插槽,则在发出信号时,这些插槽将按照建立连接的相同顺序被激活。
//该函数返回一个 QMetaObject::Connection,如果它成功地将信号连接到插槽,则它表示连接的句柄。
//如果无法创建连接,则连接句柄将无效,
//例如,如果 QObject 无法验证信号或方法是否存在,或者它们的签名不兼容。
//您可以通过将其转换为 bool 来检查句柄是否有效。
//默认情况下,您建立的每个连接都会发出一个信号;
//为重复连接发出两个信号。
//您可以通过一个 disconnect() 调用断开所有这些连接。
//如果您传递 Qt::UniqueConnection 类型,则只有在它不是重复的情况下才会建立连接。
//如果已经有重复(相同对象上完全相同的插槽的完全相同的信号),则连接将失败并且连接将返回无效的 QMetaObject::Connection。
//注意:Qt::UniqueConnections 不适用于 lambda、非成员函数和函子; 它们仅适用于连接到成员函数。
//可选的类型参数描述了要建立的连接的类型。
//特别地,它确定特定信号是立即传送到时隙还是排队等待稍后传送。
//如果信号排队,参数必须是 Qt 元对象系统已知的类型,因为 Qt 需要复制参数以将它们存储在幕后的事件中。
//如果您尝试使用排队连接并收到错误消息
QObject::connect(torusCB, &QCheckBox::stateChanged,
modifier, &SceneModifier::enableTorus);
QObject::connect(coneCB, &QCheckBox::stateChanged,
modifier, &SceneModifier::enableCone);
QObject::connect(cylinderCB, &QCheckBox::stateChanged,
modifier, &SceneModifier::enableCylinder);
QObject::connect(cuboidCB, &QCheckBox::stateChanged,
modifier, &SceneModifier::enableCuboid);
QObject::connect(planeCB, &QCheckBox::stateChanged,
modifier, &SceneModifier::enablePlane);
QObject::connect(sphereCB, &QCheckBox::stateChanged,
modifier, &SceneModifier::enableSphere);
torusCB->setChecked(true);
coneCB->setChecked(true);
cylinderCB->setChecked(true);
cuboidCB->setChecked(true);
planeCB->setChecked(true);
sphereCB->setChecked(true);
// Show window
widget->show();
widget->resize(1200, 800);
//exec 进入主事件循环并等待 exit() 被调用,
//然后返回设置为 exit() 的值(如果 exit() 通过 quit() 调用,则为 0)。
//需要调用这个函数来启动事件处理。 主事件循环从窗口系统接收事件并将这些事件分派给应用程序小部件。
//通常,在调用 exec() 之前不能进行用户交互。
//作为一种特殊情况,可以在调用 exec() 之前使用像 QMessageBox 这样的模态小部件,因为模态小部件调用 exec() 来启动本地事件循环。
//要使您的应用程序执行空闲处理,即在没有挂起事件时执行特殊功能,请使用超时为 0 的 QTimer。
//使用 processEvents() 可以实现更高级的空闲处理方案。
//我们建议您将清理代码连接到 aboutToQuit() 信号,
//而不是将它放在应用程序的 main() 函数中。
//。 这是因为,在某些平台上, QApplication::exec() 调用可能不会返回。
//例如,在Windows平台上,当用户注销时,系统会在Qt关闭所有顶级窗口后终止进程。
//因此,无法保证应用程序在调用 QApplication::exec() 之后有时间退出其事件循环并在 main() 函数的末尾执行代码。
return app.exec();
}
scenemodifier.h
#ifndef SCENEMODIFIER_H
#define SCENEMODIFIER_H
#include <QtCore/QObject>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qtransform.h>
#include <Qt3DExtras/QTorusMesh>
#include <Qt3DExtras/QConeMesh>
#include <Qt3DExtras/QCylinderMesh>
#include <Qt3DExtras/QCuboidMesh>
#include <Qt3DExtras/QPlaneMesh>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QPhongMaterial>
class SceneModifier : public QObject
{
Q_OBJECT
//QObject 类是所有 Qt 对象的基类。
//QObject 是 Qt 对象模型的核心。
//该模型的核心特征是一种非常强大的机制,用于无缝对象通信,称为信号和槽。
//您可以使用 connect() 将信号连接到插槽并使用 disconnect() 破坏连接。
//为了避免永无止境的通知循环,您可以使用 blockSignals() 临时阻止信号。
//受保护的函数 connectNotify() 和 disconnectNotify() 使跟踪连接成为可能。
//QObjects 在对象树中组织自己。
//当您使用另一个对象作为父对象创建 QObject 时,该对象将自动将自己添加到父对象的 children() 列表中。
//父对象拥有对象的所有权;
//即,它将在其析构函数中自动删除其子项。
//您可以使用 findChild() 或 findChildren() 按名称和可选的类型查找对象。
//每个对象都有一个 objectName(),它的类名可以通过相应的 metaObject() 找到(参见 QMetaObject::className())。
//您可以使用inherits() 函数确定对象的类是否继承了QObject 继承层次结构中的另一个类。
//当一个对象被删除时,它会发出一个 destroy() 信号。 您可以捕获此信号以避免悬空对 QObject 的引用。
//QObjects 可以通过 event() 接收事件并过滤其他对象的事件。 有关详细信息,请参阅
//installEventFilter() 和 eventFilter()。 可以重新实现便利处理程序 childEvent() 以捕获子事件。
//QObjects 可以通过 event() 接收事件并过滤其他对象的事件。
//有关详细信息,请参阅 installEventFilter() 和 eventFilter()。
//可以重新实现便利处理程序 childEvent() 以捕获子事件。
//最后但同样重要的是,QObject 在 Qt 中提供了基本的计时器支持; 请参阅 QTimer 以获取对计时器的高级支持。
//请注意,Q_OBJECT 宏对于实现信号、槽或属性的任何对象都是必需的。
//您还需要在源文件上运行元对象编译器。
//我们强烈建议在 QObject 的所有子类中使用这个宏,无论它们是否实际使用信号、槽和属性,因为不这样做可能会导致某些函数表现出奇怪的行为。
//所有 Qt 小部件都继承 QObject。
//便利函数 isWidgetType() 返回对象是否实际上是小部件。
//它比 qobject_cast<QWidget *>(obj) 或 obj->inherits("QWidget") 快得多。
//线程亲和度
//一个 QObject 实例被称为具有线程关联性,或者它存在于某个线程中。
//当 QObject 收到排队信号或发布的事件时,槽或事件处理程序将在对象所在的线程中运行。
//注意:如果 QObject 没有线程关联(即,如果 thread() 返回零),
//或者如果它位于没有运行事件循环的线程中,则它无法接收排队信号或发布的事件。
//默认情况下,QObject 存在于创建它的线程中。
//可以使用 thread() 查询对象的线程关联,并使用 moveToThread() 进行更改。
//所有 QObject 必须与其父对象位于同一线程中。 最后:
//如果涉及的两个 QObject 存在于不同的线程中,则 setParent() 将失败。
//当一个 QObject 移动到另一个线程时,它的所有子对象也将自动移动。
//如果 QObject 有父对象,moveToThread() 将失败。
//如果 QObjects 在 QThread::run() 中创建,它们就不能成为 QThread
//对象的子对象,因为 QThread 不存在于调用 QThread::run() 的线程中。
//注意:QObject 的成员变量不会自动成为它的子变量。
//必须通过将指针传递给子的构造函数或调用 setParent() 来设置父子关系。
//如果没有这一步,当调用 moveToThread() 时,对象的成员变量将保留在旧线程中。
//没有复制构造函数或赋值运算符
//QObject 既没有复制构造函数也没有赋值运算符。
//这是设计使然。 实际上,它们是被声明的,但是在带有宏 Q_DISABLE_COPY() 的私有部分中。
//事实上,所有从 QObject 派生的 Qt 类(直接或间接)都使用这个宏来声明它们的复制构造函数和赋值运算符是私有的。
//意味您应该使用指向 QObject(或您的 QObject 子类)的指针,否则您可能会试图将 QObject 子类用作值。
//例如,如果没有复制构造函数,就不能使用 QObject 的子类作为要存储在容器类之一中的值。 您必须存储指针。
//自动连接
//Qt 的元对象系统提供了一种机制来自动连接 QObject 子类及其子类之间的信号和插槽。
//只要使用合适的对象名称定义对象,并且插槽遵循简单的命名约定,就可以在运行时通过 QMetaObject::connectSlotsByName() 函数执行此连接。
//uic 生成调用此函数的代码,以便在使用 Qt Designer 创建的表单上的小部件之间执行自动连接。
//有关使用 Qt Designer 自动连接的更多信息,请参阅 Qt Designer 手册的在您的应用程序中使用设计器 UI 文件部分。
//动态属性
//从 Qt 4.2 开始,可以在运行时向 QObject 实例添加和删除动态属性。
//动态属性不需要在编译时声明,但它们提供与静态属性相同的优点,并使用相同的 API 进行操作 -
//使用 property() 读取它们并使用 setProperty() 写入它们。
//从 Qt 4.3 开始,Qt Designer 支持动态属性,并且标准 Qt 小部件和用户创建的表单都可以被赋予动态属性。
//国际化 (I18n)
//所有 QObject 子类都支持 Qt 的翻译功能,从而可以将应用程序的用户界面翻译成不同的语言。
//为了使用户可见的文本可翻译,它必须包含在对 tr() 函数的调用中。 这在翻译文档的编写源代码中有详细解释。
public:
explicit SceneModifier(Qt3DCore::QEntity *rootEntity);
~SceneModifier();
public slots:
//信号和槽用于对象之间的通信。
//信号和槽机制是 Qt 的核心特性,可能也是与其他框架提供的特性最不同的部分。
//Qt 的元对象系统使信号和槽成为可能。
//在 GUI 编程中,当我们更改一个小部件时,我们经常希望通知另一个小部件。
//更一般地说,我们希望任何类型的对象都能够相互通信。
//例如,如果用户单击关闭按钮,我们可能希望调用窗口的 close() 函数。
//其他工具包使用回调实现这种通信。
//回调是指向函数的指针,因此如果您希望处理函数通知您某个事件,您可以将指向另一个函数(回调)的指针传递给处理函数。
//然后处理函数在适当的时候调用回调。
//尽管确实存在使用此方法的成功框架,但回调可能不直观,并且在确保回调参数的类型正确性方面可能会遇到问题。
//信号和槽
//在 Qt 中,我们有一种替代回调技术的方法:
//我们使用信号和槽。 当特定事件发生时发出信号。
//Qt 的小部件有许多预定义的信号,但我们总是可以将小部件子类化以向它们添加我们自己的信号。
//槽是响应特定信号而调用的函数。
//Qt 的小部件有许多预定义的槽,但通常的做法是对小部件进行子类化并添加自己的槽,以便您可以处理您感兴趣的信号。
//信号和槽机制是类型安全的:信号的签名必须与接收槽的签名相匹配。
//(事实上,槽的签名可能比它接收到的信号更短,因为它可以忽略额外的参数。)
//由于签名是兼容的,编译器可以帮助我们在使用基于函数指针的语法时检测类型不匹配。
//基于字符串的 SIGNAL 和 SLOT 语法将在运行时检测类型不匹配。
//信号和槽松散耦合:发出信号的类既不知道也不关心哪个槽接收信号。
//Qt 的信号和槽机制确保如果您将信号连接到槽,槽将在正确的时间使用信号的参数被调用。
//信号和槽可以接受任意数量的任意类型的参数。 它们是完全类型安全的。
//从 QObject 或其子类之一(例如 QWidget)继承的所有类都可以包含信号和槽。
//当对象以其他对象可能感兴趣的方式更改其状态时,对象会发出信号。
//这就是对象所做的所有通信。
//它不知道也不关心是否有任何东西在接收它发出的信号。 这是真正的信息封装,并确保对象可以用作软件组件。
//Slots 可以用来接收信号,但它们也是普通的成员函数。
//就像一个对象不知道是否有任何东西接收到它的信号一样,一个槽也不知道它是否有任何连接到它的信号。
//这确保了可以使用 Qt 创建真正独立的组件。
//您可以将任意数量的信号连接到单个插槽,一个信号可以连接到任意数量的插槽。
//甚至可以将一个信号直接连接到另一个信号。 (这将在发出第一个信号时立即发出第二个信号。)
//信号和槽一起构成了强大的组件编程机制。
//信号
//当对象的内部状态以某种方式发生变化时,对象会发出信号,这可能会引起对象的客户端或所有者的兴趣。
//信号是公共访问函数,可以从任何地方发出,但我们建议只从定义信号的类及其子类发出它们。
//如果多个插槽连接到一个信号,则在发出信号时,插槽将按照它们连接的顺序一个接一个地执行。
//信号由 moc 自动生成,不得在 .cpp 文件中实现。 它们永远不能有返回类型(即使用 void)。
//关于参数的说明:我们的经验表明,如果信号和槽不使用特殊类型,它们的可重用性更高。
//如果 QScrollBar::valueChanged() 使用特殊类型,例如假设的 QScrollBar::Range,它只能连接到专门为 QScrollBar 设计的插槽。
// 将不同的输入小部件连接在一起是不可能的。
//Slots
//当一个连接到它的信号被发射时,一个槽被调用。
//Slots是普通的C++函数,可以正常调用;
//它们唯一的特点是可以将信号连接到它们
//由于槽是普通的成员函数,它们在直接调用时遵循普通的 C++ 规则。
//然而,作为槽,它们可以被任何组件调用,无论其访问级别如何,通过信号槽连接。
//这意味着从任意类的实例发出的信号可以导致在无关类的实例中调用私有槽
//您还可以将插槽定义为虚的,我们发现这在实践中非常有用。
//与回调相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中的差异微不足道。
//一般来说,发送连接到某些插槽的信号比直接调用接收器慢大约十倍,使用非虚拟函数调用。
//这是定位连接对象、安全地遍历所有连接(即检查后续接收器在发射期间没有被破坏)以及以通用方式编组任何参数所需的开销。
//例如,虽然十次非虚拟函数调用听起来很多,但它的开销比任何 new 或 delete 操作都要少得多。
//一旦你执行了一个在幕后需要 new 或 delete 的字符串、向量或列表操作,信号和槽的开销只占完整函数调用成本的很小一部分。
//每当您在插槽中进行系统调用时,情况也是如此。
//或者间接调用十多个函数。信号和槽机制的简单性和灵活性非常值得开销,您的用户甚至不会注意到。
//请注意,当与基于 Qt 的应用程序一起编译时,定义称为信号或槽的变量的其他库可能会导致编译器警告和错误。 为了解决这个问题,#undef 有问题的预处理器符号。
//一个小例子
/*
class Counter
{
public:
Counter(){ m_value = 0;}
int value() const { return m_value; }
void setValue(int value);
private:
int m_value;
};
*/
//一个基于 QObject 的小类可能会写成:
/*
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter(){m_value = 0;}
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
*/
//基于 QObject 的版本具有相同的内部状态,并提供访问状态的公共方法,
//但此外它还支持使用信号和槽的组件编程。
//这个类可以通过发出一个信号 valueChanged()
//来告诉外部世界它的状态已经改变,并且它有一个槽,其他对象可以将信号发送到它。
//所有包含信号或槽的类都必须在其声明的顶部提及 Q_OBJECT。
//它们还必须(直接或间接)从 QObject 派生。
//槽由应用程序员实现。 这是 Counter::setValue() 槽的可能实现:
/*
void Counter::setValue(int value)
{
if(value != m_value)
{
m_value = value;
emit valueChanged(value);
}
}
*/
//emit行对象发射valueChanged()信号 ,并将新值作为参数。
//在下面的代码片段中,我们创建了两个 Counter 对象,
//并使用 QObject::connect() 将第一个对象的 valueChanged() 信号连接到第二个对象的 setValue() 槽:
/*
Counter a,b;
QObject::connect(&a, &Counter::valueChanged,
&b, &Counter::setValue);
a.setValue(12); //a.value() == 12, b.value() == 12
b.setValue(48); //a.value() == 12, b.value() == 48
*/
//调用 a.setValue(12) 使 a 发出 valueChanged(12) 信号,b 将在其 setValue() 槽中接收该信号,即调用 b.setValue(12)。
//然后 b 发出相同的 valueChanged() 信号,但由于没有槽连接到 b 的 valueChanged() 信号,该信号被忽略。
//请注意,setValue() 函数设置值并仅在 value != m_value 时才发出信号。
//这可以防止在循环连接的情况下无限循环(例如,如果 b.valueChanged() 连接到 a.setValue())。
//默认情况下,对于您建立的每个连接,都会发出一个信号;
//为重复连接发出两个信号。
//您可以通过一个 disconnect() 调用断开所有这些连接。
//如果您传递 Qt::UniqueConnection 类型,则只有在它不是重复的情况下才会建立连接。
//如果已经有重复(相同对象上完全相同的插槽的完全相同的信号),则连接将失败并且连接将返回 false。
//这个例子说明了对象可以一起工作而无需知道彼此的任何信息
//为了实现这一点,对象只需要连接在一起,这可以通过一些简单的
//QObject::connect() 函数调用或使用 uic 的自动连接功能来实现。
//一个真实的例子
//下面是一个没有成员函数的简单小部件类的头示例。
//目的是展示如何在自己的应用程序中使用信号和槽。
/*
#ifndef LCDNUMBER_H
#define LCDNUMBER_H
#include <QFrame>
class LcdNumber : public QFrame
{
Q_OBJECT
//LcdNumber 通过 QFrame 和 QWidget 继承了 QObject 它有点类似于内置的 QLCDNumber 小部件。
//Q_OBJECT 宏由预处理器展开,声明几个由 moc 实现的成员函数;
//如果您在“未定义对 LcdNumber 的 vtable 的引用”这一行中遇到编译器错误,
//您可能忘记了运行 moc 或 在链接命令中包含 moc。
public:
LcdNumber(QWidget *parent = nullptr);
signals:
void overflow();
//在类构造函数和公共成员之后,我们声明类信号。
//当 LcdNumber 类被要求显示一个不可能的值时,它会发出一个信号 overflow()。
//如果您不关心溢出,或者您知道不会发生溢出,则可以忽略 overflow() 信号,即不要将其连接到任何插槽。
//另一方面,如果您想在数字溢出时调用两个不同的错误函数,
//只需将信号连接到两个不同的插槽即可。Qt 将调用两者(按照它们连接的顺序)。
public slots:
void display(int num);
void display(double num);
void display(const QString &str);
void setHexMode();
void setDecMode();
void setOctMode();
void setBinMode();
void setSmallDecimalPoint(bool point);
};
#endif
//槽是一个接收函数,用于获取有关其他小部件中状态更改的信息。
//如上代码所示,LcdNumber 使用它来设置显示的数字。
//display() 是该类与程序其余部分的接口的一部分,该插槽是public的。
//几个示例程序将 QScrollBar 的 valueChanged() 信号连接到display() 槽,
//因此 LCD 数字连续显示滚动条的值。
//请注意 display() 已重载;当您将信号连接到插槽时,Qt 将选择适当的版本。
//对于回调,您必须找到五个不同的名称并自己跟踪类型。
//带有默认参数的信号和槽
//信号和槽的签名可能包含参数,并且参数可以有默认值。
//考虑 QObject::destroyed():
/*
void destoryed(QObject* = nullptr);
*/
//当一个 QObject 被删除时,它会发出这个 QObject::destroyed() 信号。
//我们想要捕捉这个信号,无论我们在哪里可能有对已删除 QObject 的悬空引用,以便我们可以清理它。
//合适的槽签名可能是:
/*
void objectDestroyed(QObject* obj = nullptr);
*/
//为了将信号连接到插槽,我们使用 QObject::connect()。
//有几种方法可以连接信号和插槽。 第一种是使用函数指针:
//connect(sender,&QObject::destoryed,this,&MyObject::objectDestroyed);
//将 QObject::connect() 与函数指针一起使用有几个优点。
//首先,它允许编译器检查信号的参数是否与槽的参数兼容。如果需要,编译器也可以隐式转换参数。
//您还可以连接到函对象或 C++11 lambdas:
//connect(sender,&OObject::destroyed,this,[=](){this->m_objects.remove(sender);})
//在这两种情况下,我们都在调用 connect() 时提供 this 作为上下文。
//上下文对象提供有关接收方应在哪个线程中执行的信息。
//这很重要,因为提供上下文可确保接收器在上下文线程中执行。
//当发送方或上下文被销毁时,lambda 将断开连接。
//您应该注意在发出信号时,函子内部使用的任何对象仍然处于活动状态。
//将信号连接到插槽的另一种方法是使用 QObject::connect() 以及 SIGNAL 和 SLOT 宏。
//关于是否在 SIGNAL() 和 SLOT() 宏中包含参数的规则,如果参数具有默认值,
//则传递给 SIGNAL() 宏的签名的参数不得少于传递给 SLOT 的签名的参数 () 宏。
//所有这些都会起作用
/*
connect(sender,SIGNAL(destroyed(QObject*)),this,SLOT(objectDestroyed(QObject*)));
connect(sender,SIGNAL(destroyed(QObject*)),this,SLOT(objectDestroyed()));
connect(sender,SIGNAL(destroyed()),this,SLOT(objectDestroyed()));
*/
//但是这个是行不通的:
//connect(sender,SIGNAL(destroyed()),this,SLOT(objectDestroyed(QObject*)));
//因为插槽将期待信号不会发送的 QObject。 此连接将报告运行时错误。
//高级信号和插槽使用
//对于您可能需要有关信号发送者信息的情况,Qt 提供了 QObject::sender() 函数,该函数返回指向发送信号的对象的指针。
//Lambda 表达式是一种将自定义参数传递给插槽的便捷方式:
/*
connect(action,&QAction::triggered,engine,
[=](){engine->processAction(action->text());});
*/
//将 Qt 与第 3 方信号和插槽一起使用
//可以将 Qt 与第 3 方信号/插槽机制一起使用。您甚至可以在同一个项目中使用这两种机制。
//只需将以下行添加到您的 qmake 项目 (.pro) 文件中。
//CONFIG += no_keywords
//它告诉 Qt 不要定义 moc 关键字信号、插槽和发射,因为这些名称将被 3rd 方库使用,例如 Boost.
//然后要继续使用带有no_keywords 标志的 Qt 信号和槽,只需将源中 Qt moc 关键字的所有使用替换为相应的
//Qt 宏 Q_SIGNALS(或 Q_SIGNAL)、Q_SLOTS(或 Q_SLOT)和 Q_EMIT。
void enableTorus(bool enabled);
void enableCone(bool enabled);
void enableCylinder(bool enabled);
void enableCuboid(bool enabled);
void enablePlane(bool enabled);
void enableSphere(bool enabled);
private:
//包含作为 Qt 3D 模拟框架基础的类,以及提供使用 Qt 3D 框架进行渲染的类。
Qt3DCore::QEntity *m_rootEntity;
Qt3DExtras::QTorusMesh *m_torus;
Qt3DCore::QEntity *m_coneEntity;
Qt3DCore::QEntity *m_cylinderEntity;
Qt3DCore::QEntity *m_torusEntity;
Qt3DCore::QEntity *m_cuboidEntity;
Qt3DCore::QEntity *m_planeEntity;
Qt3DCore::QEntity *m_sphereEntity;
};
#endif // SCENEMODIFIER_H
scenemodifier.cpp
#include "scenemodifier.h"
#include <Qt3DRender/QGeometryRenderer> //封装几何渲染
#include <QtCore/QDebug>
SceneModifier::SceneModifier(Qt3DCore::QEntity *rootEntity)
: m_rootEntity(rootEntity)
{
// 环面形状数据
//设置环面网格
//例如,我们将介绍如何设置环面网格。
//首先,我们实例化 QTorusMesh,
//然后我们设置网格特定的参数
//对于圆环来说,这些参数是半径、小半径以及环和切片的数量。
m_torus = new Qt3DExtras::QTorusMesh();
m_torus->setRadius(1.0f);
m_torus->setMinorRadius(0.4f);
m_torus->setRings(100);
m_torus->setSlices(20);
// 圆环网格变换
//圆环的大小和位置可以通过变换组件进行调整。
//我们创建缩放、平移和旋转组件并将它们添加到 QTransform 组件中。
Qt3DCore::QTransform *torusTransform = new Qt3DCore::QTransform();
torusTransform->setScale(2.0f);
torusTransform->setRotation(QQuaternion::fromAxisAndAngle(QVector3D(0.0f, 1.0f, 0.0f), 25.0f));
torusTransform->setTranslation(QVector3D(5.0f, 4.0f, 0.0f));
//为了改变网格的漫反射颜色
//我们创建一个 QPhongMaterial 并设置它的漫反射颜色。
Qt3DExtras::QPhongMaterial *torusMaterial = new Qt3DExtras::QPhongMaterial();
torusMaterial->setDiffuse(QColor(QRgb(0xbeb32b)));
//最后一步是将圆环添加到实体树中,
//我们通过创建一个带有父实体的 QEntity
//并将之前创建的网格、材质和变换组件添加到其中来实现。
{
m_torusEntity = new Qt3DCore::QEntity(m_rootEntity);
m_torusEntity->addComponent(m_torus);
m_torusEntity->addComponent(torusMaterial);
m_torusEntity->addComponent(torusTransform);
}
// Cone shape data
Qt3DExtras::QConeMesh *cone = new Qt3DExtras::QConeMesh();
cone->setTopRadius(0.5);
cone->setBottomRadius(1);
cone->setLength(3);
cone->setRings(50);
cone->setSlices(20);
// ConeMesh Transform
Qt3DCore::QTransform *coneTransform = new Qt3DCore::QTransform();
coneTransform->setScale(1.5f);
coneTransform->setRotation(QQuaternion::fromAxisAndAngle(QVector3D(1.0f, 0.0f, 0.0f), 45.0f));
coneTransform->setTranslation(QVector3D(0.0f, 4.0f, -1.5));
Qt3DExtras::QPhongMaterial *coneMaterial = new Qt3DExtras::QPhongMaterial();
coneMaterial->setDiffuse(QColor(QRgb(0x928327)));
// Cone
{
m_coneEntity = new Qt3DCore::QEntity(m_rootEntity);
m_coneEntity->addComponent(cone);
m_coneEntity->addComponent(coneMaterial);
m_coneEntity->addComponent(coneTransform);
}
// Cylinder shape data
Qt3DExtras::QCylinderMesh *cylinder = new Qt3DExtras::QCylinderMesh();
cylinder->setRadius(1);
cylinder->setLength(3);
cylinder->setRings(100);
cylinder->setSlices(20);
// CylinderMesh Transform
Qt3DCore::QTransform *cylinderTransform = new Qt3DCore::QTransform();
cylinderTransform->setScale(1.5f);
cylinderTransform->setRotation(QQuaternion::fromAxisAndAngle(QVector3D(1.0f, 0.0f, 0.0f), 45.0f));
cylinderTransform->setTranslation(QVector3D(-5.0f, 4.0f, -1.5));
Qt3DExtras::QPhongMaterial *cylinderMaterial = new Qt3DExtras::QPhongMaterial();
cylinderMaterial->setDiffuse(QColor(QRgb(0x928327)));
// Cylinder
{
m_cylinderEntity = new Qt3DCore::QEntity(m_rootEntity);
m_cylinderEntity->addComponent(cylinder);
m_cylinderEntity->addComponent(cylinderMaterial);
m_cylinderEntity->addComponent(cylinderTransform);
}
// Cuboid shape data
Qt3DExtras::QCuboidMesh *cuboid = new Qt3DExtras::QCuboidMesh();
// CuboidMesh Transform
Qt3DCore::QTransform *cuboidTransform = new Qt3DCore::QTransform();
cuboidTransform->setScale(4.0f);
cuboidTransform->setTranslation(QVector3D(5.0f, -4.0f, 0.0f));
Qt3DExtras::QPhongMaterial *cuboidMaterial = new Qt3DExtras::QPhongMaterial();
cuboidMaterial->setDiffuse(QColor(QRgb(0x665423)));
//Cuboid
{
m_cuboidEntity = new Qt3DCore::QEntity(m_rootEntity);
m_cuboidEntity->addComponent(cuboid);
m_cuboidEntity->addComponent(cuboidMaterial);
m_cuboidEntity->addComponent(cuboidTransform);
}
// Plane shape data
Qt3DExtras::QPlaneMesh *planeMesh = new Qt3DExtras::QPlaneMesh();
planeMesh->setWidth(2);
planeMesh->setHeight(2);
// Plane mesh transform
Qt3DCore::QTransform *planeTransform = new Qt3DCore::QTransform();
planeTransform->setScale(1.3f);
planeTransform->setRotation(QQuaternion::fromAxisAndAngle(QVector3D(1.0f, 0.0f, 0.0f), 45.0f));
planeTransform->setTranslation(QVector3D(0.0f, -4.0f, 0.0f));
Qt3DExtras::QPhongMaterial *planeMaterial = new Qt3DExtras::QPhongMaterial();
planeMaterial->setDiffuse(QColor(QRgb(0xa69929)));
// Plane
{
m_planeEntity = new Qt3DCore::QEntity(m_rootEntity);
m_planeEntity->addComponent(planeMesh);
m_planeEntity->addComponent(planeMaterial);
m_planeEntity->addComponent(planeTransform);
}
// Sphere shape data
Qt3DExtras::QSphereMesh *sphereMesh = new Qt3DExtras::QSphereMesh();
sphereMesh->setRings(20);
sphereMesh->setSlices(20);
sphereMesh->setRadius(2);
// Sphere mesh transform
Qt3DCore::QTransform *sphereTransform = new Qt3DCore::QTransform();
sphereTransform->setScale(1.3f);
sphereTransform->setTranslation(QVector3D(-5.0f, -4.0f, 0.0f));
Qt3DExtras::QPhongMaterial *sphereMaterial = new Qt3DExtras::QPhongMaterial();
sphereMaterial->setDiffuse(QColor(QRgb(0xa69929)));
// Sphere
m_sphereEntity = new Qt3DCore::QEntity(m_rootEntity);
m_sphereEntity->addComponent(sphereMesh);
m_sphereEntity->addComponent(sphereMaterial);
m_sphereEntity->addComponent(sphereTransform);
}
SceneModifier::~SceneModifier()
{
}
//我们可以通过定义实体是否有父实体来控制实体的可见性。
//也就是说,它是否是实体树的一部分。
void SceneModifier::enableTorus(bool enabled)
{
m_torusEntity->setEnabled(enabled);
}
void SceneModifier::enableCone(bool enabled)
{
m_coneEntity->setEnabled(enabled);
}
void SceneModifier::enableCylinder(bool enabled)
{
m_cylinderEntity->setEnabled(enabled);
}
void SceneModifier::enableCuboid(bool enabled)
{
m_cuboidEntity->setEnabled(enabled);
}
void SceneModifier::enablePlane(bool enabled)
{
m_planeEntity->setEnabled(enabled);
}
void SceneModifier::enableSphere(bool enabled)
{
m_sphereEntity->setEnabled(enabled);
}