qt-C++笔记之自定义类继承自 `QObject` 与 `QWidget` 及开发方式详解

news2024/12/19 22:41:37

qt-C++笔记之自定义类继承自 QObjectQWidget 及开发方式详解

在这里插入图片描述

code review!
参考笔记
1.qt-C++笔记之父类窗口、父类控件、对象树的关系
2.qt-C++笔记之继承自 QWidget和继承自QObject 并通过 getWidget() 显示窗口或控件时的区别和原理
3.qt-C++笔记之自定义类继承自 QObjectQWidget 及开发方式详解

在 Qt C++ 开发中,自定义类的继承选择(QObjectQWidget)以及开发方式(纯代码、使用 Qt Designer 拖拽控件、通过 “Promote to” 功能)对项目的架构和维护性具有重要影响。本指南将系统性地介绍这两种继承方式的区别,并通过具体示例展示不同开发方法的整体结构和父对象管理策略。同时,还将详细解释 Q_OBJECT 宏与 moc 处理的相关要求。

目录

  • Qt C++ 开发指南:自定义类继承自 QObjectQWidget 及开发方式详解
    • 目录
    • 1. 继承自 QObjectQWidget 的区别
      • 1.1 QObject
      • 1.2 QWidget
      • 1.3 关键区别
    • 2. 开发方式概述
      • 2.1 纯代码开发
      • 2.2 使用 Qt Designer 拖拽预设控件开发
      • 2.3 使用 Qt Designer 拖拽控件并 “Promote to” 自定义类开发
    • 3. Q_OBJECT 宏与 moc 处理
      • 3.1 Q_OBJECT 宏的要求
      • 3.2 moc 文件的包含方式
        • 3.2.1 类声明在独立头文件中
        • 3.2.2 类定义在源文件中
      • 3.3 类声明单独为一个文件与 #include "moc_Class.cpp" 的关系
    • 4. 父对象传递策略
      • 4.1 纯代码开发中的父对象传递
        • 4.1.1 继承自 QObject 的类
        • 4.1.2 继承自 QWidget 的类
      • 4.2 使用 Qt Designer 开发中的父对象传递
        • 4.2.1 继承自 QObject 的类
        • 4.2.2 继承自 QWidget 的类
      • 4.3 Promote to 功能的继承要求
    • 5. 具体示例
      • 5.1 纯代码开发中继承自 QObject 的类
      • 5.2 纯代码开发中继承自 QWidget 的类
      • 5.3 使用 Qt Designer 拖拽控件并 “Promote to” 自定义 QWidget
        • 5.3.1 Promote to 功能的继承要求
    • 6. 最佳实践建议
    • 7. 总结
    • 8. 参考资料

1. 继承自 QObjectQWidget 的区别

1.1 QObject

  • 基本特性
    • QObject 是 Qt 对象模型的基类,提供了对象树、信号与槽机制、属性系统等核心功能。
    • 不具备任何用户界面(UI)元素,无法直接用于显示内容。
  • 适用场景
    • 适用于非 UI 类,如数据模型、控制器、管理器等。
    • 用于需要利用 Qt 的信号与槽机制、事件系统等特性的类。
  • 关键点
    • 无可视化:不适用于直接展示界面。
    • 逻辑与数据处理:主要用于处理应用程序的逻辑和数据。

1.2 QWidget

  • 基本特性
    • QWidget 继承自 QObject,是所有可视化组件的基类。
    • 提供了绘制、事件处理、布局管理等与 UI 相关的功能。
  • 适用场景
    • 用于构建自定义的用户界面组件,如自定义按钮、窗口、对话框等。
    • 任何需要在界面上显示的组件都应该继承自 QWidget 或其子类。
  • 关键点
    • 可视化:具备显示界面的能力。
    • 用户交互:支持用户的各种交互操作。

1.3 关键区别

特性QObjectQWidget
可视化
用途逻辑处理、数据管理构建和展示用户界面
基类QObject
适用场景非 UI 类,如控制器、数据模型UI 组件,如按钮、窗口、对话框

2. 开发方式概述

Qt 提供了多种开发方式,以满足不同项目的需求和开发者的偏好。本节将介绍三种主要的开发方式,并通过示例展示其整体结构。

2.1 纯代码开发

特点

  • 所有 UI 和逻辑通过代码手动编写。
  • 提供了最大的灵活性和控制力。

优点

  • 适用于动态生成 UI 或需要高度定制化的场景。
  • 便于版本控制,因为所有内容都在代码中。

缺点

  • 编写和维护大量 UI 代码较为繁琐。
  • 缺乏直观的可视化设计。

2.2 使用 Qt Designer 拖拽预设控件开发

