QT中QTableView与QTreeView的联合使用(委托、代理、视图)

案例效果图

  • 先看我们要实现的效果
    在这里插入图片描述

下面提供的代码将会以伪代码的形式表现,主要是提供一个思路给大家做参考

基础概念

  • 想要做出该案例效果,需要对基本概念有一定理解,若对基础概念不理解,委托与代理的基本概念可以看我的这篇博客或者自行百度。

框架

  • 案例中我们可以看到左侧为树状结构,右侧为表格结构,对树可以增删查操作,对表可以增删改操作。要实现这些功能,我们需要俩个代理一个为树的一个为表的,他们都是继承于QAbstractItemModel。树于表的代理模型都需要设置自己单独的结构体用于操作数据,其中树相对于表就复杂多。 想要实现树的查找我们还需要设置一个用于过滤的代理模型QSortFilterProxyModel,当然这只是现实过滤的一种方法,还有更简单的方法就是用正则表达式去查找保存所有数据的链表。而设置表的时候想得到不同类型的单元格,则需要为表设置委托QStyledItemDelegate
  • 准备好这些基本结构就可以new好一个QTableView与一个QTreeView操作了。

代码结构介绍

代理模型与其结构体设计

上面提到结构体是用与保存/操作数据的

  1. 表结构体
struct DeveloperTableItem {
    DeveloperTableItem(const QString& key, const QVariant& value) : m_key(key), m_value(value) { }

    QString m_key;
    QVariant m_value;
};

可以看到表只需要一个键值对就行
2. 树结构体

// 信息
typedef struct Node_t {
    QString Node; // 节点
    QString Type; // 节点类型
    Node_t()
    {
        Node = "";
        Type = "";
    }
} Node;

class TreeItem
{
public:
    // 节点类型
    enum Type
    {
        Unknown,
        HeadNode,   // 头节点
        BranchNode, // 分支节点
        LeafNode,   // 叶子节点
    };
    //*相关功能函数略*//    
public:
    QString m_uuid;
    Type m_type;  

private:
    QList<TreeItem*> m_children;    // 子节点
    TreeItem* m_parent;             // 父节点
    int m_row;                      // 此item位于父节点中第几个
    Node* _ptr;                     // 存储数据的指针
};

树的结构相对就复杂很多,它需要保存自己的节点信息(子节点,父节点,节点详细信息)
3. 表的代理

class TableModel : public QAbstractTableModel
{
public:
    enum Cloum
    {
        Key = 0,
        Value
    };
    TableModel(QObject* parent = nullptr);
    void addRow(const QString& key, const QVariant& value);
    void setTreeItem(TreeItem* item) { m_item = item; }
    //*功能函数-略*//

protected:
    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
    virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
    virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
    virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
    virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
    virtual Qt::ItemFlags flags(const QModelIndex& index) const override;

private:
    /// @brief 数据链表
    QList<DeveloperTableItem*> m_data;
    /// @brief 表头
    QStringList m_listHeader;
    TreeItem* m_item;
};

简单介绍一下上面的关键函数
m_data使用一个QList保存所有的数据。
addRow函数是用于往结构m_data中加入数据的,setTreeItem函数用于设置为该表所属的树节点。
那些保护函数都是重写QAbstractTableModel类的,可以自行百度。
4. 树的代理

class TreeModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit TreeModel(QObject* parent = nullptr);
    //*功能函数-略*//

protected:
    QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
    int rowCount(const QModelIndex& parent) const override;
    QVariant data(const QModelIndex& index, int role) const override;
    QModelIndex parent(const QModelIndex& index) const override;
    int columnCount(const QModelIndex& parent) const override;
    virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

private:
    /// @brief 根节点
    TreeItem* m_rootItem;
    /// @brief 表头
    QStringList m_header;
};

简单介绍一下上面的关键函数
树中我们只需要使用m_rootItem保存根节点就可以保存所有数据,详细的数据都保存在上述树的的结构体中。
addRow函数是用于往结构m_data中加入数据的,setTreeItem函数用于设置为该表所属的树节点。
那些保护函数都也是重写QAbstractTableModel类的,可以自行百度。

