QT框架笔记

VS上使用QT

环境搭建

C++实践之Qt学习(扩展):Visual Studio中Qt开发环境的搭建_visual studio 与 qt-CSDN博客

配置下32,64位的生成配置

【QT】visual studio QT 32位和64位生成配置_qt生成32位程序-CSDN博客

显示中文,防止乱码:

1
#pragma execution_character_set("utf-8")

Qt 在跨平台开发中广泛支持 UTF-8 , 它告诉编译器将源代码中的字符串文字编码为 UTF-8,而不是默认的系统编码(例如GBK)。

关于父类的说明

QMainWindow

可以包含菜单栏、工具栏、状态栏、标题栏等,是最常见的窗口形式,可以作为桌面应用的主窗口

QWidget

是所有用户界面对象的基类,其他的窗口和控件都是直接或间接地集成自QWidget,一般创建桌面应用程序时需要创建一个窗口,此时选择QMainWindow或者QDialog即可,QMainWindow是主窗口,QDialog表示对话框。而如果不确定是否作为一个顶级窗口或者嵌入到其他窗口中,可以使用QWidget,同时它也是实现自定义部件的基类。

QDialog

表示对话框,主要用来进行短期交互,可以设置成模态的。一般没有菜单栏、工具栏、状态栏等。

1731162783725

QT信号与槽机制

简单介绍

在 Qt 框架中,信号和槽(Signal and Slot)是一种用于对象之间通信的机制,尤其适用于事件驱动的 GUI 应用程序。信号和槽是 Qt 中的一种扩展机制,专门为简化和增强组件间的交互设计的。

信号:类似于一种“广播”机制,表明发生了某些事件。信号本身不包含处理逻辑,只是单纯的事件通知。

:槽函数用于接收信号,槽函数会定义如何处理事件。槽函数可以是任何可调用的成员函数。

类似于之前学过的MFC框架中的消息映射

QT中信号和槽的特点

(为什么信号和槽不写成统一的普通函数)

1. 松耦合(Loose Coupling)

信号和槽的机制通过解耦事件的发送者(信号)和接收者(槽)之间的关系,提供了松耦合的编程方式。松耦合是指发送信号的对象并不需要知道任何关于接收信号的对象(即槽)的细节,反之亦然。

  • 传统的函数调用:通常在 C++ 中,一个对象直接调用另一个对象的函数,这需要对象彼此了解,并且依赖于函数的实现。如果你想更改接收者对象或者函数的实现,必须更改调用者代码。这种方式很紧耦合,修改和维护变得困难。
  • 信号与槽:使用信号和槽机制时,发送信号的对象根本不需要知道谁在接收信号,它只是通过 emit 关键字发出信号。接收信号的槽函数可以在其他任何地方实现并连接到信号。这使得对象间的关系非常灵活,减少了代码之间的依赖。

2. 事件驱动机制

Qt 是一个事件驱动框架,这意味着程序的行为是由事件触发的,而不是由方法调用的。信号和槽机制就是实现事件驱动模型的核心之一。

  • 传统的调用:在传统 C++ 中,你通常会通过直接调用函数来触发某些行为,这种行为是同步的,即调用者必须等待返回。
  • 信号和槽:信号和槽机制允许事件的异步处理。例如,某个对象可以发出信号(事件),而不关心谁处理它,接收者(槽)可以在合适的时机处理这个信号,而不必与发送者直接交互。Qt 框架也支持这种信号异步传递的机制,可以在后台线程中处理信号,从而避免 UI 阻塞。

3. 自动连接机制

Qt 的信号和槽机制具有自动连接功能。通常,Qt 会根据信号和槽的名称、参数匹配情况,自动连接信号与槽。你无需手动管理函数指针或事件传递。

  • 传统的函数指针或回调:你需要显式地将回调函数与事件绑定。例如,在某些情况下,你可能需要手动管理一个函数指针或回调对象。这种方式不如信号和槽机制直观和灵活。
  • 信号与槽的自动连接:通过 QObject::connect 函数,信号和槽可以自动绑定。如果信号发射时参数类型与槽匹配,Qt 会自动调用相应的槽函数。你只需要专注于信号的触发和槽的实现,不必担心对象间如何互相调用。

4. 多个槽的连接(多重响应)

使用信号和槽机制,一个信号可以连接到多个槽函数,这意味着一个事件可以触发多个不同的行为。这种特性使得信号和槽非常适合多任务或并发编程。

  • 传统的函数调用:你只能调用一个函数。
  • 信号与槽:一个信号可以连接到多个槽,这使得你可以根据不同的需求实现不同的行为。例如,多个组件都可以监听同一个信号,而各自作出不同的反应。

5. 线程安全

Qt 的信号和槽机制本身设计时考虑了多线程环境,在多线程的情况下,信号和槽之间的调用是线程安全的。Qt 内部自动处理了信号和槽的线程间通信。

  • 传统的线程通信:在传统的 C++ 中,你需要使用显式的线程同步机制(如互斥锁、条件变量等)来保证线程安全。
  • 信号与槽的线程安全:Qt 会自动处理跨线程的信号和槽连接,例如在不同线程之间传递信号时,它会将信号的执行放到目标线程的事件队列中,这样即使在多线程环境下,信号与槽的通信也能保持安全。

6. 灵活性

信号和槽提供了非常灵活的方式来处理事件。你可以通过编程动态地添加和移除槽,甚至可以在运行时动态连接信号和槽。这种灵活性非常适用于复杂的 GUI 应用程序或需要高度自定义行为的程序。

  • 传统的函数调用:通常是固定的,函数调用的绑定在编译时就已经确定了,缺乏动态调整的能力。
  • 信号和槽:通过 QObject::connectQObject::disconnect,你可以在运行时灵活地连接和断开信号与槽的关系,极大地增加了代码的灵活性和扩展性。

参数类型、参数数量和顺序都一致 那么信号和槽就会匹配。

QObject::connect()

在 Qt 中,信号和槽是通过 QObject::connect() 函数连接的:

1
2
3
4
5
6
7
QObject::connect(
const QObject *sender,
PointerToMemberFunction signal,
const QObject *receiver,
PointerToMemberFunction slot,
Qt::ConnectionType type = Qt::AutoConnection
);

sender (发送者对象指针)

  • const QObject *sender:表示信号的发送者对象,即哪个对象发出信号。
  • 一般是类的对象指针,如 &myButtonthis

signal (信号函数指针)

  • PointerToMemberFunction signal:这是指向信号函数的成员函数指针,指定要发出的信号。
  • 格式是 &ClassName::signalName,需要明确指出信号所在类的作用域。
  • 示例&QPushButton::clicked 表示 QPushButton 类中的 clicked() 信号。

receiver (接收者对象指针)

  • const QObject *receiver:表示信号的接收者对象,即哪个对象接收信号并执行相应槽函数。
  • 一般是类的对象指针,比如 &objthis