特点

  • 使用 Qt Designer 的可视化界面,通过拖拽预设的控件(如按钮、标签等)来设计 UI。
  • 生成 .ui 文件,Qt 的 uic 工具将其转换为代码。

优点

  • 直观、快速地设计和布局 UI。
  • 易于调整和预览 UI 效果。

缺点

  • 预设控件的功能有限,复杂定制化需要额外代码。
  • 需要在代码中与生成的 UI 进行集成。

2.3 使用 Qt Designer 拖拽控件并 “Promote to” 自定义类开发

特点

  • 在 Qt Designer 中拖拽预设控件(通常是占位符,如 QWidget),然后通过 “Promote to” 功能将其替换为自定义的子类。
  • 允许在可视化设计中集成自定义的 UI 组件。

优点

  • 结合了可视化设计的便捷性和自定义组件的灵活性。
  • 使得复杂或定制化的 UI 组件能够在 Designer 中被复用和管理。

缺点

  • 需要确保自定义类正确继承自 QWidget 或其子类。
  • 需要维护自定义类与 Designer 生成的 UI 之间的同步。

3. Q_OBJECT 宏与 moc 处理

3.1 Q_OBJECT 宏的要求

  • 用途
    • 启用 Qt 的元对象特性,如信号与槽、动态属性、反射等。
  • 要求
    • 类必须继承自 QObject 或其子类(如 QWidget)。
    • 类声明中必须包含 Q_OBJECT 宏。
    • 必须经过 Qt 的 moc(Meta-Object Compiler)处理,以生成必要的元对象代码。

3.2 moc 文件的包含方式

3.2.1 类声明在独立头文件中

如果类声明在独立的头文件(如 MyClass.h)中,moc 会自动生成 moc_MyClass.cpp,并由编译系统处理。无需在源文件中手动包含 moc 文件。

示例

MyClass.h

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

class MyClass : public QObject {
    Q_OBJECT

public:
    explicit MyClass(QObject *parent = nullptr);

signals:
    void mySignal();
};

#endif // MYCLASS_H

MyClass.cpp

#include "MyClass.h"

MyClass::MyClass(QObject *parent) : QObject(parent) {}

main.cpp

#include <QCoreApplication>
#include "MyClass.h"

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    MyClass obj;
    // 使用 obj...

    return app.exec();
}

说明

  • moc 会自动处理 MyClass.h,无需手动包含 moc 文件。
  • 确保项目文件(如 .proCMakeLists.txt)正确配置,以包含 moc 处理。
3.2.2 类定义在源文件中

如果类声明和实现都在同一个源文件中(如 main.cpp),需要在文件末尾手动包含 moc 文件,以确保 moc 处理该类。

示例

main.cpp

#include <QApplication>
#include <QPushButton>
#include <QObject>
#include <QMessageBox>

class MyWidget : public QPushButton {
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = nullptr) : QPushButton("Click Me", parent) {
        connect(this, &QPushButton::clicked, this, &MyWidget::onClicked);
    }

private slots:
    void onClicked() {
        QMessageBox::information(this, "Message", "Button clicked!");
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    MyWidget w;
    w.show();

    return app.exec();
}

#include "main.moc"

说明

  • 由于 MyWidget 类在 main.cpp 中定义,需在文件末尾添加 #include "main.moc",以确保 moc 处理该类。
  • moc 生成的 main.moc 文件会被编译并链接。

3.3 类声明单独为一个文件与 #include "moc_Class.cpp" 的关系

  • 推荐做法:将类声明放在独立的头文件中,让 moc 自动处理。避免手动包含 moc 文件,这简化了构建过程,并减少了错误的可能性。

  • 不推荐做法:手动包含 moc 文件(如 #include "moc_MyClass.cpp"),这可能导致重复定义或链接错误,除非有特定需求(如某些编译器或构建系统的限制)。

4. 父对象传递策略

在 Qt 中,父对象的传递对于内存管理和对象层级关系至关重要。本节将详细介绍在不同继承类和开发方式下,如何合理传递父对象。

4.1 纯代码开发中的父对象传递

在纯代码开发中,开发者需要手动创建和管理对象及其父子关系。传递 this 或父类窗口主要取决于对象的用途和生命周期管理需求。

4.1.1 继承自 QObject 的类

示例:在主窗口中创建一个 MyObject 实例,并将主窗口作为父对象传递。

MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject {
    Q_OBJECT

public:
    explicit MyObject(QObject *parent = nullptr);

signals:
    void mySignal(const QString &message);

public slots:
    void mySlot(const QString &message);
};

#endif // MYOBJECT_H

MyObject.cpp

#include "MyObject.h"
#include <QDebug>

MyObject::MyObject(QObject *parent) : QObject(parent) {}

void MyObject::mySlot(const QString &message) {
    qDebug() << "Received message:" << message;
}

main.cpp

#include <QApplication>
#include <QMainWindow>
#include "MyObject.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QMainWindow mainWindow;
    mainWindow.show();

    // 创建 MyObject,并将 mainWindow 作为父对象
    MyObject obj(&mainWindow);

    // 连接信号与槽
    QObject::connect(&obj, &MyObject::mySignal, &obj, &MyObject::mySlot);

    // 触发信号
    emit obj.mySignal("Hello from MyObject!");

    return app.exec();
}

