Qt的学习之路

目录

一、信号槽机制

1.1 基本概念

1.2 特点

1.3 使用方法

1.4 信号槽连接类型

1.5 注意

二、元对象系统

2.1 基本概念

2.2 实现方式

2.3 主要特性

2.4 使用场景

三、国际化

3.1 标记可翻译的文本(tr函数)

3.2 生成翻译源文件(.ts文件)

3.3 翻译

3.4 编译翻译结果(.qm文件)

3.5 加载和使用翻译(QTranslator类)

3.6 注意

四、插件系统

4.1 定义插件接口

4.2 创建插件

4.3 导出插件

 4.4 加载插件

4.5 错误处理

4.6 插件的注册和发现

4.7 编译和部署

五、事件循环机制

5.1 事件循环的概念

5.2 事件处理流程

5.3 事件循环的优先级

5.4 事件循环的进入和退出

5.5 嵌套事件循环

六、多线程

6.1 继承QThread类

6.2 继承QObject类

6.3 线程池QThreadPool

6.4 QMetaObject::invokeMethod()方法使用

6.5 线程同步

6.6 线程与事件循环

七、模型/视图框架

7.1 模型

7.2 视图

7.3 委托


一、信号槽机制

Qt 的信号和槽(Signals and Slots)机制是其框架中的一个核心特性,它提供了一种强大而灵活的通信方式,允许对象之间进行交互。这种机制替代了传统的回调函数,使得代码更加清晰、易于理解和维护。

1.1 基本概念

信号(Signal):当某个特定事件发生时,对象会发射(emit)一个信号。这个事件可以是用户交互(如按钮点击)、系统事件(如定时器超时)或其他对象的状态变化。
槽(Slot):槽是响应信号的对象成员函数。当一个信号被发射时,与其关联的槽函数会被自动调用。槽可以是任何成员函数,但通常它们都是 public slots,以便其他对象可以将其与信号连接。
连接(Connection):信号和槽之间的关联是通过连接(connect)操作建立的。Qt 提供了一个灵活的连接系统,允许在运行时动态地建立或断开信号和槽之间的关联。

1.2 特点

类型安全:信号和槽的签名(即参数类型和数量)必须匹配,这保证了在连接时不会发生类型错误。
灵活性:一个信号可以连接多个槽,一个槽也可以连接多个信号。此外,信号和槽之间的连接可以是直接的(即一个对象直接调用另一个对象的槽),也可以是间接的(通过信号和槽机制进行)。
解耦:信号和槽机制允许对象之间的解耦,即对象不需要知道彼此的具体实现细节就可以进行交互。这有助于降低代码的耦合度,提高可维护性。
易于扩展:Qt 允许用户自定义信号和槽,这使得开发者可以轻松地扩展框架的功能。

1.3 使用方法

定义信号和槽:在 Qt 中,信号使用 signals 关键字定义,而槽使用 slots 关键字定义。它们都必须是类的成员函数。
连接信号和槽:使用 QObject::connect() 函数将信号和槽连接起来。该函数接受五个参数:发射信号的对象、信号、接收信号的对象、槽函数、连接类型。
发射信号:当某个事件发生时,使用 emit 关键字发射信号。这会导致与该信号连接的所有槽函数被调用。
断开信号和槽:使用 QObject::disconnect() 函数可以断开信号和槽之间的连接。这通常在对象销毁或需要重新配置连接时使用。

1.4 信号槽连接类型

当你使用 Qt::connect 来连接信号和槽时,你可以选择不同的连接类型。这些连接类型决定了当信号被发射时,槽的调用方式。Qt 提供了以下四种连接类型:
Qt::DirectConnection
当信号被发射时,槽函数立即在同一线程中被调用。
这是最快的连接类型,但只适用于信号和槽在同一线程中的情况。
如果信号和槽在不同的线程中,并且你尝试使用这种连接类型,Qt 会发出警告,并可能不调用槽函数。
Qt::QueuedConnection
当信号被发射时,槽函数的调用被放入接收对象所在线程的事件队列中,等待该线程的事件循环来处理。
这允许信号和槽在不同的线程中安全地通信。
由于涉及线程间通信和事件队列处理,这种连接类型通常比 Qt::DirectConnection 慢。
Qt::AutoConnection
这是默认的连接类型。
如果信号和槽在同一线程中,它使用 Qt::DirectConnection。
如果信号和槽在不同的线程中,它使用 Qt::QueuedConnection。
这是一种灵活的连接类型,可以根据信号和槽的位置自动选择最合适的连接方式。
Qt::BlockingQueuedConnection
当信号被发射时,发射信号的线程会被阻塞,直到接收线程的事件循环处理了该槽函数的调用。
这种连接类型通常用于需要等待槽函数执行完成的情况,但应谨慎使用,因为它可能导致线程阻塞和性能问题。
在大多数情况下,你可以使用 Qt::AutoConnection,让 Qt 自动选择最合适的连接方式。但是,在某些特定场景下,你可能需要显式地指定连接类型来满足特定的需求。