slot (槽函数指针)

  • PointerToMemberFunction slot:这是指向槽函数的成员函数指针,表示当信号触发时要调用的槽函数。
  • 格式是 &ClassName::slotName,也需要明确槽所在类的作用域。
  • 示例&MyClass::onButtonClicked 表示 MyClass 类中的 onButtonClicked() 槽函数。

**type**(连接类型,可选参数)

  • Qt::ConnectionType type = Qt::AutoConnection:指定连接类型,用来决定信号和槽的执行方式。默认为 Qt::AutoConnection,它会自动根据线程情况选择合适的连接类型。

  • 其他可选值包括:

    • Qt::DirectConnection:槽在发送信号的线程中立即执行。
    • Qt::QueuedConnection:槽在接收者所在的线程中异步执行。
    • Qt::BlockingQueuedConnection:阻塞发送者线程,直到槽函数完成(仅在多线程环境中使用)。
    • Qt::UniqueConnection:确保同一对信号和槽只连接一次。

函数返回值

返回一个 QMetaObject::Connection 类型的值,用于断开连接。如果连接成功,返回一个有效的连接对象;如果连接失败(例如信号与槽的参数不匹配),则返回一个无效的连接。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//Calculate_Volume.h
#pragma once
#pragma execution_character_set("utf-8")
#include <QtWidgets/QMainWindow>
#include "ui_Calculate_Volume.h"
#include <QMainWindow>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QString>
#include <cmath> // 用于计算球的体积
#include <windows.h>
#include <QMessageBox>
#define M_PI 3.1415926535


class Calculate_Volume : public QMainWindow
{
Q_OBJECT

public:
Calculate_Volume(QWidget *parent = nullptr);
~Calculate_Volume();

private:
Ui::Calculate_VolumeClass ui;
QLabel* label; // 标签类
QLineEdit* radiusInput; // 半径输入文本框
QLineEdit* volumeOutput; // 体积输出文本框
QPushButton* calculateButton; // 计算按钮

public slots:
void onButtonClicked()
{
bool ok;
double radius = radiusInput->text().toDouble(&ok);
if (!ok || radius <= 0) {
QMessageBox::warning(this, "输入错误", "请输入一个有效的正数作为半径!");
return;
}

// 计算球的体积 V = (4/3) * π * r^3
double volume = (4.0 / 3.0) * M_PI * std::pow(radius, 3);
volumeOutput->setText(QString::number(volume));
}

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//Calculate_Volume.cpp
#include "Calculate_Volume.h"
#include <QVBoxLayout>
#include <QDebug>
Calculate_Volume::Calculate_Volume(QWidget* parent)
: QMainWindow(parent),
label(new QLabel("请输入球的半径:", this)),
radiusInput(new QLineEdit(this)),
volumeOutput(new QLineEdit(this)),
calculateButton(new QPushButton("计算体积", this))
{
ui.setupUi(this);
volumeOutput->setReadOnly(true); // 体积输出框只读

// 创建布局
QVBoxLayout* mainLayout = new QVBoxLayout;
QHBoxLayout* firstRowLayout = new QHBoxLayout;
QHBoxLayout* secondRowLayout = new QHBoxLayout;

// 第一个行:输入提示 + 半径文本框
firstRowLayout->addWidget(label);
firstRowLayout->addWidget(radiusInput);

// 第二个行:体积输出框 + 计算按钮
secondRowLayout->addWidget(volumeOutput);
secondRowLayout->addWidget(calculateButton);

// 将行布局添加到主布局
mainLayout->addLayout(firstRowLayout);
mainLayout->addLayout(secondRowLayout);

// 创建一个中央窗口部件,并设置布局
QWidget* centralWidget = new QWidget(this);
centralWidget->setLayout(mainLayout);
setCentralWidget(centralWidget);

// 连接信号和槽
connect(calculateButton, &QPushButton::clicked, this, &Calculate_Volume::onButtonClicked);
}

Calculate_Volume::~Calculate_Volume()
{}




//main.cpp
#include "Calculate_Volume.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Calculate_Volume w;
w.show();
return a.exec();
}

效果展示:

1731206890639

一些Qt的方法类

QString类:

QString 是 Qt 中用于处理字符串的类,提供了丰富的字符串操作功能。以下是 QString 中一些常用的方法:

  1. 基础操作
  • QString::isEmpty()
    检查字符串是否为空,返回 true 表示为空。
  • QString::isNull()
    检查字符串是否为 null,与 isEmpty() 不同的是,null 的字符串没有分配任何内存,而空字符串分配了内存但没有内容。
  • QString::length()QString::size()
    返回字符串的长度(字符个数)。
  • QString::clear()
    清空字符串内容。
  1. 字符串拼接
  • QString::append(const QString &str)
    在字符串末尾追加另一个字符串。
  • operator+
    使用 + 操作符拼接两个字符串。例如:str1 + str2
  1. 查找与替换
  • QString::contains(const QString &str)
    检查字符串是否包含指定子串,返回布尔值。
  • QString::indexOf(const QString &str)
    返回子串首次出现的位置,如果未找到返回 -1
  • QString::lastIndexOf(const QString &str)
    返回子串最后一次出现的位置。
  • QString::replace(const QString &before, const QString &after)
    将指定子串替换为另一个字符串。
  1. 大小写转换
  • QString::toLower()
    将字符串转换为小写。
  • QString::toUpper()
    将字符串转换为大写。
  1. 子串与截取
  • QString::left(int n)
    返回字符串左边的 n 个字符。
  • QString::right(int n)
    返回字符串右边的 n 个字符。
  • QString::mid(int position, int n = -1)
    从指定位置开始,提取 n 个字符的子串,若 n-1 则提取到字符串末尾。
  1. 格式化输出
  • QString::arg()
    格式化字符串,将指定的参数值插入到占位符(如 %1)中。可以链式调用,插入多个参数。

    1
    QString message = QString("Hello, %1! Your score is %2.").arg("Alice").arg(90);
  • QString::number()
    将数字转换为字符串,支持整数、浮点数等。

    1
    QString str = QString::number(123.45, 'f', 2); // 123.45
  1. 转换与编码
  • QString::toStdString()
    QString 转换为标准 C++ 字符串 std::string
  • QString::toUtf8()
    QString 转换为 QByteArray,编码为 UTF-8。
  • QString::fromUtf8(const char \*str)
    将 UTF-8 编码的 char 数组转换为 QString
  • QString::fromStdString(const std::string &str)
    将标准 C++ 字符串转换为 QString
  1. 去空格
  • QString::trimmed()
    去除字符串两端的空格字符。
  • QString::simplified()
    去除字符串开头和结尾的空格,并将连续空格缩减为单个空格。
  1. 分割与连接
  • QString::split(const QString &sep)
    按照指定分隔符将字符串分割成一个字符串列表,返回 QStringList
  • QStringList::join(const QString &sep)
    QStringList 中的元素用指定字符串 sep 拼接成一个 QString