解释

  • 传递 &mainWindow 作为父对象:确保 MyObject 的生命周期与 mainWindow 一致。当 mainWindow 销毁时,obj 也会被自动销毁。
  • 内存管理:无需手动管理 MyObject 的内存,Qt 的对象树机制自动处理。
4.1.2 继承自 QWidget 的类

示例:在主窗口中创建一个自定义的 MyWidget,并将主窗口作为父对象传递。

MyWidget.h

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>

class QPushButton;

class MyWidget : public QWidget {
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = nullptr);

private slots:
    void handleButton();

private:
    QPushButton *button;
};

#endif // MYWIDGET_H

MyWidget.cpp

#include "MyWidget.h"
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>

MyWidget::MyWidget(QWidget *parent) : QWidget(parent) {
    button = new QPushButton("Click Me", this);
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(button);
    setLayout(layout);

    connect(button, &QPushButton::clicked, this, &MyWidget::handleButton);
}

void MyWidget::handleButton() {
    QMessageBox::information(this, "Message", "Button clicked!");
}

main.cpp

#include <QApplication>
#include <QMainWindow>
#include "MyWidget.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QMainWindow mainWindow;
    MyWidget *widget = new MyWidget(&mainWindow); // 传递 mainWindow 作为父对象
    mainWindow.setCentralWidget(widget);
    mainWindow.show();

    return app.exec();
}

解释

  • 传递 &mainWindow 作为父对象:使 MyWidget 成为 mainWindow 的中央控件,自动管理其生命周期和显示。
  • 层级关系MyWidget 在主窗口中显示,并且会随着主窗口一起显示和隐藏。

4.2 使用 Qt Designer 开发中的父对象传递

使用 Qt Designer 时,父对象的传递和管理主要由 Designer 生成的 UI 代码处理,开发者无需手动传递 this。以下分为两种情况:

4.2.1 继承自 QObject 的类

由于 QObject 本身不具备可视化能力,通常在 Qt Designer 中不直接使用继承自 QObject 的类。但可以在代码中作为逻辑组件创建和管理。

示例:在使用 Qt Designer 设计的主窗口中创建一个 MyObject 实例。

MyMainWindow.h

#ifndef MYMAINWINDOW_H
#define MYMAINWINDOW_H

#include <QMainWindow>
#include "MyObject.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MyMainWindow; }
QT_END_NAMESPACE

class MyMainWindow : public QMainWindow {
    Q_OBJECT

public:
    explicit MyMainWindow(QWidget *parent = nullptr);
    ~MyMainWindow();

private:
    Ui::MyMainWindow *ui;
    MyObject *obj;
};

#endif // MYMAINWINDOW_H

MyMainWindow.cpp

#include "MyMainWindow.h"
#include "ui_MyMainWindow.h"

MyMainWindow::MyMainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MyMainWindow)
    , obj(new MyObject(this)) // 传递 this 作为父对象
{
    ui->setupUi(this);

    // 连接信号与槽
    connect(obj, &MyObject::mySignal, obj, &MyObject::mySlot);

    // 触发信号
    emit obj->mySignal("Hello from MyObject in Designer!");
}

MyMainWindow::~MyMainWindow() {
    delete ui;
}

解释

  • MyObject 作为 MyMainWindow 的子对象:通过传递 thisMyObject 的生命周期由 MyMainWindow 管理。
  • UI 设计与逻辑分离MyObject 不直接参与 UI 设计,而是作为逻辑组件存在。
4.2.2 继承自 QWidget 的类

在使用 Qt Designer 拖拽控件并进行 “Promote to” 操作时,自定义的 QWidget 子类会自动被设置为相应控件的子对象,父对象由 Designer 生成的 UI 代码管理。

示例:在 Qt Designer 中将一个 QPushButton 提升为自定义的 MyButton

  1. 创建自定义 MyButton

MyButton.h

#ifndef MYBUTTON_H
#define MYBUTTON_H

#include <QPushButton>