1.5 注意

在Qt中,同一个信号可以多次连接到同一个槽,或者连接到不同的槽。这种连接被称为信号的“多播”(multicast)或“多连接”(multiple connections)。
当你使用QObject::connect()函数连接一个信号到一个槽时,Qt并不会检查是否已经存在相同的连接。每次调用connect()都会创建一个新的连接,除非在连接时指定了某种类型的连接类型(如Qt::UniqueConnection),该类型会防止创建重复的连接。
例如,以下代码展示了如何将同一个信号连接到同一个槽两次:

// 第一次连接  
QObject::connect(sender, &Sender::mySignal, receiver, &Receiver::mySlot);  
  
// 第二次连接(也是有效的,信号会两次调用mySlot)  
QObject::connect(sender, &Sender::mySignal, receiver, &Receiver::mySlot);

二、元对象系统

Qt元对象系统(Meta-Object System)是Qt框架中的一个核心概念,它提供了在运行时对对象进行反射和元数据操作的机制。以下是关于Qt元对象系统的详细解释:

2.1 基本概念

Qt元对象系统允许开发者在不了解对象实际类型的情况下,通过对象的元数据来访问和操作对象的属性、信号和槽。
在Qt中,每个从QObject派生的类都会有一个对应的元对象(MetaObject),用于存储类的元数据。这些元数据包括类的属性、信号和槽的信息以及其他相关元信息。

2.2 实现方式

元对象是通过元对象编译器(moc)根据类的声明自动生成的。moc是一个预处理器,它读取包含Q_OBJECT宏的C++源文件,并为每个类生成元对象代码。
这些生成的元对象代码或者被包含进类的源文件中,或者和类的实现同时进行编译和链接。

2.3 主要特性

动态属性系统:允许在运行时添加和访问对象的属性。
信号和槽机制:Qt的核心特性之一,用于实现对象之间的通信。
运行时类型识别(RTTI):通过QObject::inherits()函数和qobject_cast<>()函数,可以在运行时确定对象的类型并进行类型转换。
国际化支持:通过QObject::tr()和QObject::trUtf8()函数进行字符串翻译。
对象树和内存管理:QObject提供了对象树结构,支持父子关系,当父对象被删除时,其子对象也会被自动删除。
关键类和宏
QObject:所有使用元对象系统的类的基类,必须在类的开头使用Q_OBJECT宏。
Q_OBJECT:必须在类的私有声明区声明此宏,以启用元对象系统的特性。
QMetaObject:管理类的元对象,提供访问元数据的方法。
QMetaProperty、QMetaMethod、QMetaClassInfo等:用于访问类的属性、方法和类注释信息的类。

2.4 使用场景

动态连接和断开信号和槽:实现对象之间的通信。
对象复制:通过元对象系统可以复制对象的状态。
动态属性添加和访问:在运行时动态地添加和访问对象的属性。
插件系统:Qt的插件系统依赖于元对象系统来实现插件的动态加载和类型检查。

三、国际化

Qt国际化的实现主要依赖于几个关键步骤和组件,以下是对其实现的详细解释:

3.1 标记可翻译的文本(tr函数)

在Qt应用程序的源代码中,使用tr()函数来标记需要翻译的字符串。这些字符串将被Qt的国际化工具识别并提取出来用于翻译。

QLabel *label = new QLabel(this);  
label->setText(tr("Hello Qt!"));

3.2 生成翻译源文件(.ts文件)

使用Qt的lupdate工具从C++源代码中提取出所有被tr()函数标记的字符串,并生成一个或多个.ts文件。这些文件是XML格式的,包含了原始字符串和相关的上下文信息。

