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

news2025/3/15 13:26:21

文章目录

  • 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

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

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

相关文章

一键优化右键菜单,高效又清爽!

打工人们你们好&#xff01;这里是摸鱼 特供版~ 电脑右键菜单杂乱无章&#xff0c;常用功能被淹没&#xff1f;图标显示异常、打印出错让人手足无措&#xff1f;别担心&#xff0c;Easy Context Menu来帮你&#xff01;这是一款右键菜单管理工具&#xff0c;能快速清理不必要的…

成绩排序(结构体排序)

成绩排序 #include<stdio.h> #include<stdlib.h> struct student{char name[50];int score;int order; }; int flag; int cmp(const void *a,const void *b){struct student *s1(struct student*)a;struct student *s2(struct student*)b;if(s1->scores2->…

OpenHarmony-XTS测试

OpenHarmony-XTS测试 OpenHarmony-XTS测试环境搭建测试准备开始运行PS OpenHarmony-XTS测试 针对OpenHarmony版本进行XTS测试使用记录。 windows环境。 以acts套件为例。 环境搭建 获取测试套件&#xff0c;两种方法 1&#xff09;官网下载&#xff1a;https://www.openharm…

【物联网-WIFI】

物联网-WIFI ■ ESP32-C3-模块简介■ ESP32-C3-■ ESP32-C3-■ WIFI-模组■ WIFI-■ WIFI- ■ ESP32-C3-模块简介 ■ ESP32-C3- ■ ESP32-C3- ■ WIFI-模组 ■ WIFI- ■ WIFI-

linux常用基本指令汇总

文章目录 01. ls指令02. pwd指令03. cd指令04. touch指令05. mkdir指令06. rmdir指令07. rm指令08. man指令09. cp指令10. mv指令11. cat指令11. more指令12. less指令13. head指令14. tail指令15. time指令16. cal指令17. find指令18. grep指令19. zip/unzip指令20.tar指令21.…

Docker Desktop 安装与使用详解

目录 1. 前言2. Docker Desktop 安装2.1 下载及安装2.2 登录 Docker 账号2.3 进入 Docker Desktop 主界面 3. Docker 版本查看与环境检查3.1 查看 Docker Desktop 支持的 Docker 和 Kubernetes 版本3.2 检查 Docker 版本 4. Docker Hub 和常用镜像管理方式4.1 使用 Docker Hub4…

【HarmonyOS Next】鸿蒙应用常规面试题和答辩思路参考

【HarmonyOS Next】鸿蒙应用常规面试题和答辩思路参考 一、充分了解岗位JD要求 根据招聘发布的岗位JD&#xff0c;进行自我匹配分析。了解基本要求和加分项&#xff0c;以及项目节奏和英文要求等。 技术不匹配的点&#xff0c;是否会影响应聘岗位加分项自己是否掌握&#xf…

《计算机图形学》第二课笔记-----二维变换的推导

前言&#xff1a;为什么这么突兀的把这一节内容放在了第二课&#xff0c;第一是因为我急于求成&#xff0c;第二是因为这一章节太重要了&#xff0c;这几乎是二维三维变换的最核心的东西&#xff0c;理解了这一章节内容&#xff0c;后面的就会像打通了任督二脉一样&#xff0c;…

机器学习(七)

一&#xff0c;监督学习和无监督学习聚类的数据集比较&#xff1a; 监督学习&#xff1a; 数据集包括输入的数据和与之对应的标签 无监督学习&#xff1a; 数据集仅含有输入的数据&#xff0c;要求算法自己通过所给的数据集来确定决策边界 二&#xff0c;聚类(Clustering): 聚…

利用labelimg实现yolov8数据集的制作

我们在使用yolov8进行物体检测识别的时候&#xff0c;由于其内置的n,s,m等模型只包含90多种物体&#xff08;很多其他物品并未包含在其中&#xff09;&#xff0c;导致我们无法直接使用其模型进行视频或者图片的检测识别。这个时候&#xff0c;我们就需要自己制作数据集进行训练…

