使用QAbstractItemModel实现树模型

前言

Qt版本:6.8.0

代码

TreeItem.h

#ifndef TREEITEM_H // 防止头文件重复包含
#define TREEITEM_H // 定义TREEITEM_H宏

#include <QVariant> // 包含QVariant类,用于存储任意类型的数据
#include <QList>    // 包含QList类,用于子节点列表

// TreeItem类定义,代表树模型中的一个节点
class TreeItem
{
public:
    // 构造函数
    // @param data: 节点存储的数据,每个元素代表一列
    // @param parentItem: 父节点指针,默认为nullptr表示根节点或无父节点
    explicit TreeItem(const QList<QVariant> &data, TreeItem *parentItem = nullptr);
    // 析构函数
    ~TreeItem();

    // 添加子节点
    // @param child: 要添加的子节点指针
    void appendChild(TreeItem *child);
    // 在指定位置插入子节点
    // @param position: 插入的位置索引
    // @param child: 要插入的子节点指针
    // @return: 成功返回true,否则返回false
    bool insertChild(int position, TreeItem *child);
    // 移除指定位置的子节点
    // @param position: 要移除的子节点的位置索引
    // @return: 成功返回true,否则返回false
    bool removeChild(int position);

    // 获取指定行的子节点
    // @param row: 子节点的行号
    // @return: 指向子节点的指针,如果行号无效则返回nullptr
    TreeItem *child(int row);
    // 获取子节点的数量
    // @return: 子节点的数量
    int childCount() const;
    // 获取节点数据的列数
    // @return: 列数
    int columnCount() const;
    // 获取指定列的数据
    // @param column: 列号
    // @return: 该列的数据 (QVariant),如果列号无效则返回无效的QVariant
    QVariant data(int column) const;
    // 获取当前节点在其父节点中的行号
    // @return: 行号,如果是根节点或没有父节点则返回0
    int row() const;
    // 获取父节点
    // @return: 指向父节点的指针
    TreeItem *parentItem();

    // 设置指定列的数据
    // @param column: 列号
    // @param value: 要设置的新数据
    // @return: 成功返回true,否则返回false
    bool setData(int column, const QVariant &value);
    // 在指定位置插入列
    // @param position: 插入列的起始位置
    // @param columns: 要插入的列数
    // @return: 成功返回true,否则返回false
    bool insertColumns(int position, int columns);
    // 移除指定位置的列
    // @param position: 移除列的起始位置
    // @param columns: 要移除的列数
    // @return: 成功返回true,否则返回false
    bool removeColumns(int position, int columns);

private:
    QList<TreeItem *> m_childNodes; // 子节点列表
    QList<QVariant> m_itemData;     // 节点存储的数据 (每列一个QVariant)
    TreeItem *m_parentItem;         // 父节点指针
};

#endif // TREEITEM_H

TreeItem.cpp

#include "TreeItem.h" // 包含TreeItem类的头文件

// TreeItem 构造函数
// @param data: 节点存储的数据,一个QVector,其中每个QVariant代表一列的数据
// @param parent: 父节点的指针,默认为nullptr
TreeItem::TreeItem(const QList<QVariant> &data, TreeItem *parent)
    : m_itemData(data), m_parentItem(parent)
{
}

// TreeItem 析构函数
// 递归删除所有子节点,释放内存
TreeItem::~TreeItem()
{
    // qDeleteAll是Qt提供的辅助函数,用于删除容器中所有指针指向的对象
    qDeleteAll(m_childNodes); // 删除所有子节点
}

// appendChild 函数:向当前节点的子节点列表末尾添加一个新的子节点
// @param item: 指向要添加的子节点的指针
void TreeItem::appendChild(TreeItem *item)
{
    m_childNodes.append(item); // 将子节点添加到列表中
}

// insertChild 函数:在指定位置插入一个子节点
// @param position: 插入子节点的位置索引。有效范围是 [0, childCount()]。
// @param child: 指向要插入的子节点的指针。
// @return: 如果插入成功(位置有效),返回true;否则返回false。
bool TreeItem::insertChild(int position, TreeItem *child)
{
    // 检查插入位置是否有效
    if (position < 0 || position > m_childNodes.size())
        return false;                     // 位置无效,返回false
    m_childNodes.insert(position, child); // 在指定位置插入子节点
    return true;                          // 插入成功,返回true
}