3.3 翻译

使用Qt Linguist工具打开.ts文件,进行字符串的翻译工作。Linguist提供了一个用户界面,允许开发者在源语言和目标语言之间切换,并输入相应的翻译。

3.4 编译翻译结果(.qm文件)

一旦翻译完成,使用Qt的lrelease工具从.ts文件中生成.qm文件。.qm文件是二进制格式的,包含了所有翻译后的字符串,可以被Qt应用程序在运行时加载和使用。

3.5 加载和使用翻译(QTranslator类)

在Qt应用程序中,使用QTranslator类来加载.qm文件。QTranslator会根据当前设置的语言环境来加载相应的翻译文件。

QTranslator translator;  
translator.load(":/translations/myapp_" + QLocale::system().name() + ".qm");  
app.installTranslator(&translator);

3.6 注意

动态文本布局:不同语言的文本长度和排版方式可能不同,需要确保界面能够动态地适应这些变化。
日期、时间和货币格式化:根据用户的语言和地区设置,对日期、时间和货币进行格式化,以符合当地的习惯和标准。
语言和区域设置:Qt能够根据用户的地理位置、语言、货币等偏好自动调整显示的界面元素。
文化适配:考虑到用户的文化背景,如图像、符号、颜色等在不同文化中可能具有不同的含义。

四、插件系统

在Qt框架中实现插件系统主要涉及到使用Qt的插件机制来动态加载和卸载插件模块。

4.1 定义插件接口

首先,你需要定义一个或多个接口类,这些类将作为插件和主程序之间的契约。这些接口类通常包含纯虚函数,插件需要实现这些函数。

// MyPluginInterface.h  
class MyPluginInterface  
{  
public:  
    virtual ~MyPluginInterface() {}  
    virtual void load() = 0;  
    virtual void unload() = 0; 
    // 其他纯虚函数...
};  
 
Q_DECLARE_INTERFACE(MyPluginInterface, "com.example.MyPluginInterface/1.0")

4.2 创建插件

接下来,你需要创建一个或多个插件,这些插件将实现你在第一步中定义的接口。每个插件都是一个独立的库(在 Windows 上是 DLL,在 Linux/Unix 上是 .so 文件)。

// MyPlugin.h  
#include "MyPluginInterface.h"  
  
class MyPlugin : public QObject, public MyPluginInterface  
{  
    Q_OBJECT  
    Q_PLUGIN_METADATA(IID "com.example.MyPluginInterface/1.0")  
    Q_INTERFACES(MyPluginInterface)  
  
public:  
    void load() override;
    void unload() override;
};

4.3 导出插件

在你的插件实现文件中,你需要使用 Q_EXPORT_PLUGIN2 宏来导出你的插件类。这个宏告诉 Qt 如何加载你的插件。

// MyPlugin.cpp  
#include "MyPlugin.h"  
  
Q_EXPORT_PLUGIN2(MyPlugin, MyPlugin)

 4.4 加载插件

在主程序中,你可以使用 QPluginLoader 类来动态加载插件。QPluginLoader 可以加载指定的插件库,并返回一个指向插件接口的指针。

QPluginLoader loader("path/to/your/plugin.dll"); // Windows  
// 或者  
QPluginLoader loader("path/to/your/libmyplugin.so"); // Linux/Unix  
if (!loader.load()) {  
    qDebug() << "Plugin failed to load:" << loader.errorString();  
    // 处理错误  
}
QObject *plugin = loader.instance();  
if (plugin) {  
    MyPluginInterface *myPlugin = qobject_cast<MyPluginInterface *>(plugin);  
    if (myPlugin) {  
        myPlugin->load();  
    }
    else {
        qDebug() << "Failed to get plugin instance";  
        // 处理错误
    } 
}

4.5 错误处理

当加载插件时,可能会出现各种错误,如文件不存在、插件版本不匹配等。你需要使用 QPluginLoader 的错误处理功能来检测和处理这些错误。

4.6 插件的注册和发现

Qt 的插件系统还支持插件的自动注册和发现。你可以使用 Qt 的元对象系统(Meta-Object System)和插件元数据(Q_PLUGIN_METADATA)来实现这一点。这样,主程序就可以在不指定插件路径的情况下自动加载插件。

4.7 编译和部署