委托与过滤模型
  1. 表的委托
class MyDelegate : public QStyledItemDelegate
{
public:
    MyDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) { }

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    void setEditorData(QWidget* editor, const QModelIndex& index) const override;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
    void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option,
                              const QModelIndex& index) const override;
};

都是熟面孔了,我称之为委托三部曲
createEditor:用于创建一个编辑器部件,用于编辑指定的数据项
setEditorData:用于将数据从数据模型中的指定索引复制到编辑器部件中,以便用户对其进行编辑。
setModelData:用于将编辑器部件中的数据复制回数据模型中的指定索引,以便更新模型中的数据。
最后还有个updateEditorGeometry用于更新编辑器部件的位置和大小,以便与其所在的视图或窗口的布局相匹配。
2. 树的过滤模型

class TreeViewProxyModel : public QSortFilterProxyModel
{
public:
    explicit TreeViewProxyModel(QObject* parent = nullptr);
    void setFilterString(const QString& strFilter = QString());
    void begin() { beginResetModel(); }
    void end() { endResetModel(); }
    void expandFilteredNodes(const QModelIndex& parent = QModelIndex());

protected:
    bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;

private:
    QString m_strFilterString;
    ModelTreeView* mtree;
};

简单介绍一下
setFilterString:修改文本时调用的函数,其会调用invalidateFilter函数触发过滤器
filterAcceptsRow:重写的函数,为过滤的规则,通过调用invalidateFilter函数触发
expandFilteredNodes:递归展开筛选后的节点

视图
  • 视图没啥好介绍的,就设置一下样式,把代理和委托设置好, 各种功能函数实现。

部分详细的功能点介绍

  1. 在树中我们可以看到有俩列,他们是保持一定的占比的,当我们变换窗口大小时,占比也是不会变得,具体实现为如下代码。
    // 此函数用于调整列宽
    auto adjustColumnWidth = [=]() {
        int totalWidth = viewport()->width();                      // 获取视图的总宽度
        int firstColumnWidth = static_cast<int>(totalWidth * 0.8); // 计算出第一列的宽度
        setColumnWidth(0, firstColumnWidth);                       // 设置第一列的宽度
    };
    // 在视图第一次显示的时候可能需要调整列宽
    adjustColumnWidth();
    // 为了响应后续的尺寸变化,比如用户调整窗口大小,我们需要连接 resize 事件
    QObject::connect(header(), &QHeaderView::sectionResized, this, adjustColumnWidth);
  1. 如何在树中设置菜单,如下代码
//视图构造函数中定义
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &ModelTreeView::customContextMenuRequested, this, &ModelTreeView::onTreeContextMenu);
//实现函数
void ModelTreeView::onTreeContextMenu(const QPoint& pos)
{
    m_menu->clear();
    QModelIndex index = indexAt(pos);
    if (!index.isValid()) {
        return;
    }
    //通过Index得到节点信息
    TreeItem* item = getItemByIndex(index);
    //有了节点信息既可以设置相应的右键菜单
    switch (item->m_type) {
    case TreeItem::HeadNode: {
        m_menu->addAction("添加组", this, SLOT(onAddGroup()));
    } break;
    case TreeItem::BranchNode: {
        //略
    } break;
    case TreeItem::LeafNode: {
         //略
    } break;
    default:
        break;
    }
    m_menu->exec(cursor().pos());
}
  1. 表的菜单,与树的菜单做法差不多