// removeChild 函数:移除并删除指定位置的子节点
// @param position: 要移除的子节点的位置索引。有效范围是 [0, childCount() - 1]。
// @return: 如果移除成功(位置有效),返回true;否则返回false。
bool TreeItem::removeChild(int position)
{
    // 检查移除位置是否有效
    if (position < 0 || position >= m_childNodes.size())
        return false;                     // 位置无效,返回false
    delete m_childNodes.takeAt(position); // 从列表中移除指针并删除对象
    return true;                          // 移除成功,返回true
}

// child 函数:获取指定行的子节点
// @param row: 要获取的子节点的行号(在当前节点的子节点列表中的索引)
// @return: 如果行号有效,返回指向该子节点的指针;否则返回nullptr
TreeItem *TreeItem::child(int row)
{
    // 检查行号是否在有效范围内
    if (row < 0 || row >= m_childNodes.size())
        return nullptr;          // 行号无效,返回nullptr
    return m_childNodes.at(row); // 返回指定位置的子节点指针
}

// childCount 函数:获取当前节点的子节点数量
// @return: 子节点的数量
int TreeItem::childCount() const
{
    return m_childNodes.count(); // 返回子节点列表的大小
}

// columnCount 函数:获取当前节点存储数据的列数
// @return: 数据的列数
int TreeItem::columnCount() const
{
    return m_itemData.count(); // 返回节点数据QVector的大小
}

// data 函数:获取当前节点指定列的数据
// @param column: 要获取数据的列号
// @return: 如果列号有效,返回该列的数据 (QVariant);否则返回一个无效的QVariant
QVariant TreeItem::data(int column) const
{
    // 检查列号是否在有效范围内
    if (column < 0 || column >= m_itemData.size())
        return QVariant();        // 列号无效,返回无效QVariant
    return m_itemData.at(column); // 返回指定列的数据
}

// parentItem 函数:获取当前节点的父节点
// @return: 指向父节点的指针;如果当前节点没有父节点(例如是根节点的直接子节点,或者本身是根的辅助节点),则返回m_parentItem (可能为nullptr)
TreeItem *TreeItem::parentItem()
{
    return m_parentItem; // 返回父节点指针
}

// row 函数:获取当前节点在其父节点的子节点列表中的行号(索引)
// @return: 如果有父节点,返回当前节点在父节点子列表中的索引;如果没有父节点,返回0
int TreeItem::row() const
{
    if (m_parentItem) // 如果存在父节点
        // const_cast用于临时移除this指针的const属性,以便调用非const成员函数indexOf
        return m_parentItem->m_childNodes.indexOf(const_cast<TreeItem *>(this)); // 返回在父节点子列表中的索引

    return 0; // 没有父节点,返回0
}

// setData 函数:设置当前节点指定列的数据
// @param column: 要设置数据的列号
// @param value: 要设置的新数据 (QVariant)
// @return: 如果列号有效且数据设置成功,返回true;否则返回false
bool TreeItem::setData(int column, const QVariant &value)
{
    // 检查列号是否在有效范围内
    if (column < 0 || column >= m_itemData.size())
        return false; // 列号无效,返回false

    m_itemData[column] = value; // 设置新数据
    return true;                // 设置成功,返回true
}

// insertColumns 函数:在当前节点和其所有子节点的指定位置插入指定数量的空列
// @param position: 插入列的起始位置索引。有效范围 [0, columnCount()]。
// @param columns: 要插入的列的数量。
// @return: 如果位置有效,返回true;否则返回false。
bool TreeItem::insertColumns(int position, int columns)
{
    // 检查插入位置是否有效
    if (position < 0 || position > m_itemData.size())
        return false; // 位置无效,返回false

    // 为当前节点插入列,并用空的QVariant填充
    for (int i = 0; i < columns; ++i)
        m_itemData.insert(position, QVariant());

    // 递归地为所有子节点插入列
    for (TreeItem *child : qAsConst(m_childNodes))
        child->insertColumns(position, columns);

    return true; // 插入成功,返回true
}