class MyButton : public QPushButton {
    Q_OBJECT

public:
    explicit MyButton(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;

private slots:
    void onClicked();
};

#endif // MYBUTTON_H

MyButton.cpp

#include "MyButton.h"
#include <QPainter>
#include <QMessageBox>

MyButton::MyButton(QWidget *parent) : QPushButton(parent) {
    setText("Custom Button");
    connect(this, &QPushButton::clicked, this, &MyButton::onClicked);
}

void MyButton::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    painter.setBrush(Qt::cyan);
    painter.drawRect(rect());
    QPushButton::paintEvent(event);
}

void MyButton::onClicked() {
    QMessageBox::information(this, "Custom Button", "MyButton clicked!");
}
  1. 在 Qt Designer 中进行 “Promote to” 操作

    • 打开 mywidget.ui
    • 拖拽一个 QPushButton 到设计区域,设置对象名称为 myButton
    • 右键点击该按钮,选择 “Promote to…”。
    • 在弹出的对话框中填写:
      • Promoted class name: MyButton
      • Header file: MyButton.h
    • 点击 “Add”,然后 “Promote”。
  2. 更新自定义类

MyWidget.h

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>

namespace Ui {
class MyWidget;
}

class MyWidget : public QWidget {
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = nullptr);
    ~MyWidget();

private:
    Ui::MyWidget *ui;
};

#endif // MYWIDGET_H

MyWidget.cpp

#include "MyWidget.h"
#include "ui_mywidget.h"
#include "MyButton.h" // 确保包含自定义类的头文件

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MyWidget)
{
    ui->setupUi(this);
    // 无需手动传递父对象,Designer 自动处理
}

MyWidget::~MyWidget() {
    delete ui;
}

main.cpp

#include <QApplication>
#include "MyWidget.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    MyWidget w;
    w.show();

    return app.exec();
}

解释

  • 父对象自动管理:通过 “Promote to” 功能,MyButton 实例的父对象由 Designer 生成的 UI 代码自动设置为包含它的父控件(如 MyWidget)。
  • 无需手动传递 this:在 UI 设计中,父子关系由布局和 UI 文件决定,开发者无需手动传递 this

4.3 Promote to 功能的继承要求

在使用 Qt Designer 的 “Promote to” 功能时,确保自定义类能够正确替换预设控件,需要满足以下继承要求:

  • 继承自相同或兼容的基类

    • 自定义类必须继承自被替换的控件的基类。
    • 具体来说:
      • 替换 QPushButton:自定义类应继承自 QPushButton
      • 替换 QLabel:自定义类应继承自 QLabel
      • 替换 QWidget:自定义类应继承自 QWidget
    • 示例
      • 如果在 Qt Designer 中将 QPushButton 提升为 MyButton,则 MyButton 应继承自 QPushButton 而不是直接继承自 QWidget。这样可以确保接口兼容性和行为一致性,避免运行时错误。
  • 构造函数匹配

    • 自定义类应实现与基类兼容的构造函数,通常接受一个 QWidget 指针作为父对象。
    • 例如:
      explicit MyButton(QWidget *parent = nullptr);
      
    • 这确保在 Designer 生成的代码中,自定义控件能够正确初始化。
  • 正确的头文件包含

    • 在 “Promote to” 对话框中,需提供自定义类的头文件路径(如 MyButton.h),确保编译器能够找到并包含自定义类的定义。
    • 这对于项目的构建系统(如 qmake 或 CMake)来说至关重要,必须确保头文件路径正确配置。
  • 使用 Q_OBJECT 宏(如果需要)

    • 如果自定义类需要使用信号与槽机制,必须在类声明中包含 Q_OBJECT 宏,并确保 moc 正确处理。
    • 例如:
      class MyButton : public QPushButton {
          Q_OBJECT
          // ...
      };
      
  • 确保自定义类已在项目中编译

    • 自定义类必须包含在项目的编译过程中,确保 moc 处理和编译生成的元对象代码。
    • 在使用 CMake 时,通常需要将自定义类的头文件添加到 CMakeLists.txt 中的 HEADERSSOURCES 部分。
  • 处理虚函数和事件

    • 如果自定义类重写了基类的虚函数(如 paintEvent),需确保调用基类的实现,以保持控件的基本功能。
    • 例如:
      void MyButton::paintEvent(QPaintEvent *event) {
          // 自定义绘制
          QPushButton::paintEvent(event);
      }
      
  • 信号与槽的兼容性

    • 自定义类中定义的信号与槽应与基类的信号与槽兼容,确保在 Designer 中能够正确连接和使用。
    • 避免更改基类信号与槽的签名,除非有充分的理由和处理逻辑。