1
2
3
4
5
6
QString str = "  Hello World!  ";
qDebug() << str.trimmed(); // 输出 "Hello World!"
qDebug() << str.toLower(); // 输出 " hello world! "
qDebug() << str.mid(2, 5); // 输出 "Hello"
qDebug() << str.contains("World"); // 输出 true
qDebug() << str.replace("World", "Qt"); // 输出 " Hello Qt! "

总之,要用查AI就行,没必要细究

QMap,QHash,QVector类

QMapQHash 是 Qt 中的键值对容器类,类似于标准库中的 std::mapstd::unordered_map,它们用于存储键值对并支持快速查找。

QMap

  • 定义QMap 是一个基于二叉树实现的键值对容器,键值对按键自动排序。
  • 特点:查找、插入、删除操作的时间复杂度为 O(log⁡n)
  • 适用场景:适合需要按顺序遍历键值对,或对键进行排序的场景。

常用方法

  • **insert(key, value)**:插入键值对。
  • **value(key)**:通过键查找值。
  • **contains(key)**:检查键是否存在。
  • **remove(key)**:移除指定键的键值对。
  • keys() 和 **values()**:分别获取所有键和所有值的列表

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <QMap>
#include <QDebug>

void exampleQMap() {
QMap<QString, int> map;
map.insert("Alice", 25);
map.insert("Charlie", 28);
map.insert("Bob", 30);


qDebug() << "Keys in sorted order:";
for (auto it = map.begin(); it != map.end(); ++it) {
qDebug() << it.key() << ":" << it.value();
}

if (map.contains("Alice")) {
qDebug() << "Alice's age:" << map.value("Alice");
}
}

QMap 中,会自动按字典序(或称为”字母顺序”)排序,这种排序是基于键的自然顺序实现的。也就是说,当插入键值对时,QMap 会自动将键按顺序排列,而不考虑插入顺序。

1731208247189

QHash

定义QHash 是一个基于哈希表实现的键值对容器,键值对的顺序不固定。

特点:查找、插入、删除的平均时间复杂度为 O(1)O(1)O(1),速度通常快于 QMap

适用场景:适合需要快速查找、插入和删除键值对,但不需要顺序或排序的场景。

常用方法

**insert(key, value)**:插入键值对。

**value(key)**:通过键查找值。

**contains(key)**:检查键是否存在。

**remove(key)**:移除指定键的键值对。

keys() 和 **values()**:分别获取所有键和所有值的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <QHash>
#include <QDebug>

void exampleQHash() {
QHash<QString, int> hash;
hash.insert("Alice", 25);
hash.insert("Charlie", 28);
hash.insert("Bob", 30);


qDebug() << "Keys in arbitrary order:";
for (auto it = hash.begin(); it != hash.end(); ++it) {
qDebug() << it.key() << ":" << it.value();
}

if (hash.contains("Bob")) {
qDebug() << "Bob's age:" << hash.value("Bob");
}
}

此时就是没有顺序了

1731208388403

QMap vs QHash

  • 顺序QMap 自动排序键,QHash 没有顺序。
  • 性能QMap 的查找速度较 QHash 慢,但在需要排序时是理想选择。QHash 在查找、插入和删除方面更快。

选择建议

  • QMap:需要排序或按顺序遍历键值对时。
  • QHash:仅需快速查找时,且不关心键的顺序时。

QVector

QVector(Qt库的Vector类) 和C++标准库里面的Vector类很相近

常用的方法有

1. appendprepend

  • append 用于在向量末尾追加元素。
  • prepend 用于在向量头部添加元素。

2. atoperator[]

  • at 用于访问指定索引的元素,带边界检查。
  • operator[] 直接访问元素,不检查越界。

3. sizeisEmpty

  • size 返回元素数量。
  • isEmpty 判断是否为空。

4. insertremove

  • insert 在指定位置插入元素。
  • remove 删除指定位置的元素。

5. containsindexOf

  • contains 判断是否包含某个值。
  • indexOf 查找元素的索引,未找到返回-1。

6. fillresize

  • fill 用指定值填充整个向量。
  • resize 调整向量大小,可能会丢失或补充数据。

7. toListfromList

  • toList 转换为 QList
  • fromListQList 转换为 QVector

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <QVector>
#include <QDebug>

int main()
{
QVector<int> vec;

// 1. 添加元素
vec.append(10); // 在末尾添加 10
vec.append(20); // 在末尾添加 20
vec.prepend(5); // 在头部添加 5

// 2. 访问元素
qDebug() << "First element:" << vec.at(0); // 输出 5
qDebug() << "Second element:" << vec[1]; // 输出 10
qDebug() << "Vector size:" << vec.size(); // 输出 3

// 3. 判断和查找
if (vec.contains(20)) {
qDebug() << "Contains 20 at index:" << vec.indexOf(20); // 输出索引
}

// 4. 插入和删除
vec.insert(1, 15); // 在索引 1 处插入 15 原来索引1处及其后续的元素会向后移动一个位置,以腾出空间放入新元素。
vec.remove(2); // 删除索引 2 的元素

// 5. 修改大小
vec.resize(5); // 调整大小为 5,新增元素初始化为 0

// 6. 转换
QList<int> list = vec.toList(); // 转为 QList

// 输出所有元素
for (int i : vec) {
qDebug() << i;
}

return 0;
}

QList和QLinkedList类

在 Qt 中,QListQLinkedList 是两种常用的容器类,分别适用于不同的使用场景。它们都是存储一组元素的线性容器,但在内存布局和性能上有所区别。

QList

QList 存储结构体时,会选择存储指向结构体的指针,以减少内存开销,尤其是在隐式共享(copy-on-write)机制下,不同的 QList 可以共享结构体数据。然而,在 插入删除 操作时,它的效率相对较低

常用方法

  • append(const T &value): 在列表末尾添加元素。
  • prepend(const T &value): 在列表开头添加元素。
  • insert(int index, const T &value): 在指定索引位置插入元素。
  • removeAt(int index): 删除指定索引的元素。
  • at(int index): 获取指定索引处的元素(只读)。
  • contains(const T &value): 检查列表中是否包含某个元素。
  • size(): 返回列表中的元素数量。
  • isEmpty(): 检查列表是否为空。
  • clear(): 清空列表中的所有元素。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <QList>
#include <QDebug>

int main() {
QList<int> list;
list.append(10);
list.append(20);
list.prepend(5);

qDebug() << "List elements:";
for (int i = 0; i < list.size(); ++i) {
qDebug() << list.at(i);
}

if (list.contains(10)) {
qDebug() << "List contains 10.";
}

list.removeAt(1); // Removes element at index 1

qDebug() << "After removing element at index 1:";
for (int val : list) {
qDebug() << val;
}

return 0;
}

QLinkedList

QLinkedList 是基于链表实现的容器类,适合需要频繁在列表头部或中间插入或删除元素的场景。由于链表不支持随机访问,因此获取指定位置的元素会比较慢。

常用方法

  • append(const T &value): 在链表末尾添加元素。
  • prepend(const T &value): 在链表开头添加元素。
  • insert(iterator pos, const T &value): 在指定位置的迭代器处插入元素。
  • removeFirst(): 删除第一个元素。
  • removeLast(): 删除最后一个元素。
  • size(): 返回链表中的元素数量。
  • isEmpty(): 检查链表是否为空。
  • clear(): 清空链表中的所有元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <QLinkedList>