// removeColumns 函数:移除当前节点和其所有子节点的指定位置开始的指定数量的列
// @param position: 移除列的起始位置索引。有效范围 [0, columnCount() - columns]。
// @param columns: 要移除的列的数量。
// @return: 如果位置和数量有效,返回true;否则返回false。
bool TreeItem::removeColumns(int position, int columns)
{
    // 检查移除范围是否有效
    if (position < 0 || position + columns > m_itemData.size())
        return false; // 范围无效,返回false

    // 从当前节点移除列
    for (int i = 0; i < columns; ++i)
        m_itemData.remove(position); // 注意:每次移除后,后续元素索引会改变,所以总是移除'position'处的元素

    // 递归地从所有子节点移除列
    for (TreeItem *child : qAsConst(m_childNodes))
        child->removeColumns(position, columns);

    return true; // 移除成功,返回true
}

TreeModel.h

#ifndef TREEMODEL_H
#define TREEMODEL_H

#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include <QStringList>

class TreeItem;

class TreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    // 构造函数
    // @param headers: QStringList类型的表头数据列表,每个字符串代表一列的标题。
    // @param data: QString类型的数据,用于初始化模型内容。
    //              通常是多行文本,每行代表一个节点,通过前导空格表示层级,
    //              同级节点内的数据列通过制表符(\t)分隔。
    // @param parent: QObject类型的父对象,默认为nullptr。
    explicit TreeModel(const QStringList &headers, const QString &data, QObject *parent = nullptr);
    ~TreeModel();

    // == QAbstractItemModel的必须重载函数 ==

    // headerData 函数:获取表头数据
    // @param section: 列号(对于水平表头 Qt::Horizontal)或行号(对于垂直表头 Qt::Vertical)。
    // @param orientation: 表头方向 (Qt::Horizontal 或 Qt::Vertical)。
    // @param role: 请求的数据角色 (例如 Qt::DisplayRole 用于显示文本, Qt::EditRole 用于编辑)。
    // @return: QVariant类型的表头数据;如果请求的 section, orientation 或 role 无效或不支持,则返回无效QVariant。
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

    // setHeaderData 函数:设置表头数据(如果模型支持)
    // @param section: 要设置的列号(对于水平表头)或行号(对于垂直表头)。
    // @param orientation: 表头方向。
    // @param value: 要设置的新表头数据 (QVariant)。
    // @param role: 数据角色 (通常是 Qt::EditRole)。
    // @return: 如果设置成功返回true,否则返回false。
    bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override;

    // index 函数:获取指定行、列和父索引下的子项的模型索引
    // @param row: 子项在父项中的行号(从0开始)。
    // @param column: 子项的列号(从0开始)。
    // @param parent: 父项的模型索引。如果parent无效(QModelIndex()),则表示请求顶层项的索引。
    // @return: 对应子项的QModelIndex;如果行、列无效或父项下不存在该子项,则返回QModelIndex()。
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;

    // parent 函数:获取指定子项索引的父项的模型索引
    // @param index: 子项的模型索引。
    // @return: 父项的QModelIndex;如果index是顶层项或无效,则返回QModelIndex()。
    QModelIndex parent(const QModelIndex &index) const override;

    // rowCount 函数:获取指定父索引下的行数(即子项数量)
    // @param parent: 父项的模型索引。如果parent无效(QModelIndex()),则表示请求顶层项的数量。
    // @return: 子项的数量。
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    // columnCount 函数:获取指定父索引下的列数
    // @param parent: 父项的模型索引。如果parent无效(QModelIndex()),则表示请求顶层项的列数(通常由根节点的列数决定)。
    // @return: 列的数量。
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    // data 函数:获取指定模型索引处的数据
    // @param index: 要获取数据的模型索引。
    // @param role: 请求的数据角色 (例如 Qt::DisplayRole, Qt::EditRole, Qt::ToolTipRole 等)。
    // @return: QVariant类型的数据;如果请求无效、索引无效或角色不支持,则返回无效QVariant。
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    // setData 函数:设置指定模型索引处的数据(如果项是可编辑的)
    // @param index: 要设置数据的模型索引。
    // @param value: 要设置的新数据 (QVariant)。
    // @param role: 数据角色 (通常是 Qt::EditRole)。
    // @return: 如果设置成功返回true,否则返回false。成功设置后必须发射dataChanged()信号。
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;

    // flags 函数:获取指定模型索引处的项的标志
    // 这些标志决定了项的行为,例如是否可选(Qt::ItemIsSelectable)、可编辑(Qt::ItemIsEditable)、可拖拽(Qt::ItemIsDragEnabled)等。
    // @param index: 模型索引。
    // @return: Qt::ItemFlags的组合。对于无效索引,通常返回Qt::NoItemFlags。
    Qt::ItemFlags flags(const QModelIndex &index) const override;

    // == 用于修改模型结构的函数 (插入/删除行和列) ==

    // insertColumns 函数:在指定父项下的给定位置插入列
    // 必须在操作前调用beginInsertColumns(),操作后调用endInsertColumns()。
    // @param position: 插入列的起始列号。
    // @param columns: 要插入的列数 (必须 >= 1)。
    // @param parent: 父项的模型索引。对于顶层操作,通常是QModelIndex()。
    // @return: 如果插入成功返回true,否则返回false。
    bool insertColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) override;

    // removeColumns 函数:从指定父项下的给定位置移除列
    // 必须在操作前调用beginRemoveColumns(),操作后调用endRemoveColumns()。
    // @param position: 移除列的起始列号。
    // @param columns: 要移除的列数 (必须 >= 1)。
    // @param parent: 父项的模型索引。
    // @return: 如果移除成功返回true,否则返回false。
    bool removeColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) override;

    // insertRows 函数:在指定父项下的给定位置插入行
    // 必须在操作前调用beginInsertRows(),操作后调用endInsertRows()。
    // @param position: 插入行的起始行号。
    // @param rows: 要插入的行数 (必须 >= 1)。
    // @param parent: 父项的模型索引。
    // @return: 如果插入成功返回true,否则返回false。
    bool insertRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override;

    // removeRows 函数:从指定父项下的给定位置移除行
    // 必须在操作前调用beginRemoveRows(),操作后调用endRemoveRows()。
    // @param position: 移除行的起始行号。
    // @param rows: 要移除的行数 (必须 >= 1)。
    // @param parent: 父项的模型索引。
    // @return: 如果移除成功返回true,否则返回false。
    bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override;

   
    QStringList mimeTypes() const override;
    QMimeData *mimeData(const QModelIndexList &indexes) const override;
    bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
    bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
    Qt::DropActions supportedDropActions() const override;