最后,你需要确保你的插件和主程序都正确编译,并且插件库被放置在主程序可以访问的位置。在部署时,你可能还需要考虑不同平台上的库依赖问题。

五、事件循环机制

Qt事件循环机制是Qt框架中用于处理用户输入、事件响应以及应用程序逻辑的核心机制。它通过事件队列和事件分发机制,实现了对事件的异步处理和优先级管理。

5.1 事件循环的概念

定义:事件循环是一个无限循环,用于从操作系统接收事件并将其分发给合适的对象进行处理。这些事件可以来自用户交互、定时器、网络和其他外部设备。
核心作用:Qt事件循环是Qt框架中的核心概念之一,也被称为事件驱动编程。它使得Qt应用程序能够响应用户输入和系统事件,从而实现交互性和动态性。

5.2 事件处理流程

事件生成:事件可以由多种来源产生,包括用户交互(如鼠标点击、键盘按键)、定时器超时、网络活动等。
事件队列:当事件生成后,它们并不是立即被处理的,而是被放入一个事件队列中等待处理。这个队列按照一定的优先级顺序来管理事件,确保重要的事件能够优先得到处理。
事件分发:事件循环不断地从事件队列中取出事件,并将其分发给合适的对象进行处理。这个过程是通过调用每个QObject派生类的事件处理函数(event handler)来实现的。
事件处理:当某个对象接收到一个事件时,它会首先尝试自己处理该事件。如果该对象不能处理该事件,则会将该事件传递给其父级对象,直到找到能够处理该事件的对象或者最终没有任何对象处理该事件。

5.3 事件循环的优先级

Qt的事件循环中,事件按照一定的优先级顺序被处理。通常情况下,事件队列中最先处理的是以下类型的事件(按优先级从高到低):

QTimerEvent:定时器事件,用于处理定时器超时。
QMouseEvent:鼠标事件,例如鼠标点击、移动等操作。
QKeyEvent:键盘事件,例如按键按下、释放等操作。
QWheelEvent:滚轮事件,用于处理滚轮滚动操作。
QResizeEvent:窗口大小调整事件,当窗口大小发生变化时触发。
QCloseEvent:窗口关闭事件,当窗口被关闭时触发。

5.4 事件循环的进入和退出

进入事件循环:通过调用QCoreApplication::exec()函数,Qt应用程序就进入了一个事件循环中。这个函数会启动一个无限循环,等待并处理事件。
退出事件循环:当调用QCoreApplication::exit()或QCoreApplication::quit()函数时,事件循环就会终止。这通常发生在应用程序关闭或用户请求退出时。

5.5 嵌套事件循环

Qt应用通常至少有一个事件循环,即main()函数中调用的QCoreApplication::exec()。除此之外,还可能有其他的事件循环,如通过QEventLoop::exec()进入的本地事件循环。这些嵌套的事件循环允许在特定的代码段中处理特定的事件,而不会阻塞整个应用程序。

六、多线程

6.1 继承QThread类

一个QThread类对象管理一个子线程,自定义一个继承自QThread类,并重写虚函数run(),在run()函数里实现线程需要完成的复杂操作(注意QThread只有run函数是在新线程里的)。
一般在主线程创建工作子线程,并调用start(),开始执行工作子线程的任务。start()会在内部调用run()函数,进入工作线程的事件循环,在run()函数里调用exit()或quit()可以结束线程的事件循环,或者在工作主线程里调用terminate()强制结束线程。

class subThread : public QThread
{
    ...
protected:
    void run(){
        //全部在这里处理子线程的复杂业务
    }
};

在主线程中创建子线程,并调用start()方法启动子线程。

subThread* st = new subThread;
st->start();

6.2 继承QObject类

创建一个继承自QObject的业务类,处理相关业务逻辑,然后将该类对象移动到子线程中(调用moveToThread()方法)执行,可读性也更强,更易于维护。

class subObject : public QObject
{
    ...
public:
    void working();    //函数名称随意取,传入的参数根据实际需求添加
}

在主线程中创建一个子线程subThread对象,创建一个业务类subObject对象(创建该类对象千万不要指定父对象),再将业务类对象移动到子线程对象中,最后启动子线程。

QThread* subThread =  new QThread;
// subObject* subObj = new subObject(this);      //error
subObject* subObj = new subObject;            //OK
subObj->moveToThread(subThread);
subThread->start();

 在主线程中通过信号槽调用线程类subObject对象的工作函数,这时候才会到子线程中运行该工作函数。

