前言
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;
}