#include <QDebug>

int main() {
QLinkedList<int> linkedList;
linkedList.append(10);
linkedList.append(20);
linkedList.prepend(5);

qDebug() << "LinkedList elements:";
for (int val : linkedList) {
qDebug() << val;
}

linkedList.removeFirst(); // Removes the first element

qDebug() << "After removing the first element:";
for (int val : linkedList) {
qDebug() << val;
}

return 0;
}

使用场景总结

  • QList:适用于需要频繁访问元素的场景,且数据的增删操作主要集中在末尾。
  • QLinkedList:适用于需要频繁在头部或中间插入或删除元素的场景。
  • QMap:适合需要按顺序存储键值对,或在键的基础上进行排序的场景。
  • QHash:适合不关心元素顺序且需要快速查找的场景。

如果你存储的是结构体对象,并且这些结构体通常很大,那么 QList 存储结构体时的内存占用相对 QVector 更小 ,毕竟QList存的是指针

QVariant类

QVariant 是 Qt 中一个非常重要的类,它是一个通用容器,能够存储多种类型的数据。QVariant 可以用来在不同类型之间进行转换,尤其在 Qt 中的数据模型和视图类(如 QTableView, QListView)中非常常见。它提供了一种标准化的方式来存储不同类型的数据,同时也支持类型安全的类型转换。

在 Qt 中,QVariant 常用于存储如字符串、数字、日期、时间等多种不同类型的数据,并且支持这些数据类型之间的互相转换。

方法介绍:

1. 构造函数

  • QVariant():创建一个空的 QVariant
  • QVariant(int)QVariant(double) 等:直接用基本数据类型来构造 QVariant
1
2
3
4
QVariant intVariant(42);           // 存储整数
QVariant doubleVariant(3.14); // 存储浮点数
QVariant stringVariant("Hello"); // 存储字符串
QVariant emptyVariant; // 创建空的 QVariant

2. isValid()

判断 QVariant 是否包含有效数据。

1
2
3
4
5
QVariant v;
qDebug() << v.isValid(); // 输出:false

v = 100;
qDebug() << v.isValid(); // 输出:true

3. isNull()

检查 QVariant 是否为空(空指针或未初始化的值)。

1
2
3
4
5
QVariant nullVariant;
qDebug() << nullVariant.isNull(); // 输出:true

QVariant intVariant(10);
qDebug() << intVariant.isNull(); // 输出:false

4. clear()

清除 QVariant 的值,使其变成空。

1
2
3
4
QVariant v(123);
qDebug() << v; // 输出:QVariant(int, 123)
v.clear();
qDebug() << v.isNull(); // 输出:true

5. typeName()

返回 QVariant 中存储数据的类型名称。

1
2
3
4
5
cpp复制代码QVariant intVariant(42);
qDebug() << intVariant.typeName(); // 输出:int

QVariant stringVariant("Hello");
qDebug() << stringVariant.typeName(); // 输出:QString

6. canConvert()

检查 QVariant 是否可以转换为指定类型。

1
2
3
QVariant v(42);
qDebug() << v.canConvert<QString>(); // 输出:true
qDebug() << v.canConvert<QDate>(); // 输出:false

7. convert()

QVariant 转换为指定类型,如果转换成功则返回 true

1
2
3
QVariant v(42);
v.convert(QVariant::String);
qDebug() << v; // 输出:QVariant(QString, "42")

8. toInt()、toDouble()、toString() 等

QVariant 转换为不同的数据类型。常用的转换方法包括 toInt()toDouble()toString()toBool() 等。

1
2
3
4
5
6
7
8
9
QVariant v("123");

int intValue = v.toInt(); // 转换为 int
double doubleValue = v.toDouble(); // 转换为 double
QString stringValue = v.toString(); // 转换为 QString

qDebug() << intValue; // 输出:123
qDebug() << doubleValue; // 输出:123.0
qDebug() << stringValue; // 输出:"123"

9. setValue()

使用模板方法存储任意类型的数据。

1
2
3
QVariant v;
v.setValue<QString>("Hello");
qDebug() << v.toString(); // 输出:"Hello"

10. value()

QVariant 中提取特定类型的数据。这个方法使用模板类型 T 来指定需要提取的类型。

1
2
3
QVariant v = QVariant::fromValue(3.14);  // 存储一个 double
double d = v.value<double>(); // 提取 double 值
qDebug() << d; // 输出:3.14

11. fromValue()

静态模板方法,用于将任意类型的数据转换为 QVariant

1
2
QVariant variant = QVariant::fromValue<QString>("Hello World");
qDebug() << variant.toString(); // 输出:"Hello World"

12. type()

返回 QVariant::Type 类型的枚举值,表示当前存储的数据类型。

1
2
3
4
QVariant v(100);
if (v.type() == QVariant::Int) {
qDebug() << "It's an int";
}

13. isDetached()

判断 QVariant 是否已从其共享数据中分离(浅拷贝时用于检测)。

1
2
3
4
5
6
QVariant v(42);
QVariant vCopy = v;
qDebug() << v.isDetached(); // 输出:false

v = 100;
qDebug() << v.isDetached(); // 输出:true

14. swap()

交换两个 QVariant 的值。

1
2
3
4
5
6
QVariant v1(42);
QVariant v2("Hello");

v1.swap(v2);
qDebug() << v1.toString(); // 输出:"Hello"
qDebug() << v2.toInt(); // 输出:42

使用代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <QtCore/QCoreApplication>
#include <QVariant>
#include <QDebug>
#include <QMap>

struct Student
{
int iNo;
QString strName;
int score;
};

Q_DECLARE_METATYPE(Student);


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


QVariant qv1(298);
qDebug() << "qv1" << qv1.toInt() << endl;

QVariant qv2("this is string");
qDebug() << "qv2" << qv2.toString() << endl;


QMap<QString, QVariant> qmap;

qmap["int"] = 2000; //整型
qmap["double"] = 2000.0; //浮点型
qmap["string"] = "20000"; //字符型

//输出:转换函数来处理
qDebug() << qmap["int"] << qmap["int"].toInt();
qDebug() << qmap["double"] << qmap["double"].toInt();
qDebug() << qmap["string"] << qmap["string"].toString();




//创建一个字符串列表:QStringList
qDebug() << endl;
QStringList qsl;//QStringList是QList<QString>的别名
qsl << "A" << "B" << "C" << "D" << "E" << "F" << "G";

QVariant qvsl(qsl); //将列表存在一个QVariant变量里面
if (qvsl.type() == QVariant::StringList)
{
QStringList qlist = qvsl.toStringList();
for (int i = 0; i < qlist.size(); i++)
{
qDebug() << qlist.at(i);
}
}





qDebug() << endl;
Student stu;
stu.iNo = 202201;
stu.score = 715;
stu.strName = "sunny";


