当前位置: 首页 > news >正文

【QT5 Widgets示例】Model/View编程初探

文章目录

  • Model/View
    • Model/View编程的优点
    • 常见Model类和View类
    • Model/View应用程序示例
      • 只读的表格
      • 修改文本外观
      • 显示变化的数据
      • 设置表格标头
      • 可编辑视图示例
      • 树结构视图示例
      • 获取视图选中项

Model/View

Model/View编程的优点

Model/View编程介绍:https://doc.qt.io/qt-5/model-view-programming.html

表格(Table)、列表(List)和树(Tree )形控件(widget)是图形用户界面(GUI)中经常使用的组件。这些控件有两种不同的方式来访问其数据,即直接访问和通过Model/View的模式访问。

直接访问
一般的控件自身包含了用于存储数据的容器,可以直接访问数据:

在这里插入图片描述
这样使用起来很直观,但是有以下几个问题:

(1)数据冗余与同步问题:每个控件都维护自己的数据容器,多个控件可能会存储相同的数据,导致冗余。如果数据需要在多个控件间同步,开发者需要手动维护,容易导致数据不一致。
(2)难以复用和扩展:由于数据存储和视图绑定在一起,无法轻松替换数据源或调整数据结构。
不能灵活适配不同的数据来源,例如数据库、文件、远程 API 等。
(3)耦合度高:视图和数据紧密耦合,控件既要管理 UI,又要管理数据,违反了单一职责原则。难以单独测试数据逻辑或 UI 逻辑,不利于单元测试和维护。
(4)性能问题:当数据量较大时,控件自身存储数据可能导致高内存占用。需要加载完整数据集,而不是按需加载,影响性能。

Model/View

  • Model-View将控件的数据与视图分离。控件不维护内部数据容器,它们通过标准化接口访问外部数据。在Model-View中,模型(Model)是数据的抽象表示,负责存储和管理数据。视图(View)是用于显示模型中的数据,并与用户进行交互的组件。视图通常会提供一个setModel()方法,允许开发者将视图与一个特定的模型实例相关联。一旦设置了模型,视图就可以通过该模型访问和显示数据。

相比于直接访问,Model/View有以下优点:
(1)数据和视图分离,低耦合:视图仅负责显示数据,数据逻辑由模型管理,符合MVC(Model-View-Controller)设计思想。修改数据源或替换模型不会影响视图,增强了代码的灵活性和可维护性。
(2)数据共享与同步更方便:多个视图可以共享同一个模型,避免数据冗余。数据变更时,模型可以通知所有关联视图(例如 Qt 的 QAbstractItemModel 提供 dataChanged() 信号),自动更新 UI。

(3)支持大规模数据处理:视图只会访问模型提供的接口,而模型可以实现懒加载(按需加载数据),提高性能。例如,视图可以根据需要请求数据,而不是一次性加载所有数据。

(4)提高代码复用性:相同的模型可以被不同的视图复用,例如列表视图、树状视图、表格视图等都可以使用同一个数据模型。适用于不同的 UI 组件,减少重复代码。

(5)更好的测试和维护性:由于视图和数据分离,可以单独测试数据模型,提高代码质量。UI 变更不会影响数据逻辑,降低了维护成本。

常见Model类和View类

QT5中常见的模型(Model)类包括:

  1. QAbstractItemModel:所有模型类的基类,定义了一些纯虚函数,需要子类来实现以提供自定义的数据存储和访问方式。
  2. QAbstractListModel:列表模型的抽象基类,适用于一维数据。
  3. QAbstractTableModel:表格模型的抽象基类,适用于二维数据。
  4. QStringListModel:用于处理字符串列表数据的数据模型类。
  5. QStandardItemModel:标准的基于数据项的数据模型类,每个数据项都可以是任何数据类型。
  6. QFileSystemModel:计算机上文件系统的数据模型类,提供了对本地文件系统的访问和操作。
  7. QSortFilterProxyModel:与其他数据模型结合,提供排序和过滤功能的数据类型模型类。
  8. QSqlTableModel:用于数据库的一个数据表的数据模型类。
  9. QSqlRelationalTableModel:用于关系类型数据表的数据模型。