总结
使用 “Promote to” 功能时,遵循以上继承要求能够确保自定义控件在 Qt Designer 中正确工作,并与生成的 UI 代码无缝集成。这不仅提高了开发效率,还保持了代码的可维护性和扩展性。特别是 继承自被替换控件的基类 是确保功能和行为一致性的关键步骤,避免因继承不当导致的兼容性问题。

5. 具体示例

通过以下具体示例,进一步展示不同继承方式和开发方法下的整体结构。

5.1 纯代码开发中继承自 QObject 的类

MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject {
    Q_OBJECT

public:
    explicit MyObject(QObject *parent = nullptr);

signals:
    void mySignal(const QString &message);

public slots:
    void mySlot(const QString &message);
};

#endif // MYOBJECT_H

MyObject.cpp

#include "MyObject.h"
#include <QDebug>

MyObject::MyObject(QObject *parent) : QObject(parent) {}

void MyObject::mySlot(const QString &message) {
    qDebug() << "Received message:" << message;
}

main.cpp

#include <QApplication>
#include <QMainWindow>
#include "MyObject.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QMainWindow mainWindow;
    mainWindow.show();

    MyObject obj(&mainWindow); // 传递 mainWindow 作为父对象
    QObject::connect(&obj, &MyObject::mySignal, &obj, &MyObject::mySlot);

    emit obj.mySignal("Hello from MyObject!");

    return app.exec();
}

5.2 纯代码开发中继承自 QWidget 的类

MyWidget.h

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>

class QPushButton;

class MyWidget : public QWidget {
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = nullptr);

private slots:
    void handleButton();

private:
    QPushButton *button;
};

#endif // MYWIDGET_H

MyWidget.cpp

#include "MyWidget.h"
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>

MyWidget::MyWidget(QWidget *parent) : QWidget(parent) {
    button = new QPushButton("Click Me", this);
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(button);
    setLayout(layout);

    connect(button, &QPushButton::clicked, this, &MyWidget::handleButton);
}

void MyWidget::handleButton() {
    QMessageBox::information(this, "Message", "Button clicked!");
}

main.cpp

#include <QApplication>
#include <QMainWindow>
#include "MyWidget.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QMainWindow mainWindow;
    MyWidget *widget = new MyWidget(&mainWindow); // 传递 mainWindow 作为父对象
    mainWindow.setCentralWidget(widget);
    mainWindow.show();

    return app.exec();
}

5.3 使用 Qt Designer 拖拽控件并 “Promote to” 自定义 QWidget

步骤

  1. 创建自定义 MyButton

    MyButton.h

    #ifndef MYBUTTON_H
    #define MYBUTTON_H
    
    #include <QPushButton>
    
    class MyButton : public QPushButton {
        Q_OBJECT
    
    public:
        explicit MyButton(QWidget *parent = nullptr);
    
    protected:
        void paintEvent(QPaintEvent *event) override;
    
    private slots:
        void onClicked();
    };
    
    #endif // MYBUTTON_H
    

    MyButton.cpp

    #include "MyButton.h"
    #include <QPainter>
    #include <QMessageBox>
    
    MyButton::MyButton(QWidget *parent) : QPushButton(parent) {
        setText("Custom Button");
        connect(this, &QPushButton::clicked, this, &MyButton::onClicked);
    }
    
    void MyButton::paintEvent(QPaintEvent *event) {
        QPainter painter(this);
        painter.setBrush(Qt::cyan);
        painter.drawRect(rect());
        QPushButton::paintEvent(event);
    }
    
    void MyButton::onClicked() {
        QMessageBox::information(this, "Custom Button", "MyButton clicked!");
    }
    
  2. 在 Qt Designer 中进行 “Promote to” 操作

    • 打开 mywidget.ui
    • 拖拽一个 QPushButton 到设计区域,设置对象名称为 myButton
    • 右键点击该按钮,选择 “Promote to…”。
    • 在弹出的对话框中填写:
      • Promoted class name: MyButton
      • Header file: MyButton.h
    • 点击 “Add”,然后 “Promote”。
  3. 更新自定义类

    MyWidget.h

    #ifndef MYWIDGET_H
    #define MYWIDGET_H
    
    #include <QWidget>
    
    namespace Ui {
    class MyWidget;
    }
    
    class MyWidget : public QWidget {
        Q_OBJECT
    
    public:
        explicit MyWidget(QWidget *parent = nullptr);
        ~MyWidget();
    
    private:
        Ui::MyWidget *ui;
    };
    
    #endif // MYWIDGET_H
    

    MyWidget.cpp

    #include "MyWidget.h"
    #include "ui_mywidget.h"
    #include "MyButton.h" // 确保包含自定义类的头文件
    
    MyWidget::MyWidget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::MyWidget)
    {
        ui->setupUi(this);
        // 无需手动传递父对象,Designer 自动处理
    }
    
    MyWidget::~MyWidget() {
        delete ui;
    }
    

    main.cpp

    #include <QApplication>
    #include "MyWidget.h"
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
    
        MyWidget w;
        w.show();
    
        return app.exec();
    }
    