//使用静态的方法保存数据
QVariant Qvstu = QVariant::fromValue(stu);
if (Qvstu.canConvert<Student>())
{
Student temp_stu = Qvstu.value<Student>();//获得数据
Student qtemp = qvariant_cast<Student>(Qvstu);//获取数据
/*qvariant_cast<Student>(Qvstu):这是一个独立的模板函数,对 QVariant 进行安全转换。
如果类型不匹配,它会返回一个默认构造的对象(如 int 类型返回 0,类对象返回默认构造的实例)。
比直接调用 value<T>() 更安全,可以避免在类型不匹配时引发错误。*/

qDebug() << temp_stu.iNo << temp_stu.score << temp_stu.strName;

qDebug() << qtemp.iNo << qtemp.score << qtemp.strName;
}



system("pause");

return a.exec();
}

运行结果:

1731228124532

QT开发常见控件

Layouts&Spacers

手动操作

首先我们创建一个Qt Widgets Application (qt窗体程序)

1731376698193

进来之后,解决方案资源管理器有一个ui文件

1731376779502

双击这个.ui文件,即可进入Qt的设计板块

1731376805940

其中,这里有Layouts(布局)和Spacers(间隔)

  • Vertical Layout 垂直布局
  • Horizontal Layout 横向(水平)布局
  • Grid Layout 网格(栅格)布局
  • Form Layout 表单布局
  • Horizontal Spacer 水平弹簧
  • Vertical Spacer 垂直弹簧

菜单栏有布局的快捷键:

例如下图显示的垂直布局

1731377196766

先选中这三个控件,然后点击一下垂直布局
1731377247782

就会自动帮我们布局

1731377267567

  • Vertical Layout 垂直布局

包含对象都垂直排列开

  • Horizontal Layout 横向(水平)布局

包含的对象都横向排列开

  • Grid Layout 网格(栅格)布局

将控件放置到网格中布局,它本身会从父窗口或父布局中占据尽可能多的界面空间,然后把自己的空间划分为行和列,再把每个控件塞到设置好的一个或多个单元格中。通常情况下 QGridLayout不需要自己添加空白条 QSpacerltem,因为其他功能控件把各自的单元格占据之后,剩下没控件占据的单元格自然就是空的,空的格子默认里面什么都没有,也没有空白条。

  • Form Layout 表单布局

专门用于管理输入控件和与之相关的标签等表单布局,QFormLavout固定为两列布局,并针对表单做了建模,配套了一堆方便使用的函数。网格布局器的基本单元是单元格,而表单布局器的基本单元是行。表单布局器是高度建模并封装的,它没有 addWidget0和 addLavout0)之类的函数,它只有addRow()函数。表单布局器中一行的空间可以由多个控件占据,也可以由一个控件占据。

弹簧:

  • Horizontal Spacer 水平弹簧

  • Vertical Spacer 垂直弹簧

用于在布局中创建灵活的空白空间。弹簧的主要作用是控制控件之间的间距,提供空间的拉伸或压缩效果,以实现布局中的自适应。

在Qt中,给控件设置布局和给顶层窗口设置布局是两个不同层次的事情,目的是为了实现自适应布局效果和确保整个界面按期望排布。

顶层窗口(如QMainWindowQWidget)需要有一个布局来管理其内部的控件或子布局。如果不为顶层窗口设置布局,虽然可以在其中手动放置控件,但它们不会自动调整位置和大小,不具备自适应能力。

为顶层窗口设置布局后,Qt会自动管理窗口内部的空间分配,确保在窗口大小变化时,布局中的控件能够合理调整,达到更好的用户体验。

就像这样,这样是没有设置顶层布局,即使运行起来了,弹簧也不会起作用

1731410143158

所以我们要在空白处右键,点击布局,选择一个布局,例如这里选择了栅格布局,ctrl+s保存之后,运行,这时候弹簧就可以进行伸缩了

代码实现:

栅格布局:

我们先实现往栅格布局上面放一个按钮,注释都标注好了

1
2
3
4
5
6
7
8
9
10
11
12
13
Grid_Layout::Grid_Layout(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);//this 是当前对象,表示setupUi会把设计的UI加载到这个对象上。((通常由 Qt Designer 生成的 .ui 文件编译而成))
button1 = new QPushButton(this);//创建一个新的按钮控件 button1,并将其父对象设置为当前窗口或小部件 (this)。在 Qt 中,设置父对象后,父对象负责管理子对象的内存,即在父对象销毁时会自动销毁子对象。
button1->setText("第一区:顶不菜单栏选项");//设置 button1 按钮的文本内容为 "第一区:顶不菜单栏选项"。这段文本将会显示在按钮上。
button1->setFixedHeight(40);//设置 button1 按钮的固定高度为 40 像素,使其在布局中不受拉伸影响。
button1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);//设置 button1 的尺寸策略,将水平方向和垂直方向的尺寸策略都设置为 Expanding。Expanding 表示按钮在布局中会尽可能扩展以占满可用空间(受限于布局约束),而不是保持固定大小。
pGrid_layouts = new QGridLayout();//创建一个新的网格布局对象 pGrid_layouts。
pGrid_layouts->setContentsMargins(0, 0, 0, 0);//设置 pGrid_layouts 布局的内容边距为 0,使布局的控件紧贴窗口边缘。参数 (0, 0, 0, 0) 依次表示左、上、右、下的边距为 0。
pGrid_layouts->addWidget(button1, 0, 0);//将 button1 添加到 pGrid_layouts 的第 0 行、第 1 列(从 0 开始计数)的单元格中。在网格布局中,可以通过指定行和列的位置来组织控件的位置。
setLayout(pGrid_layouts);//将 pGrid_layouts 设置为当前窗口或小部件的布局。一旦设置了布局,布局管理器将会负责自动调整和排列该窗口中的所有控件。
}

以上代码有一些疑问:

1.固定高度和 Expanding 尺寸策略的

固定高度优先于 Expanding,导致按钮高度固定,只有水平方向会尝试扩展。所以这两者并不冲突,固定高度在垂直方向优先,Expanding 只在水平方向有效。

2. 网格布局的行列数

QGridLayout 的行列数是动态的,由添加的控件位置决定。

行列数是根据 addWidget 方法中给出的行和列参数动态生成的。没有预先设置网格大小,布局会根据所添加的控件自动计算需要的行和列数。

例如

1
2
3
pGrid_layouts->addWidget(button1, 0, 1);
pGrid_layouts->addWidget(button2, 1, 0);
pGrid_layouts->addWidget(button3, 1, 1);

pGrid_layouts 会自动创建一个 2 行 × 2 列的网格布局。

再介绍下addWidget

指定控件的对齐方式和弹性策略:

1
2
3
4
5
6
7
addWidget(
QWidget *widget,
int row, int column, int rowSpan, int columnSpan,
Qt::Alignment alignment,
QSizePolicy::Policy horizontalPolicy,
QSizePolicy::Policy verticalPolicy
)

参数

widget:要添加的控件。