QT5中常见的视图(View)类包括:

  1. QListView:用于显示单列的列表数据,适用于一维数据的操作。
  2. QTreeView:用于显示树状结构数据,适用于树状结构数据的操作。
  3. QTableView:用于显示表格状数据,适用于二维表格型数据的操作。
  4. QColumnView:用多个QListView显示树状层次结构,树状结构的一层用一个QListView显示。

注:一些标准控件类,如QListWidget、QTreeWidget和QTableWidget,它们是上述视图类的子类。

下面是部分的标准控件(item-based)和对应的Model/View 控件(Model-based)的效果示例:

列表List:QListWidget,QListView

在这里插入图片描述
表格:QTableWidget QTableView

树:QTreeWidget QTreeView

在这里插入图片描述

QColumnView:

在这里插入图片描述

特别地,QComboBox既可以作为标准的控件,也可以作为Model/View 控件:

对于那些操作单个值而不是数据集的控件(如QLineEdit、QCheckBox等),没有直接的Model/View对应项来分离数据和视图,因此我们需要一个适配器(Adapters )来将表单连接到数据源。例如,QDataWidgetMapper和QCompleter。

QT官网提供了各个类的使用示例:
https://doc.qt.io/qt-5/examples-itemviews.html

下面将以一些简单的例子展示Model/View用法。

Model/View应用程序示例

下面用七个简单的应用程序,展示模型/视图编程:

只读的表格

代码:[github]
我们从一个使用QTableView显示数据的应用程序开始。

// main.cpp
#include <QApplication>
#include <QTableView>   // 引入的视图类,用于以表格形式显示数据。
#include "mymodel.h"    // 引入自定义的模型类头文件,用于提供数据给视图。

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

    QApplication a(argc, argv);
    // 创建QTableView对象tableView
    QTableView tableView;
    // 创建自定义模型对象myModel
    MyModel myModel;
    // 通过调用tableView的setModel方法,将自定义模型myModel设置为tableView的数据源。
    // 这样,tableView就可以从myModel中获取数据并进行显示了。
    tableView.setModel(&myModel);
    // 调用tableView的show方法,显示表格视图。
    tableView.show();
    return a.exec();
}

tableView.setModel(&myModel); 将它的指针传递给 tableViewtableView 会调用它所接收到的指针的方法来了解两件事:

  • 应该显示多少行和列。
  • 每个单元格应该打印什么内容。

模型需要一些代码来响应这些请求。假设需要展示一个表格数据,让我们以 QAbstractTableModel作为基类来自定义模型:

// mymodel.h
#include <QAbstractTableModel>

// 声明MyModel类,它继承自QAbstractTableModel
class MyModel : public QAbstractTableModel {
    Q_OBJECT 

public:

    explicit MyModel(QObject *parent = nullptr);
    // 重写rowCount函数,返回模型中的行数
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    // 重写columnCount函数,返回模型中的列数
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    // 重写data函数,用于提供模型中特定单元格的数据
    // index参数指定了单元格的位置(行和列),role参数指定了所需数据的类型(如显示文本、工具提示等)
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};

模型的源文件:

// mymodel.cpp
#include "mymodel.h"


MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

// 实现rowCount函数,返回模型中的行数
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
    // 表格模型没有使用父/子结构,
    // 所以我们可以忽略parent参数(它通常用于树形或层次结构模型)
    // 在这个例子中,我们简单地返回2,表示模型有两行
    return 2;
}

// 实现columnCount函数,返回模型中的列数
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
    // 与rowCount同样地,忽略parent参数
    // 简单地返回3,表示模型有三列
    return 3;
}

// 实现data函数,用于提供模型中特定单元格的数据
QVariant MyModel::data(const QModelIndex &index, int role) const
{
    // 我们只对Qt::DisplayRole感兴趣,这是用于在视图中显示的数据
    if (role == Qt::DisplayRole) {
        // 使用QString的arg函数来格式化字符串,
        // QModelIndex是Qt中用于定位数据模型数据的一个类,
        // 调用其方法,将行号和列号(都加1以符合人类阅读习惯,从1开始计数)插入到字符串中
        return QString("Row%1, Column%2")
                   .arg(index.row() + 1)
                   .arg(index.column() + 1);
    }
    // 对于其他类型的role(如编辑、工具提示等),我们返回QVariant的默认值,
    // 表示没有提供这些数据
    return QVariant();
}

