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.mzph.cn/news/860676.shtml

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

相关文章

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC&#xff0c;即客户之声&#xff0c;是一种通过收集和分析客户反馈、需求和期望&#xff0c;来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下&#xff0c;VOC不仅能够帮助企业了解客户的真实需求&#xff0c;还能为企业提供宝贵的竞争情报&#xff0c;助力企业…

构建家庭NAS之三:在TrueNAS SCALE上安装qBittorrent

本系列文章索引&#xff1a; 构建家庭NAS之一&#xff1a;用途和软硬件选型 构建家庭NAS之二&#xff1a;TrueNAS Scale规划、安装与配置 构建家庭NAS之三&#xff1a;在TrueNAS SCALE上安装qBittorrent 大部分家庭NAS用户应该都会装一个下载工具。本篇以qBittorrent为例&…

LabVIEW与PLC通讯方式及比较

LabVIEW与PLC之间的通讯方式多样&#xff0c;包括使用MODBUS协议、OPC&#xff08;OLE for Process Control&#xff09;、Ethernet/IP以及串口通讯等。这些通讯方式各有特点&#xff0c;选择合适的通讯方式可以提高系统的效率和稳定性。以下将详细介绍每种通讯方式的特点、优点…

Edge 浏览器退出后,后台占用问题

Edge 浏览器退出后&#xff0c;后台占用问题 环境 windows 11 Microsoft Edge版本 126.0.2592.68 (正式版本) (64 位)详情 在关闭Edge软件后&#xff0c;查看后台&#xff0c;还占用很多系统资源。实在不明白&#xff0c;关了浏览器还不能全关了&#xff0c;微软也学流氓了。…

C语言数据结构-分析期末选择题考点(一)

昔我往矣&#xff0c;杨柳依依 今我来思&#xff0c;雨雪霏霏 契子✨ 有道是&#xff1a;得选择题者得天下。临近考试&#xff0c;便总结一下数据结构选择题的常考题型吧&#xff0c;以及预测一下考点&#xff0c;一来是为了备考&#xff0c;二来可以水文。祝各位老铁 “挂柯南…

18.枚举

学习知识&#xff1a;枚举类型、相关的使用方法 Main.java&#xff1a; public class Main {public static void main(String[] args) {myenum[] colorlist myenum.values();//获取枚举中所有对象的引用数组for (myenum one : colorlist){System.out.println(one.toString(…

kafka的命令行操作

kafka-topics.bat 该命令行和主题相关 kafka启动后&#xff0c;默认端口为9092,可修改 找到kafka_2.13-3.6.2\bin\windows目录下的kafka-topics.bat&#xff0c;用cmd执行 按下会有提示&#xff0c;REQURIED代表为必输项 创建topic 创建一个名为test的topic队列 kafka-t…

【阅读论文】-- IDmvis:面向1型糖尿病治疗决策支持的时序事件序列可视化

IDMVis: Temporal Event Sequence Visualization for Type 1 Diabetes Treatment Decision Support 摘要1 引言2 1 型糖尿病的背景3 相关工作3.1 时间事件序列可视化3.2 电子健康记录可视化3.3 1 型糖尿病可视化3.4 任务分析与抽象 4 数据抽象5 层次化任务抽象5.1 临床医生工作…

绘制全球各大洲典型流域的时间序列图

流量世界第一、长度第二的亚马逊流域&#xff08;Amazon&#xff09;、南美洲第四大、整条河流位于巴西的圣弗朗西斯科流域&#xff08;Sao Francisco&#xff09;、世界第四长、北美洲最长的密西西比流域&#xff08;Mississippi&#xff09;、欧洲最长的伏尔加流域&#xff0…

小程序简单版音乐播放器

小程序简单版音乐播放器 结构 先来看看页面结构 <!-- wxml --><!-- 标签页标题 --> <view class"tab"><view class"tab-item {{tab0?active:}}" bindtap"changeItem" data-item"0">音乐推荐</view><…

SAP ABAP 之容器

文章目录 前言一、案例介绍/笔者需求二、自定义容器 a.实例化对象 b.自定义容器效果演示 c.Copy Code 三、自适应容器 a.常用 必须 参数理解 b.METRIC 度量单位 c.RATIO 百分比尺寸 d.STYLE 容器…

springboot网上商城系统-计算机毕业设计源码08789

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设网上商城系统。 本设…

MUR6060PT-ASEMI逆变焊机MUR6060PT

编辑&#xff1a;ll MUR6060PT-ASEMI逆变焊机MUR6060PT 型号&#xff1a;MUR6060PT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;60A 最大循环峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;600V 最大…

C++:C与C++混合编程

混合编程 为什么需要混合编程 (1)C有很多优秀成熟项目和库&#xff0c;丢了可惜&#xff0c;重写没必要&#xff0c;C程序里要调用 (2)庞大项目划分后一部分适合用C&#xff0c;一部分适合用C (3)其他情况&#xff0c;如项目组一部分人习惯用C&#xff0c;一部分习惯用C 为什么…

echarts隔行背景色

看了下使用说明&#xff0c;试了半天终于搞对了 参考文档&#xff1a;Documentation - Apache ECharts option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [120, 200, 150, 80, 70, 110, 130],type: bar,mar…

【实用软件】Internet Download Manager(IDM6.41)下载及安装教程

​数据表明但是能够通过搭配下载的方式来使用IDM&#xff08;比如用迅雷离线下载&#xff0c;115离线&#xff0c;百度网盘等离线下载好的资源&#xff0c;然后结合HTTP协议的特性再用IDM下载&#xff09;能够达到事半功倍的效果。有目共睹的是IDM下载HTTP链接十分快&#xff0…

华为升腾显卡选型备忘

目录 1. 开发套件 2. 加速模块 3. 加速卡 4. 训练卡 官方地址&#xff1a;https://www.hiascend.com/ 备注&#xff1a; &#xff08;1&#xff09;V后缀的都是Video视频解析卡&#xff0c;本质是推理卡&#xff1b; &#xff08;2&#xff09;I后缀的都是推理卡&#…

(python)小学出题热门词汇可视化绘制

1.代码 import pandas as pd from wordcloud import WordCloud import matplotlib.pyplot as plt from collections import Counter import jieba # 如果你处理的是中文文本&#xff0c;需要jieba分词 import re # 停用词列表&#xff0c;这里只是示例&#xff0c…

Swift 周报 第五十四期

文章目录 前言新闻和社区苹果上架 iPhone 14/Pro 官翻机&#xff1a;起售价不到 5000 元苹果 iPhone SE 4 售价曝光苹果针对欧盟 20 亿美元罚单提起上诉 双方另一场“缠斗”已跨越近八年 提案通过的提案正在审查的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编…

SAP ABAP 之OOALV

文章目录 前言一、案例介绍/笔者需求二、SE24 查看类 a.基本属性 Properties b.接口 Interfaces c.友元 Friends d.属性 Attributes e.方法 Methods f.事件 Events g.局部类型 Types …