connect(ui->pushButton,&QPushButton::clicked,subObj,&subObject::working);

6.3 线程池QThreadPool

创建一个继承自 QRunnable 的类,并实现 run() 方法。这个方法将包含你的任务代码。

class MyTask : public QRunnable  
{  
public:  
    void run() override {  
        // 在这里编写你的任务代码  
    }  
};

使用 QThreadPool::globalInstance() 获取全局线程池的实例,然后调用 start() 方法来提交任务。

MyTask *task = new MyTask();  
QThreadPool::globalInstance()->start(task);  
// 注意:task 对象将在任务完成后自动删除,除非你设置了不同的删除策略

 你可以使用 QThreadPool 的各种方法来配置线程池的行为,例如设置最大线程数:

QThreadPool::globalInstance()->setMaxThreadCount(4); // 设置最大线程数为 4

QRunnable类是所有可运行对象的基类,没有继承于QObject,所以就不能使用信号槽功能与外界通信。如果想要任务类MyTask与主线程通信,有两种办法:
(1)使用多继承,就是让线程类同时继承QObject和QRunnable(不推荐),让该线程类能够支持信号槽的使用。
(2)使用QMetaObject::invokeMethod()方法(推荐)。

6.4 QMetaObject::invokeMethod()方法使用

QMetaObject::invokeMethod() 是 Qt 框架中用于跨线程或在当前线程中安全地调用对象的槽(slot)函数的方法。这个函数非常有用,因为它允许你在不直接调用对象方法的情况下,通过元对象系统来调用对象的槽函数。
以下是 QMetaObject::invokeMethod() 的基本使用方式:

#include <QMetaObject>

// 假设你有一个指向 QObject 派生类的指针,名为 obj  
QObject *obj = ...; // 从某个地方获得的对象  
  
// 使用 invokeMethod 调用该对象的槽函数  
// 例如,我们假设 obj 有一个名为 "mySlot" 的槽函数  
QMetaObject::invokeMethod(obj, "mySlot", Qt::QueuedConnection);  
  
// 如果有参数需要传递,你可以这样做:  
QList<QVariant> args;  
args << QVariant(123) << QVariant(QString("Hello"));  
QMetaObject::invokeMethod(obj, "mySlotWithArgs", Qt::QueuedConnection, args.constBegin(), args.size());

注意几点:
连接类型:在上面的例子中,我们使用了 Qt::QueuedConnection,这意味着如果 obj 在另一个线程中,则槽函数的调用将被排队到该线程的事件循环中。如果你在同一线程中调用,并且希望立即执行槽函数,可以使用 Qt::DirectConnection。
参数:如果你需要传递参数给槽函数,你可以使用 QList<QVariant> 来存储这些参数,并将它们作为 invokeMethod 的参数传递。
返回值:invokeMethod 本身不返回槽函数的返回值。如果你需要返回值,你可能需要设计一种不同的通信机制,例如使用信号和槽,并通过信号传递返回值。
线程安全:invokeMethod 是线程安全的,这意味着你可以在一个线程中安全地调用另一个线程中对象的槽函数。但是,你仍然需要确保你传递给槽函数的任何数据都是线程安全的。
错误处理:如果槽函数不存在或无法调用,invokeMethod 将不会抛出异常或返回错误代码。但是,你可以通过连接 QObject::destroyed() 信号来检查对象是否已被销毁,这可能导致 invokeMethod 失败。

6.5 线程同步

Qt 提供了多种线程同步的方式,以确保线程之间的协调和数据的一致性。这些同步机制包括互斥锁(QMutex)、读写锁(QReadWriteLock)、条件变量(QWaitCondition)、信号和槽(Signals and Slots)以及 Qt 的并发类(如 QFuture、QThreadPool)。
以下是使用 QMutex 和 QWaitCondition 实现线程同步的示例代码:

#include <QCoreApplication>  
#include <QThread>  
#include <QMutex>  
#include <QWaitCondition>  
#include <QDebug>  
  