关键点

  • 自定义控件的父对象由 Qt Designer 自动设置:确保代码简洁且维护简单。
  • 自定义控件必须继承自 QWidget 或其子类:以确保在 UI 中正确显示和交互。
5.3.1 Promote to 功能的继承要求

在使用 Qt Designer 的 “Promote to” 功能时,确保自定义类能够正确替换预设控件,需要满足以下继承要求:

  • 继承自相同或兼容的基类

    • 自定义类必须继承自被替换的控件的基类。
    • 具体来说:
      • 替换 QPushButton:自定义类应继承自 QPushButton
      • 替换 QLabel:自定义类应继承自 QLabel
      • 替换 QWidget:自定义类应继承自 QWidget
    • 示例
      • 如果在 Qt Designer 中将 QPushButton 提升为 MyButton,则 MyButton 应继承自 QPushButton 而不是直接继承自 QWidget。这样可以确保接口兼容性和行为一致性,避免运行时错误。
  • 构造函数匹配

    • 自定义类应实现与基类兼容的构造函数,通常接受一个 QWidget 指针作为父对象。
    • 例如:
      explicit MyButton(QWidget *parent = nullptr);
      
    • 这确保在 Designer 生成的代码中,自定义控件能够正确初始化。
  • 正确的头文件包含

    • 在 “Promote to” 对话框中,需提供自定义类的头文件路径(如 MyButton.h),确保编译器能够找到并包含自定义类的定义。
    • 这对于项目的构建系统(如 qmake 或 CMake)来说至关重要,必须确保头文件路径正确配置。
  • 使用 Q_OBJECT 宏(如果需要)

    • 如果自定义类需要使用信号与槽机制,必须在类声明中包含 Q_OBJECT 宏,并确保 moc 正确处理。
    • 例如:
      class MyButton : public QPushButton {
          Q_OBJECT
          // ...
      };
      
  • 确保自定义类已在项目中编译

    • 自定义类必须包含在项目的编译过程中,确保 moc 处理和编译生成的元对象代码。
    • 在使用 CMake 时,通常需要将自定义类的头文件添加到 CMakeLists.txt 中的 HEADERSSOURCES 部分。
  • 处理虚函数和事件

    • 如果自定义类重写了基类的虚函数(如 paintEvent),需确保调用基类的实现,以保持控件的基本功能。
    • 例如:
      void MyButton::paintEvent(QPaintEvent *event) {
          // 自定义绘制
          QPushButton::paintEvent(event);
      }
      
  • 信号与槽的兼容性

    • 自定义类中定义的信号与槽应与基类的信号与槽兼容,确保在 Designer 中能够正确连接和使用。
    • 避免更改基类信号与槽的签名,除非有充分的理由和处理逻辑。

总结
使用 “Promote to” 功能时,遵循以上继承要求能够确保自定义控件在 Qt Designer 中正确工作,并与生成的 UI 代码无缝集成。这不仅提高了开发效率,还保持了代码的可维护性和扩展性。特别是 继承自被替换控件的基类 是确保功能和行为一致性的关键步骤,避免因继承不当导致的兼容性问题。

6. 最佳实践建议

  • 合理管理父对象

    • 确保对象的父子关系符合逻辑需求,避免内存泄漏或对象过早销毁。
    • 使用 Qt 的对象树机制自动管理内存,减少手动内存管理的负担。
  • 使用 Qt Designer 时依赖自动管理

    • 利用 Qt Designer 的布局和父对象管理功能,减少手动传递 this 的需要。
    • 通过 “Promote to” 功能集成自定义控件,保持代码与 UI 设计的同步。
  • 独立逻辑类使用 QObject 继承

    • 将不涉及 UI 的逻辑类继承自 QObject,并合理设置父对象以管理生命周期。
    • 保持 UI 设计与业务逻辑的分离,提高代码的可维护性和复用性。
  • 自定义 UI 组件继承自 QWidget 或其子类

    • 确保所有自定义的 UI 组件继承自 QWidget,以利用 Qt 的可视化和事件处理能力。
    • 在需要时通过 “Promote to” 功能集成到 Designer 中,提升开发效率。
  • 正确使用 Q_OBJECT

    • 对于需要信号与槽机制的类,确保在类声明中包含 Q_OBJECT 宏。
    • 将类声明放在独立的头文件中,让 moc 自动处理,避免手动包含 moc 文件带来的复杂性。
  • 保持代码和 UI 的同步

    • 在使用 Qt Designer 设计 UI 时,及时更新自定义类和代码,确保 UI 与逻辑的一致性。
    • 定期进行全量重新构建(Clean Build),避免 moc 处理遗漏的问题。