row:控件所在的起始行。

column:控件所在的起始列。

rowSpan:控件跨越的行数。

columnSpan:控件跨越的列数。

alignment:控件的对齐方式(如 Qt::AlignCenterQt::AlignLeft)。

horizontalPolicy:控件在水平方向上的尺寸策略(如 QSizePolicy::Expanding,表示水平方向上可以扩展)。

verticalPolicy:控件在垂直方向上的尺寸策略(如 QSizePolicy::Fixed,表示垂直方向上的尺寸固定)。

作用:允许更精确地控制控件的对齐方式和尺寸策略。尺寸策略用于指定控件如何在布局中调整自己的大小。

表单布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Form_Layout::Form_Layout(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);

// 创建表单布局指针
QFormLayout* qLayout = new QFormLayout(this);



le1 = new QLineEdit(); // 输入学号
le2 = new QLineEdit(); // 输入姓名
le3 = new QLineEdit(); // 输入学校

// 添加行:第一列是标签,第二列是QLineEdit
qLayout->addRow("学号", le1);
qLayout->addRow("姓名", le2);
qLayout->addRow("学校", le3);

qLayout->setSpacing(8);

qLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
qLayout->setLabelAlignment(Qt::AlignLeft);//设置标签对其方式

setWindowTitle("表单布局测试案例");

// 设置当前窗口的布局为 qLayout
setLayout(qLayout);
}

其中

QFormLayout::setRowWrapPolicy 方法用于设置行的换行策略,即当空间不足以显示一行时,控件在布局中的排列方式。

1
void QFormLayout::setRowWrapPolicy(QFormLayout::RowWrapPolicy policy);

参数:RowWrapPolicy

RowWrapPolicy 是一个枚举类型,提供了三种策略:

  • **QFormLayout::DontWrapRows**:不换行。当空间不足时,控件会被截断,但不会自动换行。
  • **QFormLayout::WrapLongRows**:仅在行的内容过长时换行。此策略在行内容(标签和控件)超过窗口宽度时,才会将控件换到下一行。
  • **QFormLayout::WrapAllRows**:总是换行。标签在一行,控件自动放置在下一行,形成两行布局(标签一行,控件一行)。

setLabelAlignment 用于设置标签的对齐方式,即标签在控件旁边的布局位置(如左对齐或右对齐)。

方法定义

1
void QFormLayout::setLabelAlignment(Qt::Alignment alignment);

参数 :

alignment 使用 Qt::Alignment 枚举类进行设置。常用的值包括:

  • **Qt::AlignLeft**:左对齐标签。
  • **Qt::AlignRight**:右对齐标签,常用于在窗口宽度较大时使标签在布局中右对齐。
  • **Qt::AlignHCenter**:水平居中对齐标签。
  • **Qt::AlignJustify**:标签的对齐方式为两端对齐。

Button控件

控件介绍

Push Button (QPushButton)

这是最常见的按钮,用于触发简单的命令或操作,比如“确定”或“取消”。

Tool Button (QToolButton)

通常用于工具栏或小型控件,可以显示一个图标(或者文本)并触发特定的操作。

Radio Button (QRadioButton)

用于创建一组互斥的选项(只能选择一个)。

Check Box (QCheckBox)

允许用户选择多个选项或一个布尔值。

Command Link Button (QCommandLinkButton)

这是一个扩展了 QPushButton 的按钮,通常用于对话框中提供详细的操作说明。

Dialog Button Box (QDialogButtonBox)

功能:
用于对话框中管理多个标准按钮(如“确定”、“取消”等)。
特点:

  • 提供了标准按钮(如 QDialogButtonBox::Ok)。
  • 布局自动调整,不需要手动管理按钮的位置。
  • 方便处理对话框的信号和槽。

代码示例

做一个按钮的集合,并结合上一期学到的布局

头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#pragma once
#include <QMainWindow>
#include <QPushButton>
#include <QToolButton>
#include <QRadioButton>
#include <QCheckBox>
#include <QCommandLinkButton>
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QtWidgets/QMainWindow>
#include "ui_Button.h"

class Button : public QMainWindow
{
Q_OBJECT

public:
Button(QWidget *parent = nullptr);
~Button();

private:
Ui::ButtonClass ui;


private:
QPushButton* pb1;
QToolButton* tb1;
QRadioButton* rb1;
QRadioButton* rb2;
QCheckBox* cb1;
QCheckBox* cb2;
QCommandLinkButton* clb1;
QDialogButtonBox* dbb1;


private slots:
void pushbutton_clicked(); //QPushButton
void toolbutton_clicked(); // QToolButton
void radiobutton1_clicked(); // QRadioButton 1
void radiobutton2_clicked(); // QRadioButton 2
void checkbox1_clicked(); // QCheckBox 1
void checkbox2_clicked(); // QCheckBox 2
void commandlinkbutton_clicked(); // QCommandLinkButton
void dialogbuttonbox_clicked(QAbstractButton* button); // QDialogButtonBox
};

cpp文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include "Button.h"

#include <QDebug>
Button::Button(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);


// 创建中央窗口
QWidget* centralWidget = new QWidget(this);
setCentralWidget(centralWidget);

// 创建垂直布局
QVBoxLayout* mainLayout = new QVBoxLayout();

// 1. QPushButton
pb1 = new QPushButton("Push Button", this);
mainLayout->addWidget(pb1);


// 2. QToolButton
tb1 = new QToolButton(this);
tb1->setText("Tool Button");
tb1->setIcon(QIcon(":/icons/tool.png"));
tb1->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
mainLayout->addWidget(tb1);



// 3. QRadioButton
rb1 = new QRadioButton("Radio Button 1", this);
rb2 = new QRadioButton("Radio Button 2", this);
QHBoxLayout* radioLayout = new QHBoxLayout();
radioLayout->addWidget(rb1);
radioLayout->addWidget(rb2);
mainLayout->addLayout(radioLayout);

// 4. QCheckBox
cb1 = new QCheckBox("Check Box 1", this);
cb2 = new QCheckBox("Check Box 2", this);
QHBoxLayout* checkBoxLayout = new QHBoxLayout();
checkBoxLayout->addWidget(cb1);
checkBoxLayout->addWidget(cb2);
mainLayout->addLayout(checkBoxLayout);

// 5. QCommandLinkButton
clb1 = new QCommandLinkButton("Command Link Button", "Description goes here", this);
clb1->setIcon(QIcon(":/icons/info.png"));
mainLayout->addWidget(clb1);

// 6. QDialogButtonBox
dbb1 = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
mainLayout->addWidget(dbb1);


// 将布局应用到中央窗口
centralWidget->setLayout(mainLayout);

// 设置窗口标题
setWindowTitle("Qt Button Controls Example");
resize(400, 300);



