QAbstractListModel 详细解析

概述

QAbstractListModel 是 Qt 框架中为列表视图提供数据的抽象基类,它是 QAbstractItemModel 的简化版本,专门用于一维列表数据。

复制代码
QObject
└── QAbstractItemModel
    └── QAbstractListModel

核心特性

1. 简化的一维数据模型

  • 专门为列表数据设计(ListView、Repeater等)

  • 不需要处理复杂的行列和父子关系

  • 相比 QAbstractItemModel 更易于实现

2. 自动索引管理

  • 自动为每个数据项创建 QModelIndex

  • 索引的 row 对应列表位置,column 始终为 0

  • parent 索引始终为无效索引

🎯 必须重写的纯虚函数

1. rowCount()

复制代码
int rowCount(const QModelIndex &parent = QModelIndex()) const override;

作用 : 返回模型中的数据行数
参数 : parent - 对于列表模型,此参数应始终为无效索引
返回: 数据项的总数

示例:

复制代码
int StudentModel::rowCount(const QModelIndex &parent) const
{
    // 列表模型中parent应该无效
    if (parent.isValid())
        return 0;
    
    return m_students.size();
}

2. data()

复制代码
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

作用 : 返回指定索引和角色的数据
参数:

  • index: 数据项索引(row, column)

  • role: 数据角色(显示、装饰、工具提示等)

返回: 包含数据的 QVariant

示例:

复制代码
QVariant StudentModel::data(const QModelIndex &index, int role) const
{
    // 检查索引有效性
    if (!index.isValid() || index.row() < 0 || index.row() >= m_students.size())
        return QVariant();

    const Student &student = m_students.at(index.row());
    
    switch (role) {
    case Qt::DisplayRole:
    case NameRole:
        return student.name;
    case AgeRole:
        return student.age;
    case ScoreRole:
        return student.score;
    // ... 其他角色
    default:
        return QVariant();
    }
}

3. roleNames()

复制代码
QHash<int, QByteArray> roleNames() const override;

作用 : 定义角色ID与角色名称的映射关系
返回: 角色ID到角色名称的哈希表

示例:

复制代码
QHash<int, QByteArray> StudentModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[AgeRole] = "age";
    roles[ScoreRole] = "score";
    roles[ColorRole] = "color";
    return roles;
}

数据变更通知机制

1. 数据插入

复制代码
void insertData(int position, int count)
{
    beginInsertRows(QModelIndex(), position, position + count - 1);
    // ... 实际插入数据操作
    endInsertRows();
}

2. 数据删除

复制代码
void removeData(int position, int count)
{
    beginRemoveRows(QModelIndex(), position, position + count - 1);
    // ... 实际删除数据操作
    endRemoveRows();
}

3. 数据修改

复制代码
void updateData(int position)
{
    QModelIndex topLeft = createIndex(position, 0);
    QModelIndex bottomRight = createIndex(position, 0);
    emit dataChanged(topLeft, bottomRight, {ScoreRole, ColorRole});
}

4. 模型重置

复制代码
void resetModel()
{
    beginResetModel();
    // ... 完全重置数据
    endResetModel();
}

📊 内置数据角色

Qt 提供了一些标准角色:

角色 描述
Qt::DisplayRole 0 主要显示数据
Qt::DecorationRole 1 图标等装饰数据
Qt::EditRole 2 可编辑的数据
Qt::ToolTipRole 3 工具提示文本
Qt::StatusTipRole 4 状态栏提示
Qt::WhatsThisRole 5 "这是什么"帮助
Qt::SizeHintRole 13 项的大小提示

🛠️ 完整实现示例

基础列表模型

复制代码
class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    explicit StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), m_strings(strings) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        if (parent.isValid())
            return 0;
        return m_strings.size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        if (!index.isValid() || index.row() >= m_strings.size())
            return QVariant();

        if (role == Qt::DisplayRole || role == Qt::EditRole)
            return m_strings.at(index.row());

        return QVariant();
    }

    QHash<int, QByteArray> roleNames() const override
    {
        QHash<int, QByteArray> roles;
        roles[Qt::DisplayRole] = "display";
        return roles;
    }

private:
    QStringList m_strings;
};

高级自定义模型

复制代码
class AdvancedListModel : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)

public:
    enum CustomRoles {
        TitleRole = Qt::UserRole + 1,
        DescriptionRole,
        IconRole,
        ProgressRole,
        StatusRole
    };

    explicit AdvancedListModel(QObject *parent = nullptr) : QAbstractListModel(parent) {}

    // 必须实现的方法
    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        Q_UNUSED(parent)
        return m_items.size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
            return QVariant();

        const DataItem &item = m_items.at(index.row());
        
        switch (role) {
        case TitleRole:
        case Qt::DisplayRole:
            return item.title;
        case DescriptionRole:
            return item.description;
        case IconRole:
            return item.icon;
        case ProgressRole:
            return item.progress;
        case StatusRole:
            return item.status;
        case Qt::ToolTipRole:
            return QString("%1\n%2").arg(item.title).arg(item.description);
        default:
            return QVariant();
        }
    }

    QHash<int, QByteArray> roleNames() const override
    {
        QHash<int, QByteArray> roles;
        roles[TitleRole] = "title";
        roles[DescriptionRole] = "description";
        roles[IconRole] = "icon";
        roles[ProgressRole] = "progress";
        roles[StatusRole] = "status";
        return roles;
    }

    // 自定义方法
    Q_INVOKABLE void addItem(const QString &title, const QString &description)
    {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_items.append(DataItem{title, description, "default.png", 0, "active"});
        endInsertRows();
        emit countChanged();
    }

    Q_INVOKABLE void removeItem(int index)
    {
        if (index < 0 || index >= m_items.size())
            return;

        beginRemoveRows(QModelIndex(), index, index);
        m_items.removeAt(index);
        endRemoveRows();
        emit countChanged();
    }

    Q_INVOKABLE void updateProgress(int index, int progress)
    {
        if (index < 0 || index >= m_items.size())
            return;

        m_items[index].progress = progress;
        QModelIndex modelIndex = createIndex(index, 0);
        emit dataChanged(modelIndex, modelIndex, {ProgressRole});
    }