当视图需要知道单元格的文本内容时,它会调用 MyModel::data() 方法。行和列的信息通过参数 index 指定,并且role被设置为 Qt::DisplayRole

在这个例子中,需要显示的数据是指定的。在实际应用中,MyModel 一般会有一个名为 MyData 的成员,它作为所有读写操作的目标。

修改文本外观

代码:[github]
除了控制视图显示的文本外,模型还可以控制文本的外观。只需要添加更多的role条件,就可以以下结果:
在这里插入图片描述

每个格式化属性都将通过单独调用data()方法从模型中请求。role参数用于让模型知道正在请求哪个属性:

// mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const {
    int row = index.row();    // 从QModelIndex对象中获取当前行的索引
    int col = index.column(); // 从QModelIndex对象中获取当前列的索引


    switch (role) {
    case Qt::DisplayRole: // 处理显示角色的数据
        // 为特定单元格设置自定义显示文本
        if (row == 0 && col == 1) return QString("<--left");
        if (row == 1 && col == 1) return QString("right-->");
        // 为其他单元格返回默认的行列信息文本
        return QString("Row%1, Column%2")
                .arg(row + 1)
                .arg(col + 1);

    case Qt::FontRole: // 处理字体角色的数据
        // 仅为单元格(0,0)设置粗体字体
        if (row == 0 && col == 0) {
            QFont boldFont;
            boldFont.setBold(true);
            return boldFont;
        }
        break;

    case Qt::BackgroundRole: // 处理背景角色的数据
        // 仅为单元格(1,2)设置红色背景
        if (row == 1 && col == 2)
            return QBrush(Qt::red);
        break;

    case Qt::TextAlignmentRole: // 处理文本对齐角色的数据
        // 仅为单元格(1,1)设置文本对齐方式(右对齐和垂直居中)
        if (row == 1 && col == 1)
            return int(Qt::AlignRight | Qt::AlignVCenter);
        break;

    case Qt::CheckStateRole: // 处理复选框状态角色的数据
        // 在单元格(1,0)中添加一个已选中的复选框
        if (row == 1 && col == 0)
            return Qt::Checked;
        break;
    }

    return QVariant();
}

显示变化的数据

代码:[github]
以上的例子展示了模型的被动性质。模型不知道它何时会被使用,也不知道需要哪些数据。它只是在视图每次请求数据时提供数据。那么,当模型的数据需要改变时会发生什么呢?视图如何意识到数据已经改变并需要重新读取呢?

简而言之,当模型的数据发生变化时,它必须通知视图。这通常是通过发出一个信号来完成的,该信号携带了关于哪些数据已经改变的信息。视图接收到这个信号后,就会知道它需要重新从模型中读取数据来更新其显示。

在一个表格的第一行第一列展示当前时间:

// mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    int col = index.column();

    if (role == Qt::DisplayRole && row == 0 && col == 0)
        return QTime::currentTime().toString();

    return QVariant();
}

下面使用定时器和信号槽机制来实现表格中时间的更新,每隔1000ms,更新一次。首先在头文件中声明定时器和槽函数。

// mymodel.h
class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit MyModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

private slots: 
 	// 每隔1000ms,模型发出一个信号,告诉视图哪些单元格的数据已经改变。
    void timerHit();

private:   
    QTimer *timer; // 计时器
};

在构造函数中,我们将定时器的间隔设置为1秒,并连接其超时信号到槽函数timerHit()

// mymodel.cpp
MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
    , timer(new QTimer(this))
{
	// 超时信号,时间间隔设置为1000ms(1s)
    timer->setInterval(1000);
    connect(timer, &QTimer::timeout , this, &MyModel::timerHit);
    timer->start();
}

槽函数接收到超时信号后,进一步地发出dataChanged()信号来请求视图再次读取左上角单元格对应的数据:

// mymodel.cpp
void MyModel::timerHit()
{
    
    QModelIndex topLeft = createIndex(0,0);
    //发出信号,使视图重新读取topLeft中已识别的数据
    emit dataChanged(topLeft, topLeft, {Qt::DisplayRole});
}