connect(pb1, &QPushButton::clicked, this, &Button::pushbutton_clicked);
connect(tb1, &QToolButton::clicked, this, &Button::toolbutton_clicked);
connect(rb1, &QRadioButton::clicked, this, &Button::radiobutton1_clicked);
connect(rb2, &QRadioButton::clicked, this, &Button::radiobutton2_clicked);
connect(cb1, &QCheckBox::clicked, this, &Button::checkbox1_clicked);
connect(cb2, &QCheckBox::clicked, this, &Button::checkbox2_clicked);
connect(clb1, &QCommandLinkButton::clicked, this, &Button::commandlinkbutton_clicked);
connect(dbb1, &QDialogButtonBox::clicked, this, &Button::dialogbuttonbox_clicked);
}

Button::~Button()
{}



void Button::pushbutton_clicked() // QPushButton
{
qDebug() << "QPushButton";
}


void Button::toolbutton_clicked() // QToolButton
{
qDebug() << "QToolButton";
}


void Button::radiobutton1_clicked() // QRadioButton 1
{
qDebug() << "QRadioButton 1";
}


void Button::radiobutton2_clicked() // QRadioButton 2
{
qDebug() << "QRadioButton 2";
}


void Button::checkbox1_clicked() // QCheckBox 1
{
qDebug() << "QCheckBox 1";
}


void Button::checkbox2_clicked() // QCheckBox 2
{
qDebug() << "QCheckBox 2";
}


void Button::commandlinkbutton_clicked() // QCommandLinkButton
{
qDebug() << "QCommandLinkButton";
}


void Button::dialogbuttonbox_clicked(QAbstractButton* button) // QDialogButtonBox
{
qDebug() << "QDialogButtonBox";
}

效果图:

1731983793959

Item Views&ItemWidgets

Item Widgets

Item Views 是基于 Model/View 架构的视图组件,提供了一种高效且灵活的方式来显示和编辑数据。常用的包括:

  • QListView:显示垂直列表。
  • QTableView:显示表格。
  • QTreeView:显示树状结构。

Model/View 架构

Model(数据模型):负责管理数据的存储和组织,例如 QStandardItemModel 或自定义的模型。

View(视图):负责渲染数据,例如 QTableView

Delegate(委托):负责数据的显示和编辑,例如 QStyledItemDelegate

头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_ItemViews.h"
#include <QTableView>
#include <QStandardItemModel>
#include <QHeaderView>
#include <QListView>
#include <QStringListModel>
#include <QTreeView>
#include <QStandardItemModel>
class ItemViews : public QMainWindow
{
Q_OBJECT

public:
ItemViews(QWidget *parent = nullptr);
~ItemViews();

private:
Ui::ItemViewsClass* ui;


private:
QListView* listView; // 列表视图
QStringListModel* listModel; // 数据模型

QTableView* tableView; // 视图
QStandardItemModel* tablemodel; // 数据模型


QTreeView* treeView; // 树状视图
QStandardItemModel* treeModel; // 数据模型

void setuptableViewModel(); // 初始化模型和视图
void setuplistViewModel(); // 初始化模型和视图
void setupTreeViewModel(); // 初始化模型和视图
};

QListView:显示垂直列表。

cpp文件为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "ItemViews.h"


ItemViews::ItemViews(QWidget* parent)
: QMainWindow(parent),
listView(new QListView(this)), // 创建视图
listModel(new QStringListModel(this))// 创建数据模型
{
// 设置窗口标题
setWindowTitle("Item Views Example");

// 初始化模型和视图
setuplistViewModel();

// 设置模型到视图
listView->setModel(listModel);

// 设置视图为窗口的中心部件
setCentralWidget(listView);
}

ItemViews::~ItemViews()
{
// tableView 和 model 由 Qt 的 parent-child 机制自动释放,无需手动 delete
}


void ItemViews::setuplistViewModel()
{
// 设置数据列表
QStringList data;
data << "Alice" << "Bob" << "Charlie" << "David" << "Eve";

// 将数据加载到模型
listModel->setStringList(data);
}

效果图:

1732025673933

QTableView:

cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "ItemViews.h"


ItemViews::ItemViews(QWidget* parent)
: QMainWindow(parent),
tableView(new QTableView(this)), // 创建视图
model(new QStandardItemModel(this)) // 创建数据模型
{
// 设置窗口标题
setWindowTitle("Item Views Example");

// 初始化模型和视图
setupModel();

// 设置视图
tableView->setModel(model); // 绑定模型
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // 列宽自适应
tableView->setSelectionBehavior(QAbstractItemView::SelectRows); // 点击选择整行
tableView->setEditTriggers(QAbstractItemView::DoubleClicked); // 双击单元格编辑
setCentralWidget(tableView); // 将 tableView 设置为主窗口中心部件
}

ItemViews::~ItemViews()
{
// tableView 和 model 由 Qt 的 parent-child 机制自动释放,无需手动 delete
}

void ItemViews::setupModel()
{
// 设置模型的行列数
model->setRowCount(3);
model->setColumnCount(3);

// 设置列标题
model->setHeaderData(0, Qt::Horizontal, "Name");
model->setHeaderData(1, Qt::Horizontal, "Age");
model->setHeaderData(2, Qt::Horizontal, "Country");

// 填充数据
model->setItem(0, 0, new QStandardItem("Alice"));
model->setItem(0, 1, new QStandardItem("25"));
model->setItem(0, 2, new QStandardItem("USA"));

model->setItem(1, 0, new QStandardItem("Bob"));
model->setItem(1, 1, new QStandardItem("30"));
model->setItem(1, 2, new QStandardItem("UK"));

model->setItem(2, 0, new QStandardItem("Charlie"));
model->setItem(2, 1, new QStandardItem("28"));
model->setItem(2, 2, new QStandardItem("Canada"));
}

效果如下:

1732024657348

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "ItemViews.h"


ItemViews::ItemViews(QWidget* parent)
: QMainWindow(parent),
tableView(new QTableView(this)),
listView(new QListView(this)), // 创建视图
tablemodel(new QStandardItemModel(this)), // 创建数据模型
listModel(new QStringListModel(this)),// 创建数据模型
treeView(new QTreeView(this)), // 创建树状视图
treeModel(new QStandardItemModel(this)) // 创建数据模型
{
// 设置窗口标题
setWindowTitle("Item Views Example");

// 初始化模型和视图
setupTreeViewModel();

// 将模型绑定到视图
treeView->setModel(treeModel);

// 设置视图的显示属性
treeView->setHeaderHidden(false); // 显示表头
treeView->setSelectionBehavior(QAbstractItemView::SelectRows); // 整行选择
treeView->expandAll(); // 默认展开所有节点

// 将视图设置为主窗口的中心控件
setCentralWidget(treeView);

}

ItemViews::~ItemViews()
{
// tableView 和 model 由 Qt 的 parent-child 机制自动释放,无需手动 delete
}