7. 总结

在 Qt C++ 开发中,选择继承自 QObjectQWidget 取决于类的功能需求。QObject 适用于非 UI 类,提供信号与槽等核心功能;而 QWidget 则用于构建和展示用户界面组件,具备可视化和事件处理能力。开发方式方面,纯代码开发提供了最大的灵活性,适用于高度定制化的场景;使用 Qt Designer 拖拽控件则提高了 UI 设计的效率;通过 “Promote to” 功能,可以结合可视化设计与自定义组件,实现更复杂和定制化的界面设计。

此外,正确管理父对象的传递和理解 Q_OBJECT 宏与 moc 的处理机制,对于确保应用程序的稳定性和可维护性至关重要。通过遵循本指南中的最佳实践,开发者可以更高效地在 Qt C++ 中开发自定义类,构建结构良好、功能强大的应用程序。

8. 参考资料

  • Qt 官方文档
  • Qt Designer 使用指南
  • Qt 信号与槽机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262389.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elastic 8.17:Elasticsearch logsdb 索引模式、Elastic Rerank 等

作者&#xff1a;来自 Elastic Brian Bergholm 今天&#xff0c;我们很高兴地宣布 Elastic 8.17 正式发布&#xff01; 紧随一个月前发布的 Elastic 8.16 之后&#xff0c;我们将 Elastic 8.17 的重点放在快速跟踪关键功能上&#xff0c;这些功能将带来存储节省和搜索性能优势…

[C++]类的继承

一、什么是继承 1.定义&#xff1a; 在 C 中&#xff0c;继承是一种机制&#xff0c;允许一个类&#xff08;派生类&#xff09;继承另一个类&#xff08;基类&#xff09;的成员&#xff08;数据和函数&#xff09;。继承使得派生类能够直接访问基类的公有和保护成员&#xf…

Docker 用法详解

文章目录 一、Docker 快速入门1.1 部署 MYSQL1.2 命令解读&#xff1a; 二、Docker 基础2.1 常见命令&#xff1a;2.1.1 命令介绍&#xff1a;2.1.2 演示&#xff1a;2.1.3 命令别名&#xff1a; 2.2 数据卷&#xff1a;2.2.1 数据卷简介&#xff1a;2.2.2 数据卷命令&#xff…

【自动化】Python SeleniumUtil 油猴 工具 自动安装用户脚本

【自动化】Python SeleniumUtil 油猴 工具 【自动化】Python SeleniumUtil 工具-CSDN博客【自动化】Python SeleniumUtil 工具。https://blog.csdn.net/G971005287W/article/details/144565691 油猴工具 import timefrom selenium.webdriver.support.wait import WebDriverW…

盛元广通畜牧与水产品检验技术研究所LIMS系统

一、系统概述 盛元广通畜牧与水产品检验技术研究所LIMS系统集成了检测流程管理、样品管理、仪器设备管理、质量控制、数据记录与分析、合规性管理等功能于一体&#xff0c;能够帮助实验室实现全流程的数字化管理。在水产、畜牧产品的质检实验室中&#xff0c;LIMS系统通过引入…

clickhouse-数据库引擎

1、数据库引擎和表引擎 数据库引擎默认是Ordinary&#xff0c;在这种数据库下面的表可以是任意类型引擎。 生产环境中常用的表引擎是MergeTree系列&#xff0c;也是官方主推的引擎。 MergeTree是基础引擎&#xff0c;有主键索引、数据分区、数据副本、数据采样、删除和修改等功…

GEE+本地XGboot分类

GEE本地XGboot分类 我想做提取耕地提取&#xff0c;想到了一篇董金玮老师的一篇论文&#xff0c;这个论文是先提取的耕地&#xff0c;再做作物分类&#xff0c;耕地的提取代码是开源的。 但这个代码直接在云端上进行分类&#xff0c;GEE会爆内存&#xff0c;因此我准备把数据下…

Docker搭建kafka环境

系统&#xff1a;MacOS Sonoma 14.1 Docker版本&#xff1a;Docker version 27.3.1, build ce12230 Docker desktop版本&#xff1a;Docker Desktop 4.36.0 (175267) 1.拉取镜像 先打开Docker Desktop&#xff0c;然后在终端执行命令 docker pull lensesio/fast-data-dev …