signals:
    void countChanged();

private:
    struct DataItem {
        QString title;
        QString description;
        QString icon;
        int progress;
        QString status;
    };
    
    QList<DataItem> m_items;
};

🔗 QML 中的使用

基本使用

复制代码
ListView {
    model: advancedModel
    delegate: Rectangle {
        width: parent.width
        height: 60
        
        Row {
            spacing: 10
            
            Image {
                source: model.icon
                width: 40; height: 40
            }
            
            Column {
                Text { text: model.title; font.bold: true }
                Text { text: model.description; color: "gray" }
            }
            
            ProgressBar {
                value: model.progress
                width: 100
            }
        }
    }
}

角色访问方式

复制代码
// 方式1: 通过角色名直接访问
text: model.title

// 方式2: 通过 modelData 访问(当只有 DisplayRole 时)
text: model.modelData

// 方式3: 通过索引访问
text: model.display

⚡ 性能优化技巧

1. 批量操作

复制代码
// 不好的做法:逐个添加
for (const auto &item : items) {
    beginInsertRows(...);
    // 添加一个
    endInsertRows();
}

// 好的做法:批量添加
beginInsertRows(QModelIndex(), rowCount(), rowCount() + items.size() - 1);
m_items.append(items);
endInsertRows();

2. 精确的数据变更通知

复制代码
// 只通知实际变化的角色
emit dataChanged(index, index, {SpecificRole});

// 而不是通知所有角色
emit dataChanged(index, index);

3. 使用正确的父索引

复制代码
// 列表模型始终使用无效的父索引
beginInsertRows(QModelIndex(), ...);
beginRemoveRows(QModelIndex(), ...);

🎨 高级特性

1. 可编辑模型

复制代码
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
    if (!index.isValid() || role != Qt::EditRole)
        return false;

    m_data[index.row()] = value.toString();
    emit dataChanged(index, index, {role});
    return true;
}

Qt::ItemFlags flags(const QModelIndex &index) const override
{
    if (!index.isValid())
        return Qt::NoItemFlags;
        
    return Qt::ItemIsEditable | QAbstractListModel::flags(index);
}

2. 拖放支持

复制代码
Qt::DropActions supportedDropActions() const override
{
    return Qt::MoveAction;
}

QMimeData *mimeData(const QModelIndexList &indexes) const override
{
    // 实现 MIME 数据创建
}

bool dropMimeData(const QMimeData *data, Qt::DropAction action, 
                  int row, int column, const QModelIndex &parent) override
{
    // 实现拖放数据处理
}

🔍 调试技巧

1. 角色名称调试

复制代码
void printRoleNames()
{
    auto roles = roleNames();
    for (auto it = roles.begin(); it != roles.end(); ++it) {
        qDebug() << "Role:" << it.key() << "Name:" << it.value();
    }
}

2. 数据验证

复制代码
bool isIndexValid(const QModelIndex &index) const
{
    return index.isValid() && 
           index.row() >= 0 && 
           index.row() < rowCount() && 
           index.model() == this;
}

QAbstractListModel 提供了强大而灵活的方式来管理列表数据,通过与 Qt 的视图组件(特别是 QML ListView)紧密集成,可以创建高性能、响应式的用户界面。

相关推荐
国服第二切图仔3 小时前
Rust开发实战之操作SQLite数据库——从零构建数据持久化应用
数据库·rust·sqlite
安审若无7 小时前
图数据库neoj4安装部署使用
linux·运维·数据库
fenglllle7 小时前
mybatis-plus SQL 注入漏洞导致版本升级引发的问题
数据库·sql·mybatis
learning-striving8 小时前
SQL server创建数据表
数据库·sql·mysql·sql server
Yeats_Liao8 小时前
时序数据库系列(三):InfluxDB数据写入Line Protocol详解
数据库·后端·时序数据库
天地之于壹炁兮8 小时前
编程I/O入门指南:核心操作全解析
数据库·windows·microsoft
切糕师学AI8 小时前
SQL中的函数索引/表达式索引
数据库·sql·mysql·postgresql·oracle
武子康8 小时前
Java-166 Neo4j 安装与最小闭环 | 10 分钟跑通 + 远程访问 Docker neo4j.conf
java·数据库·sql·docker·系统架构·nosql·neo4j
S_h_a_9 小时前
八股-Mysql 基础篇(1)
数据库·mysql