输入关键词开始搜索

Qt Model/View 架构

为什么需要 Model/View

传统做法(QTableWidget):数据直接存在控件里 → 数据一变就要手动更新控件 → 数据和 UI 耦合。

Model/View 把数据管理和显示分离:

QAbstractItemModel           QAbstractItemView
   (数据)       ← 问答协议 →     (显示)

View 不碰数据,只通过标准接口问 Model:
  - rowCount() — 几行?
  - columnCount() — 几列?
  - data(index, role) — 第(r,c)格显示什么?
  - headerData(section, orientation, role) — 表头是什么?

内置 Model

// QStringListModel — 简单字符串列表
QStringListModel model;
model.setStringList({"Alice", "Bob", "Charlie"});

QListView view;
view.setModel(&model);  // View 绑定 Model

// QStandardItemModel — 通用表格/树
QStandardItemModel model(3, 2);  // 3 行 2 列
model.setHeaderData(0, Qt::Horizontal, "Name");
model.setHeaderData(1, Qt::Horizontal, "Age");
model.setItem(0, 0, new QStandardItem("Alice"));
model.setItem(0, 1, new QStandardItem("25"));

QTableView view;
view.setModel(&model);

// QFileSystemModel — 文件系统
QFileSystemModel model;
model.setRootPath(QDir::homePath());

QTreeView view;
view.setModel(&model);
view.setRootIndex(model.index(QDir::homePath()));

自定义 Model

class MyTableModel : public QAbstractTableModel {
    QVector<QVector<QVariant>> m_data;
public:
    // 必须实现的 3 个虚函数
    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        return parent.isValid() ? 0 : m_data.size();
    }
    int columnCount(const QModelIndex &parent = QModelIndex()) const override {
        return parent.isValid() ? 0 : 3;  // 3 列
    }
    QVariant data(const QModelIndex &index, int role) const override {
        if (!index.isValid()) return {};
        if (role == Qt::DisplayRole)
            return m_data[index.row()][index.column()];
        if (role == Qt::TextAlignmentRole)
            return int(Qt::AlignCenter);
        return {};
    }

    // 可选:表头
    QVariant headerData(int section, Qt::Orientation o, int role) const override {
        if (role != Qt::DisplayRole) return {};
        if (o == Qt::Horizontal)
            return QStringList{"Name", "Age", "Email"}[section];
        return section + 1;  // 行号
    }

    // 通知 View 数据变了
    void appendRow(const QVector<QVariant> &row) {
        beginInsertRows(QModelIndex(), m_data.size(), m_data.size());
        m_data.append(row);
        endInsertRows();
    }
};

关键规则

// ⚠️ 修改数据前后必须调用 begin/end 方法
beginInsertRows(parent, first, last);
// 修改 m_data ...
endInsertRows();

beginRemoveRows(parent, first, last);
// 修改 m_data ...
endRemoveRows();

beginResetModel();
// 全量替换数据 ...
endResetModel();

// 单个单元格变化
dataChanged(topLeft, bottomRight);

Data Role — data() 的核心参数

QVariant data(const QModelIndex &index, int role) const override {
    switch (role) {
    case Qt::DisplayRole:    return m_data[index.row()].name;
    case Qt::ToolTipRole:    return m_data[index.row()].description;
    case Qt::DecorationRole: return QIcon(":/icon.png");
    case Qt::ForegroundRole: return QColor(Qt::red);     // 文字颜色
    case Qt::BackgroundRole: return QColor(Qt::lightGray);
    case Qt::FontRole:       return QFont("Consolas", 12);
    case Qt::TextAlignmentRole: return int(Qt::AlignCenter);
    case Qt::UserRole:       return m_data[index.row()].id;  // 自定义数据
    default: return {};
    }
}
Role作用
DisplayRole显示文本
DecorationRole图标
ToolTipRole鼠标悬停提示
ForegroundRole文字颜色
BackgroundRole背景色
FontRole字体
UserRole自定义(隐藏数据,如 ID)

Delegate — 自定义渲染和编辑

class ColorDelegate : public QStyledItemDelegate {
    void paint(QPainter *painter, const QStyleOptionViewItem &option,
               const QModelIndex &index) const override {
        // 自定义绘制
        painter->save();
        if (index.data(Qt::UserRole).toBool()) {
            painter->fillRect(option.rect, QColor("#e8f5e9"));
        }
        // 调用父类画文字
        QStyledItemDelegate::paint(painter, option, index);
        painter->restore();
    }
};

view->setItemDelegate(new ColorDelegate);

View 常用设置

QTableView *view = new QTableView;
view->setModel(&model);

// 外观
view->setSelectionBehavior(QAbstractItemView::SelectRows);  // 整行选中
view->setSelectionMode(QAbstractItemView::SingleSelection);
view->setAlternatingRowColors(true);    // 交替行颜色
view->verticalHeader()->setVisible(false);
view->setSortingEnabled(true);          // 点击表头排序

// 列宽
view->horizontalHeader()->setStretchLastSection(true);
view->setColumnWidth(0, 150);

// 隐藏列
view->setColumnHidden(2, true);

// 滚动到底部(数据追加后)
view->scrollToBottom();