class WorkerThread : public QThread  
{  
    Q_OBJECT  
  
public:  
    WorkerThread(QMutex *mutex, QWaitCondition *condition, QObject *parent = nullptr)  
        : QThread(parent), mutex(mutex), condition(condition), workDone(false) {}  
  
protected:  
    void run() override {  
        // 模拟一些工作  
        qDebug() << "WorkerThread: 开始工作";  
        QThread::sleep(2); // 模拟耗时操作  
  
        // 工作完成后,锁定互斥锁并设置条件  
        QMutexLocker locker(mutex);  
        workDone = true;  
        condition->wakeOne();  
        qDebug() << "WorkerThread: 工作完成";  
    }  
  
    bool workDone;  
  
private:  
    QMutex *mutex;  
    QWaitCondition *condition;  
};  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    QMutex mutex;  
    QWaitCondition condition;  
  
    WorkerThread thread(&mutex, &condition);  
    thread.start();  
  
    // 等待工作线程完成  
    QMutexLocker locker(&mutex);  
    while (!thread.workDone)  
        condition.wait(&mutex);  
  
    qDebug() << "主线程: 接收到工作完成信号";  
  
    thread.wait(); // 等待线程安全退出  
  
    return a.exec();  
}  

6.6 线程与事件循环

QThread中run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列)中的事件。exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使退出线程,尽量不要使用terminate()退出线程,terminate()退出线程过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。
线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。
在QApplication前创建的对象,QObject::thread()返回NULL,意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。可以用QObject::moveToThread()来改变对象及其子对象的线程亲缘关系,假如对象有父亲,不能移动这种关系。在另一个线程(而不是创建它的线程)中delete QObject对象是不安全的。除非可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。假如没有事件循环运行,事件不会分发给对象。假如在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号,deleteLater()也不会工作。可以手工使用线程安全的函数QCoreApplication::postEvent(),在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。

七、模型/视图框架

Qt中的模型/视图架构用来实现大量的数据存储、处理及显示。


模型(model)用来存储数据;视图(View)用来显示数据;控制(Controller)用来处理数据;委托(Delegate)用来定制数据的渲染和编辑方式。

7.1 模型

所有模型都基于 QAbstractItemModel 类。视图和委托使用此类的接口来访问数据。
数据本身不必存储在模型中,它可以保存在由单独的类、文件、数据库或某些其他应用程序组件提供的数据结构或存储库中。
QAbstractItemModel 提供了一个数据接口,该接口足够灵活,可以处理以表格、列表和树的形式表示数据的视图。但是,在为列表(1列n行)和类似表格(n行m列)的数据结构实现新模型时,QAbstractListModel 和 QAbstractTableModel 类是更好的起点,因为它们提供了常用函数的适当默认实现。这些类中的每一个都可以被子类化以提供支持特殊类型列表和表格的模型。
Qt 提供了一些现成的模型,可用于处理数据项
QStringListModel 用于存储 QString 项的简单列表。
QStandardItemModel 管理更复杂的项目树结构,每个项目都可以包含任意数据。
QFileSystemModel 提供有关本地文件系统中文件和目录的信息。
QSqlQueryModel、QSqlTableModel 、QSqlRelationalTableModel 用于使用模型/视图方式访问数据库。
如果这些标准模型不符合要求,可以将 QAbstractItemModel、QAbstractListModel 、QAbstractTableModel 子类化以创建自定义模型。

7.2 视图

Qt为不同类型的视图提供了完整的实现:
QListView 显示项目列表。
QTableView 在表格中显示模型中的数据。
QTreeView 在分层列表中显示模型数据项。
这些类中都基于 QAbstractItemView 抽象基类。虽然这些类是现成的实现,但它们也可以被子类化以提供自定义视图。

下面是一个简单的Qt模型/视图框架的示例代码,其中使用QStandardItemModel作为模型,QTableView作为视图。 

#include <QApplication>  
#include <QTableView>  
#include <QStandardItemModel>  
  
int main(int argc, char *argv[])  
{  
    QApplication app(argc, argv);  
  
    // 创建一个标准模型  
    QStandardItemModel model(4, 3); // 4行3列  
  
    // 设置模型的水平和垂直表头  
    QStringList headers;  
    headers << "Name" << "Age" << "City";  
    model.setHorizontalHeaderLabels(headers);  
  
    // 填充模型数据  
    for (int row = 0; row < 4; ++row) {  
        for (int column = 0; column < 3; ++column) {  
            QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));  
            model.setItem(row, column, item);  
        }  
    }  
  
    // 创建一个表格视图  
    QTableView tableView;  
    tableView.setModel(&model); // 设置模型  
  
    // 显示视图  
    tableView.show();  
  
    return app.exec();  
}