void ItemViews::setupTreeViewModel()
{
// 设置列标题
treeModel->setHorizontalHeaderLabels(QStringList() << "Category" << "Details");

// 创建根节点
QStandardItem* rootNode = treeModel->invisibleRootItem();

// 添加分类1
QStandardItem* category1 = new QStandardItem("Fruits");
QStandardItem* detail1 = new QStandardItem("Delicious and healthy");
category1->appendRow(QList<QStandardItem*>() << new QStandardItem("Apple") << new QStandardItem("Red or green"));
category1->appendRow(QList<QStandardItem*>() << new QStandardItem("Banana") << new QStandardItem("Yellow"));
rootNode->appendRow(QList<QStandardItem*>() << category1 << detail1);

// 添加分类2
QStandardItem* category2 = new QStandardItem("Animals");
QStandardItem* detail2 = new QStandardItem("Different types");
category2->appendRow(QList<QStandardItem*>() << new QStandardItem("Cat") << new QStandardItem("Cute pet"));
category2->appendRow(QList<QStandardItem*>() << new QStandardItem("Dog") << new QStandardItem("Loyal friend"));
rootNode->appendRow(QList<QStandardItem*>() << category2 << detail2);

// 添加分类3
QStandardItem* category3 = new QStandardItem("Vehicles");
QStandardItem* detail3 = new QStandardItem("Used for transport");
category3->appendRow(QList<QStandardItem*>() << new QStandardItem("Car") << new QStandardItem("Fast and private"));
category3->appendRow(QList<QStandardItem*>() << new QStandardItem("Bus") << new QStandardItem("Public transport"));
rootNode->appendRow(QList<QStandardItem*>() << category3 << detail3);
}

效果图:

1732026092852

ItemWidgets

Item Widgets 是基于 QWidget 的组件,用于在表格或列表中插入小部件,例如按钮、复选框等。它们通常用于需要显示复杂内容的场景。常见类包括:

  • QTableWidget:用于表格。
  • QTreeWidget:用于树状结构。
  • QListWidget:用于列表。

工作机制

  • 基于 QWidget 的传统方式,直接在单元格中添加小部件(如按钮或复选框)。
  • 典型用法是通过 QTableWidget::setCellWidget()QTreeWidget::setItemWidget() 方法为某个单元格插入一个小部件。

TreeWidgetItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ItemWidgets::ItemWidgets(QWidget *parent)
: QMainWindow(parent), tableWidget(new QTableWidget(this)), // 创建表格控件
treeWidget(new QTreeWidget(this)) // 创建树形控件
{
ui.setupUi(this);
setWindowTitle("QTableWidget Example"); // 设置窗口标题

// 初始化表格
setupTree();

// 将表格控件设置为主窗口的中心部件
setCentralWidget(treeWidget);
}

ItemWidgets::~ItemWidgets()
{}



void ItemWidgets::setupTree()
{
// 创建根节点
QTreeWidgetItem* root = new QTreeWidgetItem(treeWidget);
root->setText(0, "Root");
root->setText(1, "Root Node");

// 添加子节点到根节点
QTreeWidgetItem* child1 = new QTreeWidgetItem(root);
child1->setText(0, "Child 1");
child1->setText(1, "This is child 1");

QTreeWidgetItem* child2 = new QTreeWidgetItem(root);
child2->setText(0, "Child 2");
child2->setText(1, "This is child 2");

// 为子节点添加孙子节点
QTreeWidgetItem* grandchild1 = new QTreeWidgetItem(child1);
grandchild1->setText(0, "Grandchild 1");
grandchild1->setText(1, "This is grandchild 1");

QTreeWidgetItem* grandchild2 = new QTreeWidgetItem(child2);
grandchild2->setText(0, "Grandchild 2");
grandchild2->setText(1, "This is grandchild 2");

// 展开根节点
treeWidget->expandAll();
}

tableWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "ItemWidgets.h"

ItemWidgets::ItemWidgets(QWidget *parent)
: QMainWindow(parent), tableWidget(new QTableWidget(this)) // 创建表格控件
{
ui.setupUi(this);
setWindowTitle("QTableWidget Example"); // 设置窗口标题

// 初始化表格
setupTable();

// 将表格控件设置为主窗口的中心部件
setCentralWidget(tableWidget);
}

ItemWidgets::~ItemWidgets()
{}



void ItemWidgets::setupTable()
{
// 设置表格的行和列
tableWidget->setRowCount(3); // 设置行数
tableWidget->setColumnCount(2); // 设置列数

// 设置表头
tableWidget->setHorizontalHeaderLabels(QStringList() << "Name" << "Age");

// 填充数据
tableWidget->setItem(0, 0, new QTableWidgetItem("Alice"));
tableWidget->setItem(0, 1, new QTableWidgetItem("25"));

tableWidget->setItem(1, 0, new QTableWidgetItem("Bob"));
tableWidget->setItem(1, 1, new QTableWidgetItem("30"));

tableWidget->setItem(2, 0, new QTableWidgetItem("Charlie"));
tableWidget->setItem(2, 1, new QTableWidgetItem("35"));

// 设置列宽自适应
tableWidget->resizeColumnsToContents();

// 设置单元格编辑模式
tableWidget->setEditTriggers(QAbstractItemView::DoubleClicked); // 双击编辑单元格
}

listWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include "ItemWidgets.h"
#include <QDebug>
ItemWidgets::ItemWidgets(QWidget *parent)
: QMainWindow(parent), tableWidget(new QTableWidget(this)), // 创建表格控件
treeWidget(new QTreeWidget(this)), // 创建树形控件,
listWidget(new QListWidget(this)), // 创建列表控件
addButton(new QPushButton("Add Item", this)), // 创建添加按钮
removeButton(new QPushButton("Remove Item", this)) // 创建删除按钮
{
ui.setupUi(this);
setWindowTitle("QTableWidget Example"); // 设置窗口标题

// 设置按钮的布局
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(listWidget);
layout->addWidget(addButton);
layout->addWidget(removeButton);

QWidget* container = new QWidget();
container->setLayout(layout);

setCentralWidget(container); // 设置中央控件为容器

// 设置初始列表
setupList();

// 连接按钮的信号和槽
connect(addButton, &QPushButton::clicked, [this]() {
// 点击“添加”按钮,添加一个新项
listWidget->addItem("New Item");
});

connect(removeButton, &QPushButton::clicked, [this]() {
// 点击“删除”按钮,删除选中的项
delete listWidget->currentItem();
});

// 连接项点击事件
connect(listWidget, &QListWidget::itemClicked, [this](QListWidgetItem* item) {
qDebug() << "Item clicked: " << item->text();
});
}

ItemWidgets::~ItemWidgets()
{}

void ItemWidgets::setupList()
{
// 向列表中添加初始项
listWidget->addItem("Item 1");
listWidget->addItem("Item 2");
listWidget->addItem("Item 3");
listWidget->addItem("Item 4");
}

区别:

特点 Item Views Item Widgets
架构 基于 Model/View 架构 基于 QWidget 的传统方式
性能 更高效,适合处理大量数据 数据量大时性能差
灵活性 借助委托实现自定义渲染和编辑 可直接插入复杂小部件,灵活但不高效
开发复杂性 需要实现数据模型,稍复杂 使用传统方法开发,简单直观
适用场景 数据量大,需求偏重于数据展示 小数据量,需求偏重于复杂交互