校园点餐订餐外卖跑腿Java源码

简介&#xff1a; 一个非常实用的校园外卖系统&#xff0c;基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化&#xff0c;提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合&am…

Linux文件属性 --- 硬链接、所有者、所属组

三、硬链接数 1.目录 使用“ll”命令查看&#xff0c;在文件权限的后面有一列数字&#xff0c;这是文件的硬链接数。 对于目录&#xff0c;硬链接的数量是它具有的直接子目录的数量加上其父目录和自身。 下图的“qwe”目录就是“abc”目录的直接子目录。 2.文件 对于文件可…

Centos7 部署ZLMediakit

1、拉取代码 #国内用户推荐从同步镜像网站gitee下载 git clone --depth 1 https://gitee.com/xia-chu/ZLMediaKit cd ZLMediaKit #千万不要忘记执行这句命令 git submodule update --init 2、安装编译器 sudo yum -y install gcc 3、安装cmake sudo yum -y install cmake 4…

无管理员权限 LCU auth-token、port 获取(全网首发 go)

一&#xff1a; 提要&#xff1a; 参考项目&#xff1a; https://github.com/Zzaphkiel/Seraphine 想做一个 lol 查战绩的软件&#xff0c;并且满足自己的需求&#xff08;把混子和大爹都表示出来&#xff09;&#xff0c;做的第一步就是获取 lcu token &#xff0c;网上清一色…

《云原生安全攻防》-- K8s安全框架:认证、鉴权与准入控制

从本节课程开始&#xff0c;我们将来介绍K8s安全框架&#xff0c;这是保障K8s集群安全比较关键的安全机制。接下来&#xff0c;让我们一起来探索K8s安全框架的运行机制。 在这个课程中&#xff0c;我们将学习以下内容&#xff1a; K8s安全框架&#xff1a;由认证、鉴权和准入控…

spring\strust\springboot\isp前后端那些事儿

后端 一. 插入\更新一条数据&#xff08;老&#xff09; Map<String, Object> parameterMap MybatisUtil.initParameterSave("Send_ProjectFrozenLog", sendProjectFrozenLog); commonMapper.insert(parameterMap);parameterMap MybatisUtil.initParameter…

UE5安装Fab插件

今天才知道原来Fab也有类似Quixel Bridge的插件&#xff0c;于是立马就安装上了&#xff0c;这里分享一下安装方法 在Epic客户端 - 库 - Fab Library 搜索 Fab 即可安装Fab插件 然后重启引擎&#xff0c;在插件面板勾选即可 然后在窗口这就有了 引擎左下角也会多出一个Fab图标…

对于使用exe4j打包,出现“NoClassDefFoundError: BOOT-INF/classes”的解决方案

jar使用exe4j打包exe&#xff0c;出现NoClassDefFoundError: BOOT-INF/classes 注意选取的jar包是使用build&#xff0c;而不是maven中的install 本文介绍解决这个方法的方案 点击Project Structure 按照如图所示选择 选择main class&#xff0c;选择你要打的main 如果遇到/M…

文件上传之文件内容检测

一.基本概念 介绍&#xff1a;文件内容检测就是检测上传的文件里的内容。 文件幻数检测 通常情况下&#xff0c;通过判断前10个字节&#xff0c;基本就能判断出一个文件的真实类型。 文件加载检测 一般是调用API或函数对文件进行加载测试。常见的是图像渲染测试&#xff0c;再…

WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程

前言 Kettle简介 Kettle是一款国外开源的ETL工具&#xff0c;纯Java编写&#xff0c;可以在Window、Linux、Unix上运行&#xff0c;绿色无需安装&#xff0c;数据抽取高效稳定 WebSpoon是Kettle的Web版本&#xff0c;由Kettle社区维护&#xff0c;不受Pentaho支持&#xff0c;…

搭建Tomcat(三)---重写service方法

目录 引入 一、在Java中创建一个新的空项目&#xff08;初步搭建&#xff09; 问题&#xff1a; 要求在tomcat软件包下的MyTomcat类中编写main文件&#xff0c;实现在MyTomcat中扫描myweb软件包中的所有Java文件&#xff0c;并返回“WebServlet(url"myFirst")”中…

CAN配置---波特率中断引脚等---autochips-AC7811-ARM-M3内核

1、配置工具 虽然不怎么好用&#xff0c;但比没有强多了。具体看图&#xff1a; 时钟选着 NVIC配置 GPIO配置 2、生成的具体配置信息 NXP的配置工具里面&#xff0c;具体的波特率可以直接显示&#xff0c;这个工具没有&#xff0c;怎么办&#xff1f; 它放到了生成的代码里面…