7.3 委托

QAbstractItemDelegate 是模型/视图框架中委托的抽象基类。
默认委托实现由 QStyledItemDelegate 提供,它被 Qt 的标准视图用作默认委托。
QStyledItemDelegate 和 QItemDelegate 是为视图中的项目绘制和提供编辑器的两个独立替代方案。
它们之间的区别在于 QStyledItemDelegate 使用当前样式来绘制其项目。因此建议在实现自定义委托时使用 QStyledItemDelegate 作为基类。详细参见:QStyledItemDelegate的使用方法

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

顺序栈与链式栈

目录 1. 栈 1.1 栈的概念 2. 栈的实现 3. 顺序栈的实现 3.1 顺序栈的声明 3.2 顺序栈的初始化 3.3 顺序栈的入栈 3.4 顺序栈的出栈 3.5 顺序栈获取栈顶元素 3.6 顺序栈获取栈内有效数据个数 3.7 顺序栈判断栈是否为空 3.8 顺序栈打印栈内元素 3.9 顺序栈销毁栈 3…

高频面试题基本总结回顾1(含笔试高频算法整理)

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

10.XSS绕过之htmlspecialchars()函数

XSS绕过之htmlspecialchars()函数 首先可以测试一下是否将字符被转移成html实体&#xff0c;输入字符测试 1111"<>$点击提交 查看页面元素代码&#xff0c;发现单引号不变&#xff0c;可以利用 重新输入攻击代码&#xff0c;用单引号闭合前面的&#xff0c;进…

AI智能写作工具,AI写作助手大全

随着人工智能技术的快速发展&#xff0c;AI智能写作工具助手已成为学术研究、内容创作和商业文案等领域的重要辅助工具。它们不仅能够提高写作效率&#xff0c;还能激发创意灵感&#xff0c;为各行各业的专业人士提供了强大的支持。下面小编将为大家全面介绍目前市场上备受瞩目…

架构是怎样练成的-楼宇监控系统案例

目录 概要 项目背景 原系统设计方案 改进后的设计方案 小结 概要 绝大多数人掌握的架构都是直接学习&#xff0c;慢慢地才能体会到一个架构的好处。架构是一种抽象&#xff0c;是为了复用目的而对代码做的抽象。通过一个项目的改造&#xff0c;理解架构是如何产生的&…

HTML+CSS 彩色浮雕按钮

效果演示 实现了一个彩色按钮特效&#xff0c;包括一个按钮&#xff08;button&#xff09;和一个前景色&#xff08;::before&#xff09;。按钮具有四种不同的颜色&#xff0c;当鼠标悬停在按钮上时&#xff0c;前景色会出现渐变效果&#xff0c;并且按钮的颜色、文本阴影和边…

04 Shell编程之正则表达式与文本处理器

目录 4.1 正则表达式 4.1.1 正则表达式概述 1. 正则表达式的定义 2. 正则表达式用途 4.1.2 基础正则表达式 1. 基础正则表达式示例 1. 查找特点字符 2. 利用中括号"[]"来查找集合字符 3. 查找行首"^"与行尾字符"$" 4. 查找任意一个字符".&…

供应链攻击是什么?

随着企业对技术和连接性的依赖日益增加&#xff0c;以及对第三方的普遍依赖&#xff0c;供应链攻击变得越来越普遍。这些攻击旨在通过供应商和商业伙伴损害企业。 供应链攻击可能对企业和组织构成重大威胁&#xff0c;因为它们可能危及它们的安全以及向客户提供的产品和服务的…

算法训练营day19--530.二叉搜索树的最小绝对差+501.二叉搜索树中的众数+236. 二叉树的最近公共祖先

一、530.二叉搜索树的最小绝对差 题目链接&#xff1a;https://leetcode.cn/problems/minimum-absolute-difference-in-bst/ 文章讲解&#xff1a;https://programmercarl.com/0530.%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BB%9D%E5%AF…

基于KNN的旋转机械故障诊断(MATLAB)