【0x80070666】-已安装另一个版本...(Tableau 安装失败)

第一种是之前安装过tableau相关软件&#xff0c;但是没卸载干净。 方法1&#xff1a;卸载旧版本 打开 控制面板 → 程序和功能&#xff08;或 添加/删除程序&#xff09;。查找 Tableau Desktop&#xff0c;如果已安装旧版本&#xff0c;卸载它。重新启动电脑后再尝试安装。 …

Word填写窗口功能详解:如何让文档填写更高效?

在日常办公中&#xff0c;我们经常需要让他人填写一些固定格式的文档&#xff0c;比如合同、申请表、调查问卷等。如果直接使用普通文本编辑&#xff0c;填写时可能会破坏排版&#xff0c;甚至修改了不该改动的内容。这时候&#xff0c;Word的填写窗口&#xff08;即“内容控件…

Oracle数据库存储结构--逻辑存储结构

数据库存储结构&#xff1a;分为物理存储结构和逻辑存储结构。 物理存储结构&#xff1a;操作系统层面如何组织和管理数据 逻辑存储结构&#xff1a;Oracle数据库内部数据组织和管理数据&#xff0c;数据库管理系统层面如何组织和管理数据 Oracle逻辑存储结构 数据库的逻…

简单创建一个Django项目并配置neo4j数据库

创建项目&#xff0c;项目的文件夹就是项目的名称 创建项目的基本框架 安装djangorestframework 单击运行 查看浏览器运行效果&#xff1a; 运行效果如下&#xff1a; 创建应用(假如说是创建一个名为myapp的应用)&#xff1a; python manage.py startapp myapp创建之后的…

java实现智能家居控制系统——入门版

文章目录 一、需求二、业务分析三、具体实现创建一个功能接口&#xff0c;实现设备的开关创建一个家电类&#xff0c;作为功能接口的实现类&#xff0c;定义名字和状态分别创建电视机、洗衣机、电灯的类&#xff0c;继承家电类Tv类WashMachine类Lamp类 定义智能控制系统类&…

VSCode C/C++ 开发环境完整配置及常见问题(自用)

这里主要记录了一些与配置相关的内容。由于网上教程众多&#xff0c;部分解决方法并不能完全契合我遇到的问题&#xff0c;因此我选择以自己偏好的方式&#xff0c;对 VSCode 进行完整的配置&#xff0c;并记录在使用过程中遇到的问题及解决方案。后续内容也会持续更新和完善。…

实用小工具——快速获取数据库时间写法

最近我遇到了一个比较棘手的问题&#xff1a;在工作中&#xff0c;各个项目所使用的数据库类型各不相同。这导致我习惯性地使用Oracle的SQL语句进行编写&#xff0c;但每次完成后都会遇到报错&#xff0c;最终才意识到项目的数据库并非Oracle。为了避免这种情况&#xff0c;我需…

基于ssm的宠物医院信息管理系统(全套)

一、系统架构 前端&#xff1a;html | layui | vue | element-ui 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | tomcat | idea | nodejs 二、代码及数据库 三、功能介绍 01. web端-首页1 02. web端-首页…

从 YOLOv1 到 YOLOv2:目标检测的进化之路

引言 你有没有想过&#xff0c;当你用手机拍一张照片&#xff0c;里面的人、车、狗是怎么被自动识别出来的&#xff1f;这背后靠的就是目标检测技术。目标检测是计算机视觉中的一个重要领域&#xff0c;它不仅要回答“图片里有什么”&#xff0c;还要告诉你“这些东西在哪里”…

RTDETR融合[CVPR205]ARConv中的自适应矩阵卷积

RT-DETR使用教程&#xff1a; RT-DETR使用教程 RT-DETR改进汇总贴&#xff1a;RT-DETR更新汇总贴 《Adaptive Rectangular Convolution for Remote Sensing Pansharpening》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/pdf/2503.00467 代码链接&#xff1a;https:/…