1. QTreeView与Model概述
在Qt框架中,QTreeView是一个强大的树形视图控件,用于展示层次化数据。它遵循MVC(Model-View-Controller)设计模式,通过Model/View架构实现数据与显示的分离。这种架构的核心优势在于:
- 数据与显示解耦:Model负责数据存储和逻辑,View负责展示
- 多视图共享数据:同一个Model可以被多个不同的View使用
- 标准化接口:通过统一的接口实现数据访问
Qt提供了两种主要的Model实现方式:
- QStandardItemModel:预定义的通用模型
- 自定义Model:继承QAbstractItemModel或其子类实现
2. QStandardItemModel基础用法
2.1 创建基本树形结构
QStandardItemModel是Qt提供的一个现成的模型类,非常适合快速构建树形结构。以下是创建简单树形结构的代码示例:
cpp复制// 创建模型和视图
QStandardItemModel *model = new QStandardItemModel();
QTreeView *treeView = new QTreeView();
treeView->setModel(model);
// 添加根节点
QStandardItem *rootItem = model->invisibleRootItem();
// 创建第一层节点
QStandardItem *item1 = new QStandardItem("节点1");
QStandardItem *item2 = new QStandardItem("节点2");
rootItem->appendRow(item1);
rootItem->appendRow(item2);
// 添加子节点
QStandardItem *child1 = new QStandardItem("子节点1");
QStandardItem *child2 = new QStandardItem("子节点2");
item1->appendRow(child1);
item1->appendRow(child2);
2.2 设置节点属性
QStandardItem提供了丰富的属性设置方法:
cpp复制// 设置字体
QFont font;
font.setBold(true);
item1->setFont(font);
// 设置背景色
item1->setBackground(QBrush(Qt::yellow));
// 设置文本颜色
item1->setForeground(QBrush(Qt::blue));
// 设置图标
item1->setIcon(QIcon(":/icons/node.png"));
// 设置复选框
item1->setCheckable(true);
item1->setCheckState(Qt::Checked);
// 设置工具提示
item1->setToolTip("这是一个节点示例");
2.3 数据角色与自定义数据
Qt使用数据角色(Data Role)系统来区分不同类型的数据:
cpp复制// 设置不同角色的数据
item1->setData("显示文本", Qt::DisplayRole);
item1->setData("工具提示文本", Qt::ToolTipRole);
item1->setData(QIcon(":/icons/node.png"), Qt::DecorationRole);
item1->setData(Qt::Checked, Qt::CheckStateRole);
// 存取自定义数据
item1->setData(12345, Qt::UserRole + 1); // 自定义数据
int customData = item1->data(Qt::UserRole + 1).toInt();
3. 自定义Model实现
3.1 自定义Model的必要性
当遇到以下情况时,需要自定义Model:
- 数据源不是简单的内存结构(如数据库、网络数据)
- 需要特殊的数据处理逻辑
- 需要优化大数据集的性能
- 需要实现特殊的显示或编辑行为
3.2 基础实现步骤
自定义Model通常继承自QAbstractItemModel或其子类:
cpp复制class CustomTreeModel : public QAbstractItemModel {
Q_OBJECT
public:
// 必须实现的纯虚函数
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
// 可选实现的函数
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
};
3.3 关键方法详解
3.3.1 index()方法
cpp复制QModelIndex CustomTreeModel::index(int row, int column, const QModelIndex &parent) const {
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeNode *parentNode = parent.isValid() ?
static_cast<TreeNode*>(parent.internalPointer()) : m_rootNode;
TreeNode *childNode = parentNode->child(row);
return childNode ? createIndex(row, column, childNode) : QModelIndex();
}
3.3.2 parent()方法
cpp复制QModelIndex CustomTreeModel::parent(const QModelIndex &child) const {
if (!child.isValid())
return QModelIndex();
TreeNode *childNode = static_cast<TreeNode*>(child.internalPointer());
TreeNode *parentNode = childNode->parent();
if (parentNode == m_rootNode)
return QModelIndex();
return createIndex(parentNode->row(), 0, parentNode);
}
3.3.3 data()方法
cpp复制QVariant CustomTreeModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return QVariant();
TreeNode *node = static_cast<TreeNode*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return node->data(index.column());
case Qt::DecorationRole:
return node->icon();
case Qt::ToolTipRole:
return node->toolTip();
// 处理其他角色...
default:
return QVariant();
}
}
4. 性能优化技巧
4.1 懒加载技术
对于大型树形结构,实现懒加载可以显著提高性能:
cpp复制bool CustomTreeModel::canFetchMore(const QModelIndex &parent) const {
if (!parent.isValid()) return false;
TreeNode *node = static_cast<TreeNode*>(parent.internalPointer());
return !node->isPopulated();
}
void CustomTreeModel::fetchMore(const QModelIndex &parent) {
if (!parent.isValid()) return;
TreeNode *node = static_cast<TreeNode*>(parent.internalPointer());
if (node->isPopulated()) return;
beginInsertRows(parent, 0, node->childCount() - 1);
node->populate(); // 实际加载数据的操作
endInsertRows();
}
4.2 批量更新信号
当需要更新大量数据时,使用批量信号:
cpp复制// 不推荐:逐个更新
for (int i = 0; i < 100; ++i) {
QModelIndex idx = index(i, 0);
emit dataChanged(idx, idx);
}
// 推荐:批量更新
emit dataChanged(index(0, 0), index(99, columnCount() - 1));
4.3 代理模型的使用
QSortFilterProxyModel可以提供排序和过滤功能而不修改原始模型:
cpp复制QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(customModel);
treeView->setModel(proxyModel);
// 设置动态过滤
proxyModel->setFilterWildcard("searchText*");
proxyModel->setFilterKeyColumn(0); // 过滤第一列
// 设置排序
treeView->setSortingEnabled(true);
proxyModel->sort(0, Qt::AscendingOrder);
5. 高级功能实现
5.1 拖放支持
实现拖放功能需要重写几个关键方法:
cpp复制Qt::DropActions CustomTreeModel::supportedDropActions() const {
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags CustomTreeModel::flags(const QModelIndex &index) const {
Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
if (index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
bool CustomTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) {
// 实现拖放数据处理逻辑
}
5.2 自定义委托
自定义委托可以控制项目的显示和编辑方式:
cpp复制class StarDelegate : public QStyledItemDelegate {
public:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
// 自定义绘制逻辑
}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
// 返回项目大小
}
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 {
// 将编辑器数据保存回模型
}
};
// 使用自定义委托
treeView->setItemDelegate(new StarDelegate(this));
5.3 复杂数据验证
在setData()方法中实现数据验证:
cpp复制bool CustomTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid() || role != Qt::EditRole)
return false;
// 获取当前值
QString oldValue = data(index, role).toString();
QString newValue = value.toString();
// 验证数据
if (newValue.isEmpty()) {
emit editFailed(tr("值不能为空"));
return false;
}
if (newValue.length() > 100) {
emit editFailed(tr("值长度不能超过100个字符"));
return false;
}
// 更新数据
TreeNode *node = static_cast<TreeNode*>(index.internalPointer());
node->setData(index.column(), newValue);
emit dataChanged(index, index, {role});
return true;
}
6. 实际应用案例
6.1 文件系统浏览器
使用QFileSystemModel快速实现文件浏览器:
cpp复制QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::homePath());
QTreeView *treeView = new QTreeView;
treeView->setModel(model);
treeView->setRootIndex(model->index(QDir::homePath()));
// 自定义显示
treeView->setAnimated(false);
treeView->setIndentation(20);
treeView->setSortingEnabled(true);
treeView->setColumnHidden(1, true); // 隐藏大小列
treeView->setColumnHidden(2, true); // 隐藏类型列
6.2 数据库数据显示
自定义Model显示数据库查询结果:
cpp复制class DatabaseTreeModel : public QAbstractItemModel {
public:
DatabaseTreeModel(QObject *parent = nullptr)
: QAbstractItemModel(parent) {
// 初始化数据库连接等
}
// 实现必要的虚函数...
private:
QSqlDatabase m_db;
QVector<Record> m_records;
};
// 使用示例
DatabaseTreeModel *model = new DatabaseTreeModel;
model->setQuery("SELECT * FROM products WHERE category = 'Electronics'");
QTreeView *view = new QTreeView;
view->setModel(model);
6.3 配置选项编辑器
实现层次化配置编辑器:
cpp复制class ConfigItem {
public:
QString name;
QVariant value;
QList<ConfigItem*> children;
// ...
};
class ConfigModel : public QAbstractItemModel {
// 实现自定义Model
};
// 使用示例
ConfigModel *model = new ConfigModel;
model->loadFromFile("settings.conf");
QTreeView *configEditor = new QTreeView;
configEditor->setModel(model);
configEditor->setItemDelegate(new ConfigDelegate);