KNN算法又称K-近邻算法&#xff0c;其主要思想是&#xff1a;对于要分类的样本按照一定的相似性度量方法寻找与之最近的K个邻居&#xff0c;计算这K个邻居中类别出现次数最多的那个类作为该样本所属类。其算法步骤如下。 (1)计算待分类样本与训练集中各个数据之间的距离。 (2…

React 19 新特性集合

前言&#xff1a;https://juejin.cn/post/7337207433868197915 新 React 版本信息 伴随 React v19 Beta 的发布&#xff0c;React v18.3 也一并发布。 React v18.3相比最后一个 React v18 的版本 v18.2 &#xff0c;v18.3 添加了一些警告提示&#xff0c;便于尽早发现问题&a…

数学建模--Matlab操作与运算

目录 1.点运算 2.文件介绍 &#xff08;1&#xff09;文件分类 &#xff08;2&#xff09;温度转换 &#xff08;2&#xff09;函数调用 &#xff08;3&#xff09;建模经验 &#xff08;4&#xff09;注意事项 &#xff08;5&#xff09;多个返回值情况 &#xff08;6…

离线部署OpenIM

目录 1.提取相关安装包和镜像 2.安装docker和docker-compose 3.依次导入镜像 4.解压安装包 5.执行安装命令 6.PC Web 验证 7.开放端口 7.1IM 端口 7.2Chat 端口 7.3 PC Web 及管理后台前端资源端口 “如果您在解决类似问题时也遇到了困难&#xff0c;希望我的经验分享…

将深度相机的实时三维坐标数据保存为excel文档(Python+Pyrealsense2+YOLOv8)

一、如何将数据保存为excel文档 1.excel文件库与相关使用 &#xff08;1&#xff09;导入相应的excel文件库&#xff0c;导入前先要进行pip安装&#xff0c;pip install xlwt import xlwt # 导入用于创建和写入Excel文件的库 (2) 建立一个excel文档&#xff0c;并在第0行写…

Python | Leetcode Python题解之第198题打家劫舍

题目&#xff1a; 题解&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:if not nums:return 0size len(nums)if size 1:return nums[0]first, second nums[0], max(nums[0], nums[1])for i in range(2, size):first, second second, max(first nu…

读AI新生:破解人机共存密码笔记12人工智能辩论

1. 言论 1.1. 对一个人终身职业的威胁&#xff0c;可能会使一个非常聪明的、通常很有思想的人说出一些话&#xff0c;但在进一步分析后&#xff0c;他们很可能希望收回这些话 1.2. 电子计算器在算术方面是“超人”&#xff0c;但是计算器并没有接管世界&#xff0c;因此&…

IMX6ULL SD卡启动uboot+kernel+rootfs

目录 1. 背景说明 2.SD卡启动 2.1准备条件 2.2 对SD卡分区格式化 2.3 制作sd卡镜像 3.效果测试 1. 背景说明 网络上绝大数教程&#xff0c;教大家把uboot烧录到SD卡&#xff0c;然后uboot启动后&#xff0c;通过TFTP下载kernel和设备树&#xff0c;然后通过nfs挂载文件系…

laravel的日志使用说明

文章目录 了解系统的默认支持多个通道时它们的关系如何使用驱动 了解系统的默认支持 Laravel 日志基于「 通道 」和 「 驱动 」的。那么这个通道是干嘛的&#xff1f;驱动又是干嘛的&#xff1f; 通道 &#xff1a; 1.它表示了某种日志格式化的方式&#xff08;或可理解为某个…

理解CNN模型如何学习

深度学习模型常常被认为是不可解释的。但是人们正在探索不同的技术来解释这些模型内发生了什么。对于图像&#xff0c;由卷积神经网络学习的特征是可解释的。我们将探索两种流行的技术来理解卷积神经网络。 可视化中间层的输出 可视化中间层的输出将有助于我们理解输入图像如何…

办公软件的答案?ONLYOFFICE 桌面应用编辑器会是最好用的 Office 软件?ONLYOFFICE 桌面编辑器使用初体验

文章目录 &#x1f4cb;前言&#x1f3af;什么是 ONLYOFFICE&#x1f3af; 主要功能介绍及 8.1 新功能体验&#x1f3af; 在线体验&#x1f4dd;最后 &#x1f4cb;前言 提到办公软件&#xff0c;大家最常用的可能就是微软的 Microsoft Office 和国产的 WPS Office。这两款软件…