// 设置允许自定义上下文菜单
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested, this, &TableView::showContextMenu);
void TableView::showContextMenu(const QPoint& pos)
{
    QModelIndex index = indexAt(pos);
    QMenu contextMenu(tr("菜单"), this);
    QAction addAction(tr("添加键值"), this);
    QAction removeAction(tr("删除"), this);
    // 只有在存在有效行时添加删除选项
    if (index.isValid()) {
        contextMenu.addAction(&removeAction);
    }
    contextMenu.addAction(&addAction);
    // 显示菜单
    contextMenu.exec(mapToGlobal(pos));
}
  1. 每次点击树节点右侧出现相对应的表,先获取点击的节点信息,清空原有的表代理数据,为什么要清除呢,我使用的思想是多对一,多个树节点对应一个表,因为数据都存放在树的结构体中,所以每次就是清除上一次数据,把这一次数据重新刷新到表格中。数据与界面是要分开的,这是我们使用model-view这个设计模式的核心思想 如下代码实现。
    // 连接树的信号到插槽,以对项目点击作出响应
    connect(dataPtr->treeView, &QTreeView::clicked, this, &DeveloperModeWidget::onTreeClicked);
void DeveloperModeWidget::onTreeClicked(const QModelIndex& index)
{
    // 根据index获取被点击的项
    auto item = dataPtr->treeView->getItemByIndex(index);
    if (item && item->parent()) {
        dataPtr->tableModel->cleraData();//清除原有数据
        setTabelData(item);//设置表格数据
        dataPtr->tableView->update(); // 刷新表格视图
    }
}
  1. 我们给树设置了了一个过滤模型,那么在点击节点时要获取正确的节点,需要进行映射。
TreeItem* ModelTreeView::getItemByIndex(const QModelIndex& index)
{
    auto mapIndex = proxymodel->mapToSource(index);
    return m_treeModel->itemFromIndex(mapIndex);
}

总结

  • 其实还有很多详细的地方,特别是对树节点的增删的操作,beginxxxendxxx这俩个函数的使用,以及indexparent等函数的使用都有很多门道的。由于篇幅限制就不一一说明了,案例源码奉上,不过这只是我做项目的一部分,是跑不起来的,代码的层次还算清楚,关键注释都是有的,有问题可以留言。树结构思路是这个demo提供的,可以正常使用,参考了这位大佬的文章
  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt,结构体(struct)和结构体指针(struct pointer)与C++的用法类似。 结构体(struct)是一种自定义的数据类型,可以将多个不同类型的变量组合在一起,形成一个单独的数据单元。在Qt,可以使用结构体来表示一组相关的数据。 以下是一个示例,展示了如何定义和使用一个结构体: ```cpp #include <iostream> #include <QString> struct Person { QString name; int age; }; int main() { Person person; person.name = "John"; person.age = 25; std::cout << "Name: " << person.name.toStdString() << std::endl; std::cout << "Age: " << person.age << std::endl; return 0; } ``` 在上面的示例,我们定义了一个名为 `Person` 的结构体,它包含了一个 `QString` 类型的 `name` 变量和一个 `int` 类型的 `age` 变量。然后,在 `main()` 函数,我们创建了一个名为 `person` 的结构体对象,并对其成员变量进行赋值。最后,我们使用 `std::cout` 将成员变量的值打印到控制台。 结构体指针(struct pointer)是指向结构体对象的指针。通过使用指针,可以动态地分配和访问结构体对象。在Qt,可以使用指针操作符 `->` 来访问结构体指针所指向的成员。 以下是一个示例,展示了如何定义和使用一个结构体指针: ```cpp #include <iostream> #include <QString> struct Person { QString name; int age; }; int main() { Person* personPtr = new Person; personPtr->name = "John"; personPtr->age = 25; std::cout << "Name: " << personPtr->name.toStdString() << std::endl; std::cout << "Age: " << personPtr->age << std::endl; delete personPtr; // 释放内存 return 0; } ``` 在上面的示例,我们定义了一个名为 `personPtr` 的结构体指针,并使用 `new` 运算符动态地分配了一个 `Person` 结构体对象。然后,我们通过指针操作符 `->` 访问结构体指针所指向的成员变量,并对其进行赋值。最后,我们使用 `delete` 运算符释放了通过 `new` 分配的内存。 需要注意的是,在使用结构体指针时,必须确保在不再使用该对象时释放内存,以避免内存泄漏。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值