private:
    // setupModelData 函数:用给定的字符串列表数据来初始化模型内部的树结构。
    // 这个函数解析输入字符串,创建TreeItem对象,并构建它们之间的父子关系。
    // @param lines: 包含树数据的字符串列表,每行代表一个节点。节点的层级通过行首的空格数确定,
    //               同一节点的多列数据通过制表符(\t)分隔。
    // @param parent: 构建树时当前层正在处理的TreeItem的父节点指针。
    void setupModelData(const QStringList &lines, TreeItem *parent);

    // getItem 函数:根据模型索引获取对应的TreeItem指针。这是一个辅助函数,方便通过QModelIndex访问内部TreeItem。
    // @param index: 模型索引。
    // @return: 指向对应TreeItem的指针;如果索引无效或不指向一个合法的TreeItem,则返回根项(m_rootItem)的指针。
    //          注意:直接返回根项可能不是所有情况下的最佳选择,取决于具体逻辑。
    TreeItem *getItem(const QModelIndex &index) const;

    TreeItem *m_rootItem;         // 指向根节点的TreeItem指针(这个根节点本身通常是不可见的,它的子节点构成模型的第一级项)。
    QList<QVariant> m_headerData; // 存储表头数据的QList
};

#endif // TREEMODEL_H

TreeModel.cpp

/**
 * @brief 树形数据模型的实现,继承自QAbstractItemModel
 */
#include "TreeModel.h"
#include "TreeItem.h"

#include <QStringList>
#include <QMimeData>
#include <QDataStream>

/**
 * @brief 构造函数,初始化树形模型
 * @param headers 表头数据列表
 * @param data 用于构建树的原始字符串数据
 * @param parent 父对象指针
 */
TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent)
    : QAbstractItemModel(parent)
{
    for (const QString &header : headers)
        m_headerData.append(QVariant(header));

    m_rootItem = new TreeItem(m_headerData);
    setupModelData(data.split(QString::fromUtf8("\n")), m_rootItem);
}

/**
 * @brief 析构函数,释放根节点及其所有子节点
 */
TreeModel::~TreeModel()
{
    delete m_rootItem;
}

/**
 * @brief 获取表头数据
 * @param section 列索引
 * @param orientation 方向(水平/垂直)
 * @param role 数据角色
 * @return 返回表头数据,如果无效则返回空QVariant
 */
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section < m_headerData.size())
        return m_headerData.at(section);

    return QVariant();
}