我们通过值得注意的是,我们并没有明确地将dataChanged()信号与视图相连接。当我们调用setModel()方法时,这一连接是自动完成的。

设置表格标头

代码:[github]

表格的标头可以通过视图方法隐藏:

// main.cpp
#include <QApplication>
#include <QTableView>
#include "mymodel.h"
#include <QHeaderView>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTableView tableView;
    MyModel myModel;
    tableView.setModel(&myModel);
    tableView.horizontalHeader()->hide(); // 隐藏水平标头
    tableView.verticalHeader()->hide();   // 隐藏垂直标头

    tableView.show();
    return a.exec();
}

另一方面,标头内容可以通过重写headerData()方法实现修改:

//mymodel.h
#include <QAbstractTableModel>

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit MyModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override; // 重写headerData()方法
};
//mymodel.cpp
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        switch (section) {
        case 0:
            return QString("first");
        case 1:
            return QString("second");
        case 2:
            return QString("third");
        }
    }else if (role == Qt::DisplayRole && orientation == Qt::Vertical) {
        switch (section) {
        case 0:
            return QString("first");
        case 1:
            return QString("second");

        }
    }
    return QVariant();
}

在这里插入图片描述

可编辑视图示例

代码:[github]
在这个例子中,我们将构建一个应用程序,该程序将获取用户输入到表格单元格中的值,并自动填充到窗口标题。

为了方便地访问窗口标题,我们将QTableView放置在QMainWindow中。

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , tableView(new QTableView(this))
{
    setCentralWidget(tableView);
    auto *myModel = new MyModel(this);
    tableView->setModel(myModel);

    // 将模型所作的更改显示到窗口标题
    connect(myModel, &MyModel::editCompleted,
            this, &QWidget::setWindowTitle);
}

模型决定了是否提供编辑功能。我们只需要修改模型,重写虚函数setData()flags()即可启用编辑功能。

// mymodel.h
#include <QAbstractTableModel>
#include <QString>

const int COLS= 3;
const int ROWS= 2;

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    MyModel(QObject *parent = nullptr);
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
    QString m_gridData[ROWS][COLS];  // 保存输入QTableView的文本
signals:
    void editCompleted(const QString &); // 编辑完成的信号
};

我们使用二维数组QString m_gridData来存储数据。这使得m_gridData成为MyModel的核心。MyModel的其余部分则像一个包装器,将m_gridData适配到QAbstractItemModel接口

// mymodel.cpp
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole) {
        if (!checkIndex(index))
            return false;
        //将值从编辑器保存到成员m_gridData
        m_gridData[index.row()][index.column()] = value.toString();
        // 将各个单元格的值凭拼接成字符串,通过editCompleted信号发送给标题
        QString result;
        for (int row = 0; row < ROWS; row++) {
            for (int col= 0; col < COLS; col++)
                result += m_gridData[row][col] + ' ';
        }
        emit editCompleted(result);
        return true;
    }
    return false;
}

每次用户编辑一个单元格时,都会调用setData()方法。index参数告诉我们哪个字段被编辑了,而value参数提供了编辑过程的结果。
由于我们的单元格只包含文本,因此role将始终被设置为Qt::EditRole。如果存在一个复选框,并且用户权限被设置为允许选择该复选框,那么当role被设置为Qt::CheckStateRole时,也会进行调用。

// mymodel.cpp
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
    return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}

通过flags()方法可以调整单元格的各种属性。

  • Qt::ItemIsSelectable:表示单元格是可以被选中的。这是模型/视图框架中的默认行为之一,通常不需要显式指定,因为QAbstractTableModel::flags(index)默认会包含这个标志
  • Qt::ItemIsEditable:表示单元格是可以被编辑的。
  • Qt::ItemIsEnabled:表示单元格是启用的,即它是可交互的。这同样是模型/视图框架中的默认行为之一,通常不需要显式指定。

树结构视图示例

代码:[github]
使用模型/视图的典型方法是包装特定数据,使其可用于视图类。然而,Qt也为常见的底层数据结构提供了预定义的模型。例如,下面我们将通过预定义的模型QStandardItemModel,用树形结构展示数据:

QStandardItemModel它是一个用于存储层次结构数据的容器,必须被填充QStandardItems

// mainwindow.h
#include <QMainWindow>

// Qt 的命名空间开始,这里使用了前向声明来减少头文件的依赖
QT_BEGIN_NAMESPACE
class QTreeView;       // QTreeView 类的前向声明,用于在 MainWindow 中作为成员变量
class QStandardItemModel; // QStandardItemModel 类的前向声明,用于存储树形视图的数据
class QStandardItem;    // QStandardItem 类的前向声明,通常用于构建 QStandardItemModel 的内容
QT_END_NAMESPACE


class MainWindow : public QMainWindow {
    Q_OBJECT

public:

    explicit MainWindow(QWidget *parent = nullptr);

private:
    // 辅助函数,用于准备一行数据,并返回一个包含三个 QStandardItem* 的列表
    // 这些 QStandardItem 分别对应于行中的第一、第二和第三个数据项
    QList<QStandardItem*> prepareRow(const QString &first,
                                     const QString &second,
                                     const QString &third) const;

    // 成员变量
    QTreeView *treeView;          // 指向 QTreeView 的指针,用于显示树形结构的数据
    QStandardItemModel *standardModel; // 指向 QStandardItemModel 的指针,作为树形视图的数据源
};;
// mainwindow.cpp
#include "mainwindow.h"

#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , treeView(new QTreeView(this)) // 创建 QTreeView 对象,并将其父对象设置为当前 MainWindow
    , standardModel(new QStandardItemModel(this)) // 创建 QStandardItemModel 对象,并将其父对象设置为当前 MainWindow
{
    // 将 treeView 设置为 MainWindow 的中心部件
    setCentralWidget(treeView);

    // 准备一行数据
    QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third");

    // 获取模型的不可见根项(即模型的顶层项,它本身不显示)
    QStandardItem *item = standardModel->invisibleRootItem();

    // 向不可见根项添加一行数据,这将作为模型的根元素显示
    item->appendRow(preparedRow);

    // 准备第二行数据
    QList<QStandardItem *> secondRow = prepareRow("111", "222", "333");

    // 将第二行数据添加到第一行数据的第一个单元格对应的项下,这将创建一个子树
    preparedRow[0]->appendRow(secondRow);

    // 为 treeView 设置模型
    treeView->setModel(standardModel);

    // 展开 treeView 中的所有项
    treeView->expandAll();
}

// prepareRow 成员函数,用于准备一行数据
QList<QStandardItem *> MainWindow::prepareRow(const QString &first,
                                              const QString &second,
                                              const QString &third) const
{
    // 创建一个包含三个 QStandardItem 对象的列表,并将它们分别初始化为 first, second, third 字符串
    return {new QStandardItem(first),
            new QStandardItem(second),
            new QStandardItem(third)};
}

在这里插入图片描述

获取视图选中项

代码:[github]
下面提供的代码段旨在实现以下功能:将用户在树状视图中选定的每一项,连同其在树状结构中的层级信息(Level),共同展示在应用程序的窗口标题中。

在这里插入图片描述

// mainwindow.h
#include <QMainWindow>


QT_BEGIN_NAMESPACE
// 前向声明 QTreeView 类, 这对于减少编译时间和避免循环依赖很有用
class QTreeView;
class QStandardItemModel;
class QItemSelection;

QT_END_NAMESPACE


class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    // 一个私有槽函数,它将在 QTreeView 的选择发生变化时被调用
    void selectionChangedSlot(const QItemSelection &newSelection, const QItemSelection &oldSelection);

private:

    QTreeView *treeView;  // 树视图
    QStandardItemModel *standardModel;   // 为 treeView 提供数据模型
};
#include "mainwindow.h"