/**
 * @brief 设置表头数据
 * @param section 列索引
 * @param orientation 方向(水平/垂直)
 * @param value 要设置的值
 * @param role 数据角色
 * @return 设置成功返回true,否则返回false
 */
bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
    if (role != Qt::EditRole || orientation != Qt::Horizontal || section >= m_headerData.size())
        return false;

    m_headerData[section] = value;
    emit headerDataChanged(orientation, section, section);
    return true;
}

/**
 * @brief 根据行列和父索引创建模型索引
 * @param row 行号
 * @param column 列号
 * @param parent 父项的模型索引
 * @return 返回创建的模型索引,如果无效则返回空索引
 */
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    TreeItem *parentItem;

    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = static_cast<TreeItem *>(parent.internalPointer());

    TreeItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    return QModelIndex();
}

/**
 * @brief 获取指定索引项的父项索引
 * @param index 当前项的模型索引
 * @return 返回父项的模型索引,如果是顶层项则返回空索引
 */
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    TreeItem *childItem = static_cast<TreeItem *>(index.internalPointer());
    TreeItem *parentItem = childItem->parentItem();

    if (parentItem == m_rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

/**
 * @brief 获取指定父项下的行数
 * @param parent 父项的模型索引
 * @return 返回子项的数量
 */
int TreeModel::rowCount(const QModelIndex &parent) const
{
    TreeItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = static_cast<TreeItem *>(parent.internalPointer());

    return parentItem->childCount();
}

/**
 * @brief 获取指定父项下的列数
 * @param parent 父项的模型索引
 * @return 返回列数
 */
int TreeModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return static_cast<TreeItem *>(parent.internalPointer())->columnCount();
    return m_rootItem->columnCount();
}

/**
 * @brief 获取指定索引的数据
 * @param index 模型索引
 * @param role 数据角色
 * @return 返回索引对应的数据,如果无效则返回空QVariant
 */
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role != Qt::DisplayRole && role != Qt::EditRole)
        return QVariant();

    TreeItem *item = getItem(index);

    return item->data(index.column());
}

/**
 * @brief 设置指定索引的数据
 * @param index 模型索引
 * @param value 要设置的值
 * @param role 数据角色
 * @return 设置成功返回true,否则返回false
 */
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role != Qt::EditRole)
        return false;

    TreeItem *item = getItem(index);
    bool result = item->setData(index.column(), value);

    if (result)
        emit dataChanged(index, index, {role});

    return result;
}

/**
 * @brief 获取指定索引项的标志
 * @param index 模型索引
 * @return 返回项的标志(如可编辑、可选择等)
 */
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;

    return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}

/**
 * @brief 获取指定索引对应的TreeItem指针
 * @param index 模型索引
 * @return 返回TreeItem指针,如果索引无效则返回根节点
 */
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
    if (index.isValid())
    {
        TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
        if (item)
            return item;
    }
    return m_rootItem;
}

/**
 * @brief 在指定位置插入列
 * @param position 插入位置
 * @param columns 要插入的列数
 * @param parent 父项的模型索引
 * @return 插入成功返回true,否则返回false
 */
bool TreeModel::insertColumns(int position, int columns, const QModelIndex &parent)
{
    beginInsertColumns(parent, position, position + columns - 1);
    const bool success = m_rootItem->insertColumns(position, columns);
    endInsertColumns();

    return success;
}

/**
 * @brief 删除指定位置的列
 * @param position 删除位置
 * @param columns 要删除的列数
 * @param parent 父项的模型索引
 * @return 删除成功返回true,否则返回false
 */
bool TreeModel::removeColumns(int position, int columns, const QModelIndex &parent)
{
    beginRemoveColumns(parent, position, position + columns - 1);
    const bool success = m_rootItem->removeColumns(position, columns);
    endRemoveColumns();

    if (m_rootItem->columnCount() == 0)
        removeRows(0, rowCount());

    return success;
}

/**
 * @brief 在指定位置插入行
 * @param position 插入位置
 * @param rows 要插入的行数
 * @param parent 父项的模型索引
 * @return 插入成功返回true,否则返回false
 */
bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    TreeItem *parentItem = getItem(parent);
    if (!parentItem)
        return false;

    beginInsertRows(parent, position, position + rows - 1);
    QList<QVariant> emptyData(columnCount());
    const bool success = parentItem->insertChild(position, new TreeItem(emptyData, parentItem));
    endInsertRows();

    return success;
}

/**
 * @brief 删除指定位置的行
 * @param position 删除位置
 * @param rows 要删除的行数
 * @param parent 父项的模型索引
 * @return 删除成功返回true,否则返回false
 */
bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    TreeItem *parentItem = getItem(parent);
    if (!parentItem)
        return false;

    beginRemoveRows(parent, position, position + rows - 1);
    bool success = true;
    for (int row = 0; row < rows; ++row)
    {
        if (!parentItem->removeChild(position))
        {
            success = false;
            break;
        }
    }
    endRemoveRows();

    return success;
}

/**
 * @brief 根据文本数据构建树形结构
 * @param lines 文本行列表
 * @param parent 父节点指针
 */
void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent)
{
    QList<TreeItem *> parents;
    QList<int> indentations;
    parents << parent;
    indentations << 0;

    int number = 0;

    while (number < lines.count())
    {
        int position = 0;
        while (position < lines[number].length())
        {
            if (lines[number].at(position) != QChar(' '))
                break;
            position++;
        }

        const QString lineData = lines[number].mid(position).trimmed();

        if (!lineData.isEmpty())
        {
            const QStringList columnStrings = lineData.split(QString::fromUtf8("\t"), Qt::SkipEmptyParts);
            QList<QVariant> columnData;
            columnData.reserve(columnStrings.count());
            for (const QString &columnString : columnStrings)
                columnData << QVariant(columnString);

            if (position > indentations.last())
            {
                if (parents.last()->childCount() > 0)
                {
                    parents << parents.last()->child(parents.last()->childCount() - 1);
                    indentations << position;
                }
            }
            else
            {
                while (position < indentations.last() && parents.count() > 0)
                {
                    parents.pop_back();
                    indentations.pop_back();
                }
            }

            TreeItem *parentItem = parents.last();
            parentItem->appendChild(new TreeItem(columnData, parentItem));
        }
        ++number;
    }
}

/**
 * @brief 获取支持的MIME类型列表
 * @return 返回可以被拖放操作处理的MIME类型字符串列表
 */
QStringList TreeModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    return types;
}

/**
 * @brief 创建要拖动项的MIME数据
 * @param indexes 被拖动项的模型索引列表
 * @return 返回包含被拖动数据的QMimeData对象指针
 */
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;
    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    for (const QModelIndex &index : indexes)
    {
        if (index.isValid())
        {
            QString text = data(index, Qt::DisplayRole).toString();
            stream << text;
        }
    }

    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

/**
 * @brief 检查是否可以在指定位置放下MIME数据
 * @param data MIME数据指针
 * @param action 拖放动作类型
 * @param row 目标行号
 * @param column 目标列号
 * @param parent 目标父项的模型索引
 * @return 如果可以接受放下操作返回true,否则返回false
 */
bool TreeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action,
                                int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(parent);

    if (!data->hasFormat("application/vnd.text.list"))
        return false;

    if (column > 0)
        return false;

    return true;
}

/**
 * @brief 处理放下的MIME数据
 * @param data MIME数据指针
 * @param action 拖放动作类型
 * @param row 目标行号
 * @param column 目标列号
 * @param parent 目标父项的模型索引
 * @return 如果成功处理数据返回true,否则返回false
 */
bool TreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
                             int row, int column, const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    if (action == Qt::IgnoreAction)
        return true;

    int beginRow;
    if (row != -1)
        beginRow = row;
    else if (parent.isValid())
        beginRow = parent.row();
    else
        beginRow = rowCount(QModelIndex());

    QByteArray encodedData = data->data("application/vnd.text.list");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QList<QVariant> newItems;
    int rows = 0;

    while (!stream.atEnd())
    {
        QString text;
        stream >> text;
        newItems << text;
        ++rows;
    }

    insertRows(beginRow, rows, parent);
    for (int i = 0; i < newItems.size(); ++i)
    {
        QModelIndex idx = index(beginRow + i, 0, parent);
        setData(idx, newItems[i]);
    }

    return true;
}

/**
 * @brief 获取支持的拖放动作类型
 * @return 返回支持的拖放动作标志
 */
Qt::DropActions TreeModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值