#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , treeView(new QTreeView(this))
    , standardModel(new QStandardItemModel(this))
{
    setCentralWidget(treeView);
    auto *rootNode = standardModel->invisibleRootItem();


    // 让我们创建几个`QStandardItem`:
    auto *americaItem = new QStandardItem("America");
    auto *mexicoItem =  new QStandardItem("Canada");
    auto *usaItem =     new QStandardItem("USA");
    auto *bostonItem =  new QStandardItem("Boston");
    auto *europeItem =  new QStandardItem("Europe");
    auto *italyItem =   new QStandardItem("Italy");
    auto *romeItem =    new QStandardItem("Rome");
    auto *veronaItem =  new QStandardItem("Verona");

    // 建立层次结构
    rootNode->    appendRow(americaItem);
    rootNode->    appendRow(europeItem);
    americaItem-> appendRow(mexicoItem);
    americaItem-> appendRow(usaItem);
    usaItem->     appendRow(bostonItem);
    europeItem->  appendRow(italyItem);
    italyItem->   appendRow(romeItem);
    italyItem->   appendRow(veronaItem);

    // 将模型设置给 treeView
    treeView->setModel(standardModel);
    treeView->expandAll();  // 展开树视图中的所有项

    // 连接选择模型的 selectionChanged 信号到 MainWindow 的 selectionChangedSlot 槽函数
    QItemSelectionModel *selectionModel = treeView->selectionModel();
    connect(selectionModel, &QItemSelectionModel::selectionChanged,
            this, &MainWindow::selectionChangedSlot);
}
// mainwindow.cpp
void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/)
{
    // 不使用 newSelection 和 oldSelection 参数,因此用注释标记为未使用
    // 获取当前选中的项的 QModelIndex
    const QModelIndex index = treeView->selectionModel()->currentIndex();

    // 从 QModelIndex 中获取显示数据(通常是文本),并将其转换为 QString
    QString selectedText = index.data(Qt::DisplayRole).toString();

    // 初始化层级级别为 1,因为顶层项(直接挂在根节点下的项)的层级为 1
    int hierarchyLevel = 1;

    // 从当前选中项的 QModelIndex 开始,向上遍历其父节点,计算层级级别
    QModelIndex seekRoot = index;
    while (seekRoot.parent().isValid()) // 当父节点存在时继续循环
    {
        seekRoot = seekRoot.parent(); // 移动到父节点
        hierarchyLevel++; // 每向上移动一级,层级级别加 1
    }

    // 构建一个包含选中项文本和层级级别的字符串
    QString showString = QString("%1, Level %2").arg(selectedText).arg(hierarchyLevel);

    // 将构建的字符串设置为 MainWindow 的窗口标题
    setWindowTitle(showString);
}

参考:
https://doc.qt.io/qt-5/modelview.html#2-a-simple-model-view-application

相关文章:

  • 【蓝桥杯集训·每日一题2025】 AcWing 4905. 面包店 python
  • Qt QML实现弹球消砖块小游戏
  • 从0到1实现项目Docker编排部署
  • 百年匠心焕新居:约克VRF中央空调以科技赋能健康理想家
  • Java多线程基石—内存模型
  • CTF--Web安全--SQL注入之报错注入
  • 单元测试、系统测试、集成测试、回归测试的步骤、优点、缺点、注意点梳理说明
  • TF-IDF:文本挖掘中的关键词提取利器
  • 正则表达式 - 修饰符
  • Jetson Orin NX jupyter lab的安装和使用
  • C语言中的指针与数组:概念、关系与应用
  • 深入解读WT软件湍流强度计算与分析
  • python-leetcode 52.课程表
  • 深入理解 HTML 中的<div>和元素:构建网页结构与样式的基石
  • 方差缩减梯度算法
  • camellia redis proxy v1.3.3对redis主从进行读写分离(非写死,自动识别故障转移)
  • wlwrap 与 rlwrap 的区别对比:图形显示协议的演变
  • Kafka相关的面试题
  • 淘宝商品数据采集一键采集
  • 【数据分享】2000—2024年我国省市县三级逐月归一化植被指数(NDVI)数据(Shp/Excel格式)
  • 2025年新季夏粮收购量将达到2000亿斤左右
  • 自然资源部:适应存量时代城市更新发展,严控增量盘活存量
  • 甘肃白银煤矿透水事故仍有3人失联,现场约510立方米煤泥拥堵巷道
  • 对话作家吉井忍:“滚石”般的生活,让我看到多种人生可能
  • “当代阿炳”甘柏林逝世,创办了国内第一所残疾人高等学府
  • 浙江广厦:诚挚道歉,涉事责任人交公安机关